Index: ps/trunk/binaries/data/mods/_test.sim/simulation/templates/special_filter/foundation.xml =================================================================== --- ps/trunk/binaries/data/mods/_test.sim/simulation/templates/special_filter/foundation.xml +++ ps/trunk/binaries/data/mods/_test.sim/simulation/templates/special_filter/foundation.xml @@ -0,0 +1,44 @@ + + + + + + + + 0 + + + + 1 + + + + + + + + + + true + true + + + + + + + + + + + + + 0 + false + + + + + + Index: ps/trunk/binaries/data/mods/public/simulation/templates/special_filter/construction.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/special_filter/construction.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/special_filter/construction.xml @@ -0,0 +1,7 @@ + + + + + + + Index: ps/trunk/binaries/data/mods/public/simulation/templates/special_filter/corpse.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/special_filter/corpse.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/special_filter/corpse.xml @@ -0,0 +1,31 @@ + + + + + + + + true + + + + + + false + + + + + + + + true + + + + false + + Index: ps/trunk/binaries/data/mods/public/simulation/templates/special_filter/foundation.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/special_filter/foundation.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/special_filter/foundation.xml @@ -0,0 +1,44 @@ + + + + + + + + 0 + + + + + + + + 1 + + + + + + true + true + + + + + + + + + + + + + 0 + false + + + + + + Index: ps/trunk/binaries/data/mods/public/simulation/templates/special_filter/mirage.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/special_filter/mirage.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/special_filter/mirage.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + false + false + false + false + + + + + + + + + Index: ps/trunk/binaries/data/mods/public/simulation/templates/special_filter/preview.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/special_filter/preview.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/special_filter/preview.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + false + + + + + + + + true + true + + + + + + Index: ps/trunk/binaries/data/mods/public/simulation/templates/special_filter/resource.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/special_filter/resource.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/special_filter/resource.xml @@ -0,0 +1,29 @@ + + + + + + + + + true + false + false + false + false + false + false + + + + + + + + + + Index: ps/trunk/source/ps/TemplateLoader.h =================================================================== --- ps/trunk/source/ps/TemplateLoader.h +++ ps/trunk/source/ps/TemplateLoader.h @@ -82,36 +82,6 @@ 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 fogged "mirage" - * entity (position, actor) into a new entity template - */ - void CopyMirageSubset(CParamNode& out, const CParamNode& in); - - /** - * 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 Index: ps/trunk/source/ps/TemplateLoader.cpp =================================================================== --- ps/trunk/source/ps/TemplateLoader.cpp +++ ps/trunk/source/ps/TemplateLoader.cpp @@ -49,93 +49,33 @@ return true; } - // Handle special case "preview|foo" - if (templateName.find("preview|") == 0) + // Handle special case "bar|foo" + size_t pos = templateName.find_first_of('|'); + if (pos != std::string::npos) { - // Load the base entity template, if it wasn't already loaded - std::string baseName = templateName.substr(8); - if (!LoadTemplateFile(baseName, depth+1)) - { - LOGERROR("Failed to load entity template '%s'", baseName.c_str()); - return false; - } - // Copy a subset to the requested template - CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], false); - return true; - } + std::string prefix = templateName.substr(0, pos); + std::string baseName = templateName.substr(pos+1); - // 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("Failed to load entity template '%s'", 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 "mirage|foo" - if (templateName.find("mirage|") == 0) - { - // Load the base entity template, if it wasn't already loaded - std::string baseName = templateName.substr(7); - if (!LoadTemplateFile(baseName, depth+1)) + VfsPath path = VfsPath(TEMPLATE_ROOT) / L"special_filter" / wstring_from_utf8(prefix + ".xml"); + if (!VfsFileExists(path)) { - LOGERROR("Failed to load entity template '%s'", baseName.c_str()); + LOGERROR("Invalid subset '%s'", prefix.c_str()); return false; } - // Copy a subset to the requested template - CopyMirageSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]); - 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("Failed to load entity template '%s'", baseName.c_str()); - return false; - } - // Copy a subset to the requested template - CopyFoundationSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]); - return true; - } + CXeromyces xero; + PSRETURN ok = xero.Load(g_VFS, path); + if (ok != PSRETURN_OK) + return false; // (Xeromyces already logged an error with the full filename) - // 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("Failed to load entity template '%s'", 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("Failed to load entity template '%s'", baseName.c_str()); - return false; - } - // Copy a subset to the requested template - CopyResourceSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]); + m_TemplateFileData[templateName] = m_TemplateFileData[baseName]; + CParamNode::LoadXML(m_TemplateFileData[templateName], xero, path.string().c_str()); return true; } @@ -348,203 +288,3 @@ 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("Visibility"); - 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, "truetrue"); - } - - if (corpse) - { - // Corpses should include decay components and activate them - if (out.GetChild("Entity").GetChild("Decay").IsOk()) - CParamNode::LoadXMLString(out, "true"); - - // 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 (for the owner only) - CParamNode::LoadXMLString(out, "true"); - } -} - -void CTemplateLoader::CopyMirageSubset(CParamNode& out, const CParamNode& in) -{ - // Currently used for mirage entities replacing real ones in fog-of-war - - std::set permittedComponentTypes; - permittedComponentTypes.insert("Footprint"); - permittedComponentTypes.insert("Minimap"); - permittedComponentTypes.insert("Obstruction"); - permittedComponentTypes.insert("Ownership"); - permittedComponentTypes.insert("OverlayRenderer"); - permittedComponentTypes.insert("Position"); - permittedComponentTypes.insert("Selectable"); - permittedComponentTypes.insert("StatusBars"); - permittedComponentTypes.insert("Visibility"); - permittedComponentTypes.insert("VisualActor"); - - CParamNode::LoadXMLString(out, ""); - out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes); - - // Select a subset of identity data. We don't want to have, for example, a CC mirage - // that has also the CC class and then prevents construction of other CCs - std::set identitySubset; - identitySubset.insert("Civ"); - identitySubset.insert("GenericName"); - identitySubset.insert("SpecificName"); - identitySubset.insert("Tooltip"); - identitySubset.insert("History"); - identitySubset.insert("Icon"); - CParamNode identity; - CParamNode::LoadXMLString(identity, ""); - identity.CopyFilteredChildrenOfChild(in.GetChild("Entity"), "Identity", identitySubset); - CParamNode::LoadXMLString(out, (""+utf8_from_wstring(identity.ToXML())+"").c_str()); - - // Mirages obstruction shouldn't block anything - if (out.GetChild("Entity").GetChild("Obstruction").IsOk()) - CParamNode::LoadXMLString(out, "falsefalsefalsefalse"); - - // Set the entity as mirage entity - CParamNode::LoadXMLString(out, ""); -} - -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("Fogging"); - permittedComponentTypes.insert("Armour"); - permittedComponentTypes.insert("Health"); - permittedComponentTypes.insert("Market"); - permittedComponentTypes.insert("StatusBars"); - permittedComponentTypes.insert("OverlayRenderer"); - permittedComponentTypes.insert("Decay"); - permittedComponentTypes.insert("Cost"); - permittedComponentTypes.insert("Sound"); - permittedComponentTypes.insert("Visibility"); - 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"); - // Foundations should not have special vision capabilities either - if (out.GetChild("Entity").GetChild("Vision").GetChild("RevealShore").IsOk()) - CParamNode::LoadXMLString(out, "false"); - } -} - -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. - // Don't emit sound as our samples only apply to living animals. - std::set permittedComponentTypes; - permittedComponentTypes.insert("Ownership"); - permittedComponentTypes.insert("Position"); - permittedComponentTypes.insert("VisualActor"); - permittedComponentTypes.insert("Identity"); - permittedComponentTypes.insert("Minimap"); - permittedComponentTypes.insert("ResourceSupply"); - permittedComponentTypes.insert("Selectable"); - permittedComponentTypes.insert("Footprint"); - permittedComponentTypes.insert("StatusBars"); - permittedComponentTypes.insert("OverlayRenderer"); - permittedComponentTypes.insert("AIProxy"); - - CParamNode::LoadXMLString(out, ""); - out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes); - - // When dying, resources lose the unitMotion component - // This causes them to have no clearance. Since unit obstructions no longer have a radius, - // this makes them unreachable in some cases (see #3530). - // Instead, create a static, unblocking (see #3530 for why) static obstruction. - // TODO: this should probably be generalized as a parameter on entity death or something. - CParamNode::LoadXMLString(out, "truefalsefalsefalsefalsefalsefalse"); -} Index: ps/trunk/source/simulation2/system/ParamNode.h =================================================================== --- ps/trunk/source/simulation2/system/ParamNode.h +++ ps/trunk/source/simulation2/system/ParamNode.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2017 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -58,6 +58,15 @@ * * one two three * + * + * + * + * test + * + * + * example + * + * * * @endcode * then a second like: @@ -75,6 +84,15 @@ * four * -two * + * + * + * example + * + * + * + * text + * + * * * @endcode * is equivalent to loading a single file like: @@ -90,6 +108,15 @@ * * one three four * + * + * + * test + * example + * + * + * text + * + * * * @endcode * @@ -103,7 +130,16 @@ * "Example3": { * "D": "new" * }, - * "Example4": { "@datatype": "tokens", "_string": "one three four" } + * "Example4": { "@datatype": "tokens", "_string": "one three four" }, + * "Example5": { + * "F": { + * "I": "test", + * "K": "example" + * }, + * "H": { + * "J": "text" + * } + * } * } * } * @endcode @@ -146,14 +182,6 @@ static PSRETURN LoadXMLString(CParamNode& ret, const char* xml, const wchar_t* sourceIdentifier = NULL); /** - * Finds the childs named @a name from @a src and from @a this, and copies the source child's children - * which are in the @a permitted set into this node's child. - * Intended for use as a filtered clone of XML files. - * @a this and @a src must have childs named @a name. - */ - void CopyFilteredChildrenOfChild(const CParamNode& src, const char* name, const std::set& permitted); - - /** * Returns the (unique) child node with the given name, or a node with IsOk() == false if there is none. */ const CParamNode& GetChild(const char* name) const; Index: ps/trunk/source/simulation2/system/ParamNode.cpp =================================================================== --- ps/trunk/source/simulation2/system/ParamNode.cpp +++ ps/trunk/source/simulation2/system/ParamNode.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2017 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -76,6 +76,8 @@ // Look for special attributes int at_disable = xmb.GetAttributeID("disable"); int at_replace = xmb.GetAttributeID("replace"); + int at_filtered = xmb.GetAttributeID("filtered"); + int at_merge = xmb.GetAttributeID("merge"); int at_op = xmb.GetAttributeID("op"); int at_datatype = xmb.GetAttributeID("datatype"); enum op { @@ -84,6 +86,8 @@ MUL } op = INVALID; bool replacing = false; + bool filtering = false; + bool merging = false; { XERO_ITER_ATTR(element, attr) { @@ -97,6 +101,16 @@ m_Childs.erase(name); replacing = true; } + else if (attr.Name == at_filtered) + { + filtering = true; + } + else if (attr.Name == at_merge) + { + if (m_Childs.find(name) == m_Childs.end()) + return; + merging = true; + } else if (attr.Name == at_op) { if (std::wstring(attr.Value.begin(), attr.Value.end()) == L"add") @@ -157,6 +171,7 @@ // TODO: Support parsing of data types other than fixed; log warnings in other cases fixed oldval = node.ToFixed(); fixed mod = fixed::FromString(CStrW(value)); + switch (op) { case ADD: @@ -168,24 +183,37 @@ } hasSetValue = true; } - if (!hasSetValue) + + if (!hasSetValue && !merging) node.m_Value = value; // We also need to reset node's script val, even if it has no children // or if the attributes change. node.ResetScriptVal(); + // For the filtered case + ChildrenMap childs; + // Recurse through the element's children XERO_ITER_EL(element, child) { node.ApplyLayer(xmb, child, sourceIdentifier); + if (filtering) + { + 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]); + } } + if (filtering) + node.m_Childs.swap(childs); + // Add the element's attributes, prefixing names with "@" XERO_ITER_ATTR(element, attr) { // Skip special attributes - if (attr.Name == at_replace || attr.Name == at_op) + if (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); @@ -193,21 +221,6 @@ } } -void CParamNode::CopyFilteredChildrenOfChild(const CParamNode& src, const char* name, const std::set& permitted) -{ - ResetScriptVal(); - - ChildrenMap::iterator dstChild = m_Childs.find(name); - ChildrenMap::const_iterator srcChild = src.m_Childs.find(name); - if (dstChild == m_Childs.end() || srcChild == src.m_Childs.end()) - return; // error - - ChildrenMap::const_iterator it = srcChild->second.m_Childs.begin(); - for (; it != srcChild->second.m_Childs.end(); ++it) - if (permitted.count(it->first)) - dstChild->second.m_Childs[it->first] = it->second; -} - const CParamNode& CParamNode::GetChild(const char* name) const { ChildrenMap::const_iterator it = m_Childs.find(name); Index: ps/trunk/source/simulation2/tests/test_ParamNode.h =================================================================== --- ps/trunk/source/simulation2/tests/test_ParamNode.h +++ ps/trunk/source/simulation2/tests/test_ParamNode.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2017 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -135,6 +135,35 @@ TS_ASSERT_WSTR_EQUALS(node.ToXML(), L"Y X"); } + void test_overlay_filtered() + { + CParamNode node; + TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, " toberemoved "), PSRETURN_OK); + TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, " "), PSRETURN_OK); + TS_ASSERT_WSTR_EQUALS(node.ToXML(), L""); + + CParamNode node2; + TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node2, " bcde "), PSRETURN_OK); + TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node2, " c2 "), PSRETURN_OK); + TS_ASSERT_WSTR_EQUALS(node2.ToXML(), L"bc2"); + } + + void test_overlay_merge() + { + CParamNode node; + TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, " foobar foo "), PSRETURN_OK); + TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, " testbaz willnotbeincluded textmore text "), PSRETURN_OK); + TS_ASSERT_WSTR_EQUALS(node.ToXML(), L"testbarbazmore texttextfoo"); + } + + void test_overlay_filtered_merge() + { + CParamNode node; + TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, " 1200 "), PSRETURN_OK); + TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, " bar 1 "), PSRETURN_OK); + TS_ASSERT_WSTR_EQUALS(node.ToXML(), L"11200bar"); + } + void test_types() { CParamNode node;