mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
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:
parent
7321509d5f
commit
d73a3f59ad
26 changed files with 156 additions and 220 deletions
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue