From 567917bf4147f98e1a37e3f8b3995d0eeb9f6bd7 Mon Sep 17 00:00:00 2001 From: sanderd17 Date: Sat, 7 Jun 2014 12:12:53 +0000 Subject: [PATCH] Give RMS access to the simulation templates. Fixes #1589. Patch by Itms. This was SVN commit r15306. --- source/graphics/MapGenerator.cpp | 30 +- source/graphics/MapGenerator.h | 7 +- source/ps/TemplateLoader.cpp | 409 +++++++++++++++++ source/ps/TemplateLoader.h | 105 +++++ .../components/CCmpTemplateManager.cpp | 420 +----------------- 5 files changed, 562 insertions(+), 409 deletions(-) create mode 100644 source/ps/TemplateLoader.cpp create mode 100644 source/ps/TemplateLoader.h diff --git a/source/graphics/MapGenerator.cpp b/source/graphics/MapGenerator.cpp index a2c926178b..75b0207f02 100644 --- a/source/graphics/MapGenerator.cpp +++ b/source/graphics/MapGenerator.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2014 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -91,6 +91,9 @@ bool CMapGeneratorWorker::Run() m_ScriptInterface->RegisterFunction("SetProgress"); m_ScriptInterface->RegisterFunction("MaybeGC"); m_ScriptInterface->RegisterFunction, CMapGeneratorWorker::GetCivData>("GetCivData"); + m_ScriptInterface->RegisterFunction("GetTemplate"); + m_ScriptInterface->RegisterFunction, std::string, bool, CMapGeneratorWorker::FindTemplates>("FindTemplates"); + m_ScriptInterface->RegisterFunction, std::string, bool, CMapGeneratorWorker::FindActorTemplates>("FindActorTemplates"); // TODO: This code is a bit ugly because we have to ensure that CScriptValRooted gets destroyed before the ScriptInterface. // In the future we should work more with the standard JSAPI types for rooting on the stack, which should avoid such problems. @@ -220,6 +223,31 @@ std::vector CMapGeneratorWorker::GetCivData(ScriptInterface::CxPriv } +CParamNode CMapGeneratorWorker::GetTemplate(ScriptInterface::CxPrivate* pCxPrivate, std::string templateName) +{ + // TODO: Find a way to validate templates outside of the simulation. + // This should be implemented in TemplateLoader though. + + CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); + const CParamNode& templateRoot = self->m_TemplateLoader.GetTemplateFileData(templateName).GetChild("Entity"); + if (!templateRoot.IsOk()) + LOGERROR(L"Invalid template found for '%hs'", templateName.c_str()); + + return templateRoot; +} + +std::vector CMapGeneratorWorker::FindTemplates(ScriptInterface::CxPrivate* pCxPrivate, std::string path, bool includeSubdirectories) +{ + CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); + return self->m_TemplateLoader.FindTemplates(path, includeSubdirectories, SIMULATION_TEMPLATES); +} + +std::vector CMapGeneratorWorker::FindActorTemplates(ScriptInterface::CxPrivate* pCxPrivate, std::string path, bool includeSubdirectories) +{ + CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData); + return self->m_TemplateLoader.FindTemplates(path, includeSubdirectories, ACTOR_TEMPLATES); +} + bool CMapGeneratorWorker::LoadScripts(const std::wstring& libraryName) { // Ignore libraries that are already loaded diff --git a/source/graphics/MapGenerator.h b/source/graphics/MapGenerator.h index 4657cf42e0..525cf667d1 100644 --- a/source/graphics/MapGenerator.h +++ b/source/graphics/MapGenerator.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2013 Wildfire Games. +/* Copyright (C) 2014 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "ps/FileIo.h" #include "ps/ThreadUtil.h" +#include "ps/TemplateLoader.h" #include "scriptinterface/ScriptInterface.h" #include @@ -125,6 +126,9 @@ private: static void SetProgress(ScriptInterface::CxPrivate* pCxPrivate, int progress); static void MaybeGC(ScriptInterface::CxPrivate* pCxPrivate); static std::vector GetCivData(ScriptInterface::CxPrivate* pCxPrivate); + static CParamNode GetTemplate(ScriptInterface::CxPrivate* pCxPrivate, std::string templateName); + static std::vector FindTemplates(ScriptInterface::CxPrivate* pCxPrivate, std::string path, bool includeSubdirectories); + static std::vector FindActorTemplates(ScriptInterface::CxPrivate* pCxPrivate, std::string path, bool includeSubdirectories); std::set m_LoadedLibraries; shared_ptr m_MapData; @@ -133,6 +137,7 @@ private: ScriptInterface* m_ScriptInterface; VfsPath m_ScriptPath; std::string m_Settings; + CTemplateLoader m_TemplateLoader; // Thread static void* RunThread(void* data); diff --git a/source/ps/TemplateLoader.cpp b/source/ps/TemplateLoader.cpp new file mode 100644 index 0000000000..b571ed8ade --- /dev/null +++ b/source/ps/TemplateLoader.cpp @@ -0,0 +1,409 @@ +/* Copyright (C) 2014 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. 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. + * + * 0 A.D. 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 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "TemplateLoader.h" + +#include "ps/CLogger.h" +#include "ps/Filesystem.h" +#include "ps/XML/Xeromyces.h" +#include "lib/utf8.h" + +static const wchar_t TEMPLATE_ROOT[] = L"simulation/templates/"; +static const wchar_t ACTOR_ROOT[] = L"art/actors/"; + +static CParamNode NULL_NODE(false); + + +bool CTemplateLoader::LoadTemplateFile(const std::string& templateName, int depth) +{ + // If this file was already loaded, we don't need to do anything + if (m_TemplateFileData.find(templateName) != m_TemplateFileData.end()) + return true; + + // Handle infinite loops more gracefully than running out of stack space and crashing + if (depth > 100) + { + LOGERROR(L"Probable infinite inheritance loop in entity template '%hs'", templateName.c_str()); + return false; + } + + // Handle special case "actor|foo" + if (templateName.find("actor|") == 0) + { + ConstructTemplateActor(templateName.substr(6), m_TemplateFileData[templateName]); + return true; + } + + // Handle special case "preview|foo" + if (templateName.find("preview|") == 0) + { + // Load the base entity template, if it wasn't already loaded + std::string baseName = templateName.substr(8); + if (!LoadTemplateFile(baseName, depth+1)) + { + LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str()); + return false; + } + // Copy a subset to the requested template + CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], false); + return true; + } + + // Handle special case "corpse|foo" + if (templateName.find("corpse|") == 0) + { + // Load the base entity template, if it wasn't already loaded + std::string baseName = templateName.substr(7); + if (!LoadTemplateFile(baseName, depth+1)) + { + LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str()); + return false; + } + // Copy a subset to the requested template + CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], true); + return true; + } + + // Handle special case "foundation|foo" + if (templateName.find("foundation|") == 0) + { + // Load the base entity template, if it wasn't already loaded + std::string baseName = templateName.substr(11); + if (!LoadTemplateFile(baseName, depth+1)) + { + LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str()); + return false; + } + // Copy a subset to the requested template + CopyFoundationSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]); + return true; + } + + // Handle special case "construction|foo" + if (templateName.find("construction|") == 0) + { + // Load the base entity template, if it wasn't already loaded + std::string baseName = templateName.substr(13); + if (!LoadTemplateFile(baseName, depth+1)) + { + LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str()); + return false; + } + // Copy a subset to the requested template + CopyConstructionSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]); + return true; + } + + // Handle special case "resource|foo" + if (templateName.find("resource|") == 0) + { + // Load the base entity template, if it wasn't already loaded + std::string baseName = templateName.substr(9); + if (!LoadTemplateFile(baseName, depth+1)) + { + LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str()); + return false; + } + // Copy a subset to the requested template + CopyResourceSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]); + return true; + } + + // Normal case: templateName is an XML file: + + VfsPath path = VfsPath(TEMPLATE_ROOT) / wstring_from_utf8(templateName + ".xml"); + CXeromyces xero; + PSRETURN ok = xero.Load(g_VFS, path); + if (ok != PSRETURN_OK) + return false; // (Xeromyces already logged an error with the full filename) + + int attr_parent = xero.GetAttributeID("parent"); + CStr parentName = xero.GetRoot().GetAttributes().GetNamedItem(attr_parent); + if (!parentName.empty()) + { + // To prevent needless complexity in template design, we don't allow |-separated strings as parents + if (parentName.find('|') != parentName.npos) + { + LOGERROR(L"Invalid parent '%hs' in entity template '%hs'", parentName.c_str(), templateName.c_str()); + return false; + } + + // Ensure the parent is loaded + if (!LoadTemplateFile(parentName, depth+1)) + { + LOGERROR(L"Failed to load parent '%hs' of entity template '%hs'", parentName.c_str(), templateName.c_str()); + return false; + } + + CParamNode& parentData = m_TemplateFileData[parentName]; + + // Initialise this template with its parent + m_TemplateFileData[templateName] = parentData; + } + + // Load the new file into the template data (overriding parent values) + CParamNode::LoadXML(m_TemplateFileData[templateName], xero, wstring_from_utf8(templateName).c_str()); + + return true; +} + +static Status AddToTemplates(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData) +{ + std::vector& templates = *(std::vector*)cbData; + + // Strip the .xml extension + VfsPath pathstem = pathname.ChangeExtension(L""); + // Strip the root from the path + std::wstring name = pathstem.string().substr(ARRAY_SIZE(TEMPLATE_ROOT)-1); + + // We want to ignore template_*.xml templates, since they should never be built in the editor + if (name.substr(0, 9) == L"template_") + return INFO::OK; + + templates.push_back(std::string(name.begin(), name.end())); + return INFO::OK; +} + +static Status AddActorToTemplates(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData) +{ + std::vector& templates = *(std::vector*)cbData; + + // Strip the root from the path + std::wstring name = pathname.string().substr(ARRAY_SIZE(ACTOR_ROOT)-1); + + templates.push_back("actor|" + std::string(name.begin(), name.end())); + return INFO::OK; +} + +std::vector CTemplateLoader::FindTemplates(const std::string& path, bool includeSubdirectories, ETemplatesType templatesType) +{ + std::vector templates; + + Status ok; + VfsPath templatePath; + + if (templatesType == SIMULATION_TEMPLATES || templatesType == ALL_TEMPLATES) + { + templatePath = VfsPath(TEMPLATE_ROOT) / path; + if (includeSubdirectories) + ok = vfs::ForEachFile(g_VFS, templatePath, AddToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE); + else + ok = vfs::ForEachFile(g_VFS, templatePath, AddToTemplates, (uintptr_t)&templates, L"*.xml"); + WARN_IF_ERR(ok); + } + if (templatesType == ACTOR_TEMPLATES || templatesType == ALL_TEMPLATES) + { + templatePath = VfsPath(ACTOR_ROOT) / path; + if (includeSubdirectories) + ok = vfs::ForEachFile(g_VFS, templatePath, AddActorToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE); + else + ok = vfs::ForEachFile(g_VFS, templatePath, AddActorToTemplates, (uintptr_t)&templates, L"*.xml"); + WARN_IF_ERR(ok); + } + + if (templatesType != SIMULATION_TEMPLATES && templatesType != ACTOR_TEMPLATES && templatesType != ALL_TEMPLATES) + LOGERROR(L"Undefined template type (valid: all, simulation, actor)"); + + return templates; +} + +const CParamNode& CTemplateLoader::GetTemplateFileData(const std::string& templateName) +{ + // Load the template if necessary + if (!LoadTemplateFile(templateName, 0)) + { + LOGERROR(L"Failed to load entity template '%hs'", templateName.c_str()); + return NULL_NODE; + } + + return m_TemplateFileData[templateName]; +} + +void CTemplateLoader::ConstructTemplateActor(const std::string& actorName, CParamNode& out) +{ + // Load the base actor template if necessary + const char* templateName = "special/actor"; + if (!LoadTemplateFile(templateName, 0)) + { + LOGERROR(L"Failed to load entity template '%hs'", templateName); + return; + } + + // Copy the actor template + out = m_TemplateFileData[templateName]; + + // Initialise the actor's name and make it an Atlas selectable entity. + std::wstring actorNameW = wstring_from_utf8(actorName); + std::string name = utf8_from_wstring(CParamNode::EscapeXMLString(actorNameW)); + std::string xml = "" + "" + name + "" + // arbitrary-sized Footprint definition to make actors' selection outlines show up in Atlas + "1.0" + "" + "" + "actor.pngactor_mask.png" + "" + ""; + + CParamNode::LoadXMLString(out, xml.c_str(), actorNameW.c_str()); +} + +void CTemplateLoader::CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse) +{ + // We only want to include components which are necessary (for the visual previewing of an entity) + // and safe (i.e. won't do anything that affects the synchronised simulation state), so additions + // to this list should be carefully considered + std::set permittedComponentTypes; + permittedComponentTypes.insert("Identity"); + permittedComponentTypes.insert("Ownership"); + permittedComponentTypes.insert("Position"); + permittedComponentTypes.insert("VisualActor"); + permittedComponentTypes.insert("Footprint"); + permittedComponentTypes.insert("Obstruction"); + permittedComponentTypes.insert("Decay"); + permittedComponentTypes.insert("BuildRestrictions"); + + // Need these for the Actor Viewer: + permittedComponentTypes.insert("Attack"); + permittedComponentTypes.insert("UnitMotion"); + permittedComponentTypes.insert("Sound"); + + // (This set could be initialised once and reused, but it's not worth the effort) + + CParamNode::LoadXMLString(out, ""); + out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes); + + // Disable the Obstruction component (if there is one) so it doesn't affect pathfinding + // (but can still be used for testing this entity for collisions against others) + if (out.GetChild("Entity").GetChild("Obstruction").IsOk()) + CParamNode::LoadXMLString(out, "false"); + + if (!corpse) + { + // Previews should not cast shadows + if (out.GetChild("Entity").GetChild("VisualActor").IsOk()) + CParamNode::LoadXMLString(out, ""); + + // Previews should always be visible in fog-of-war/etc + CParamNode::LoadXMLString(out, "0falsetrue"); + } + + if (corpse) + { + // Corpses should include decay components and un-inactivate them + if (out.GetChild("Entity").GetChild("Decay").IsOk()) + CParamNode::LoadXMLString(out, ""); + + // Corpses shouldn't display silhouettes (especially since they're often half underground) + if (out.GetChild("Entity").GetChild("VisualActor").IsOk()) + CParamNode::LoadXMLString(out, "false"); + + // Corpses should remain visible in fog-of-war + CParamNode::LoadXMLString(out, "0truefalse"); + } +} + +void CTemplateLoader::CopyFoundationSubset(CParamNode& out, const CParamNode& in) +{ + // TODO: this is all kind of yucky and hard-coded; it'd be nice to have a more generic + // extensible scriptable way to define these subsets + + std::set permittedComponentTypes; + permittedComponentTypes.insert("Ownership"); + permittedComponentTypes.insert("Position"); + permittedComponentTypes.insert("VisualActor"); + permittedComponentTypes.insert("Identity"); + permittedComponentTypes.insert("BuildRestrictions"); + permittedComponentTypes.insert("Obstruction"); + permittedComponentTypes.insert("Selectable"); + permittedComponentTypes.insert("Footprint"); + permittedComponentTypes.insert("Armour"); + permittedComponentTypes.insert("Health"); + permittedComponentTypes.insert("StatusBars"); + permittedComponentTypes.insert("OverlayRenderer"); + permittedComponentTypes.insert("Decay"); + permittedComponentTypes.insert("Cost"); + permittedComponentTypes.insert("Sound"); + permittedComponentTypes.insert("Vision"); + permittedComponentTypes.insert("AIProxy"); + permittedComponentTypes.insert("RallyPoint"); + permittedComponentTypes.insert("RallyPointRenderer"); + + CParamNode::LoadXMLString(out, ""); + out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes); + + // Switch the actor to foundation mode + CParamNode::LoadXMLString(out, ""); + + // Add the Foundation component, to deal with the construction process + CParamNode::LoadXMLString(out, ""); + + // Initialise health to 1 + CParamNode::LoadXMLString(out, "1"); + + // Foundations shouldn't initially block unit movement + if (out.GetChild("Entity").GetChild("Obstruction").IsOk()) + CParamNode::LoadXMLString(out, "truetrue"); + + // Don't provide population bonuses yet (but still do take up population cost) + if (out.GetChild("Entity").GetChild("Cost").IsOk()) + CParamNode::LoadXMLString(out, "0"); + + // Foundations should be visible themselves in fog-of-war if their base template is, + // but shouldn't have any vision range + if (out.GetChild("Entity").GetChild("Vision").IsOk()) + CParamNode::LoadXMLString(out, "0"); +} + +void CTemplateLoader::CopyConstructionSubset(CParamNode& out, const CParamNode& in) +{ + // Currently used for buildings rising during construction + // Mostly serves to filter out components like Vision, UnitAI, etc. + std::set permittedComponentTypes; + permittedComponentTypes.insert("Footprint"); + permittedComponentTypes.insert("Ownership"); + permittedComponentTypes.insert("Position"); + permittedComponentTypes.insert("VisualActor"); + + CParamNode::LoadXMLString(out, ""); + out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes); +} + +void CTemplateLoader::CopyResourceSubset(CParamNode& out, const CParamNode& in) +{ + // Currently used for animals which die and leave a gatherable corpse. + // Mostly serves to filter out components like Vision, UnitAI, etc. + std::set permittedComponentTypes; + permittedComponentTypes.insert("Ownership"); + permittedComponentTypes.insert("Position"); + permittedComponentTypes.insert("VisualActor"); + permittedComponentTypes.insert("Identity"); + permittedComponentTypes.insert("Obstruction"); + permittedComponentTypes.insert("Minimap"); + permittedComponentTypes.insert("ResourceSupply"); + permittedComponentTypes.insert("Selectable"); + permittedComponentTypes.insert("Footprint"); + permittedComponentTypes.insert("StatusBars"); + permittedComponentTypes.insert("OverlayRenderer"); + permittedComponentTypes.insert("Sound"); + permittedComponentTypes.insert("AIProxy"); + + CParamNode::LoadXMLString(out, ""); + out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes); +} diff --git a/source/ps/TemplateLoader.h b/source/ps/TemplateLoader.h new file mode 100644 index 0000000000..0ff532b397 --- /dev/null +++ b/source/ps/TemplateLoader.h @@ -0,0 +1,105 @@ +/* Copyright (C) 2014 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. 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. + * + * 0 A.D. 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 0 A.D. If not, see . + */ + +#ifndef INCLUDED_TEMPLATELOADER +#define INCLUDED_TEMPLATELOADER + +#include "simulation2/system/ParamNode.h" + +enum ETemplatesType +{ + ALL_TEMPLATES, + ACTOR_TEMPLATES, + SIMULATION_TEMPLATES +}; + +/** + * Template loader: Handles the loading of entity template files for: + * - the initialisation and deserialization of entity components in the + * simulation (CmpTemplateManager). + * - access to actor templates, obstruction data, etc. in RMS/RMGEN + * + * Template names are intentionally restricted to ASCII strings for storage/serialization + * efficiency (we have a lot of strings so this is significant); + * they correspond to filenames so they shouldn't contain non-ASCII anyway. + */ +class CTemplateLoader +{ +public: + CTemplateLoader() + { + } + + /** + * Provides the file data for requested template. + */ + const CParamNode &GetTemplateFileData(const std::string& templateName); + + /** + * Returns a list of strings that could be validly passed as @c templateName to LoadTemplateFile. + * (This includes "actor|foo" etc names). + */ + std::vector FindTemplates(const std::string& path, bool includeSubdirectories, ETemplatesType templatesType); + +private: + /** + * (Re)loads the given template, regardless of whether it exists already, + * and saves into m_TemplateFileData. Also loads any parents that are not yet + * loaded. Returns false on error. + * @param templateName XML filename to load (not a |-separated string) + */ + bool LoadTemplateFile(const std::string& templateName, int depth); + + /** + * Constructs a standard static-decorative-object template for the given actor + */ + void ConstructTemplateActor(const std::string& actorName, CParamNode& out); + + /** + * Copy the non-interactive components of an entity template (position, actor, etc) into + * a new entity template + */ + void CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse); + + /** + * Copy the components of an entity template necessary for a construction foundation + * (position, actor, armour, health, etc) into a new entity template + */ + void CopyFoundationSubset(CParamNode& out, const CParamNode& in); + + /** + * Copy the components of an entity template necessary for a non-foundation construction entity + * into a new entity template + */ + void CopyConstructionSubset(CParamNode& out, const CParamNode& in); + + /** + * Copy the components of an entity template necessary for a gatherable resource + * into a new entity template + */ + void CopyResourceSubset(CParamNode& out, const CParamNode& in); + + /** + * Map from template name (XML filename or special |-separated string) to the most recently + * loaded non-broken template data. This includes files that will fail schema validation. + * (Failed loads won't remove existing entries under the same name, so we behave more nicely + * when hotloading broken files) + */ + std::map m_TemplateFileData; +}; + +#endif // INCLUDED_TEMPLATELOADER diff --git a/source/simulation2/components/CCmpTemplateManager.cpp b/source/simulation2/components/CCmpTemplateManager.cpp index f370fbeffe..0c40ef5a56 100644 --- a/source/simulation2/components/CCmpTemplateManager.cpp +++ b/source/simulation2/components/CCmpTemplateManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2013 Wildfire Games. +/* Copyright (C) 2014 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -22,14 +22,11 @@ #include "simulation2/MessageTypes.h" +#include "ps/TemplateLoader.h" + #include "lib/utf8.h" #include "ps/CLogger.h" -#include "ps/Filesystem.h" #include "ps/XML/RelaxNG.h" -#include "ps/XML/Xeromyces.h" - -static const wchar_t TEMPLATE_ROOT[] = L"simulation/templates/"; -static const wchar_t ACTOR_ROOT[] = L"art/actors/"; class CCmpTemplateManager : public ICmpTemplateManager { @@ -136,18 +133,15 @@ public: virtual std::vector GetEntitiesUsingTemplate(std::string templateName); private: + // Template loader + CTemplateLoader m_templateLoader; + // Entity template XML validator RelaxNGValidator m_Validator; // Disable validation, for test cases bool m_DisableValidation; - // Map from template name (XML filename or special |-separated string) to the most recently - // loaded non-broken template data. This includes files that will fail schema validation. - // (Failed loads won't remove existing entries under the same name, so we behave more nicely - // when hotloading broken files) - std::map m_TemplateFileData; - // Map from template name to schema validation status. // (Some files, e.g. inherited parent templates, may not be valid themselves but we still need to load // them and use them; we only reject invalid templates that were requested directly by GetTemplate/etc) @@ -157,31 +151,6 @@ private: // again for deserialization. // TODO: should store player ID etc. std::map m_LatestTemplates; - - // (Re)loads the given template, regardless of whether it exists already, - // and saves into m_TemplateFileData. Also loads any parents that are not yet - // loaded. Returns false on error. - // @param templateName XML filename to load (not a |-separated string) - bool LoadTemplateFile(const std::string& templateName, int depth); - - // Constructs a standard static-decorative-object template for the given actor - void ConstructTemplateActor(const std::string& actorName, CParamNode& out); - - // Copy the non-interactive components of an entity template (position, actor, etc) into - // a new entity template - void CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse); - - // Copy the components of an entity necessary for a construction foundation - // (position, actor, armour, health, etc) into a new entity template - void CopyFoundationSubset(CParamNode& out, const CParamNode& in); - - // Copy the components of an entity necessary for a non-foundation construction entity - // into a new entity template - void CopyConstructionSubset(CParamNode& out, const CParamNode& in); - - // Copy the components of an entity necessary for a gatherable resource - // into a new entity template - void CopyResourceSubset(CParamNode& out, const CParamNode& in); }; REGISTER_COMPONENT_TYPE(TemplateManager) @@ -201,19 +170,16 @@ const CParamNode* CCmpTemplateManager::LoadTemplate(entity_id_t ent, const std:: const CParamNode* CCmpTemplateManager::GetTemplate(std::string templateName) { - // Load the template if necessary - if (!LoadTemplateFile(templateName, 0)) - { - LOGERROR(L"Failed to load entity template '%hs'", templateName.c_str()); + const CParamNode& fileData = m_templateLoader.GetTemplateFileData(templateName); + if (!fileData.IsOk()) return NULL; - } if (!m_DisableValidation) { // Compute validity, if it's not computed before if (m_TemplateSchemaValidity.find(templateName) == m_TemplateSchemaValidity.end()) { - m_TemplateSchemaValidity[templateName] = m_Validator.Validate(wstring_from_utf8(templateName), m_TemplateFileData[templateName].ToXML()); + m_TemplateSchemaValidity[templateName] = m_Validator.Validate(wstring_from_utf8(templateName), fileData.ToXML()); // Show error on the first failure to validate the template if (!m_TemplateSchemaValidity[templateName]) @@ -224,7 +190,7 @@ const CParamNode* CCmpTemplateManager::GetTemplate(std::string templateName) return NULL; } - const CParamNode& templateRoot = m_TemplateFileData[templateName].GetChild("Entity"); + const CParamNode& templateRoot = fileData.GetChild("Entity"); if (!templateRoot.IsOk()) { // The validator should never let this happen @@ -237,14 +203,7 @@ const CParamNode* CCmpTemplateManager::GetTemplate(std::string templateName) const CParamNode* CCmpTemplateManager::GetTemplateWithoutValidation(std::string templateName) { - // Load the template if necessary - if (!LoadTemplateFile(templateName, 0)) - { - LOGERROR(L"Failed to load entity template '%hs'", templateName.c_str()); - return NULL; - } - - const CParamNode& templateRoot = m_TemplateFileData[templateName].GetChild("Entity"); + const CParamNode& templateRoot = m_templateLoader.GetTemplateFileData(templateName).GetChild("Entity"); if (!templateRoot.IsOk()) return NULL; @@ -267,218 +226,10 @@ std::string CCmpTemplateManager::GetCurrentTemplateName(entity_id_t ent) return it->second; } -bool CCmpTemplateManager::LoadTemplateFile(const std::string& templateName, int depth) -{ - // If this file was already loaded, we don't need to do anything - if (m_TemplateFileData.find(templateName) != m_TemplateFileData.end()) - return true; - - // Handle infinite loops more gracefully than running out of stack space and crashing - if (depth > 100) - { - LOGERROR(L"Probable infinite inheritance loop in entity template '%hs'", templateName.c_str()); - return false; - } - - // Handle special case "actor|foo" - if (templateName.find("actor|") == 0) - { - ConstructTemplateActor(templateName.substr(6), m_TemplateFileData[templateName]); - return true; - } - - // Handle special case "preview|foo" - if (templateName.find("preview|") == 0) - { - // Load the base entity template, if it wasn't already loaded - std::string baseName = templateName.substr(8); - if (!LoadTemplateFile(baseName, depth+1)) - { - LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str()); - return false; - } - // Copy a subset to the requested template - CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], false); - return true; - } - - // Handle special case "corpse|foo" - if (templateName.find("corpse|") == 0) - { - // Load the base entity template, if it wasn't already loaded - std::string baseName = templateName.substr(7); - if (!LoadTemplateFile(baseName, depth+1)) - { - LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str()); - return false; - } - // Copy a subset to the requested template - CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], true); - return true; - } - - // Handle special case "foundation|foo" - if (templateName.find("foundation|") == 0) - { - // Load the base entity template, if it wasn't already loaded - std::string baseName = templateName.substr(11); - if (!LoadTemplateFile(baseName, depth+1)) - { - LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str()); - return false; - } - // Copy a subset to the requested template - CopyFoundationSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]); - return true; - } - - // Handle special case "construction|foo" - if (templateName.find("construction|") == 0) - { - // Load the base entity template, if it wasn't already loaded - std::string baseName = templateName.substr(13); - if (!LoadTemplateFile(baseName, depth+1)) - { - LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str()); - return false; - } - // Copy a subset to the requested template - CopyConstructionSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]); - return true; - } - - // Handle special case "resource|foo" - if (templateName.find("resource|") == 0) - { - // Load the base entity template, if it wasn't already loaded - std::string baseName = templateName.substr(9); - if (!LoadTemplateFile(baseName, depth+1)) - { - LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str()); - return false; - } - // Copy a subset to the requested template - CopyResourceSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]); - return true; - } - - // Normal case: templateName is an XML file: - - VfsPath path = VfsPath(TEMPLATE_ROOT) / wstring_from_utf8(templateName + ".xml"); - CXeromyces xero; - PSRETURN ok = xero.Load(g_VFS, path); - if (ok != PSRETURN_OK) - return false; // (Xeromyces already logged an error with the full filename) - - int attr_parent = xero.GetAttributeID("parent"); - CStr parentName = xero.GetRoot().GetAttributes().GetNamedItem(attr_parent); - if (!parentName.empty()) - { - // To prevent needless complexity in template design, we don't allow |-separated strings as parents - if (parentName.find('|') != parentName.npos) - { - LOGERROR(L"Invalid parent '%hs' in entity template '%hs'", parentName.c_str(), templateName.c_str()); - return false; - } - - // Ensure the parent is loaded - if (!LoadTemplateFile(parentName, depth+1)) - { - LOGERROR(L"Failed to load parent '%hs' of entity template '%hs'", parentName.c_str(), templateName.c_str()); - return false; - } - - CParamNode& parentData = m_TemplateFileData[parentName]; - - // Initialise this template with its parent - m_TemplateFileData[templateName] = parentData; - } - - // Load the new file into the template data (overriding parent values) - CParamNode::LoadXML(m_TemplateFileData[templateName], xero, wstring_from_utf8(templateName).c_str()); - - return true; -} - -void CCmpTemplateManager::ConstructTemplateActor(const std::string& actorName, CParamNode& out) -{ - // Load the base actor template if necessary - const char* templateName = "special/actor"; - if (!LoadTemplateFile(templateName, 0)) - { - LOGERROR(L"Failed to load entity template '%hs'", templateName); - return; - } - - // Copy the actor template - out = m_TemplateFileData[templateName]; - - // Initialise the actor's name and make it an Atlas selectable entity. - std::wstring actorNameW = wstring_from_utf8(actorName); - std::string name = utf8_from_wstring(CParamNode::EscapeXMLString(actorNameW)); - std::string xml = "" - "" + name + "" - // arbitrary-sized Footprint definition to make actors' selection outlines show up in Atlas - "1.0" - "" - "" - "actor.pngactor_mask.png" - "" - ""; - - CParamNode::LoadXMLString(out, xml.c_str(), actorNameW.c_str()); -} - -static Status AddToTemplates(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData) -{ - std::vector& templates = *(std::vector*)cbData; - - // Strip the .xml extension - VfsPath pathstem = pathname.ChangeExtension(L""); - // Strip the root from the path - std::wstring name = pathstem.string().substr(ARRAY_SIZE(TEMPLATE_ROOT)-1); - - // We want to ignore template_*.xml templates, since they should never be built in the editor - if (name.substr(0, 9) == L"template_") - return INFO::OK; - - templates.push_back(std::string(name.begin(), name.end())); - return INFO::OK; -} - -static Status AddActorToTemplates(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData) -{ - std::vector& templates = *(std::vector*)cbData; - - // Strip the root from the path - std::wstring name = pathname.string().substr(ARRAY_SIZE(ACTOR_ROOT)-1); - - templates.push_back("actor|" + std::string(name.begin(), name.end())); - return INFO::OK; -} - std::vector CCmpTemplateManager::FindAllTemplates(bool includeActors) { - // TODO: eventually this should probably read all the template files and look for flags to - // determine which should be displayed in the editor (and in what categories etc); for now we'll - // just return all the files - - std::vector templates; - - Status ok; - - // Find all the normal entity templates first - ok = vfs::ForEachFile(g_VFS, TEMPLATE_ROOT, AddToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE); - WARN_IF_ERR(ok); - - if (includeActors) - { - // Add all the actors too - ok = vfs::ForEachFile(g_VFS, ACTOR_ROOT, AddActorToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE); - WARN_IF_ERR(ok); - } - - return templates; + ETemplatesType templatesType = includeActors ? ALL_TEMPLATES : SIMULATION_TEMPLATES; + return m_templateLoader.FindTemplates("", true, templatesType); } /** @@ -494,148 +245,3 @@ std::vector CCmpTemplateManager::GetEntitiesUsingTemplate(std::stri } return entities; } - -void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse) -{ - // We only want to include components which are necessary (for the visual previewing of an entity) - // and safe (i.e. won't do anything that affects the synchronised simulation state), so additions - // to this list should be carefully considered - std::set permittedComponentTypes; - permittedComponentTypes.insert("Identity"); - permittedComponentTypes.insert("Ownership"); - permittedComponentTypes.insert("Position"); - permittedComponentTypes.insert("VisualActor"); - permittedComponentTypes.insert("Footprint"); - permittedComponentTypes.insert("Obstruction"); - permittedComponentTypes.insert("Decay"); - permittedComponentTypes.insert("BuildRestrictions"); - - // Need these for the Actor Viewer: - permittedComponentTypes.insert("Attack"); - permittedComponentTypes.insert("UnitMotion"); - permittedComponentTypes.insert("Sound"); - - // (This set could be initialised once and reused, but it's not worth the effort) - - CParamNode::LoadXMLString(out, ""); - out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes); - - // Disable the Obstruction component (if there is one) so it doesn't affect pathfinding - // (but can still be used for testing this entity for collisions against others) - if (out.GetChild("Entity").GetChild("Obstruction").IsOk()) - CParamNode::LoadXMLString(out, "false"); - - if (!corpse) - { - // Previews should not cast shadows - if (out.GetChild("Entity").GetChild("VisualActor").IsOk()) - CParamNode::LoadXMLString(out, ""); - - // Previews should always be visible in fog-of-war/etc - CParamNode::LoadXMLString(out, "0falsetrue"); - } - - if (corpse) - { - // Corpses should include decay components and un-inactivate them - if (out.GetChild("Entity").GetChild("Decay").IsOk()) - CParamNode::LoadXMLString(out, ""); - - // Corpses shouldn't display silhouettes (especially since they're often half underground) - if (out.GetChild("Entity").GetChild("VisualActor").IsOk()) - CParamNode::LoadXMLString(out, "false"); - - // Corpses should remain visible in fog-of-war - CParamNode::LoadXMLString(out, "0truefalse"); - } -} - -void CCmpTemplateManager::CopyFoundationSubset(CParamNode& out, const CParamNode& in) -{ - // TODO: this is all kind of yucky and hard-coded; it'd be nice to have a more generic - // extensible scriptable way to define these subsets - - std::set permittedComponentTypes; - permittedComponentTypes.insert("Ownership"); - permittedComponentTypes.insert("Position"); - permittedComponentTypes.insert("VisualActor"); - permittedComponentTypes.insert("Identity"); - permittedComponentTypes.insert("BuildRestrictions"); - permittedComponentTypes.insert("Obstruction"); - permittedComponentTypes.insert("Selectable"); - permittedComponentTypes.insert("Footprint"); - permittedComponentTypes.insert("Armour"); - permittedComponentTypes.insert("Health"); - permittedComponentTypes.insert("StatusBars"); - permittedComponentTypes.insert("OverlayRenderer"); - permittedComponentTypes.insert("Decay"); - permittedComponentTypes.insert("Cost"); - permittedComponentTypes.insert("Sound"); - permittedComponentTypes.insert("Vision"); - permittedComponentTypes.insert("AIProxy"); - permittedComponentTypes.insert("RallyPoint"); - permittedComponentTypes.insert("RallyPointRenderer"); - - CParamNode::LoadXMLString(out, ""); - out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes); - - // Switch the actor to foundation mode - CParamNode::LoadXMLString(out, ""); - - // Add the Foundation component, to deal with the construction process - CParamNode::LoadXMLString(out, ""); - - // Initialise health to 1 - CParamNode::LoadXMLString(out, "1"); - - // Foundations shouldn't initially block unit movement - if (out.GetChild("Entity").GetChild("Obstruction").IsOk()) - CParamNode::LoadXMLString(out, "truetrue"); - - // Don't provide population bonuses yet (but still do take up population cost) - if (out.GetChild("Entity").GetChild("Cost").IsOk()) - CParamNode::LoadXMLString(out, "0"); - - // Foundations should be visible themselves in fog-of-war if their base template is, - // but shouldn't have any vision range - if (out.GetChild("Entity").GetChild("Vision").IsOk()) - CParamNode::LoadXMLString(out, "0"); -} - -void CCmpTemplateManager::CopyConstructionSubset(CParamNode& out, const CParamNode& in) -{ - // Currently used for buildings rising during construction - // Mostly serves to filter out components like Vision, UnitAI, etc. - std::set permittedComponentTypes; - permittedComponentTypes.insert("Footprint"); - permittedComponentTypes.insert("Ownership"); - permittedComponentTypes.insert("Position"); - permittedComponentTypes.insert("VisualActor"); - - CParamNode::LoadXMLString(out, ""); - out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes); -} - - -void CCmpTemplateManager::CopyResourceSubset(CParamNode& out, const CParamNode& in) -{ - // Currently used for animals which die and leave a gatherable corpse. - // Mostly serves to filter out components like Vision, UnitAI, etc. - std::set permittedComponentTypes; - permittedComponentTypes.insert("Ownership"); - permittedComponentTypes.insert("Position"); - permittedComponentTypes.insert("VisualActor"); - permittedComponentTypes.insert("Identity"); - permittedComponentTypes.insert("Obstruction"); - permittedComponentTypes.insert("Minimap"); - permittedComponentTypes.insert("ResourceSupply"); - permittedComponentTypes.insert("Selectable"); - permittedComponentTypes.insert("Footprint"); - permittedComponentTypes.insert("StatusBars"); - permittedComponentTypes.insert("OverlayRenderer"); - permittedComponentTypes.insert("Sound"); - permittedComponentTypes.insert("AIProxy"); - - CParamNode::LoadXMLString(out, ""); - out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes); -}