Allow arbitrary compositions in TemplateLoader template names.

- Allows compositing any two templates in TemplateLoader, and not just
filters: 'A|B|C' is now valid for any template A, B and C.
- Allows parents to be composited paths 'A|B|C'. In such a schema, if A
or B themselves specify a parent, the actual composition becomes
A|pA|B|pB|C and so on.

This allows, by leveraging the common schema of our entities, to reduce
duplication.

For convenience, templates in "special/filters/" and "mixins/" can be
included by their direct name. Others have to be completely specified.

See the two provided cases for examples:
- 'hoplite' becomes a mixin that can be used to apply the Phalanx
formation
- 'builder' becomes a mixin that can be give a template the ability to
build the standard structures, and gives the 'Builder' identity class.
This also allows deduplicating that list of tokens.

Update checkrefs & swap std::map for std::unordered_map in
TemplateLoader.

Differential Revision: https://code.wildfiregames.com/D3801
This was SVN commit r25223.
This commit is contained in:
wraitii 2021-04-09 15:43:50 +00:00
parent 7321509d5f
commit d73a3f59ad
26 changed files with 156 additions and 220 deletions

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Entity>
<Builder>
<Rate>1.0</Rate>
<Entities datatype="tokens">
structures/{civ}/civil_centre
structures/{civ}/crannog
structures/{civ}/military_colony
structures/{civ}/house
structures/{civ}/apartment
structures/{civ}/storehouse
structures/{civ}/farmstead
structures/{civ}/field
structures/{civ}/corral
structures/{civ}/dock
structures/{civ}/barracks
structures/{civ}/stable
structures/{civ}/elephant_stable
structures/{civ}/arsenal
structures/{civ}/forge
structures/{civ}/temple
structures/{civ}/market
structures/{civ}/outpost
structures/{civ}/sentry_tower
structures/{civ}/defense_tower
structures/{civ}/fortress
structures/wallset_palisade
structures/{civ}/wallset_siege
structures/{civ}/wallset_stone
structures/{civ}/theater
structures/{civ}/wonder
</Entities>
</Builder>
<Identity>
<VisibleClasses datatype="tokens">Builder</VisibleClasses>
</Identity>
</Entity>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_hero_infantry_spearman">
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Entity>
<Identity>
<Formations datatype="tokens">
special/formations/phalanx

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_champion_infantry_spearman">
<Identity>
<Formations datatype="tokens">
special/formations/phalanx
</Formations>
</Identity>
</Entity>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit">
<Entity parent="builder|template_unit">
<Attack>
<Capture>
<AttackName>Capture</AttackName>
@ -16,37 +16,6 @@
<MaxRange>2</MaxRange>
</Slaughter>
</Attack>
<Builder>
<Rate>1.0</Rate>
<Entities datatype="tokens">
structures/{civ}/civil_centre
structures/{civ}/crannog
structures/{civ}/military_colony
structures/{civ}/house
structures/{civ}/apartment
structures/{civ}/storehouse
structures/{civ}/farmstead
structures/{civ}/field
structures/{civ}/corral
structures/{civ}/dock
structures/{civ}/barracks
structures/{civ}/stable
structures/{civ}/elephant_stable
structures/{civ}/arsenal
structures/{civ}/forge
structures/{civ}/temple
structures/{civ}/market
structures/{civ}/outpost
structures/{civ}/sentry_tower
structures/{civ}/defense_tower
structures/{civ}/fortress
structures/wallset_palisade
structures/{civ}/wallset_siege
structures/{civ}/wallset_stone
structures/{civ}/theater
structures/{civ}/wonder
</Entities>
</Builder>
<Cost>
<BuildTime>12</BuildTime>
<Resources>
@ -62,7 +31,7 @@
<Identity>
<GenericName>Infantry</GenericName>
<Classes datatype="tokens">Human CitizenSoldier</Classes>
<VisibleClasses datatype="tokens">Citizen Builder Worker Soldier Infantry</VisibleClasses>
<VisibleClasses datatype="tokens">Citizen Worker Soldier Infantry</VisibleClasses>
<Rank>Basic</Rank>
</Identity>
<Loot>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_infantry_melee_spearman">
<Identity>
<Formations datatype="tokens">
special/formations/phalanx
</Formations>
</Identity>
</Entity>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_support">
<Entity parent="builder|template_unit_support">
<Attack>
<Melee>
<AttackName>Dagger</AttackName>
@ -18,37 +18,6 @@
<MaxRange>2</MaxRange>
</Slaughter>
</Attack>
<Builder>
<Rate>1.0</Rate>
<Entities datatype="tokens">
structures/{civ}/civil_centre
structures/{civ}/crannog
structures/{civ}/military_colony
structures/{civ}/house
structures/{civ}/apartment
structures/{civ}/storehouse
structures/{civ}/farmstead
structures/{civ}/field
structures/{civ}/corral
structures/{civ}/dock
structures/{civ}/barracks
structures/{civ}/stable
structures/{civ}/elephant_stable
structures/{civ}/arsenal
structures/{civ}/forge
structures/{civ}/temple
structures/{civ}/market
structures/{civ}/outpost
structures/{civ}/sentry_tower
structures/{civ}/defense_tower
structures/{civ}/fortress
structures/wallset_palisade
structures/{civ}/wallset_siege
structures/{civ}/wallset_stone
structures/{civ}/theater
structures/{civ}/wonder
</Entities>
</Builder>
<Cost>
<BuildTime>9</BuildTime>
<Resources>
@ -63,7 +32,7 @@
<GenericName>Female Citizen</GenericName>
<SelectionGroupName>template_unit_support_female_citizen</SelectionGroupName>
<Classes datatype="tokens">FemaleCitizen</Classes>
<VisibleClasses datatype="tokens">Citizen Builder Worker</VisibleClasses>
<VisibleClasses datatype="tokens">Citizen Worker</VisibleClasses>
<Formations disable=""/>
</Identity>
<Loot>

View file

@ -1,35 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_support">
<Entity parent="builder|template_unit_support">
<Builder>
<Rate>0.5</Rate>
<Entities datatype="tokens">
structures/{civ}/civil_centre
structures/{civ}/crannog
structures/{civ}/military_colony
structures/{civ}/house
structures/{civ}/apartment
structures/{civ}/storehouse
structures/{civ}/farmstead
structures/{civ}/field
structures/{civ}/corral
structures/{civ}/dock
structures/{civ}/barracks
structures/{civ}/stable
structures/{civ}/elephant_stable
structures/{civ}/arsenal
structures/{civ}/forge
structures/{civ}/temple
structures/{civ}/market
structures/{civ}/outpost
structures/{civ}/sentry_tower
structures/{civ}/defense_tower
structures/{civ}/fortress
structures/wallset_palisade
structures/{civ}/wallset_siege
structures/{civ}/wallset_stone
structures/{civ}/theater
structures/{civ}/wonder
</Entities>
</Builder>
<Cost>
<Population>0</Population>
@ -46,7 +18,7 @@
<GenericName>Slave</GenericName>
<SelectionGroupName>template_unit_support_slave</SelectionGroupName>
<Tooltip>Gatherer with a finite life span. Bonused at mining and lumbering.</Tooltip>
<VisibleClasses datatype="tokens">Builder Worker Slave</VisibleClasses>
<VisibleClasses datatype="tokens">Worker Slave</VisibleClasses>
</Identity>
<Loot>
<xp>10</xp>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_champion_infantry_spearman_hoplite">
<Entity parent="hoplite|template_unit_champion_infantry_spearman">
<Identity>
<Civ>athen</Civ>
<Lang>greek</Lang>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_hero_infantry_spearman_hoplite">
<Entity parent="hoplite|template_unit_hero_infantry_spearman">
<Auras datatype="tokens">
units/heroes/athen_hero_pericles_1
units/heroes/athen_hero_pericles_2

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_infantry_melee_spearman_hoplite">
<Entity parent="hoplite|template_unit_infantry_melee_spearman">
<Builder>
<Entities datatype="tokens">
structures/athen/gymnasium

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_champion_infantry_spearman_hoplite">
<Entity parent="hoplite|template_unit_champion_infantry_spearman">
<Identity>
<Civ>cart</Civ>
<GenericName>Sacred Band Infantry</GenericName>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_infantry_melee_spearman_hoplite">
<Entity parent="hoplite|template_unit_infantry_melee_spearman">
<Builder>
<Entities datatype="tokens">
structures/cart/super_dock

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_champion_infantry_spearman_hoplite">
<Entity parent="hoplite|template_unit_champion_infantry_spearman">
<Identity>
<Civ>mace</Civ>
<Lang>greek</Lang>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_champion_infantry_spearman_hoplite">
<Entity parent="hoplite|template_unit_champion_infantry_spearman">
<Identity>
<Civ>pers</Civ>
<VisibleClasses datatype="tokens">Mercenary</VisibleClasses>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_infantry_melee_spearman_hoplite">
<Entity parent="hoplite|template_unit_infantry_melee_spearman">
<Builder>
<Entities datatype="tokens">
structures/ptol/lighthouse

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_champion_infantry_spearman_hoplite">
<Entity parent="hoplite|template_unit_champion_infantry_spearman">
<Identity>
<VisibleClasses datatype="tokens">Mercenary</VisibleClasses>
<GenericName>Samnite Spearman</GenericName>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_infantry_melee_spearman_hoplite">
<Entity parent="hoplite|template_unit_infantry_melee_spearman">
<Identity>
<Civ>sele</Civ>
<Lang>greek</Lang>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_champion_infantry_spearman_hoplite">
<Entity parent="hoplite|template_unit_champion_infantry_spearman">
<Identity>
<Civ>spart</Civ>
<Lang>greek</Lang>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_hero_infantry_spearman_hoplite">
<Entity parent="hoplite|template_unit_hero_infantry_spearman">
<Identity>
<Civ>spart</Civ>
<Lang>greek</Lang>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_hero_infantry_spearman_hoplite">
<Entity parent="hoplite|template_unit_hero_infantry_spearman">
<Auras datatype="tokens">
units/heroes/spart_hero_leonidas
</Auras>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_infantry_melee_spearman_hoplite">
<Entity parent="hoplite|template_unit_infantry_melee_spearman">
<Builder>
<Entities datatype="tokens">
structures/spart/syssiton

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_champion_infantry_spearman_hoplite">
<Entity parent="hoplite|template_unit_champion_infantry_spearman">
<Identity>
<Lang>greek</Lang>
<GenericName>Theban Sacred Band Hoplite</GenericName>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2020 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -29,91 +29,58 @@ static const wchar_t ACTOR_ROOT[] = L"art/actors/";
static CParamNode NULL_NODE(false);
bool CTemplateLoader::LoadTemplateFile(const std::string& templateName, int depth)
bool CTemplateLoader::LoadTemplateFile(CParamNode& node, std::string_view templateName, bool compositing, int depth)
{
// If this file was already loaded, we don't need to do anything
if (m_TemplateFileData.find(templateName) != m_TemplateFileData.end())
// Handle special case "actor|foo", which does not load 'foo' at all, just uses the name.
if (templateName.compare(0, 6, "actor|") == 0)
{
ConstructTemplateActor(templateName.substr(6), node);
return true;
}
// Handle infinite loops more gracefully than running out of stack space and crashing
if (depth > 100)
{
LOGERROR("Probable infinite inheritance loop in entity template '%s'", templateName.c_str());
LOGERROR("Probable infinite inheritance loop in entity template '%s'", std::string(templateName));
return false;
}
// Handle special case "actor|foo"
if (templateName.find("actor|") == 0)
{
ConstructTemplateActor(templateName.substr(6), m_TemplateFileData[templateName]);
return true;
}
// Handle special case "bar|foo"
size_t pos = templateName.find_first_of('|');
if (pos != std::string::npos)
{
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());
// 'foo|bar' pattern: 'bar' is treated as the parent of 'foo'.
if (!LoadTemplateFile(node, templateName.substr(pos + 1), false, depth + 1))
return false;
}
VfsPath path = VfsPath(TEMPLATE_ROOT) / L"special" / L"filter" / wstring_from_utf8(prefix + ".xml");
if (!VfsFileExists(path))
{
LOGERROR("Invalid subset '%s'", prefix.c_str());
if (!LoadTemplateFile(node, templateName.substr(0, pos), true, depth + 1))
return false;
}
CXeromyces xero;
PSRETURN ok = xero.Load(g_VFS, path);
if (ok != PSRETURN_OK)
return false; // (Xeromyces already logged an error with the full filename)
m_TemplateFileData[templateName] = m_TemplateFileData[baseName];
CParamNode::LoadXML(m_TemplateFileData[templateName], xero, path.string().c_str());
return true;
}
// Normal case: templateName is an XML file:
// Load the data we need to apply on the node. This data may contain special modifiers,
// such as filters, merges, multiplying the parent values, etc. Applying it to paramnode is destructive.
// Find the XML file to load - by default, this assumes the files reside in 'special/filter'.
// If not found there, it will be searched for in 'mixins/', then from the root.
// The reason for this order is that filters are used at runtime, mixins at load time.
std::wstring wtempName = wstring_from_utf8(std::string(templateName) + ".xml");
VfsPath path = VfsPath(TEMPLATE_ROOT) / L"special" / L"filter" / wtempName;
if (!VfsFileExists(path))
path = VfsPath(TEMPLATE_ROOT) / L"mixins" / wtempName;
if (!VfsFileExists(path))
path = VfsPath(TEMPLATE_ROOT) / wtempName;
VfsPath path = VfsPath(TEMPLATE_ROOT) / wstring_from_utf8(templateName + ".xml");
CXeromyces xero;
PSRETURN ok = xero.Load(g_VFS, path);
if (ok != PSRETURN_OK)
return false; // (Xeromyces already logged an error with the full filename)
// If the layer defines an explicit parent, we must load that and apply it before ourselves.
int attr_parent = xero.GetAttributeID("parent");
CStr parentName = xero.GetRoot().GetAttributes().GetNamedItem(attr_parent);
if (!parentName.empty())
{
// To prevent needless complexity in template design, we don't allow |-separated strings as parents
if (parentName.find('|') != parentName.npos)
{
LOGERROR("Invalid parent '%s' in entity template '%s'", parentName.c_str(), templateName.c_str());
return false;
}
// Ensure the parent is loaded
if (!LoadTemplateFile(parentName, depth+1))
{
LOGERROR("Failed to load parent '%s' of entity template '%s'", parentName.c_str(), templateName.c_str());
return false;
}
CParamNode& parentData = m_TemplateFileData[parentName];
// Initialise this template with its parent
m_TemplateFileData[templateName] = parentData;
}
// Load the new file into the template data (overriding parent values)
CParamNode::LoadXML(m_TemplateFileData[templateName], xero, wstring_from_utf8(templateName).c_str());
if (!parentName.empty() && !LoadTemplateFile(node, parentName, compositing, depth + 1))
return false;
// Load the new file into the template data (overriding parent values).
// TODO: error handling.
CParamNode::LoadXML(node, xero);
return true;
}
@ -124,13 +91,14 @@ static Status AddToTemplates(const VfsPath& pathname, const CFileInfo& UNUSED(fi
// Strip the .xml extension
VfsPath pathstem = pathname.ChangeExtension(L"");
// Strip the root from the path
std::wstring name = pathstem.string().substr(ARRAY_SIZE(TEMPLATE_ROOT)-1);
std::wstring_view name = pathstem.string().substr(ARRAY_SIZE(TEMPLATE_ROOT)-1);
// We want to ignore template_*.xml templates, since they should never be built in the editor
if (name.substr(0, 9) == L"template_")
return INFO::OK;
if (name.substr(0, 8) == L"special/")
// Also ignore some subfolders.
if (name.substr(0, 8) == L"special/" || name.substr(0, 7) == L"mixins/")
return INFO::OK;
templates.push_back(std::string(name.begin(), name.end()));
@ -178,33 +146,35 @@ std::vector<std::string> CTemplateLoader::FindTemplates(const std::string& path,
const CParamNode& CTemplateLoader::GetTemplateFileData(const std::string& templateName)
{
// Load the template if necessary
if (!LoadTemplateFile(templateName, 0))
if (std::unordered_map<std::string, CParamNode>::const_iterator it = m_TemplateFileData.find(templateName); it != m_TemplateFileData.end())
return it->second;
CParamNode ret;
if (!LoadTemplateFile(ret, templateName, false, 0))
{
LOGERROR("Failed to load entity template '%s'", templateName.c_str());
return NULL_NODE;
}
return m_TemplateFileData[templateName];
return m_TemplateFileData.insert_or_assign(templateName, ret).first->second;
}
void CTemplateLoader::ConstructTemplateActor(const std::string& actorName, CParamNode& out)
void CTemplateLoader::ConstructTemplateActor(std::string_view actorName, CParamNode& out)
{
// Copy the actor template
out = GetTemplateFileData("special/actor");
// Initialize the actor's name and make it an Atlas selectable entity.
std::wstring actorNameW = wstring_from_utf8(actorName);
std::string name = utf8_from_wstring(CParamNode::EscapeXMLString(actorNameW));
std::string xml = "<Entity>"
"<VisualActor><Actor>" + name + "</Actor><ActorOnly/></VisualActor>"
// Arbitrary-sized Footprint definition to make actors' selection outlines show up in Atlas.
"<Footprint><Circle radius='2.0'/><Height>1.0</Height></Footprint>"
"<Selectable>"
"<EditorOnly/>"
"<Overlay><Texture><MainTexture>128x128/ellipse.png</MainTexture><MainTextureMask>128x128/ellipse_mask.png</MainTextureMask></Texture></Overlay>"
"</Selectable>"
"</Entity>";
CParamNode::LoadXMLString(out, xml.c_str(), actorNameW.c_str());
std::string source(actorName);
std::wstring actorNameW = wstring_from_utf8(source);
source = "<Entity>"
"<VisualActor><Actor>" + source + "</Actor><ActorOnly/></VisualActor>"
// Arbitrary-sized Footprint definition to make actors' selection outlines show up in Atlas.
"<Footprint><Circle radius='2.0'/><Height>1.0</Height></Footprint>"
"<Selectable>"
"<EditorOnly/>"
"<Overlay><Texture><MainTexture>128x128/ellipse.png</MainTexture><MainTextureMask>128x128/ellipse_mask.png</MainTextureMask></Texture></Overlay>"
"</Selectable>"
"</Entity>";
// We'll assume that actorName is valid XML, otherwise this will fail and report the error anyways.
CParamNode::LoadXMLString(out, source.c_str(), actorNameW.c_str());
}

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2017 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -20,6 +20,9 @@
#include "simulation2/system/ParamNode.h"
#include <string_view>
#include <unordered_map>
enum ETemplatesType
{
ALL_TEMPLATES,
@ -69,14 +72,16 @@ private:
* (Re)loads the given template, regardless of whether it exists already,
* and saves into m_TemplateFileData. Also loads any parents that are not yet
* loaded. Returns false on error.
* @param templateName XML filename to load (not a |-separated string)
* @param templateName - XML filename to load (may be a |-separated string)
* @param compositing - whether this template is an intermediary layer in a |-separated string.
* @param depth - the current recursion depth.
*/
bool LoadTemplateFile(const std::string& templateName, int depth);
bool LoadTemplateFile(CParamNode& node, std::string_view templateName, bool compositing, int depth);
/**
* Constructs a standard static-decorative-object template for the given actor
*/
void ConstructTemplateActor(const std::string& actorName, CParamNode& out);
void ConstructTemplateActor(std::string_view actorName, CParamNode& out);
/**
* Map from template name (XML filename or special |-separated string) to the most recently
@ -84,7 +89,7 @@ private:
* (Failed loads won't remove existing entries under the same name, so we behave more nicely
* when hotloading broken files)
*/
std::map<std::string, CParamNode> m_TemplateFileData;
std::unordered_map<std::string, CParamNode> m_TemplateFileData;
};
#endif // INCLUDED_TEMPLATELOADER

View file

@ -12,7 +12,13 @@ my $vfsroot = '../../../binaries/data/mods';
sub get_filename
{
my ($vfspath, $mod) = @_;
my $fn = "$vfsroot/$mod/simulation/templates/$vfspath.xml";
my $fn = "$vfsroot/$mod/simulation/templates/special/filter/$vfspath.xml";
if (not -e $fn) {
$fn = "$vfsroot/$mod/simulation/templates/mixins/$vfspath.xml";
}
if (not -e $fn) {
$fn = "$vfsroot/$mod/simulation/templates/$vfspath.xml";
}
return $fn;
}
@ -136,16 +142,28 @@ sub get_main_mod
sub load_inherited
{
my ($vfspath, $mods) = @_;
my ($vfspath, $mods, $base) = @_;
if ($vfspath =~ /\|/) {
my @paths = split(/\|/, $vfspath, 2);
$base = load_inherited($paths[1], $mods, $base);
$base = load_inherited($paths[0], $mods, $base);
return $base
}
my $main_mod = get_main_mod($vfspath, $mods);
my $layer = load_xml($vfspath, get_file($vfspath, $main_mod));
if ($layer->{Entity}{'@parent'}) {
my $parent = load_inherited($layer->{Entity}{'@parent'}{' content'}, $mods);
my $parent = load_inherited($layer->{Entity}{'@parent'}{' content'}, $mods, $base);
apply_layer($parent->{Entity}, $layer->{Entity});
return $parent;
} else {
return $layer;
if (not $base) {
return $layer;
}
else {
apply_layer($base->{Entity}, $layer->{Entity});
return $base
}
}
}

View file

@ -130,7 +130,14 @@ sub add_entities
push @files, $path;
my $ent = Entity::load_inherited($f, "$mod_list_string");
push @deps, [ $path, "simulation/templates/" . $ent->{Entity}{'@parent'}{' content'} . ".xml" ] if $ent->{Entity}{'@parent'};
if ($ent->{Entity}{'@parent'})
{
my @parents = split(/\|/, $ent->{Entity}{'@parent'}{' content'});
for my $parentPath (@parents)
{
push @deps, [ $path, "simulation/templates/" . $parentPath . ".xml" ];
}
}
if ($f !~ /^template_/)
{
@ -638,6 +645,11 @@ sub check_deps
for my $f (sort keys %revdeps)
{
if ($f =~ /simulation\/templates\//)
{
next if exists $files{$f =~ s/templates\//templates\/special\/filter\//r};
next if exists $files{$f =~ s/templates\//templates\/mixins\//r};
}
next if exists $files{$f};
warn "Missing file '$f' referenced by: " . (join ', ', map "'$_'", map vfs_to_relative_to_mods($_), sort @{$revdeps{$f}}) . "\n";