/* Copyright (C) 2001-2006, William Joseph. All Rights Reserved. This file is part of GtkRadiant. GtkRadiant is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GtkRadiant is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "eclass_doom3.h" #include "debugging/debugging.h" #include #include "ifilesystem.h" #include "iscriplib.h" #include "iarchive.h" #include "qerplugin.h" #include "generic/callback.h" #include "string/string.h" #include "eclasslib.h" #include "os/path.h" #include "os/dir.h" #include "stream/stringstream.h" #include "moduleobservers.h" #include "stringio.h" class RawString { const char* m_value; public: RawString(const char* value) : m_value(value) { } const char* c_str() const { return m_value; } }; inline bool operator<(const RawString& self, const RawString& other) { return string_less_nocase(self.c_str(), other.c_str()); } typedef std::map EntityClasses; EntityClasses g_EntityClassDoom3_classes; EntityClass *g_EntityClassDoom3_bad = 0; void EntityClassDoom3_clear() { for(EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i) { (*i).second->free((*i).second); } g_EntityClassDoom3_classes.clear(); } // entityClass will be inserted only if another of the same name does not already exist. // if entityClass was inserted, the same object is returned, otherwise the already-existing object is returned. EntityClass* EntityClassDoom3_insertUnique(EntityClass* entityClass) { return (*g_EntityClassDoom3_classes.insert(EntityClasses::value_type(entityClass->name(), entityClass)).first).second; } void EntityClassDoom3_forEach(EntityClassVisitor& visitor) { for(EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i) { visitor.visit((*i).second); } } inline void printParseError(const char* message) { globalErrorStream() << message; } #define PARSE_RETURN_FALSE_IF_FAIL(expression) if(!(expression)) { printParseError(FILE_LINE "\nparse failed: " #expression "\n"); return false; } else bool EntityClassDoom3_parseToken(Tokeniser& tokeniser) { const char* token = tokeniser.getToken(); PARSE_RETURN_FALSE_IF_FAIL(token != 0); return true; } bool EntityClassDoom3_parseToken(Tokeniser& tokeniser, const char* string) { const char* token = tokeniser.getToken(); PARSE_RETURN_FALSE_IF_FAIL(token != 0); return string_equal(token, string); } bool EntityClassDoom3_parseString(Tokeniser& tokeniser, const char*& s) { const char* token = tokeniser.getToken(); PARSE_RETURN_FALSE_IF_FAIL(token != 0); s = token; return true; } bool EntityClassDoom3_parseString(Tokeniser& tokeniser, CopiedString& s) { const char* token = tokeniser.getToken(); PARSE_RETURN_FALSE_IF_FAIL(token != 0); s = token; return true; } bool EntityClassDoom3_parseString(Tokeniser& tokeniser, StringOutputStream& s) { const char* token = tokeniser.getToken(); PARSE_RETURN_FALSE_IF_FAIL(token != 0); s << token; return true; } bool EntityClassDoom3_parseUnknown(Tokeniser& tokeniser) { //const char* name = PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); //globalOutputStream() << "parsing unknown block " << makeQuoted(name) << "\n"; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "{")); tokeniser.nextLine(); std::size_t depth = 1; for(;;) { const char* token; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token)); if(string_equal(token, "}")) { if(--depth == 0) { tokeniser.nextLine(); break; } } else if(string_equal(token, "{")) { ++depth; } tokeniser.nextLine(); } return true; } class Model { public: bool m_resolved; CopiedString m_mesh; CopiedString m_skin; CopiedString m_parent; typedef std::map Anims; Anims m_anims; Model() : m_resolved(false) { } }; typedef std::map Models; Models g_models; void Model_resolveInheritance(const char* name, Model& model) { if(model.m_resolved == false) { model.m_resolved = true; if(!string_empty(model.m_parent.c_str())) { Models::iterator i = g_models.find(model.m_parent); if(i == g_models.end()) { globalErrorStream() << "model " << name << " inherits unknown model " << model.m_parent.c_str() << "\n"; } else { Model_resolveInheritance((*i).first.c_str(), (*i).second); model.m_mesh = (*i).second.m_mesh; model.m_skin = (*i).second.m_skin; } } } } bool EntityClassDoom3_parseModel(Tokeniser& tokeniser) { const char* name; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, name)); Model& model = g_models[name]; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "{")); tokeniser.nextLine(); for(;;) { const char* parameter; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, parameter)); if(string_equal(parameter, "}")) { tokeniser.nextLine(); break; } else if(string_equal(parameter, "inherit")) { PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, model.m_parent)); tokeniser.nextLine(); } else if(string_equal(parameter, "remove")) { //const char* remove = PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); tokeniser.nextLine(); } else if(string_equal(parameter, "mesh")) { PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, model.m_mesh)); tokeniser.nextLine(); } else if(string_equal(parameter, "skin")) { PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, model.m_skin)); tokeniser.nextLine(); } else if(string_equal(parameter, "offset")) { PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "(")); PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, ")")); tokeniser.nextLine(); } else if(string_equal(parameter, "channel")) { //const char* channelName = PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "(")); for(;;) { const char* end; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, end)); if(string_equal(end, ")")) { tokeniser.nextLine(); break; } } } else if(string_equal(parameter, "anim")) { CopiedString animName; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, animName)); const char* animFile; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, animFile)); model.m_anims.insert(Model::Anims::value_type(animName, animFile)); const char* token; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token)); while(string_equal(token, ",")) { PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, animFile)); PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token)); } if(string_equal(token, "{")) { for(;;) { const char* end; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, end)); if(string_equal(end, "}")) { tokeniser.nextLine(); break; } tokeniser.nextLine(); } } else { tokeniser.ungetToken(); } } else { globalErrorStream() << "unknown model parameter: " << makeQuoted(parameter) << "\n"; return false; } tokeniser.nextLine(); } return true; } inline bool char_isSpaceOrTab(char c) { return c == ' ' || c == '\t'; } inline bool char_isNotSpaceOrTab(char c) { return !char_isSpaceOrTab(c); } template inline const char* string_find_if(const char* string, Predicate predicate) { for(; *string != 0; ++string) { if(predicate(*string)) { return string; } } return string; } inline const char* string_findFirstSpaceOrTab(const char* string) { return string_find_if(string, char_isSpaceOrTab); } inline const char* string_findFirstNonSpaceOrTab(const char* string) { return string_find_if(string, char_isNotSpaceOrTab); } static bool EntityClass_parse(EntityClass& entityClass, Tokeniser& tokeniser) { PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, entityClass.m_name)); PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "{")); tokeniser.nextLine(); StringOutputStream usage(256); StringOutputStream description(256); CopiedString* currentDescription = 0; StringOutputStream* currentString = 0; for(;;) { const char* key; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, key)); const char* last = string_findFirstSpaceOrTab(key); CopiedString first(StringRange(key, last)); if(!string_empty(last)) { last = string_findFirstNonSpaceOrTab(last); } if(currentString != 0 && string_equal(key, "\\")) { tokeniser.nextLine(); *currentString << " "; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, *currentString)); continue; } if(currentDescription != 0) { *currentDescription = description.c_str(); description.clear(); currentDescription = 0; } currentString = 0; if(string_equal(key, "}")) { tokeniser.nextLine(); break; } else if(string_equal(key, "model")) { const char* token; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token)); entityClass.fixedsize = true; StringOutputStream buffer(256); buffer << PathCleaned(token); entityClass.m_modelpath = buffer.c_str(); } else if(string_equal(key, "editor_color")) { const char* value; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, value)); if(!string_empty(value)) { entityClass.colorSpecified = true; bool success = string_parse_vector3(value, entityClass.color); ASSERT_MESSAGE(success, "editor_color: parse error"); } } else if(string_equal(key, "editor_ragdoll")) { //bool ragdoll = atoi(tokeniser.getToken()) != 0; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); } else if(string_equal(key, "editor_mins")) { entityClass.sizeSpecified = true; const char* value; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, value)); if(!string_empty(value) && !string_equal(value, "?")) { entityClass.fixedsize = true; bool success = string_parse_vector3(value, entityClass.mins); ASSERT_MESSAGE(success, "editor_mins: parse error"); } } else if(string_equal(key, "editor_maxs")) { entityClass.sizeSpecified = true; const char* value; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, value)); if(!string_empty(value) && !string_equal(value, "?")) { entityClass.fixedsize = true; bool success = string_parse_vector3(value, entityClass.maxs); ASSERT_MESSAGE(success, "editor_maxs: parse error"); } } else if(string_equal(key, "editor_usage")) { PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, usage)); currentString = &usage; } else if(string_equal_n(key, "editor_usage", 12)) { PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, usage)); currentString = &usage; } else if(string_equal(key, "editor_rotatable") || string_equal(key, "editor_showangle") || string_equal(key, "editor_showangles") // typo? in prey movables.def || string_equal(key, "editor_mover") || string_equal(key, "editor_model") || string_equal(key, "editor_material") || string_equal(key, "editor_combatnode") || (!string_empty(last) && string_equal(first.c_str(), "editor_gui")) || string_equal_n(key, "editor_copy", 11)) { PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); } else if(!string_empty(last) && (string_equal(first.c_str(), "editor_var") || string_equal(first.c_str(), "editor_string"))) { EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second; attribute.m_type = "string"; currentDescription = &attribute.m_description; currentString = &description; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description)); } else if(!string_empty(last) && string_equal(first.c_str(), "editor_float")) { EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second; attribute.m_type = "string"; currentDescription = &attribute.m_description; currentString = &description; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description)); } else if(!string_empty(last) && string_equal(first.c_str(), "editor_snd")) { EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second; attribute.m_type = "sound"; currentDescription = &attribute.m_description; currentString = &description; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description)); } else if(!string_empty(last) && string_equal(first.c_str(), "editor_bool")) { EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second; attribute.m_type = "boolean"; currentDescription = &attribute.m_description; currentString = &description; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description)); } else if(!string_empty(last) && string_equal(first.c_str(), "editor_int")) { EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second; attribute.m_type = "integer"; currentDescription = &attribute.m_description; currentString = &description; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description)); } else if(!string_empty(last) && string_equal(first.c_str(), "editor_model")) { EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second; attribute.m_type = "model"; currentDescription = &attribute.m_description; currentString = &description; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description)); } else if(!string_empty(last) && string_equal(first.c_str(), "editor_color")) { EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second; attribute.m_type = "color"; currentDescription = &attribute.m_description; currentString = &description; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description)); } else if(!string_empty(last) && (string_equal(first.c_str(), "editor_material") || string_equal(first.c_str(), "editor_mat"))) { EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second; attribute.m_type = "shader"; currentDescription = &attribute.m_description; currentString = &description; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description)); } else if(string_equal(key, "inherit")) { entityClass.inheritanceResolved = false; ASSERT_MESSAGE(entityClass.m_parent.empty(), "only one 'inherit' supported per entityDef"); const char* token; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token)); entityClass.m_parent.push_back(token); } // begin quake4-specific keys else if(string_equal(key, "editor_targetonsel")) { //const char* value = PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); } else if(string_equal(key, "editor_menu")) { //const char* value = PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); } else if(string_equal(key, "editor_ignore")) { //const char* value = PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); } // end quake4-specific keys // begin ignore prey (unknown/unused?) entity keys else if(string_equal(key, "editor_light") || string_equal(key, "editor_def def_debrisspawner") || string_equal(key, "editor_def def_drop") || string_equal(key, "editor_def def_guihand") || string_equal(key, "editor_def def_mine")) { //const char* value = PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); } // end ignore prey entity keys else { CopiedString tmp(key); ASSERT_MESSAGE(!string_equal_n(key, "editor_", 7), "unsupported editor key: " << makeQuoted(key)); EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, key).second; attribute.m_type = "string"; const char* value; PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, value)); if(string_equal(value, "}")) // hack for quake4 powerups.def bug { globalErrorStream() << "entityDef " << makeQuoted(entityClass.m_name.c_str()) << " key " << makeQuoted(tmp.c_str()) << " has no value\n"; break; } else { attribute.m_value = value; } } tokeniser.nextLine(); } entityClass.m_comments = usage.c_str(); if(string_equal(entityClass.m_name.c_str(), "light")) { { EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "light_radius").second; attribute.m_type = "vector3"; attribute.m_value = "300 300 300"; } { EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "light_center").second; attribute.m_type = "vector3"; } { EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "noshadows").second; attribute.m_type = "boolean"; attribute.m_value = "0"; } { EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "nospecular").second; attribute.m_type = "boolean"; attribute.m_value = "0"; } { EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "nodiffuse").second; attribute.m_type = "boolean"; attribute.m_value = "0"; } { EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "falloff").second; attribute.m_type = "real"; } } return true; } bool EntityClassDoom3_parseEntityDef(Tokeniser& tokeniser) { EntityClass* entityClass = Eclass_Alloc(); entityClass->free = &Eclass_Free; if(!EntityClass_parse(*entityClass, tokeniser)) { eclass_capture_state(entityClass); // finish constructing the entity so that it can be destroyed cleanly. entityClass->free(entityClass); return false; } EntityClass* inserted = EntityClassDoom3_insertUnique(entityClass); if(inserted != entityClass) { globalErrorStream() << "entityDef " << entityClass->name() << " is already defined, second definition ignored\n"; eclass_capture_state(entityClass); // finish constructing the entity so that it can be destroyed cleanly. entityClass->free(entityClass); } return true; } bool EntityClassDoom3_parseBlock(Tokeniser& tokeniser, const char* blockType) { if(string_equal(blockType, "entityDef")) { return EntityClassDoom3_parseEntityDef(tokeniser); } else if(string_equal(blockType, "model")) { return EntityClassDoom3_parseModel(tokeniser); } else { return EntityClassDoom3_parseUnknown(tokeniser); } } bool EntityClassDoom3_parse(TextInputStream& inputStream, const char* filename) { Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(inputStream); tokeniser.nextLine(); for(;;) { const char* blockType = tokeniser.getToken(); if(blockType == 0) { return true; } CopiedString tmp(blockType); if(!EntityClassDoom3_parseBlock(tokeniser, tmp.c_str())) { globalErrorStream() << GlobalFileSystem().findFile(filename) << filename << ":" << (unsigned int)tokeniser.getLine() << ": " << tmp.c_str() << " parse failed, skipping rest of file\n"; return false; } } tokeniser.release(); } void EntityClassDoom3_loadFile(const char* filename) { globalOutputStream() << "parsing entity classes from " << makeQuoted(filename) << "\n"; StringOutputStream fullname(256); fullname << "def/" << filename; ArchiveTextFile* file = GlobalFileSystem().openTextFile(fullname.c_str()); if(file != 0) { EntityClassDoom3_parse(file->getInputStream(), fullname.c_str()); file->release(); } } EntityClass* EntityClassDoom3_findOrInsert(const char *name, bool has_brushes) { ASSERT_NOTNULL(name); if(string_empty(name)) { return g_EntityClassDoom3_bad; } EntityClasses::iterator i = g_EntityClassDoom3_classes.find(name); if(i != g_EntityClassDoom3_classes.end() //&& string_equal((*i).first, name) ) { return (*i).second; } EntityClass* e = EntityClass_Create_Default(name, has_brushes); EntityClass* inserted = EntityClassDoom3_insertUnique(e); ASSERT_MESSAGE(inserted == e, ""); return inserted; } const ListAttributeType* EntityClassDoom3_findListType(const char* name) { return 0; } void EntityClass_resolveInheritance(EntityClass* derivedClass) { if(derivedClass->inheritanceResolved == false) { derivedClass->inheritanceResolved = true; EntityClasses::iterator i = g_EntityClassDoom3_classes.find(derivedClass->m_parent.front().c_str()); if(i == g_EntityClassDoom3_classes.end()) { globalErrorStream() << "failed to find entityDef " << makeQuoted(derivedClass->m_parent.front().c_str()) << " inherited by " << makeQuoted(derivedClass->m_name.c_str()) << "\n"; } else { EntityClass* parentClass = (*i).second; EntityClass_resolveInheritance(parentClass); if(!derivedClass->colorSpecified) { derivedClass->colorSpecified = parentClass->colorSpecified; derivedClass->color = parentClass->color; } if(!derivedClass->sizeSpecified) { derivedClass->sizeSpecified = parentClass->sizeSpecified; derivedClass->mins = parentClass->mins; derivedClass->maxs = parentClass->maxs; derivedClass->fixedsize = parentClass->fixedsize; } for(EntityClassAttributes::iterator j = parentClass->m_attributes.begin(); j != parentClass->m_attributes.end(); ++j) { EntityClass_insertAttribute(*derivedClass, (*j).first.c_str(), (*j).second); } } } } class EntityClassDoom3 : public ModuleObserver { std::size_t m_unrealised; ModuleObservers m_observers; public: EntityClassDoom3() : m_unrealised(2) { } void realise() { if(--m_unrealised == 0) { globalOutputStream() << "searching vfs directory " << makeQuoted("def") << " for *.def\n"; GlobalFileSystem().forEachFile("def/", "def", FreeCaller1()); { for(Models::iterator i = g_models.begin(); i != g_models.end(); ++i) { Model_resolveInheritance((*i).first.c_str(), (*i).second); } } { for(EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i) { EntityClass_resolveInheritance((*i).second); if(!string_empty((*i).second->m_modelpath.c_str())) { Models::iterator j = g_models.find((*i).second->m_modelpath); if(j != g_models.end()) { (*i).second->m_modelpath = (*j).second.m_mesh; (*i).second->m_skin = (*j).second.m_skin; } } eclass_capture_state((*i).second); StringOutputStream usage(256); usage << "-------- NOTES --------\n"; if(!string_empty((*i).second->m_comments.c_str())) { usage << (*i).second->m_comments.c_str() << "\n"; } usage << "\n-------- KEYS --------\n"; for(EntityClassAttributes::iterator j = (*i).second->m_attributes.begin(); j != (*i).second->m_attributes.end(); ++j) { const char* name = EntityClassAttributePair_getName(*j); const char* description = EntityClassAttributePair_getDescription(*j); if(!string_equal(name, description)) { usage << EntityClassAttributePair_getName(*j) << " : " << EntityClassAttributePair_getDescription(*j) << "\n"; } } (*i).second->m_comments = usage.c_str(); } } m_observers.realise(); } } void unrealise() { if(++m_unrealised == 1) { m_observers.unrealise(); EntityClassDoom3_clear(); } } void attach(ModuleObserver& observer) { m_observers.attach(observer); } void detach(ModuleObserver& observer) { m_observers.detach(observer); } }; EntityClassDoom3 g_EntityClassDoom3; void EntityClassDoom3_attach(ModuleObserver& observer) { g_EntityClassDoom3.attach(observer); } void EntityClassDoom3_detach(ModuleObserver& observer) { g_EntityClassDoom3.detach(observer); } void EntityClassDoom3_realise() { g_EntityClassDoom3.realise(); } void EntityClassDoom3_unrealise() { g_EntityClassDoom3.unrealise(); } void EntityClassDoom3_construct() { GlobalFileSystem().attach(g_EntityClassDoom3); // start by creating the default unknown eclass g_EntityClassDoom3_bad = EClass_Create("UNKNOWN_CLASS", Vector3(0.0f, 0.5f, 0.0f), ""); EntityClassDoom3_realise(); } void EntityClassDoom3_destroy() { EntityClassDoom3_unrealise(); g_EntityClassDoom3_bad->free(g_EntityClassDoom3_bad); GlobalFileSystem().detach(g_EntityClassDoom3); } class EntityClassDoom3Dependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef { }; class EntityClassDoom3API { EntityClassManager m_eclassmanager; public: typedef EntityClassManager Type; STRING_CONSTANT(Name, "doom3"); EntityClassDoom3API() { EntityClassDoom3_construct(); m_eclassmanager.findOrInsert = &EntityClassDoom3_findOrInsert; m_eclassmanager.findListType = &EntityClassDoom3_findListType; m_eclassmanager.forEach = &EntityClassDoom3_forEach; m_eclassmanager.attach = &EntityClassDoom3_attach; m_eclassmanager.detach = &EntityClassDoom3_detach; m_eclassmanager.realise = &EntityClassDoom3_realise; m_eclassmanager.unrealise = &EntityClassDoom3_unrealise; } ~EntityClassDoom3API() { EntityClassDoom3_destroy(); } EntityClassManager* getTable() { return &m_eclassmanager; } }; #include "modulesystem/singletonmodule.h" #include "modulesystem/moduleregistry.h" typedef SingletonModule EntityClassDoom3Module; typedef Static StaticEntityClassDoom3Module; StaticRegisterModule staticRegisterEntityClassDoom3(StaticEntityClassDoom3Module::instance());