1
0
forked from 0ad/0ad

Use XML files instead of hardcoded C++ code in the template manager to universally change template properties at load time.

Patch By: leper
Differential Revision: https://code.wildfiregames.com/D215
Fixes #2951

This was SVN commit r19302.
This commit is contained in:
elexis 2017-03-16 19:56:12 +00:00
parent 59efb76966
commit d093f714d7
12 changed files with 324 additions and 333 deletions

View File

@ -0,0 +1,44 @@
<?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=""/>
<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>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity filtered="">
<Footprint merge=""/>
<Ownership merge=""/>
<Position merge=""/>
<VisualActor merge=""/>
</Entity>

View File

@ -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>

View File

@ -0,0 +1,44 @@
<?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=""/>
<Fogging merge=""/>
<Footprint merge=""/>
<!-- Add the Foundation component, to deal with the construction process -->
<Foundation replace=""/>
<Health>
<Initial>1</Initial>
</Health>
<Identity merge=""/>
<Market 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>false</RevealShore>
</Vision>
<!-- Switch the actor to foundation mode -->
<VisualActor>
<Foundation/>
</VisualActor>
</Entity>

View File

@ -0,0 +1,27 @@
<?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=""/>
<Obstruction merge="">
<BlockMovement>false</BlockMovement>
<BlockPathfinding>false</BlockPathfinding>
<BlockFoundation>false</BlockFoundation>
<BlockConstruction>false</BlockConstruction>
</Obstruction>
<Ownership merge=""/>
<OverlayRenderer merge=""/>
<Position merge=""/>
<Selectable merge=""/>
<StatusBars merge=""/>
<Visibility merge=""/>
<VisualActor merge=""/>
</Entity>

View File

@ -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>

View File

@ -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>

View File

@ -49,93 +49,33 @@ bool CTemplateLoader::LoadTemplateFile(const std::string& templateName, int dept
return true; return true;
} }
// Handle special case "preview|foo" // Handle special case "bar|foo"
if (templateName.find("preview|") == 0) 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 prefix = templateName.substr(0, pos);
std::string baseName = templateName.substr(8); 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)) if (!LoadTemplateFile(baseName, depth+1))
{ {
LOGERROR("Failed to load entity template '%s'", baseName.c_str()); LOGERROR("Failed to load entity template '%s'", baseName.c_str());
return false; return false;
} }
// Copy a subset to the requested template
CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], true);
return true;
}
// Handle special case "mirage|foo" VfsPath path = VfsPath(TEMPLATE_ROOT) / L"special_filter" / wstring_from_utf8(prefix + ".xml");
if (templateName.find("mirage|") == 0) if (!VfsFileExists(path))
{
// 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()); LOGERROR("Invalid subset '%s'", prefix.c_str());
return false; return false;
} }
// Copy a subset to the requested template
CopyMirageSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]);
return true;
}
// Handle special case "foundation|foo" CXeromyces xero;
if (templateName.find("foundation|") == 0) PSRETURN ok = xero.Load(g_VFS, path);
{ if (ok != PSRETURN_OK)
// Load the base entity template, if it wasn't already loaded return false; // (Xeromyces already logged an error with the full filename)
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" m_TemplateFileData[templateName] = m_TemplateFileData[baseName];
if (templateName.find("construction|") == 0) CParamNode::LoadXML(m_TemplateFileData[templateName], xero, path.string().c_str());
{
// 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]);
return true; return true;
} }
@ -348,203 +288,3 @@ void CTemplateLoader::ConstructTemplateActor(const std::string& actorName, CPara
CParamNode::LoadXMLString(out, xml.c_str(), actorNameW.c_str()); 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("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, "<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());
// Mirages obstruction shouldn't block anything
if (out.GetChild("Entity").GetChild("Obstruction").IsOk())
CParamNode::LoadXMLString(out, "<Entity><Obstruction><BlockMovement>false</BlockMovement><BlockPathfinding>false</BlockPathfinding><BlockFoundation>false</BlockFoundation><BlockConstruction>false</BlockConstruction></Obstruction></Entity>");
// 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("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, "<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>");
}

View File

@ -81,36 +81,6 @@ private:
*/ */
void ConstructTemplateActor(const std::string& actorName, CParamNode& out); 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 * 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. * loaded non-broken template data. This includes files that will fail schema validation.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015 Wildfire Games. /* Copyright (C) 2017 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 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 // Look for special attributes
int at_disable = xmb.GetAttributeID("disable"); int at_disable = xmb.GetAttributeID("disable");
int at_replace = xmb.GetAttributeID("replace"); 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_op = xmb.GetAttributeID("op");
int at_datatype = xmb.GetAttributeID("datatype"); int at_datatype = xmb.GetAttributeID("datatype");
enum op { enum op {
@ -84,6 +86,8 @@ void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const
MUL MUL
} op = INVALID; } op = INVALID;
bool replacing = false; bool replacing = false;
bool filtering = false;
bool merging = false;
{ {
XERO_ITER_ATTR(element, attr) XERO_ITER_ATTR(element, attr)
{ {
@ -97,6 +101,16 @@ void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const
m_Childs.erase(name); m_Childs.erase(name);
replacing = true; 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) else if (attr.Name == at_op)
{ {
if (std::wstring(attr.Value.begin(), attr.Value.end()) == L"add") if (std::wstring(attr.Value.begin(), attr.Value.end()) == L"add")
@ -157,6 +171,7 @@ void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const
// TODO: Support parsing of data types other than fixed; log warnings in other cases // TODO: Support parsing of data types other than fixed; log warnings in other cases
fixed oldval = node.ToFixed(); fixed oldval = node.ToFixed();
fixed mod = fixed::FromString(CStrW(value)); fixed mod = fixed::FromString(CStrW(value));
switch (op) switch (op)
{ {
case ADD: case ADD:
@ -168,24 +183,37 @@ void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const
} }
hasSetValue = true; hasSetValue = true;
} }
if (!hasSetValue)
if (!hasSetValue && !merging)
node.m_Value = value; node.m_Value = value;
// We also need to reset node's script val, even if it has no children // We also need to reset node's script val, even if it has no children
// or if the attributes change. // or if the attributes change.
node.ResetScriptVal(); node.ResetScriptVal();
// For the filtered case
ChildrenMap childs;
// Recurse through the element's children // Recurse through the element's children
XERO_ITER_EL(element, child) XERO_ITER_EL(element, child)
{ {
node.ApplyLayer(xmb, child, sourceIdentifier); 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 "@" // Add the element's attributes, prefixing names with "@"
XERO_ITER_ATTR(element, attr) XERO_ITER_ATTR(element, attr)
{ {
// Skip special attributes // 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; continue;
// Add any others // Add any others
std::string attrName = xmb.GetAttributeString(attr.Name); std::string attrName = xmb.GetAttributeString(attr.Name);
@ -193,21 +221,6 @@ void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const
} }
} }
void CParamNode::CopyFilteredChildrenOfChild(const CParamNode& src, const char* name, const std::set<std::string>& 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 const CParamNode& CParamNode::GetChild(const char* name) const
{ {
ChildrenMap::const_iterator it = m_Childs.find(name); ChildrenMap::const_iterator it = m_Childs.find(name);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015 Wildfire Games. /* Copyright (C) 2017 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -58,6 +58,15 @@ class XMBElement;
* <Example4 datatype="tokens"> * <Example4 datatype="tokens">
* one two three * one two three
* </Example4> * </Example4>
* <Example5>
* <E/>
* <F>
* <I>test</I>
* </F>
* <H>
* <J>example</J>
* </H>
* </Example5>
* </Entity> * </Entity>
* @endcode * @endcode
* then a second like: * then a second like:
@ -75,6 +84,15 @@ class XMBElement;
* four <!-- add a token to the parent's set --> * four <!-- add a token to the parent's set -->
* -two <!-- remove a token from the parent's set --> * -two <!-- remove a token from the parent's set -->
* </Example4> * </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> * </Entity>
* @endcode * @endcode
* is equivalent to loading a single file like: * is equivalent to loading a single file like:
@ -90,6 +108,15 @@ class XMBElement;
* <Example4> * <Example4>
* one three four * one three four
* </Example4> * </Example4>
* <Example5>
* <F>
* <I>test</I>
* <K>example</K>
* </F>
* <H>
* <J>text</J>
* </H>
* </Example5>
* </Entity> * </Entity>
* @endcode * @endcode
* *
@ -103,7 +130,16 @@ class XMBElement;
* "Example3": { * "Example3": {
* "D": "new" * "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 * @endcode
@ -145,14 +181,6 @@ public:
*/ */
static PSRETURN LoadXMLString(CParamNode& ret, const char* xml, const wchar_t* sourceIdentifier = NULL); 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<std::string>& permitted);
/** /**
* Returns the (unique) child node with the given name, or a node with IsOk() == false if there is none. * Returns the (unique) child node with the given name, or a node with IsOk() == false if there is none.
*/ */

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games. /* Copyright (C) 2017 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -135,6 +135,35 @@ public:
TS_ASSERT_WSTR_EQUALS(node.ToXML(), L"<test><a datatype=\"tokens\">Y X</a></test>"); 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>");
CParamNode node2;
TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node2, "<test> <a><b>b</b><c>c</c><d>d</d><e>e</e></a> <f/> </test>"), PSRETURN_OK);
TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node2, "<test filtered=\"\"> <a filtered=\"\"><b merge=\"\"/><c>c2</c><d/></a> </test>"), PSRETURN_OK);
TS_ASSERT_WSTR_EQUALS(node2.ToXML(), L"<test><a><b>b</b><c>c2</c><d></d></a></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_overlay_filtered_merge()
{
CParamNode node;
TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, "<test> <a><b/></a> <c><x/></c> <Health><Max>1200</Max></Health> </test>"), PSRETURN_OK);
TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, "<test filtered=\"\"> <c merge=\"\"/> <d>bar</d> <e merge=\"\"/> <Health><Initial>1</Initial></Health> </test>"), PSRETURN_OK);
TS_ASSERT_WSTR_EQUALS(node.ToXML(), L"<test><Health><Initial>1</Initial><Max>1200</Max></Health><c><x></x></c><d>bar</d></test>");
}
void test_types() void test_types()
{ {
CParamNode node; CParamNode node;