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
22 #include "eclass_doom3.h"
24 #include "debugging/debugging.h"
28 #include "ifilesystem.h"
29 #include "iscriplib.h"
31 #include "qerplugin.h"
33 #include "generic/callback.h"
34 #include "string/string.h"
35 #include "eclasslib.h"
38 #include "stream/stringstream.h"
39 #include "moduleobservers.h"
46 RawString(const char* value) : m_value(value)
49 const char* c_str() const
55 inline bool operator<(const RawString& self, const RawString& other)
57 return string_less_nocase(self.c_str(), other.c_str());
60 typedef std::map<RawString, EntityClass*> EntityClasses;
61 EntityClasses g_EntityClassDoom3_classes;
62 EntityClass *g_EntityClassDoom3_bad = 0;
65 void EntityClassDoom3_clear()
67 for(EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i)
69 (*i).second->free((*i).second);
71 g_EntityClassDoom3_classes.clear();
74 // entityClass will be inserted only if another of the same name does not already exist.
75 // if entityClass was inserted, the same object is returned, otherwise the already-existing object is returned.
76 EntityClass* EntityClassDoom3_insertUnique(EntityClass* entityClass)
78 return (*g_EntityClassDoom3_classes.insert(EntityClasses::value_type(entityClass->name(), entityClass)).first).second;
81 void EntityClassDoom3_forEach(EntityClassVisitor& visitor)
83 for(EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i)
85 visitor.visit((*i).second);
89 void EntityClassDoom3_parseUnknown(Tokeniser& tokeniser)
94 //globalOutputStream() << "parsing unknown block " << makeQuoted(name) << "\n";
96 const char* token = tokeniser.getToken();
97 ASSERT_MESSAGE(string_equal(token, "{"), "error parsing entity definition");
100 std::size_t depth = 1;
103 const char* token = tokeniser.getToken();
104 if(string_equal(token, "}"))
108 tokeniser.nextLine();
112 else if(string_equal(token, "{"))
116 tokeniser.nextLine();
127 CopiedString m_parent;
128 typedef std::map<CopiedString, CopiedString> Anims;
130 Model() : m_resolved(false)
135 typedef std::map<CopiedString, Model> Models;
139 void Model_resolveInheritance(const char* name, Model& model)
141 if(model.m_resolved == false)
143 model.m_resolved = true;
145 if(!string_empty(model.m_parent.c_str()))
147 Models::iterator i = g_models.find(model.m_parent);
148 if(i == g_models.end())
150 globalErrorStream() << "model " << name << " inherits unknown model " << model.m_parent.c_str() << "\n";
154 Model_resolveInheritance((*i).first.c_str(), (*i).second);
155 model.m_mesh = (*i).second.m_mesh;
156 model.m_skin = (*i).second.m_skin;
162 void EntityClassDoom3_parseModel(Tokeniser& tokeniser)
164 const char* name = tokeniser.getToken();
166 Model& model = g_models[name];
168 const char* token = tokeniser.getToken();
169 ASSERT_MESSAGE(string_equal(token, "{"), "error parsing model definition");
170 tokeniser.nextLine();
174 const char* parameter = tokeniser.getToken();
175 if(string_equal(parameter, "}"))
177 tokeniser.nextLine();
180 else if(string_equal(parameter, "inherit"))
182 model.m_parent = tokeniser.getToken();
184 else if(string_equal(parameter, "remove"))
186 //const char* remove =
187 tokeniser.getToken();
189 else if(string_equal(parameter, "mesh"))
191 model.m_mesh = tokeniser.getToken();
193 else if(string_equal(parameter, "skin"))
195 model.m_skin = tokeniser.getToken();
197 else if(string_equal(parameter, "offset"))
199 tokeniser.getToken(); // (
200 tokeniser.getToken();
201 tokeniser.getToken();
202 tokeniser.getToken();
203 tokeniser.getToken(); // )
204 tokeniser.nextLine();
206 else if(string_equal(parameter, "channel"))
208 //const char* channelName =
209 tokeniser.getToken();
210 tokeniser.getToken(); // (
213 const char* end = tokeniser.getToken();
214 if(string_equal(end, ")"))
216 tokeniser.nextLine();
221 else if(string_equal(parameter, "anim"))
223 CopiedString animName(tokeniser.getToken());
224 const char* animFile = tokeniser.getToken();
225 model.m_anims.insert(Model::Anims::value_type(animName, animFile));
227 const char* token = tokeniser.getToken();
229 while(string_equal(token, ","))
231 animFile = tokeniser.getToken();
232 token = tokeniser.getToken();
235 if(string_equal(token, "{"))
239 const char* end = tokeniser.getToken();
240 if(string_equal(end, "}"))
242 tokeniser.nextLine();
245 tokeniser.nextLine();
250 tokeniser.ungetToken();
255 ERROR_MESSAGE("unknown model parameter: " << makeQuoted(parameter));
257 tokeniser.nextLine();
261 inline bool char_isSpaceOrTab(char c)
263 return c == ' ' || c == '\t';
266 inline bool char_isNotSpaceOrTab(char c)
268 return !char_isSpaceOrTab(c);
271 template<typename Predicate>
272 inline const char* string_find_if(const char* string, Predicate predicate)
274 for(; *string != 0; ++string)
276 if(predicate(*string))
284 inline const char* string_findFirstSpaceOrTab(const char* string)
286 return string_find_if(string, char_isSpaceOrTab);
289 inline const char* string_findFirstNonSpaceOrTab(const char* string)
291 return string_find_if(string, char_isNotSpaceOrTab);
295 void EntityClassDoom3_parseEntityDef(Tokeniser& tokeniser)
297 EntityClass* entityClass = Eclass_Alloc();
298 entityClass->free = &Eclass_Free;
300 entityClass->m_name = tokeniser.getToken();
302 const char* token = tokeniser.getToken();
303 ASSERT_MESSAGE(string_equal(token, "{"), "error parsing entity definition");
304 tokeniser.nextLine();
306 StringOutputStream usage(256);
307 StringOutputStream description(256);
308 CopiedString* currentDescription = 0;
309 StringOutputStream* currentString = 0;
313 const char* key = tokeniser.getToken();
315 const char* last = string_findFirstSpaceOrTab(key);
316 CopiedString first(key, last);
318 if(!string_empty(last))
320 last = string_findFirstNonSpaceOrTab(last);
323 if(currentString != 0 && string_equal(key, "\\"))
325 tokeniser.nextLine();
326 *currentString << " " << tokeniser.getToken();
330 if(currentDescription != 0)
332 *currentDescription = description.c_str();
334 currentDescription = 0;
338 if(string_equal(key, "}"))
340 tokeniser.nextLine();
343 else if(string_equal(key, "model"))
345 entityClass->fixedsize = true;
346 StringOutputStream buffer(256);
347 buffer << PathCleaned(tokeniser.getToken());
348 entityClass->m_modelpath = buffer.c_str();
350 else if(string_equal(key, "editor_color"))
352 const char* value = tokeniser.getToken();
353 if(!string_empty(value))
355 entityClass->colorSpecified = true;
356 bool success = string_parse_vector3(value, entityClass->color);
357 ASSERT_MESSAGE(success, "editor_color: parse error");
360 else if(string_equal(key, "editor_ragdoll"))
362 //bool ragdoll = atoi(tokeniser.getToken()) != 0;
363 tokeniser.getToken();
365 else if(string_equal(key, "editor_mins"))
367 entityClass->sizeSpecified = true;
368 const char* value = tokeniser.getToken();
369 if(!string_empty(value) && !string_equal(value, "?"))
371 entityClass->fixedsize = true;
372 bool success = string_parse_vector3(value, entityClass->mins);
373 ASSERT_MESSAGE(success, "editor_mins: parse error");
376 else if(string_equal(key, "editor_maxs"))
378 entityClass->sizeSpecified = true;
379 const char* value = tokeniser.getToken();
380 if(!string_empty(value) && !string_equal(value, "?"))
382 entityClass->fixedsize = true;
383 bool success = string_parse_vector3(value, entityClass->maxs);
384 ASSERT_MESSAGE(success, "editor_maxs: parse error");
387 else if(string_equal(key, "editor_usage"))
389 const char* value = tokeniser.getToken();
391 currentString = &usage;
393 else if(string_equal_n(key, "editor_usage", 12))
395 const char* value = tokeniser.getToken();
396 usage << "\n" << value;
397 currentString = &usage;
399 else if(string_equal(key, "editor_rotatable")
400 || string_equal(key, "editor_showangle")
401 || string_equal(key, "editor_mover")
402 || string_equal(key, "editor_model")
403 || string_equal(key, "editor_material")
404 || string_equal(key, "editor_combatnode")
405 || (!string_empty(last) && string_equal(first.c_str(), "editor_gui"))
406 || string_equal_n(key, "editor_copy", 11))
408 tokeniser.getToken();
410 else if(!string_empty(last) && (string_equal(first.c_str(), "editor_var") || string_equal(first.c_str(), "editor_string")))
412 EntityClassAttribute& attribute = EntityClass_insertAttribute(*entityClass, last).second;
413 attribute.m_type = "string";
414 currentDescription = &attribute.m_description;
415 currentString = &description;
416 description << tokeniser.getToken();
418 else if(!string_empty(last) && string_equal(first.c_str(), "editor_float"))
420 EntityClassAttribute& attribute = EntityClass_insertAttribute(*entityClass, last).second;
421 attribute.m_type = "string";
422 currentDescription = &attribute.m_description;
423 currentString = &description;
424 description << tokeniser.getToken();
426 else if(!string_empty(last) && string_equal(first.c_str(), "editor_snd"))
428 EntityClassAttribute& attribute = EntityClass_insertAttribute(*entityClass, last).second;
429 attribute.m_type = "sound";
430 currentDescription = &attribute.m_description;
431 currentString = &description;
432 description << tokeniser.getToken();
434 else if(!string_empty(last) && string_equal(first.c_str(), "editor_bool"))
436 EntityClassAttribute& attribute = EntityClass_insertAttribute(*entityClass, last).second;
437 attribute.m_type = "boolean";
438 currentDescription = &attribute.m_description;
439 currentString = &description;
440 description << tokeniser.getToken();
442 else if(!string_empty(last) && string_equal(first.c_str(), "editor_int"))
444 EntityClassAttribute& attribute = EntityClass_insertAttribute(*entityClass, last).second;
445 attribute.m_type = "integer";
446 currentDescription = &attribute.m_description;
447 currentString = &description;
448 description << tokeniser.getToken();
450 else if(!string_empty(last) && string_equal(first.c_str(), "editor_model"))
452 EntityClassAttribute& attribute = EntityClass_insertAttribute(*entityClass, last).second;
453 attribute.m_type = "model";
454 currentDescription = &attribute.m_description;
455 currentString = &description;
456 description << tokeniser.getToken();
458 else if(!string_empty(last) && string_equal(first.c_str(), "editor_color"))
460 EntityClassAttribute& attribute = EntityClass_insertAttribute(*entityClass, last).second;
461 attribute.m_type = "color";
462 currentDescription = &attribute.m_description;
463 currentString = &description;
464 description << tokeniser.getToken();
466 else if(!string_empty(last) && (string_equal(first.c_str(), "editor_material") || string_equal(first.c_str(), "editor_mat")))
468 EntityClassAttribute& attribute = EntityClass_insertAttribute(*entityClass, last).second;
469 attribute.m_type = "shader";
470 currentDescription = &attribute.m_description;
471 currentString = &description;
472 description << tokeniser.getToken();
474 else if(string_equal(key, "inherit"))
476 entityClass->inheritanceResolved = false;
477 ASSERT_MESSAGE(entityClass->m_parent.empty(), "only one 'inherit' supported per entityDef");
478 entityClass->m_parent.push_back(tokeniser.getToken());
480 // begin quake4-specific keys
481 else if(string_equal(key, "editor_targetonsel"))
483 //const char* value =
484 tokeniser.getToken();
486 else if(string_equal(key, "editor_menu"))
488 //const char* value =
489 tokeniser.getToken();
491 else if(string_equal(key, "editor_ignore"))
493 //const char* value =
494 tokeniser.getToken();
496 // end quake4-specific keys
499 ASSERT_MESSAGE(!string_equal_n(key, "editor_", 7), "unsupported editor key: " << makeQuoted(key));
500 EntityClassAttribute& attribute = EntityClass_insertAttribute(*entityClass, key).second;
501 attribute.m_type = "string";
502 attribute.m_value = tokeniser.getToken();
504 tokeniser.nextLine();
507 entityClass->m_comments = usage.c_str();
509 if(string_equal(entityClass->m_name.c_str(), "light"))
512 EntityClassAttribute& attribute = EntityClass_insertAttribute(*entityClass, "light_radius").second;
513 attribute.m_type = "vector3";
514 attribute.m_value = "300 300 300";
517 EntityClassAttribute& attribute = EntityClass_insertAttribute(*entityClass, "light_center").second;
518 attribute.m_type = "vector3";
521 EntityClassAttribute& attribute = EntityClass_insertAttribute(*entityClass, "noshadows").second;
522 attribute.m_type = "boolean";
523 attribute.m_value = "0";
526 EntityClassAttribute& attribute = EntityClass_insertAttribute(*entityClass, "nospecular").second;
527 attribute.m_type = "boolean";
528 attribute.m_value = "0";
531 EntityClassAttribute& attribute = EntityClass_insertAttribute(*entityClass, "nodiffuse").second;
532 attribute.m_type = "boolean";
533 attribute.m_value = "0";
536 EntityClassAttribute& attribute = EntityClass_insertAttribute(*entityClass, "falloff").second;
537 attribute.m_type = "real";
541 EntityClass* inserted = EntityClassDoom3_insertUnique(entityClass);
542 if(inserted != entityClass)
544 globalErrorStream() << "entityDef " << entityClass->name() << " is already defined, second definition ignored\n";
545 eclass_capture_state(entityClass); // finish constructing the entity so that it can be destroyed cleanly.
546 entityClass->free(entityClass);
550 void EntityClassDoom3_parse(TextInputStream& inputStream)
552 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(inputStream);
554 tokeniser.nextLine();
558 const char* blockType = tokeniser.getToken();
563 if(string_equal(blockType, "entityDef"))
565 EntityClassDoom3_parseEntityDef(tokeniser);
567 else if(string_equal(blockType, "model"))
569 EntityClassDoom3_parseModel(tokeniser);
573 EntityClassDoom3_parseUnknown(tokeniser);
581 void EntityClassDoom3_loadFile(const char* filename)
583 globalOutputStream() << "parsing entity classes from " << makeQuoted(filename) << "\n";
585 StringOutputStream fullname(256);
586 fullname << "def/" << filename;
588 ArchiveTextFile* file = GlobalFileSystem().openTextFile(fullname.c_str());
591 EntityClassDoom3_parse(file->getInputStream());
596 EntityClass* EntityClassDoom3_findOrInsert(const char *name, bool has_brushes)
598 ASSERT_NOTNULL(name);
600 if(string_empty(name))
602 return g_EntityClassDoom3_bad;
605 EntityClasses::iterator i = g_EntityClassDoom3_classes.find(name);
606 if(i != g_EntityClassDoom3_classes.end()
607 //&& string_equal((*i).first, name)
613 EntityClass* e = EntityClass_Create_Default(name, has_brushes);
614 EntityClass* inserted = EntityClassDoom3_insertUnique(e);
615 ASSERT_MESSAGE(inserted == e, "");
619 const ListAttributeType* EntityClassDoom3_findListType(const char* name)
625 void EntityClass_resolveInheritance(EntityClass* derivedClass)
627 if(derivedClass->inheritanceResolved == false)
629 derivedClass->inheritanceResolved = true;
630 EntityClasses::iterator i = g_EntityClassDoom3_classes.find(derivedClass->m_parent.front().c_str());
631 if(i == g_EntityClassDoom3_classes.end())
633 globalErrorStream() << "failed to find entityDef " << makeQuoted(derivedClass->m_parent.front().c_str()) << " inherited by " << makeQuoted(derivedClass->m_name.c_str()) << "\n";
637 EntityClass* parentClass = (*i).second;
638 EntityClass_resolveInheritance(parentClass);
639 if(!derivedClass->colorSpecified)
641 derivedClass->colorSpecified = parentClass->colorSpecified;
642 derivedClass->color = parentClass->color;
644 if(!derivedClass->sizeSpecified)
646 derivedClass->sizeSpecified = parentClass->sizeSpecified;
647 derivedClass->mins = parentClass->mins;
648 derivedClass->maxs = parentClass->maxs;
649 derivedClass->fixedsize = parentClass->fixedsize;
652 for(EntityClassAttributes::iterator j = parentClass->m_attributes.begin(); j != parentClass->m_attributes.end(); ++j)
654 EntityClass_insertAttribute(*derivedClass, (*j).first.c_str(), (*j).second);
660 class EntityClassDoom3 : public ModuleObserver
662 std::size_t m_unrealised;
663 ModuleObservers m_observers;
665 EntityClassDoom3() : m_unrealised(2)
670 if(--m_unrealised == 0)
672 globalOutputStream() << "searching vfs directory " << makeQuoted("def") << " for *.def\n";
673 GlobalFileSystem().forEachFile("def/", "def", FreeCaller1<const char*, EntityClassDoom3_loadFile>());
676 for(Models::iterator i = g_models.begin(); i != g_models.end(); ++i)
678 Model_resolveInheritance((*i).first.c_str(), (*i).second);
682 for(EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i)
684 EntityClass_resolveInheritance((*i).second);
685 if(!string_empty((*i).second->m_modelpath.c_str()))
687 Models::iterator j = g_models.find((*i).second->m_modelpath);
688 if(j != g_models.end())
690 (*i).second->m_modelpath = (*j).second.m_mesh;
691 (*i).second->m_skin = (*j).second.m_skin;
694 eclass_capture_state((*i).second);
696 StringOutputStream usage(256);
698 usage << "-------- KEYS --------\n";
700 for(EntityClassAttributes::iterator j = (*i).second->m_attributes.begin(); j != (*i).second->m_attributes.end(); ++j)
702 const char* name = EntityClassAttributePair_getName(*j);
703 const char* description = EntityClassAttributePair_getDescription(*j);
704 if(!string_equal(name, description))
706 usage << EntityClassAttributePair_getName(*j) << " : " << EntityClassAttributePair_getDescription(*j) << "\n";
710 (*i).second->m_comments = usage.c_str();
714 m_observers.realise();
719 if(++m_unrealised == 1)
721 m_observers.unrealise();
722 EntityClassDoom3_clear();
725 void attach(ModuleObserver& observer)
727 m_observers.attach(observer);
729 void detach(ModuleObserver& observer)
731 m_observers.detach(observer);
735 EntityClassDoom3 g_EntityClassDoom3;
737 void EntityClassDoom3_attach(ModuleObserver& observer)
739 g_EntityClassDoom3.attach(observer);
741 void EntityClassDoom3_detach(ModuleObserver& observer)
743 g_EntityClassDoom3.detach(observer);
746 void EntityClassDoom3_realise()
748 g_EntityClassDoom3.realise();
750 void EntityClassDoom3_unrealise()
752 g_EntityClassDoom3.unrealise();
755 void EntityClassDoom3_construct()
757 GlobalFileSystem().attach(g_EntityClassDoom3);
759 // start by creating the default unknown eclass
760 g_EntityClassDoom3_bad = EClass_Create("UNKNOWN_CLASS", Vector3(0.0f, 0.5f, 0.0f), "");
762 EntityClassDoom3_realise();
765 void EntityClassDoom3_destroy()
767 EntityClassDoom3_unrealise();
769 g_EntityClassDoom3_bad->free(g_EntityClassDoom3_bad);
771 GlobalFileSystem().detach(g_EntityClassDoom3);
774 class EntityClassDoom3Dependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef
778 class EntityClassDoom3API
780 EntityClassManager m_eclassmanager;
782 typedef EntityClassManager Type;
783 STRING_CONSTANT(Name, "doom3");
785 EntityClassDoom3API()
787 EntityClassDoom3_construct();
789 m_eclassmanager.findOrInsert = &EntityClassDoom3_findOrInsert;
790 m_eclassmanager.findListType = &EntityClassDoom3_findListType;
791 m_eclassmanager.forEach = &EntityClassDoom3_forEach;
792 m_eclassmanager.attach = &EntityClassDoom3_attach;
793 m_eclassmanager.detach = &EntityClassDoom3_detach;
794 m_eclassmanager.realise = &EntityClassDoom3_realise;
795 m_eclassmanager.unrealise = &EntityClassDoom3_unrealise;
797 ~EntityClassDoom3API()
799 EntityClassDoom3_destroy();
801 EntityClassManager* getTable()
803 return &m_eclassmanager;
807 #include "modulesystem/singletonmodule.h"
808 #include "modulesystem/moduleregistry.h"
810 typedef SingletonModule<EntityClassDoom3API, EntityClassDoom3Dependencies> EntityClassDoom3Module;
811 typedef Static<EntityClassDoom3Module> StaticEntityClassDoom3Module;
812 StaticRegisterModule staticRegisterEntityClassDoom3(StaticEntityClassDoom3Module::instance());