/* 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) do { if (!( expression)) { printParseError(FILE_LINE "\nparse failed: " #expression "\n"); return false; } } while (0) 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); if (string_equal_n(key, "editor_", 7)) { globalErrorStream() << "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", makeCallbackF(EntityClassDoom3_loadFile)); { 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());