From 2d06008d6b32499a28a547abd39946ff1b9fd83f Mon Sep 17 00:00:00 2001
From: leper <leper@wildfiregames.com>
Date: Thu, 25 Feb 2016 15:47:31 +0100
Subject: [PATCH] Make special templates (foo|templatename) (apart from actor|)
 more moddable. Fixes #2951.

This moves template additions to xml files.
Permitted components are now done via filtered and merge.
For adding new nodes while ignoring possible previous values use replace.

Some explanation on how to use the new filter templates:
 * Previously permitted component types should be replaced by
   <Foo merge=""/>, which does not add the <Foo> node if it wasn't present.
 * To actually remove the other components the encompassing node should
   specify filtered="", which will remove all nodes which are present in the
   original, but not in the filter.
 * Additions that should be performed while ignoring a possible original
   node should specify replace="".
 * To add something to an existing node (or add that node if it does not exist)
   just use <Foo>...</Foo>.
---
 .../templates/special_filter/foundation.xml        |  45 +++
 .../templates/special_filter/construction.xml      |   7 +
 .../simulation/templates/special_filter/corpse.xml |  31 ++
 .../templates/special_filter/foundation.xml        |  45 +++
 .../simulation/templates/special_filter/mirage.xml |  21 ++
 .../templates/special_filter/preview.xml           |  29 ++
 .../templates/special_filter/resource.xml          |  29 ++
 source/ps/TemplateLoader.cpp                       | 317 ++-------------------
 source/ps/TemplateLoader.h                         |  34 +--
 source/simulation2/system/ParamNode.cpp            |  27 +-
 source/simulation2/system/ParamNode.h              |  40 ++-
 source/simulation2/tests/test_ParamNode.h          |  18 +-
 12 files changed, 320 insertions(+), 323 deletions(-)
 create mode 100644 binaries/data/mods/_test.sim/simulation/templates/special_filter/foundation.xml
 create mode 100644 binaries/data/mods/public/simulation/templates/special_filter/construction.xml
 create mode 100644 binaries/data/mods/public/simulation/templates/special_filter/corpse.xml
 create mode 100644 binaries/data/mods/public/simulation/templates/special_filter/foundation.xml
 create mode 100644 binaries/data/mods/public/simulation/templates/special_filter/mirage.xml
 create mode 100644 binaries/data/mods/public/simulation/templates/special_filter/preview.xml
 create mode 100644 binaries/data/mods/public/simulation/templates/special_filter/resource.xml

diff --git a/binaries/data/mods/_test.sim/simulation/templates/special_filter/foundation.xml b/binaries/data/mods/_test.sim/simulation/templates/special_filter/foundation.xml
new file mode 100644
index 0000000..e294934
--- /dev/null
+++ b/binaries/data/mods/_test.sim/simulation/templates/special_filter/foundation.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Entity filtered="">
+  <AIProxy merge=""/>
+  <Armour merge=""/>
+  <BuildRestrictions merge=""/>
+  <!-- Don't provide population bonuses yet (but still do take up population cost) -->
+  <Cost merge="">
+    <PopulationBonus>0</PopulationBonus>
+  </Cost>
+  <Decay merge=""/>
+  <!-- Initialise health to 1 -->
+  <Health>
+    <Initial>1</Initial>
+  </Health>
+  <Fogging merge=""/>
+  <Footprint merge=""/>
+  <!-- Add the Foundation component, to deal with the construction process -->
+  <Foundation replace=""/>
+  <Health merge=""/>
+  <Identity merge=""/>
+  <!-- Foundations shouldn't initially block unit movement -->
+  <Obstruction merge="">
+    <DisableBlockMovement>true</DisableBlockMovement>
+    <DisableBlockPathfinding>true</DisableBlockPathfinding>
+  </Obstruction>
+  <OverlayRenderer merge=""/>
+  <Ownership merge=""/>
+  <Position merge=""/>
+  <RallyPoint merge=""/>
+  <RallyPointRenderer merge=""/>
+  <Selectable merge=""/>
+  <Sound merge=""/>
+  <StatusBars merge=""/>
+  <Visibility merge=""/>
+  <!-- Foundations should be visible themselves in fog-of-war if their base template is,
+       but shouldn't have any vision range -->
+  <Vision merge="">
+    <Range>0</Range>
+    <RevealShore merge="">false</RevealShore>
+  </Vision>
+  <!-- Switch the actor to foundation mode -->
+  <VisualActor>
+    <Foundation/>
+  </VisualActor>
+</Entity>
diff --git a/binaries/data/mods/public/simulation/templates/special_filter/construction.xml b/binaries/data/mods/public/simulation/templates/special_filter/construction.xml
new file mode 100644
index 0000000..c7f7da7
--- /dev/null
+++ b/binaries/data/mods/public/simulation/templates/special_filter/construction.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Entity filtered="">
+  <Footprint merge=""/>
+  <Ownership merge=""/>
+  <Position merge=""/>
+  <VisualActor merge=""/>
+</Entity>
diff --git a/binaries/data/mods/public/simulation/templates/special_filter/corpse.xml b/binaries/data/mods/public/simulation/templates/special_filter/corpse.xml
new file mode 100644
index 0000000..fe75d3f
--- /dev/null
+++ b/binaries/data/mods/public/simulation/templates/special_filter/corpse.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Entity filtered="">
+  <!-- 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 -->
+  <Attack merge=""/> <!-- Needed for the Actor Viewer -->
+  <BuildRestrictions merge=""/>
+  <!-- Corpses should include decay components and activate them -->
+  <Decay merge="">
+    <Active>true</Active>
+  </Decay>
+  <Footprint merge=""/>
+  <Identity merge=""/>
+  <!-- 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) -->
+  <Obstruction merge="">
+    <Active>false</Active>
+  </Obstruction>
+  <Ownership merge=""/>
+  <Position merge=""/>
+  <Sound merge=""/> <!-- Needed for the Actor Viewer -->
+  <UnitMotion merge=""/> <!-- Needed for the Actor Viewer -->
+  <!-- Corpses should remain visible in fog-of-war (for the owner only) -->
+  <Visibility>
+    <Corpse>true</Corpse>
+  </Visibility>
+  <!-- Corpses shouldn't display silhouettes (especially since they're often half underground) -->
+  <VisualActor merge="">
+    <SilhouetteDisplay>false</SilhouetteDisplay>
+  </VisualActor>
+</Entity>
diff --git a/binaries/data/mods/public/simulation/templates/special_filter/foundation.xml b/binaries/data/mods/public/simulation/templates/special_filter/foundation.xml
new file mode 100644
index 0000000..e294934
--- /dev/null
+++ b/binaries/data/mods/public/simulation/templates/special_filter/foundation.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Entity filtered="">
+  <AIProxy merge=""/>
+  <Armour merge=""/>
+  <BuildRestrictions merge=""/>
+  <!-- Don't provide population bonuses yet (but still do take up population cost) -->
+  <Cost merge="">
+    <PopulationBonus>0</PopulationBonus>
+  </Cost>
+  <Decay merge=""/>
+  <!-- Initialise health to 1 -->
+  <Health>
+    <Initial>1</Initial>
+  </Health>
+  <Fogging merge=""/>
+  <Footprint merge=""/>
+  <!-- Add the Foundation component, to deal with the construction process -->
+  <Foundation replace=""/>
+  <Health merge=""/>
+  <Identity merge=""/>
+  <!-- Foundations shouldn't initially block unit movement -->
+  <Obstruction merge="">
+    <DisableBlockMovement>true</DisableBlockMovement>
+    <DisableBlockPathfinding>true</DisableBlockPathfinding>
+  </Obstruction>
+  <OverlayRenderer merge=""/>
+  <Ownership merge=""/>
+  <Position merge=""/>
+  <RallyPoint merge=""/>
+  <RallyPointRenderer merge=""/>
+  <Selectable merge=""/>
+  <Sound merge=""/>
+  <StatusBars merge=""/>
+  <Visibility merge=""/>
+  <!-- Foundations should be visible themselves in fog-of-war if their base template is,
+       but shouldn't have any vision range -->
+  <Vision merge="">
+    <Range>0</Range>
+    <RevealShore merge="">false</RevealShore>
+  </Vision>
+  <!-- Switch the actor to foundation mode -->
+  <VisualActor>
+    <Foundation/>
+  </VisualActor>
+</Entity>
diff --git a/binaries/data/mods/public/simulation/templates/special_filter/mirage.xml b/binaries/data/mods/public/simulation/templates/special_filter/mirage.xml
new file mode 100644
index 0000000..565456d
--- /dev/null
+++ b/binaries/data/mods/public/simulation/templates/special_filter/mirage.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Entity filtered="">
+  <Footprint merge=""/>
+  <Identity filtered="">
+    <Civ merge=""/>
+    <GenericName merge=""/>
+    <SpecificName merge=""/>
+    <Tooltip merge=""/>
+    <History merge=""/>
+    <Icon merge=""/>
+  </Identity>
+  <Minimap merge=""/>
+  <Mirage replace=""/>
+  <Ownership merge=""/>
+  <OverlayRenderer merge=""/>
+  <Position merge=""/>
+  <Selectable merge=""/>
+  <StatusBars merge=""/>
+  <Visibility merge=""/>
+  <VisualActor merge=""/>
+</Entity>
diff --git a/binaries/data/mods/public/simulation/templates/special_filter/preview.xml b/binaries/data/mods/public/simulation/templates/special_filter/preview.xml
new file mode 100644
index 0000000..9ae9cf9
--- /dev/null
+++ b/binaries/data/mods/public/simulation/templates/special_filter/preview.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Entity filtered="">
+  <!-- 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 -->
+  <Attack merge=""/> <!-- Needed for the Actor Viewer -->
+  <BuildRestrictions merge=""/>
+  <Decay merge=""/>
+  <Footprint merge=""/>
+  <Identity merge=""/>
+  <!-- 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) -->
+  <Obstruction merge="">
+    <Active>false</Active>
+  </Obstruction>
+  <Ownership merge=""/>
+  <Position merge=""/>
+  <Sound merge=""/> <!-- Needed for the Actor Viewer -->
+  <UnitMotion merge=""/> <!-- Needed for the Actor Viewer -->
+  <!-- Previews should always be visible in fog-of-war/etc -->
+  <Visibility>
+    <AlwaysVisible>true</AlwaysVisible>
+    <Preview>true</Preview>
+  </Visibility>
+  <!-- Previews should not cast shadows -->
+  <VisualActor merge="">
+    <DisableShadows/>
+  </VisualActor>
+</Entity>
diff --git a/binaries/data/mods/public/simulation/templates/special_filter/resource.xml b/binaries/data/mods/public/simulation/templates/special_filter/resource.xml
new file mode 100644
index 0000000..ee62748
--- /dev/null
+++ b/binaries/data/mods/public/simulation/templates/special_filter/resource.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Entity filtered="">
+  <AIProxy merge=""/>
+  <Footprint merge=""/>
+  <Identity merge=""/>
+  <Minimap merge=""/>
+  <!-- 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.
+  -->
+  <Obstruction replace="">
+    <Active>true</Active>
+    <BlockMovement>false</BlockMovement>
+    <BlockPathfinding>false</BlockPathfinding>
+    <BlockFoundation>false</BlockFoundation>
+    <BlockConstruction>false</BlockConstruction>
+    <DisableBlockMovement>false</DisableBlockMovement>
+    <DisableBlockPathfinding>false</DisableBlockPathfinding>
+    <Static width="2.0" depth="2.0"/>
+  </Obstruction>
+  <OverlayRenderer merge=""/>
+  <Ownership merge=""/>
+  <Position merge=""/>
+  <ResourceSupply merge=""/>
+  <Selectable merge=""/>
+  <StatusBars merge=""/>
+  <VisualActor merge=""/>
+</Entity>
diff --git a/source/ps/TemplateLoader.cpp b/source/ps/TemplateLoader.cpp
index a9d067b..88701ac 100644
--- a/source/ps/TemplateLoader.cpp
+++ b/source/ps/TemplateLoader.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015 Wildfire Games.
+/* Copyright (C) 2016 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -29,7 +29,6 @@ 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
@@ -50,93 +49,33 @@ bool CTemplateLoader::LoadTemplateFile(const std::string& templateName, int dept
 		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);
+		std::string prefix = templateName.substr(0, pos);
+		std::string baseName = templateName.substr(pos+1);
+		
 		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;
-	}
 
-	// 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))
+		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
-		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))
-		{
-			LOGERROR("Failed to load entity template '%s'", baseName.c_str());
-			return false;
-		}
-		// Copy a subset to the requested template
-		CopyMirageSubset(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 "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;
-	}
-
-	// 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;
 	}
 
@@ -215,6 +154,12 @@ bool CTemplateLoader::TemplateExists(const std::string& templateName) const
 
 std::vector<std::string> CTemplateLoader::FindPlaceableTemplates(const std::string& path, bool includeSubdirectories, ETemplatesType templatesType, ScriptInterface& scriptInterface)
 {
+	if (templatesType != SIMULATION_TEMPLATES && templatesType != ACTOR_TEMPLATES && templatesType != ALL_TEMPLATES)
+	{
+		LOGERROR("Undefined template type (valid: all, simulation, actor)");
+		return std::vector<std::string>();
+	}
+
 	JSContext* cx = scriptInterface.GetContext();
 	JSAutoRequest rq(cx);
 	
@@ -222,7 +167,6 @@ std::vector<std::string> CTemplateLoader::FindPlaceableTemplates(const std::stri
 	Status ok;
 	VfsPath templatePath;
 
-
 	if (templatesType == SIMULATION_TEMPLATES || templatesType == ALL_TEMPLATES)
 	{
 		JS::RootedValue placeablesFilter(cx);
@@ -289,9 +233,6 @@ std::vector<std::string> CTemplateLoader::FindPlaceableTemplates(const std::stri
 		WARN_IF_ERR(ok);
 	}
 
-	if (templatesType != SIMULATION_TEMPLATES && templatesType != ACTOR_TEMPLATES && templatesType != ALL_TEMPLATES)
-		LOGERROR("Undefined template type (valid: all, simulation, actor)");
-
 	return templates;
 }
 
@@ -299,31 +240,29 @@ std::vector<std::string> CTemplateLoader::FindTemplates(const std::string& path,
 {
 	std::vector<std::string> templates;
 
+	if (templatesType != SIMULATION_TEMPLATES && templatesType != ACTOR_TEMPLATES && templatesType != ALL_TEMPLATES)
+	{
+		LOGERROR("Undefined template type (valid: all, simulation, actor)");
+		return templates;
+	}
+
 	Status ok;
 	VfsPath templatePath;
+	size_t flags = includeSubdirectories ? vfs::DIR_RECURSIVE : 0;
 
 	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");
+		ok = vfs::ForEachFile(g_VFS, templatePath, AddToTemplates, (uintptr_t)&templates, L"*.xml", flags);
 		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");
+		ok = vfs::ForEachFile(g_VFS, templatePath, AddActorToTemplates, (uintptr_t)&templates, L"*.xml", flags);
 		WARN_IF_ERR(ok);
 	}
 
-	if (templatesType != SIMULATION_TEMPLATES && templatesType != ACTOR_TEMPLATES && templatesType != ALL_TEMPLATES)
-		LOGERROR("Undefined template type (valid: all, simulation, actor)");
-
 	return templates;
 }
 
@@ -367,197 +306,3 @@ void CTemplateLoader::ConstructTemplateActor(const std::string& actorName, CPara
 
 	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<std::string> 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, "<Entity/>");
-	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, "<Entity><Obstruction><Active>false</Active></Obstruction></Entity>");
-
-	if (!corpse)
-	{
-		// Previews should not cast shadows
-		if (out.GetChild("Entity").GetChild("VisualActor").IsOk())
-			CParamNode::LoadXMLString(out, "<Entity><VisualActor><DisableShadows/></VisualActor></Entity>");
-
-		// Previews should always be visible in fog-of-war/etc
-		CParamNode::LoadXMLString(out, "<Entity><Visibility><AlwaysVisible>true</AlwaysVisible><Preview>true</Preview></Visibility></Entity>");
-	}
-
-	if (corpse)
-	{
-		// Corpses should include decay components and activate them
-		if (out.GetChild("Entity").GetChild("Decay").IsOk())
-			CParamNode::LoadXMLString(out, "<Entity><Decay><Active>true</Active></Decay></Entity>");
-
-		// Corpses shouldn't display silhouettes (especially since they're often half underground)
-		if (out.GetChild("Entity").GetChild("VisualActor").IsOk())
-			CParamNode::LoadXMLString(out, "<Entity><VisualActor><SilhouetteDisplay>false</SilhouetteDisplay></VisualActor></Entity>");
-
-		// Corpses should remain visible in fog-of-war (for the owner only)
-		CParamNode::LoadXMLString(out, "<Entity><Visibility><Corpse>true</Corpse></Visibility></Entity>");
-	}
-}
-
-void CTemplateLoader::CopyMirageSubset(CParamNode& out, const CParamNode& in)
-{
-	// Currently used for mirage entities replacing real ones in fog-of-war
-
-	std::set<std::string> permittedComponentTypes;
-	permittedComponentTypes.insert("Footprint");
-	permittedComponentTypes.insert("Minimap");
-	permittedComponentTypes.insert("Ownership");
-	permittedComponentTypes.insert("OverlayRenderer");
-	permittedComponentTypes.insert("Position");
-	permittedComponentTypes.insert("Selectable");
-	permittedComponentTypes.insert("StatusBars");
-	permittedComponentTypes.insert("Visibility");
-	permittedComponentTypes.insert("VisualActor");
-
-	CParamNode::LoadXMLString(out, "<Entity/>");
-	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<std::string> 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/>");
-	identity.CopyFilteredChildrenOfChild(in.GetChild("Entity"), "Identity", identitySubset);
-	CParamNode::LoadXMLString(out, ("<Entity>"+utf8_from_wstring(identity.ToXML())+"</Entity>").c_str());
-
-	// Set the entity as mirage entity
-	CParamNode::LoadXMLString(out, "<Entity><Mirage/></Entity>");
-}
-
-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<std::string> 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("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, "<Entity/>");
-	out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
-
-	// Switch the actor to foundation mode
-	CParamNode::LoadXMLString(out, "<Entity><VisualActor><Foundation/></VisualActor></Entity>");
-
-	// Add the Foundation component, to deal with the construction process
-	CParamNode::LoadXMLString(out, "<Entity><Foundation/></Entity>");
-
-	// Initialise health to 1
-	CParamNode::LoadXMLString(out, "<Entity><Health><Initial>1</Initial></Health></Entity>");
-
-	// Foundations shouldn't initially block unit movement
-	if (out.GetChild("Entity").GetChild("Obstruction").IsOk())
-		CParamNode::LoadXMLString(out, "<Entity><Obstruction><DisableBlockMovement>true</DisableBlockMovement><DisableBlockPathfinding>true</DisableBlockPathfinding></Obstruction></Entity>");
-
-	// Don't provide population bonuses yet (but still do take up population cost)
-	if (out.GetChild("Entity").GetChild("Cost").IsOk())
-		CParamNode::LoadXMLString(out, "<Entity><Cost><PopulationBonus>0</PopulationBonus></Cost></Entity>");
-
-	// 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, "<Entity><Vision><Range>0</Range></Vision></Entity>");
-		// Foundations should not have special vision capabilities either
-		if (out.GetChild("Entity").GetChild("Vision").GetChild("RevealShore").IsOk())
-			CParamNode::LoadXMLString(out, "<Entity><Vision><RevealShore>false</RevealShore></Vision></Entity>");
-	}
-}
-
-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<std::string> permittedComponentTypes;
-	permittedComponentTypes.insert("Footprint");
-	permittedComponentTypes.insert("Ownership");
-	permittedComponentTypes.insert("Position");
-	permittedComponentTypes.insert("VisualActor");
-
-	CParamNode::LoadXMLString(out, "<Entity/>");
-	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<std::string> 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, "<Entity/>");
-	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, "<Entity><Obstruction><Active>true</Active><BlockMovement>false</BlockMovement><BlockPathfinding>false</BlockPathfinding><BlockFoundation>false</BlockFoundation><BlockConstruction>false</BlockConstruction><DisableBlockMovement>false</DisableBlockMovement><DisableBlockPathfinding>false</DisableBlockPathfinding><Static width=\"2.0\" depth=\"2.0\"/></Obstruction></Entity>");
-}
diff --git a/source/ps/TemplateLoader.h b/source/ps/TemplateLoader.h
index c34d303..d2a6514 100644
--- a/source/ps/TemplateLoader.h
+++ b/source/ps/TemplateLoader.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015 Wildfire Games.
+/* Copyright (C) 2016 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -48,7 +48,7 @@ public:
 	CTemplateLoader()
 	{
 	}
-	
+
 	/**
 	 * Provides the file data for requested template.
 	 */
@@ -81,36 +81,6 @@ private:
 	 */
 	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.
diff --git a/source/simulation2/system/ParamNode.cpp b/source/simulation2/system/ParamNode.cpp
index 173117b..74ca203 100644
--- a/source/simulation2/system/ParamNode.cpp
+++ b/source/simulation2/system/ParamNode.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015 Wildfire Games.
+/* Copyright (C) 2016 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 @@ void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const
 	// 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,7 @@ void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const
 		MUL
 	} op = INVALID;
 	bool replacing = false;
+	bool filtering = false;
 	{
 		XERO_ITER_ATTR(element, attr)
 		{
@@ -97,6 +100,15 @@ void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const
 				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;
+			}
 			else if (attr.Name == at_op)
 			{
 				if (std::wstring(attr.Value.begin(), attr.Value.end()) == L"add") 
@@ -150,6 +162,9 @@ void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const
 		}
 	}
 
+	// For the filtered case
+	ChildrenMap childs;
+
 	// Add this element as a child node
 	CParamNode& node = m_Childs[name];
 	if (op != INVALID) 
@@ -175,13 +190,21 @@ void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const
 	XERO_ITER_EL(element, child)
 	{
 		node.ApplyLayer(xmb, child, sourceIdentifier);
+		if (filtering)
+		{
+			std::string childname = xmb.GetElementString(child.GetNodeName()); // TODO: is GetElementString inefficient? (especially if we call it twice)
+			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);
diff --git a/source/simulation2/system/ParamNode.h b/source/simulation2/system/ParamNode.h
index 4fc911f..9ce530e 100644
--- a/source/simulation2/system/ParamNode.h
+++ b/source/simulation2/system/ParamNode.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015 Wildfire Games.
+/* Copyright (C) 2016 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 @@ class XMBElement;
  *   <Example4 datatype="tokens">
  *     one two three
  *   </Example4>
+ *   <Example5>
+ *     <E/>
+ *     <F>
+ *       <I>test</I>
+ *     </F>
+ *     <H>
+ *       <J>example</J>
+ *     </H>
+ *   </Example5>
  * </Entity>
  * @endcode
  * then a second like:
@@ -75,6 +84,15 @@ class XMBElement;
  *     four             <!-- add a token to the parent's set -->
  *     -two             <!-- remove a token from the parent's set -->
  *   </Example4>
+ *   <Example5 filtered=""> <!-- drop all children of this node that are not in this file -->
+ *     <F merge="">  <!-- only add this element if it is also present in the parent -->
+ *       <K>example</K> <!-- if F is present merge its children normally -->
+ *     </F>
+ *     <G merge=""/>  <!-- keep the G element of the parent if it exists -->
+ *     <H>
+ *       <J>text</J>
+ *     </H>
+ *   </Example5>
  * </Entity>
  * @endcode
  * is equivalent to loading a single file like:
@@ -90,6 +108,15 @@ class XMBElement;
  *   <Example4>
  *     one three four
  *   </Example4>
+ *   <Example5>
+ *     <F>
+ *       <I>test</I>
+ *       <K>example</K>
+ *     </F>
+ *     <H>
+ *       <J>text</J>
+ *     </H>
+ *   </Example5>
  * </Entity>
  * @endcode
  *
@@ -103,7 +130,16 @@ class XMBElement;
  *     "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
diff --git a/source/simulation2/tests/test_ParamNode.h b/source/simulation2/tests/test_ParamNode.h
index 043554b..d329424 100644
--- a/source/simulation2/tests/test_ParamNode.h
+++ b/source/simulation2/tests/test_ParamNode.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2016 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,22 @@ public:
 		TS_ASSERT_WSTR_EQUALS(node.ToXML(), L"<test><a datatype=\"tokens\">Y X</a></test>");
 	}
 
+	void test_overlay_filtered()
+	{
+		CParamNode node;
+		TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, "<test> <a><b/></a> <c>toberemoved</c> <d><e/></d> </test>"), PSRETURN_OK);
+		TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, "<test filtered=\"\"> <a/> <d><f/></d> <g/> </test>"), PSRETURN_OK);
+		TS_ASSERT_WSTR_EQUALS(node.ToXML(), L"<test><a><b></b></a><d><e></e><f></f></d><g></g></test>");
+	}
+
+	void test_overlay_merge()
+	{
+		CParamNode node;
+		TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, "<test> <a><b>foo</b><c>bar</c></a> <x><y><z>foo</z></y></x> </test>"), PSRETURN_OK);
+		TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, "<test> <a merge=\"\"><b>test</b><d>baz</d></a> <i merge=\"\"><j>willnotbeincluded</j></i> <x merge=\"\"><y merge=\"\"><v>text</v></y><w>more text</w></x> </test>"), PSRETURN_OK);
+		TS_ASSERT_WSTR_EQUALS(node.ToXML(), L"<test><a><b>test</b><c>bar</c><d>baz</d></a><x><w>more text</w><y><v>text</v><z>foo</z></y></x></test>");
+	}
+
 	void test_types()
 	{
 		CParamNode node;
-- 
2.7.2

