Index: binaries/data/mods/public/simulation/templates/special/generic/builder_entities.xml =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/templates/special/generic/builder_entities.xml @@ -0,0 +1,29 @@ + + + structures/{civ}/civil_centre + structures/{civ}/crannog + structures/{civ}/military_colony + structures/{civ}/house + structures/{civ}/apartment + structures/{civ}/storehouse + structures/{civ}/farmstead + structures/{civ}/field + structures/{civ}/corral + structures/{civ}/dock + structures/{civ}/barracks + structures/{civ}/stable + structures/{civ}/elephant_stable + structures/{civ}/arsenal + structures/{civ}/forge + structures/{civ}/temple + structures/{civ}/market + structures/{civ}/outpost + structures/{civ}/sentry_tower + structures/{civ}/defense_tower + structures/{civ}/fortress + structures/wallset_palisade + structures/{civ}/wallset_siege + structures/{civ}/wallset_stone + structures/{civ}/theater + structures/{civ}/wonder + Index: binaries/data/mods/public/simulation/templates/template_unit_infantry.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_unit_infantry.xml +++ binaries/data/mods/public/simulation/templates/template_unit_infantry.xml @@ -18,34 +18,7 @@ 1.0 - - structures/{civ}/civil_centre - structures/{civ}/crannog - structures/{civ}/military_colony - structures/{civ}/house - structures/{civ}/apartment - structures/{civ}/storehouse - structures/{civ}/farmstead - structures/{civ}/field - structures/{civ}/corral - structures/{civ}/dock - structures/{civ}/barracks - structures/{civ}/stable - structures/{civ}/elephant_stable - structures/{civ}/arsenal - structures/{civ}/forge - structures/{civ}/temple - structures/{civ}/market - structures/{civ}/outpost - structures/{civ}/sentry_tower - structures/{civ}/defense_tower - structures/{civ}/fortress - structures/wallset_palisade - structures/{civ}/wallset_siege - structures/{civ}/wallset_stone - structures/{civ}/theater - structures/{civ}/wonder - + 12 Index: binaries/data/mods/public/simulation/templates/template_unit_support_female_citizen.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_unit_support_female_citizen.xml +++ binaries/data/mods/public/simulation/templates/template_unit_support_female_citizen.xml @@ -20,34 +20,7 @@ 1.0 - - structures/{civ}/civil_centre - structures/{civ}/crannog - structures/{civ}/military_colony - structures/{civ}/house - structures/{civ}/apartment - structures/{civ}/storehouse - structures/{civ}/farmstead - structures/{civ}/field - structures/{civ}/corral - structures/{civ}/dock - structures/{civ}/barracks - structures/{civ}/stable - structures/{civ}/elephant_stable - structures/{civ}/arsenal - structures/{civ}/forge - structures/{civ}/temple - structures/{civ}/market - structures/{civ}/outpost - structures/{civ}/sentry_tower - structures/{civ}/defense_tower - structures/{civ}/fortress - structures/wallset_palisade - structures/{civ}/wallset_siege - structures/{civ}/wallset_stone - structures/{civ}/theater - structures/{civ}/wonder - + 9 Index: binaries/data/mods/public/simulation/templates/template_unit_support_slave.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_unit_support_slave.xml +++ binaries/data/mods/public/simulation/templates/template_unit_support_slave.xml @@ -2,34 +2,7 @@ 0.5 - - structures/{civ}/civil_centre - structures/{civ}/crannog - structures/{civ}/military_colony - structures/{civ}/house - structures/{civ}/apartment - structures/{civ}/storehouse - structures/{civ}/farmstead - structures/{civ}/field - structures/{civ}/corral - structures/{civ}/dock - structures/{civ}/barracks - structures/{civ}/stable - structures/{civ}/elephant_stable - structures/{civ}/arsenal - structures/{civ}/forge - structures/{civ}/temple - structures/{civ}/market - structures/{civ}/outpost - structures/{civ}/sentry_tower - structures/{civ}/defense_tower - structures/{civ}/fortress - structures/wallset_palisade - structures/{civ}/wallset_siege - structures/{civ}/wallset_stone - structures/{civ}/theater - structures/{civ}/wonder - + 0 Index: source/gui/ObjectTypes/CMiniMap.cpp =================================================================== --- source/gui/ObjectTypes/CMiniMap.cpp +++ source/gui/ObjectTypes/CMiniMap.cpp @@ -744,7 +744,7 @@ float CMiniMap::GetShallowPassageHeight() { float shallowPassageHeight = 0.0f; - CParamNode externalParamNode; + CParamNode externalParamNode(nullptr); CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses"); if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk()) Index: source/gui/Scripting/ScriptFunctions.cpp =================================================================== --- source/gui/Scripting/ScriptFunctions.cpp +++ source/gui/Scripting/ScriptFunctions.cpp @@ -34,6 +34,7 @@ #include "ps/scripting/JSInterface_Mod.h" #include "ps/scripting/JSInterface_ModIo.h" #include "ps/scripting/JSInterface_SavedGame.h" +#include "ps/scripting/JSInterface_TemplateLoader.h" #include "ps/scripting/JSInterface_UserReport.h" #include "ps/scripting/JSInterface_VFS.h" #include "ps/scripting/JSInterface_VisualReplay.h" @@ -70,6 +71,7 @@ JSI_SavedGame::RegisterScriptFunctions(rq); JSI_Simulation::RegisterScriptFunctions(rq); JSI_Sound::RegisterScriptFunctions(rq); + JSI_TemplateLoader::RegisterScriptFunctions(rq); JSI_UserReport::RegisterScriptFunctions(rq); JSI_VFS::RegisterScriptFunctions_GUI(rq); JSI_VisualReplay::RegisterScriptFunctions(rq); Index: source/ps/TemplateLoader.h =================================================================== --- source/ps/TemplateLoader.h +++ source/ps/TemplateLoader.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -43,6 +43,7 @@ */ class CTemplateLoader { + friend class CParamNode; public: CTemplateLoader() { @@ -65,6 +66,8 @@ std::vector FindTemplates(const std::string& path, bool includeSubdirectories, ETemplatesType templatesType) const; private: + CParamNode& GetData(const std::string& templateName); + /** * (Re)loads the given template, regardless of whether it exists already, * and saves into m_TemplateFileData. Also loads any parents that are not yet Index: source/ps/TemplateLoader.cpp =================================================================== --- source/ps/TemplateLoader.cpp +++ source/ps/TemplateLoader.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -27,7 +27,7 @@ static const wchar_t TEMPLATE_ROOT[] = L"simulation/templates/"; static const wchar_t ACTOR_ROOT[] = L"art/actors/"; -static CParamNode NULL_NODE(false); +static CParamNode NULL_NODE(nullptr, false); bool CTemplateLoader::LoadTemplateFile(const std::string& templateName, int depth) { @@ -45,7 +45,7 @@ // Handle special case "actor|foo" if (templateName.find("actor|") == 0) { - ConstructTemplateActor(templateName.substr(6), m_TemplateFileData[templateName]); + ConstructTemplateActor(templateName.substr(6), m_TemplateFileData.try_emplace(templateName, this).first->second); return true; } @@ -74,8 +74,8 @@ if (ok != PSRETURN_OK) return false; // (Xeromyces already logged an error with the full filename) - m_TemplateFileData[templateName] = m_TemplateFileData[baseName]; - CParamNode::LoadXML(m_TemplateFileData[templateName], xero, path.string().c_str()); + m_TemplateFileData.insert_or_assign(templateName, m_TemplateFileData.at(baseName)); + CParamNode::LoadXML(m_TemplateFileData.at(templateName), xero, path.string().c_str()); return true; } @@ -87,32 +87,8 @@ 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("Invalid parent '%s' in entity template '%s'", parentName.c_str(), templateName.c_str()); - return false; - } - - // Ensure the parent is loaded - if (!LoadTemplateFile(parentName, depth+1)) - { - LOGERROR("Failed to load parent '%s' of entity template '%s'", 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()); + CParamNode::LoadXML(m_TemplateFileData.try_emplace(templateName, this).first->second, xero, wstring_from_utf8(templateName).c_str()); return true; } @@ -185,7 +161,19 @@ return NULL_NODE; } - return m_TemplateFileData[templateName]; + return m_TemplateFileData.at(templateName); +} + +CParamNode& CTemplateLoader::GetData(const std::string& templateName) +{ + // Load the template if necessary + if (!LoadTemplateFile(templateName, 0)) + { + LOGERROR("Failed to load entity template '%s'", templateName.c_str()); + return NULL_NODE; + } + + return m_TemplateFileData.at(templateName); } void CTemplateLoader::ConstructTemplateActor(const std::string& actorName, CParamNode& out) Index: source/simulation2/components/CCmpAIManager.cpp =================================================================== --- source/simulation2/components/CCmpAIManager.cpp +++ source/simulation2/components/CCmpAIManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -330,7 +330,7 @@ CParamNode GetTemplate(const std::string& name) { if (!m_TemplateLoader.TemplateExists(name)) - return CParamNode(false); + return CParamNode(&m_TemplateLoader, false); return m_TemplateLoader.GetTemplateFileData(name).GetChild("Entity"); } Index: source/simulation2/components/CCmpPathfinder.cpp =================================================================== --- source/simulation2/components/CCmpPathfinder.cpp +++ source/simulation2/components/CCmpPathfinder.cpp @@ -67,7 +67,7 @@ // Since this is used as a system component (not loaded from an entity template), // we can't use the real paramNode (it won't get handled properly when deserializing), // so load the data from a special XML file. - CParamNode externalParamNode; + CParamNode externalParamNode(nullptr); CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); // Previously all move commands during a turn were Index: source/simulation2/components/CCmpTerritoryManager.cpp =================================================================== --- source/simulation2/components/CCmpTerritoryManager.cpp +++ source/simulation2/components/CCmpTerritoryManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -138,7 +138,7 @@ // Register Relax NG validator CXeromyces::AddValidator(g_VFS, "territorymanager", "simulation/data/territorymanager.rng"); - CParamNode externalParamNode; + CParamNode externalParamNode(nullptr); CParamNode::LoadXML(externalParamNode, L"simulation/data/territorymanager.xml", "territorymanager"); int impassableCost = externalParamNode.GetChild("TerritoryManager").GetChild("ImpassableCost").ToInt(); Index: source/simulation2/system/ComponentManager.cpp =================================================================== --- source/simulation2/system/ComponentManager.cpp +++ source/simulation2/system/ComponentManager.cpp @@ -665,7 +665,7 @@ void CComponentManager::AddSystemComponents(bool skipScriptedComponents, bool skipAI) { - CParamNode noParam; + CParamNode noParam(nullptr); AddComponent(m_SystemEntity, CID_TemplateManager, noParam); AddComponent(m_SystemEntity, CID_CinemaManager, noParam); AddComponent(m_SystemEntity, CID_CommandQueue, noParam); Index: source/simulation2/system/ComponentManagerSerialization.cpp =================================================================== --- source/simulation2/system/ComponentManagerSerialization.cpp +++ source/simulation2/system/ComponentManagerSerialization.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -313,7 +313,7 @@ deserializer.NumberU32_Unbounded("num system component types", numSystemComponentTypes); ICmpTemplateManager* templateManager = NULL; - CParamNode noParam; + CParamNode noParam(nullptr); for (size_t i = 0; i < numSystemComponentTypes; ++i) { Index: source/simulation2/system/ParamNode.h =================================================================== --- source/simulation2/system/ParamNode.h +++ source/simulation2/system/ParamNode.h @@ -32,6 +32,8 @@ class ScriptRequest; +class CTemplateLoader; + /** * An entity initialisation parameter node. * Each node has a text value, plus a number of named child nodes (in a tree structure). @@ -155,7 +157,7 @@ /** * Constructs a new, empty node. */ - CParamNode(bool isOk = true); + CParamNode(CTemplateLoader* templateLoader, bool isOk = true); /** * Loads the XML data specified by @a file into the node @a ret. @@ -274,6 +276,7 @@ * the data getting applied. Used for output to log messages if an error occurs. */ void ApplyLayer(const XMBFile& xmb, const XMBElement& element, const wchar_t* sourceIdentifier = NULL); + void ApplyLayer(const CParamNode& node); void ResetScriptVal(); @@ -283,6 +286,8 @@ ChildrenMap m_Childs; bool m_IsOk; + CTemplateLoader* m_TemplateLoader; + /** * Caches the ToJSVal script representation of this node. */ Index: source/simulation2/system/ParamNode.cpp =================================================================== --- source/simulation2/system/ParamNode.cpp +++ source/simulation2/system/ParamNode.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -23,6 +23,7 @@ #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Filesystem.h" +#include "ps/TemplateLoader.h" #include "ps/XML/Xeromyces.h" #include "scriptinterface/ScriptInterface.h" @@ -31,10 +32,10 @@ #include #include // this isn't in string.hpp in old Boosts -static CParamNode g_NullNode(false); +static CParamNode g_NullNode(nullptr, false); -CParamNode::CParamNode(bool isOk) : - m_IsOk(isOk) +CParamNode::CParamNode(CTemplateLoader* templateLoader, bool isOk) : + m_TemplateLoader(templateLoader), m_IsOk(isOk) { } @@ -75,6 +76,7 @@ bool hasSetValue = false; // Look for special attributes + int at_parent = xmb.GetAttributeID("parent"); int at_disable = xmb.GetAttributeID("disable"); int at_replace = xmb.GetAttributeID("replace"); int at_filtered = xmb.GetAttributeID("filtered"); @@ -93,6 +95,10 @@ { XERO_ITER_ATTR(element, attr) { + if (attr.Name == at_parent) + { + ApplyLayer(m_TemplateLoader->GetTemplateFileData(attr.Value)); + } if (attr.Name == at_disable) { m_Childs.erase(name); @@ -131,7 +137,7 @@ { if (attr.Name == at_datatype && std::wstring(attr.Value.begin(), attr.Value.end()) == L"tokens") { - CParamNode& node = m_Childs[name]; + CParamNode& node = m_Childs.try_emplace(name, m_TemplateLoader).first->second; // Split into tokens std::vector oldTokens; @@ -169,7 +175,7 @@ } // Add this element as a child node - CParamNode& node = m_Childs[name]; + CParamNode& node = m_Childs.try_emplace(name, m_TemplateLoader).first->second; if (op != INVALID) { // TODO: Support parsing of data types other than fixed; log warnings in other cases @@ -211,7 +217,7 @@ { std::string childname = xmb.GetElementString(child.GetNodeName()); if (node.m_Childs.find(childname) != node.m_Childs.end()) - childs[childname] = std::move(node.m_Childs[childname]); + childs.try_emplace(childname, std::move(node.m_Childs.at(childname))); } } @@ -222,14 +228,23 @@ XERO_ITER_ATTR(element, attr) { // Skip special attributes - if (attr.Name == at_replace || attr.Name == at_op || attr.Name == at_merge || attr.Name == at_filtered) + if (attr.Name == at_parent || attr.Name == at_replace || attr.Name == at_op || attr.Name == at_merge || attr.Name == at_filtered) continue; // Add any others std::string attrName = xmb.GetAttributeString(attr.Name); - node.m_Childs["@" + attrName].m_Value = attr.Value.FromUTF8(); + node.m_Childs.try_emplace("@" + attrName, m_TemplateLoader).first->second.m_Value = attr.Value.FromUTF8(); } } +void CParamNode::ApplyLayer(const CParamNode& node) +{ + ResetScriptVal(); + + m_Value = node.m_Value; + for (const std::pair& child : node.m_Childs) + m_Childs.insert_or_assign(child.first, child.second); +} + const CParamNode& CParamNode::GetChild(const char* name) const { ChildrenMap::const_iterator it = m_Childs.find(name);