Ticket #1589: template_manager.patch

File template_manager.patch, 39.8 KB (added by Itms, 10 years ago)
  • source/ps/TemplateLoader.cpp

     
     1/* Copyright (C) 2014 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#include "precompiled.h"
     19
     20#include "TemplateLoader.h"
     21
     22#include "ps/CLogger.h"
     23#include "ps/Filesystem.h"
     24#include "ps/XML/Xeromyces.h"
     25#include "lib/utf8.h"
     26
     27static const wchar_t TEMPLATE_ROOT[] = L"simulation/templates/";
     28static const wchar_t ACTOR_ROOT[] = L"art/actors/";
     29
     30static CParamNode NULL_NODE(false);
     31
     32
     33bool CTemplateLoader::LoadTemplateFile(const std::string& templateName, int depth)
     34{
     35    // If this file was already loaded, we don't need to do anything
     36    if (m_TemplateFileData.find(templateName) != m_TemplateFileData.end())
     37        return true;
     38
     39    // Handle infinite loops more gracefully than running out of stack space and crashing
     40    if (depth > 100)
     41    {
     42        LOGERROR(L"Probable infinite inheritance loop in entity template '%hs'", templateName.c_str());
     43        return false;
     44    }
     45
     46    // Handle special case "actor|foo"
     47    if (templateName.find("actor|") == 0)
     48    {
     49        ConstructTemplateActor(templateName.substr(6), m_TemplateFileData[templateName]);
     50        return true;
     51    }
     52
     53    // Handle special case "preview|foo"
     54    if (templateName.find("preview|") == 0)
     55    {
     56        // Load the base entity template, if it wasn't already loaded
     57        std::string baseName = templateName.substr(8);
     58        if (!LoadTemplateFile(baseName, depth+1))
     59        {
     60            LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
     61            return false;
     62        }
     63        // Copy a subset to the requested template
     64        CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], false);
     65        return true;
     66    }
     67
     68    // Handle special case "corpse|foo"
     69    if (templateName.find("corpse|") == 0)
     70    {
     71        // Load the base entity template, if it wasn't already loaded
     72        std::string baseName = templateName.substr(7);
     73        if (!LoadTemplateFile(baseName, depth+1))
     74        {
     75            LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
     76            return false;
     77        }
     78        // Copy a subset to the requested template
     79        CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], true);
     80        return true;
     81    }
     82
     83    // Handle special case "foundation|foo"
     84    if (templateName.find("foundation|") == 0)
     85    {
     86        // Load the base entity template, if it wasn't already loaded
     87        std::string baseName = templateName.substr(11);
     88        if (!LoadTemplateFile(baseName, depth+1))
     89        {
     90            LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
     91            return false;
     92        }
     93        // Copy a subset to the requested template
     94        CopyFoundationSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]);
     95        return true;
     96    }
     97
     98    // Handle special case "construction|foo"
     99    if (templateName.find("construction|") == 0)
     100    {
     101        // Load the base entity template, if it wasn't already loaded
     102        std::string baseName = templateName.substr(13);
     103        if (!LoadTemplateFile(baseName, depth+1))
     104        {
     105            LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
     106            return false;
     107        }
     108        // Copy a subset to the requested template
     109        CopyConstructionSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]);
     110        return true;
     111    }
     112
     113    // Handle special case "resource|foo"
     114    if (templateName.find("resource|") == 0)
     115    {
     116        // Load the base entity template, if it wasn't already loaded
     117        std::string baseName = templateName.substr(9);
     118        if (!LoadTemplateFile(baseName, depth+1))
     119        {
     120            LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
     121            return false;
     122        }
     123        // Copy a subset to the requested template
     124        CopyResourceSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]);
     125        return true;
     126    }
     127
     128    // Normal case: templateName is an XML file:
     129
     130    VfsPath path = VfsPath(TEMPLATE_ROOT) / wstring_from_utf8(templateName + ".xml");
     131    CXeromyces xero;
     132    PSRETURN ok = xero.Load(g_VFS, path);
     133    if (ok != PSRETURN_OK)
     134        return false; // (Xeromyces already logged an error with the full filename)
     135
     136    int attr_parent = xero.GetAttributeID("parent");
     137    CStr parentName = xero.GetRoot().GetAttributes().GetNamedItem(attr_parent);
     138    if (!parentName.empty())
     139    {
     140        // To prevent needless complexity in template design, we don't allow |-separated strings as parents
     141        if (parentName.find('|') != parentName.npos)
     142        {
     143            LOGERROR(L"Invalid parent '%hs' in entity template '%hs'", parentName.c_str(), templateName.c_str());
     144            return false;
     145        }
     146
     147        // Ensure the parent is loaded
     148        if (!LoadTemplateFile(parentName, depth+1))
     149        {
     150            LOGERROR(L"Failed to load parent '%hs' of entity template '%hs'", parentName.c_str(), templateName.c_str());
     151            return false;
     152        }
     153
     154        CParamNode& parentData = m_TemplateFileData[parentName];
     155
     156        // Initialise this template with its parent
     157        m_TemplateFileData[templateName] = parentData;
     158    }
     159
     160    // Load the new file into the template data (overriding parent values)
     161    CParamNode::LoadXML(m_TemplateFileData[templateName], xero, wstring_from_utf8(templateName).c_str());
     162
     163    return true;
     164}
     165
     166static Status AddToTemplates(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData)
     167{
     168    std::vector<std::string>& templates = *(std::vector<std::string>*)cbData;
     169
     170    // Strip the .xml extension
     171    VfsPath pathstem = pathname.ChangeExtension(L"");
     172    // Strip the root from the path
     173    std::wstring name = pathstem.string().substr(ARRAY_SIZE(TEMPLATE_ROOT)-1);
     174
     175    // We want to ignore template_*.xml templates, since they should never be built in the editor
     176    if (name.substr(0, 9) == L"template_")
     177        return INFO::OK;
     178
     179    templates.push_back(std::string(name.begin(), name.end()));
     180    return INFO::OK;
     181}
     182
     183static Status AddActorToTemplates(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData)
     184{
     185    std::vector<std::string>& templates = *(std::vector<std::string>*)cbData;
     186
     187    // Strip the root from the path
     188    std::wstring name = pathname.string().substr(ARRAY_SIZE(ACTOR_ROOT)-1);
     189
     190    templates.push_back("actor|" + std::string(name.begin(), name.end()));
     191    return INFO::OK;
     192}
     193
     194std::vector<std::string> CTemplateLoader::FindTemplates(const std::string& path, bool includeSubdirectories, ETemplatesType templatesType)
     195{
     196    std::vector<std::string> templates;
     197
     198    Status ok;
     199    VfsPath templatePath;
     200
     201    if (templatesType == SIMULATION_TEMPLATES || templatesType == ALL_TEMPLATES)
     202    {
     203        templatePath = VfsPath(TEMPLATE_ROOT) / path;
     204        if (includeSubdirectories)
     205            ok = vfs::ForEachFile(g_VFS, templatePath, AddToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE);
     206        else
     207            ok = vfs::ForEachFile(g_VFS, templatePath, AddToTemplates, (uintptr_t)&templates, L"*.xml");
     208        WARN_IF_ERR(ok);
     209    }
     210    if (templatesType == ACTOR_TEMPLATES || templatesType == ALL_TEMPLATES)
     211    {
     212        templatePath = VfsPath(ACTOR_ROOT) / path;
     213        if (includeSubdirectories)
     214            ok = vfs::ForEachFile(g_VFS, templatePath, AddActorToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE);
     215        else
     216            ok = vfs::ForEachFile(g_VFS, templatePath, AddActorToTemplates, (uintptr_t)&templates, L"*.xml");
     217        WARN_IF_ERR(ok);
     218    }
     219
     220    if (templatesType != SIMULATION_TEMPLATES && templatesType != ACTOR_TEMPLATES && templatesType != ALL_TEMPLATES)
     221        LOGERROR(L"Undefined template type (valid: all, simulation, actor)");
     222
     223    return templates;
     224}
     225
     226const CParamNode& CTemplateLoader::GetTemplateFileData(const std::string& templateName)
     227{
     228    // Load the template if necessary
     229    if (!LoadTemplateFile(templateName, 0))
     230    {
     231        LOGERROR(L"Failed to load entity template '%hs'", templateName.c_str());
     232        return NULL_NODE;
     233    }
     234
     235    return m_TemplateFileData[templateName];
     236}
     237
     238void CTemplateLoader::ConstructTemplateActor(const std::string& actorName, CParamNode& out)
     239{
     240    // Load the base actor template if necessary
     241    const char* templateName = "special/actor";
     242    if (!LoadTemplateFile(templateName, 0))
     243    {
     244        LOGERROR(L"Failed to load entity template '%hs'", templateName);
     245        return;
     246    }
     247
     248    // Copy the actor template
     249    out = m_TemplateFileData[templateName];
     250
     251    // Initialise the actor's name and make it an Atlas selectable entity.
     252    std::wstring actorNameW = wstring_from_utf8(actorName);
     253    std::string name = utf8_from_wstring(CParamNode::EscapeXMLString(actorNameW));
     254    std::string xml = "<Entity>"
     255                          "<VisualActor><Actor>" + name + "</Actor><ActorOnly/></VisualActor>"
     256                          // arbitrary-sized Footprint definition to make actors' selection outlines show up in Atlas
     257                          "<Footprint><Circle radius='2.0'/><Height>1.0</Height></Footprint>"
     258                          "<Selectable>"
     259                              "<EditorOnly/>"
     260                              "<Overlay><Texture><MainTexture>actor.png</MainTexture><MainTextureMask>actor_mask.png</MainTextureMask></Texture></Overlay>"
     261                          "</Selectable>"
     262                      "</Entity>";
     263
     264    CParamNode::LoadXMLString(out, xml.c_str(), actorNameW.c_str());
     265}
     266
     267void CTemplateLoader::CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse)
     268{
     269    // We only want to include components which are necessary (for the visual previewing of an entity)
     270    // and safe (i.e. won't do anything that affects the synchronised simulation state), so additions
     271    // to this list should be carefully considered
     272    std::set<std::string> permittedComponentTypes;
     273    permittedComponentTypes.insert("Identity");
     274    permittedComponentTypes.insert("Ownership");
     275    permittedComponentTypes.insert("Position");
     276    permittedComponentTypes.insert("VisualActor");
     277    permittedComponentTypes.insert("Footprint");
     278    permittedComponentTypes.insert("Obstruction");
     279    permittedComponentTypes.insert("Decay");
     280    permittedComponentTypes.insert("BuildRestrictions");
     281
     282    // Need these for the Actor Viewer:
     283    permittedComponentTypes.insert("Attack");
     284    permittedComponentTypes.insert("UnitMotion");
     285    permittedComponentTypes.insert("Sound");
     286
     287    // (This set could be initialised once and reused, but it's not worth the effort)
     288
     289    CParamNode::LoadXMLString(out, "<Entity/>");
     290    out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
     291
     292    // Disable the Obstruction component (if there is one) so it doesn't affect pathfinding
     293    // (but can still be used for testing this entity for collisions against others)
     294    if (out.GetChild("Entity").GetChild("Obstruction").IsOk())
     295        CParamNode::LoadXMLString(out, "<Entity><Obstruction><Active>false</Active></Obstruction></Entity>");
     296
     297    if (!corpse)
     298    {
     299        // Previews should not cast shadows
     300        if (out.GetChild("Entity").GetChild("VisualActor").IsOk())
     301            CParamNode::LoadXMLString(out, "<Entity><VisualActor><DisableShadows/></VisualActor></Entity>");
     302
     303        // Previews should always be visible in fog-of-war/etc
     304        CParamNode::LoadXMLString(out, "<Entity><Vision><Range>0</Range><RetainInFog>false</RetainInFog><AlwaysVisible>true</AlwaysVisible></Vision></Entity>");
     305    }
     306
     307    if (corpse)
     308    {
     309        // Corpses should include decay components and un-inactivate them
     310        if (out.GetChild("Entity").GetChild("Decay").IsOk())
     311            CParamNode::LoadXMLString(out, "<Entity><Decay><Inactive disable=''/></Decay></Entity>");
     312
     313        // Corpses shouldn't display silhouettes (especially since they're often half underground)
     314        if (out.GetChild("Entity").GetChild("VisualActor").IsOk())
     315            CParamNode::LoadXMLString(out, "<Entity><VisualActor><SilhouetteDisplay>false</SilhouetteDisplay></VisualActor></Entity>");
     316
     317        // Corpses should remain visible in fog-of-war
     318        CParamNode::LoadXMLString(out, "<Entity><Vision><Range>0</Range><RetainInFog>true</RetainInFog><AlwaysVisible>false</AlwaysVisible></Vision></Entity>");
     319    }
     320}
     321
     322void CTemplateLoader::CopyFoundationSubset(CParamNode& out, const CParamNode& in)
     323{
     324    // TODO: this is all kind of yucky and hard-coded; it'd be nice to have a more generic
     325    // extensible scriptable way to define these subsets
     326
     327    std::set<std::string> permittedComponentTypes;
     328    permittedComponentTypes.insert("Ownership");
     329    permittedComponentTypes.insert("Position");
     330    permittedComponentTypes.insert("VisualActor");
     331    permittedComponentTypes.insert("Identity");
     332    permittedComponentTypes.insert("BuildRestrictions");
     333    permittedComponentTypes.insert("Obstruction");
     334    permittedComponentTypes.insert("Selectable");
     335    permittedComponentTypes.insert("Footprint");
     336    permittedComponentTypes.insert("Armour");
     337    permittedComponentTypes.insert("Health");
     338    permittedComponentTypes.insert("StatusBars");
     339    permittedComponentTypes.insert("OverlayRenderer");
     340    permittedComponentTypes.insert("Decay");
     341    permittedComponentTypes.insert("Cost");
     342    permittedComponentTypes.insert("Sound");
     343    permittedComponentTypes.insert("Vision");
     344    permittedComponentTypes.insert("AIProxy");
     345    permittedComponentTypes.insert("RallyPoint");
     346    permittedComponentTypes.insert("RallyPointRenderer");
     347
     348    CParamNode::LoadXMLString(out, "<Entity/>");
     349    out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
     350
     351    // Switch the actor to foundation mode
     352    CParamNode::LoadXMLString(out, "<Entity><VisualActor><Foundation/></VisualActor></Entity>");
     353
     354    // Add the Foundation component, to deal with the construction process
     355    CParamNode::LoadXMLString(out, "<Entity><Foundation/></Entity>");
     356
     357    // Initialise health to 1
     358    CParamNode::LoadXMLString(out, "<Entity><Health><Initial>1</Initial></Health></Entity>");
     359
     360    // Foundations shouldn't initially block unit movement
     361    if (out.GetChild("Entity").GetChild("Obstruction").IsOk())
     362        CParamNode::LoadXMLString(out, "<Entity><Obstruction><DisableBlockMovement>true</DisableBlockMovement><DisableBlockPathfinding>true</DisableBlockPathfinding></Obstruction></Entity>");
     363
     364    // Don't provide population bonuses yet (but still do take up population cost)
     365    if (out.GetChild("Entity").GetChild("Cost").IsOk())
     366        CParamNode::LoadXMLString(out, "<Entity><Cost><PopulationBonus>0</PopulationBonus></Cost></Entity>");
     367
     368    // Foundations should be visible themselves in fog-of-war if their base template is,
     369    // but shouldn't have any vision range
     370    if (out.GetChild("Entity").GetChild("Vision").IsOk())
     371        CParamNode::LoadXMLString(out, "<Entity><Vision><Range>0</Range></Vision></Entity>");
     372}
     373
     374void CTemplateLoader::CopyConstructionSubset(CParamNode& out, const CParamNode& in)
     375{
     376    // Currently used for buildings rising during construction
     377    // Mostly serves to filter out components like Vision, UnitAI, etc.
     378    std::set<std::string> permittedComponentTypes;
     379    permittedComponentTypes.insert("Footprint");
     380    permittedComponentTypes.insert("Ownership");
     381    permittedComponentTypes.insert("Position");
     382    permittedComponentTypes.insert("VisualActor");
     383
     384    CParamNode::LoadXMLString(out, "<Entity/>");
     385    out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
     386}
     387
     388void CTemplateLoader::CopyResourceSubset(CParamNode& out, const CParamNode& in)
     389{
     390    // Currently used for animals which die and leave a gatherable corpse.
     391    // Mostly serves to filter out components like Vision, UnitAI, etc.
     392    std::set<std::string> permittedComponentTypes;
     393    permittedComponentTypes.insert("Ownership");
     394    permittedComponentTypes.insert("Position");
     395    permittedComponentTypes.insert("VisualActor");
     396    permittedComponentTypes.insert("Identity");
     397    permittedComponentTypes.insert("Obstruction");
     398    permittedComponentTypes.insert("Minimap");
     399    permittedComponentTypes.insert("ResourceSupply");
     400    permittedComponentTypes.insert("Selectable");
     401    permittedComponentTypes.insert("Footprint");
     402    permittedComponentTypes.insert("StatusBars");
     403    permittedComponentTypes.insert("OverlayRenderer");
     404    permittedComponentTypes.insert("Sound");
     405    permittedComponentTypes.insert("AIProxy");
     406
     407    CParamNode::LoadXMLString(out, "<Entity/>");
     408    out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
     409}
  • source/ps/TemplateLoader.h

    Property changes on: source/ps/TemplateLoader.cpp
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1/* Copyright (C) 2014 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#ifndef INCLUDED_TEMPLATELOADER
     19#define INCLUDED_TEMPLATELOADER
     20
     21#include "simulation2/system/ParamNode.h"
     22
     23enum ETemplatesType
     24{
     25    ALL_TEMPLATES,
     26    ACTOR_TEMPLATES,
     27    SIMULATION_TEMPLATES
     28};
     29
     30/**
     31 * Template loader: Handles the loading of entity template files for:
     32 * - the initialisation and deserialization of entity components in the
     33 *   simulation (CmpTemplateManager).
     34 * - access to actor templates, obstruction data, etc. in RMS/RMGEN
     35 *
     36 * Template names are intentionally restricted to ASCII strings for storage/serialization
     37 * efficiency (we have a lot of strings so this is significant);
     38 * they correspond to filenames so they shouldn't contain non-ASCII anyway.
     39 */
     40class CTemplateLoader
     41{
     42public:
     43    CTemplateLoader()
     44    {
     45    }
     46   
     47    /**
     48     * Provides the file data for requested template.
     49     */
     50    const CParamNode &GetTemplateFileData(const std::string& templateName);
     51
     52    /**
     53     * Returns a list of strings that could be validly passed as @c templateName to LoadTemplateFile.
     54     * (This includes "actor|foo" etc names).
     55     */
     56    std::vector<std::string> FindTemplates(const std::string& path, bool includeSubdirectories, ETemplatesType templatesType);
     57
     58private:
     59    /**
     60     * (Re)loads the given template, regardless of whether it exists already,
     61     * and saves into m_TemplateFileData. Also loads any parents that are not yet
     62     * loaded. Returns false on error.
     63     * @param templateName XML filename to load (not a |-separated string)
     64     */
     65    bool LoadTemplateFile(const std::string& templateName, int depth);
     66
     67    /**
     68     * Constructs a standard static-decorative-object template for the given actor
     69     */
     70    void ConstructTemplateActor(const std::string& actorName, CParamNode& out);
     71
     72    /**
     73     * Copy the non-interactive components of an entity template (position, actor, etc) into
     74     * a new entity template
     75     */
     76    void CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse);
     77
     78    /**
     79     * Copy the components of an entity template necessary for a construction foundation
     80     * (position, actor, armour, health, etc) into a new entity template
     81     */
     82    void CopyFoundationSubset(CParamNode& out, const CParamNode& in);
     83
     84    /**
     85     * Copy the components of an entity template necessary for a non-foundation construction entity
     86     * into a new entity template
     87     */
     88    void CopyConstructionSubset(CParamNode& out, const CParamNode& in);
     89
     90    /**
     91     * Copy the components of an entity template necessary for a gatherable resource
     92     * into a new entity template
     93     */
     94    void CopyResourceSubset(CParamNode& out, const CParamNode& in);
     95
     96    /**
     97     * Map from template name (XML filename or special |-separated string) to the most recently
     98     * loaded non-broken template data. This includes files that will fail schema validation.
     99     * (Failed loads won't remove existing entries under the same name, so we behave more nicely
     100     * when hotloading broken files)
     101     */
     102    std::map<std::string, CParamNode> m_TemplateFileData;
     103};
     104
     105#endif // INCLUDED_TEMPLATELOADER
  • source/simulation2/components/CCmpTemplateManager.cpp

    Property changes on: source/ps/TemplateLoader.h
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    1 /* Copyright (C) 2013 Wildfire Games.
     1/* Copyright (C) 2014 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
     
    2222
    2323#include "simulation2/MessageTypes.h"
    2424
     25#include "ps/TemplateLoader.h"
     26
    2527#include "lib/utf8.h"
    2628#include "ps/CLogger.h"
    27 #include "ps/Filesystem.h"
    2829#include "ps/XML/RelaxNG.h"
    29 #include "ps/XML/Xeromyces.h"
    3030
    31 static const wchar_t TEMPLATE_ROOT[] = L"simulation/templates/";
    32 static const wchar_t ACTOR_ROOT[] = L"art/actors/";
    33 
    3431class CCmpTemplateManager : public ICmpTemplateManager
    3532{
    3633public:
     
    136133    virtual std::vector<entity_id_t> GetEntitiesUsingTemplate(std::string templateName);
    137134
    138135private:
     136    // Template loader
     137    CTemplateLoader m_templateLoader;
     138
    139139    // Entity template XML validator
    140140    RelaxNGValidator m_Validator;
    141141
     
    142142    // Disable validation, for test cases
    143143    bool m_DisableValidation;
    144144
    145     // Map from template name (XML filename or special |-separated string) to the most recently
    146     // loaded non-broken template data. This includes files that will fail schema validation.
    147     // (Failed loads won't remove existing entries under the same name, so we behave more nicely
    148     // when hotloading broken files)
    149     std::map<std::string, CParamNode> m_TemplateFileData;
    150 
    151145    // Map from template name to schema validation status.
    152146    // (Some files, e.g. inherited parent templates, may not be valid themselves but we still need to load
    153147    // them and use them; we only reject invalid templates that were requested directly by GetTemplate/etc)
     
    157151    // again for deserialization.
    158152    // TODO: should store player ID etc.
    159153    std::map<entity_id_t, std::string> m_LatestTemplates;
    160 
    161     // (Re)loads the given template, regardless of whether it exists already,
    162     // and saves into m_TemplateFileData. Also loads any parents that are not yet
    163     // loaded. Returns false on error.
    164     // @param templateName XML filename to load (not a |-separated string)
    165     bool LoadTemplateFile(const std::string& templateName, int depth);
    166 
    167     // Constructs a standard static-decorative-object template for the given actor
    168     void ConstructTemplateActor(const std::string& actorName, CParamNode& out);
    169 
    170     // Copy the non-interactive components of an entity template (position, actor, etc) into
    171     // a new entity template
    172     void CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse);
    173 
    174     // Copy the components of an entity necessary for a construction foundation
    175     // (position, actor, armour, health, etc) into a new entity template
    176     void CopyFoundationSubset(CParamNode& out, const CParamNode& in);
    177 
    178     // Copy the components of an entity necessary for a non-foundation construction entity
    179     // into a new entity template
    180     void CopyConstructionSubset(CParamNode& out, const CParamNode& in);
    181 
    182     // Copy the components of an entity necessary for a gatherable resource
    183     // into a new entity template
    184     void CopyResourceSubset(CParamNode& out, const CParamNode& in);
    185154};
    186155
    187156REGISTER_COMPONENT_TYPE(TemplateManager)
     
    201170
    202171const CParamNode* CCmpTemplateManager::GetTemplate(std::string templateName)
    203172{
    204     // Load the template if necessary
    205     if (!LoadTemplateFile(templateName, 0))
    206     {
    207         LOGERROR(L"Failed to load entity template '%hs'", templateName.c_str());
     173    const CParamNode& fileData = m_templateLoader.GetTemplateFileData(templateName);
     174    if (!fileData.IsOk())
    208175        return NULL;
    209     }
    210176
    211177    if (!m_DisableValidation)
    212178    {
     
    213179        // Compute validity, if it's not computed before
    214180        if (m_TemplateSchemaValidity.find(templateName) == m_TemplateSchemaValidity.end())
    215181        {
    216             m_TemplateSchemaValidity[templateName] = m_Validator.Validate(wstring_from_utf8(templateName), m_TemplateFileData[templateName].ToXML());
     182            m_TemplateSchemaValidity[templateName] = m_Validator.Validate(wstring_from_utf8(templateName), fileData.ToXML());
    217183
    218184            // Show error on the first failure to validate the template
    219185            if (!m_TemplateSchemaValidity[templateName])
     
    224190            return NULL;
    225191    }
    226192
    227     const CParamNode& templateRoot = m_TemplateFileData[templateName].GetChild("Entity");
     193    const CParamNode& templateRoot = fileData.GetChild("Entity");
    228194    if (!templateRoot.IsOk())
    229195    {
    230196        // The validator should never let this happen
     
    237203
    238204const CParamNode* CCmpTemplateManager::GetTemplateWithoutValidation(std::string templateName)
    239205{
    240     // Load the template if necessary
    241     if (!LoadTemplateFile(templateName, 0))
    242     {
    243         LOGERROR(L"Failed to load entity template '%hs'", templateName.c_str());
    244         return NULL;
    245     }
    246 
    247     const CParamNode& templateRoot = m_TemplateFileData[templateName].GetChild("Entity");
     206    const CParamNode& templateRoot = m_templateLoader.GetTemplateFileData(templateName).GetChild("Entity");
    248207    if (!templateRoot.IsOk())
    249208        return NULL;
    250209
     
    267226    return it->second;
    268227}
    269228
    270 bool CCmpTemplateManager::LoadTemplateFile(const std::string& templateName, int depth)
    271 {
    272     // If this file was already loaded, we don't need to do anything
    273     if (m_TemplateFileData.find(templateName) != m_TemplateFileData.end())
    274         return true;
    275 
    276     // Handle infinite loops more gracefully than running out of stack space and crashing
    277     if (depth > 100)
    278     {
    279         LOGERROR(L"Probable infinite inheritance loop in entity template '%hs'", templateName.c_str());
    280         return false;
    281     }
    282 
    283     // Handle special case "actor|foo"
    284     if (templateName.find("actor|") == 0)
    285     {
    286         ConstructTemplateActor(templateName.substr(6), m_TemplateFileData[templateName]);
    287         return true;
    288     }
    289 
    290     // Handle special case "preview|foo"
    291     if (templateName.find("preview|") == 0)
    292     {
    293         // Load the base entity template, if it wasn't already loaded
    294         std::string baseName = templateName.substr(8);
    295         if (!LoadTemplateFile(baseName, depth+1))
    296         {
    297             LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
    298             return false;
    299         }
    300         // Copy a subset to the requested template
    301         CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], false);
    302         return true;
    303     }
    304 
    305     // Handle special case "corpse|foo"
    306     if (templateName.find("corpse|") == 0)
    307     {
    308         // Load the base entity template, if it wasn't already loaded
    309         std::string baseName = templateName.substr(7);
    310         if (!LoadTemplateFile(baseName, depth+1))
    311         {
    312             LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
    313             return false;
    314         }
    315         // Copy a subset to the requested template
    316         CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], true);
    317         return true;
    318     }
    319 
    320     // Handle special case "foundation|foo"
    321     if (templateName.find("foundation|") == 0)
    322     {
    323         // Load the base entity template, if it wasn't already loaded
    324         std::string baseName = templateName.substr(11);
    325         if (!LoadTemplateFile(baseName, depth+1))
    326         {
    327             LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
    328             return false;
    329         }
    330         // Copy a subset to the requested template
    331         CopyFoundationSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]);
    332         return true;
    333     }
    334 
    335     // Handle special case "construction|foo"
    336     if (templateName.find("construction|") == 0)
    337     {
    338         // Load the base entity template, if it wasn't already loaded
    339         std::string baseName = templateName.substr(13);
    340         if (!LoadTemplateFile(baseName, depth+1))
    341         {
    342             LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
    343             return false;
    344         }
    345         // Copy a subset to the requested template
    346         CopyConstructionSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]);
    347         return true;
    348     }
    349 
    350     // Handle special case "resource|foo"
    351     if (templateName.find("resource|") == 0)
    352     {
    353         // Load the base entity template, if it wasn't already loaded
    354         std::string baseName = templateName.substr(9);
    355         if (!LoadTemplateFile(baseName, depth+1))
    356         {
    357             LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
    358             return false;
    359         }
    360         // Copy a subset to the requested template
    361         CopyResourceSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]);
    362         return true;
    363     }
    364 
    365     // Normal case: templateName is an XML file:
    366 
    367     VfsPath path = VfsPath(TEMPLATE_ROOT) / wstring_from_utf8(templateName + ".xml");
    368     CXeromyces xero;
    369     PSRETURN ok = xero.Load(g_VFS, path);
    370     if (ok != PSRETURN_OK)
    371         return false; // (Xeromyces already logged an error with the full filename)
    372 
    373     int attr_parent = xero.GetAttributeID("parent");
    374     CStr parentName = xero.GetRoot().GetAttributes().GetNamedItem(attr_parent);
    375     if (!parentName.empty())
    376     {
    377         // To prevent needless complexity in template design, we don't allow |-separated strings as parents
    378         if (parentName.find('|') != parentName.npos)
    379         {
    380             LOGERROR(L"Invalid parent '%hs' in entity template '%hs'", parentName.c_str(), templateName.c_str());
    381             return false;
    382         }
    383 
    384         // Ensure the parent is loaded
    385         if (!LoadTemplateFile(parentName, depth+1))
    386         {
    387             LOGERROR(L"Failed to load parent '%hs' of entity template '%hs'", parentName.c_str(), templateName.c_str());
    388             return false;
    389         }
    390 
    391         CParamNode& parentData = m_TemplateFileData[parentName];
    392 
    393         // Initialise this template with its parent
    394         m_TemplateFileData[templateName] = parentData;
    395     }
    396 
    397     // Load the new file into the template data (overriding parent values)
    398     CParamNode::LoadXML(m_TemplateFileData[templateName], xero, wstring_from_utf8(templateName).c_str());
    399 
    400     return true;
    401 }
    402 
    403 void CCmpTemplateManager::ConstructTemplateActor(const std::string& actorName, CParamNode& out)
    404 {
    405     // Load the base actor template if necessary
    406     const char* templateName = "special/actor";
    407     if (!LoadTemplateFile(templateName, 0))
    408     {
    409         LOGERROR(L"Failed to load entity template '%hs'", templateName);
    410         return;
    411     }
    412 
    413     // Copy the actor template
    414     out = m_TemplateFileData[templateName];
    415 
    416     // Initialise the actor's name and make it an Atlas selectable entity.
    417     std::wstring actorNameW = wstring_from_utf8(actorName);
    418     std::string name = utf8_from_wstring(CParamNode::EscapeXMLString(actorNameW));
    419     std::string xml = "<Entity>"
    420                           "<VisualActor><Actor>" + name + "</Actor><ActorOnly/></VisualActor>"
    421                           // arbitrary-sized Footprint definition to make actors' selection outlines show up in Atlas
    422                           "<Footprint><Circle radius='2.0'/><Height>1.0</Height></Footprint>"
    423                           "<Selectable>"
    424                               "<EditorOnly/>"
    425                               "<Overlay><Texture><MainTexture>actor.png</MainTexture><MainTextureMask>actor_mask.png</MainTextureMask></Texture></Overlay>"
    426                           "</Selectable>"
    427                       "</Entity>";
    428 
    429     CParamNode::LoadXMLString(out, xml.c_str(), actorNameW.c_str());
    430 }
    431 
    432 static Status AddToTemplates(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData)
    433 {
    434     std::vector<std::string>& templates = *(std::vector<std::string>*)cbData;
    435 
    436     // Strip the .xml extension
    437     VfsPath pathstem = pathname.ChangeExtension(L"");
    438     // Strip the root from the path
    439     std::wstring name = pathstem.string().substr(ARRAY_SIZE(TEMPLATE_ROOT)-1);
    440 
    441     // We want to ignore template_*.xml templates, since they should never be built in the editor
    442     if (name.substr(0, 9) == L"template_")
    443         return INFO::OK;
    444 
    445     templates.push_back(std::string(name.begin(), name.end()));
    446     return INFO::OK;
    447 }
    448 
    449 static Status AddActorToTemplates(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData)
    450 {
    451     std::vector<std::string>& templates = *(std::vector<std::string>*)cbData;
    452 
    453     // Strip the root from the path
    454     std::wstring name = pathname.string().substr(ARRAY_SIZE(ACTOR_ROOT)-1);
    455 
    456     templates.push_back("actor|" + std::string(name.begin(), name.end()));
    457     return INFO::OK;
    458 }
    459 
    460229std::vector<std::string> CCmpTemplateManager::FindAllTemplates(bool includeActors)
    461230{
    462     // TODO: eventually this should probably read all the template files and look for flags to
    463     // determine which should be displayed in the editor (and in what categories etc); for now we'll
    464     // just return all the files
    465 
    466     std::vector<std::string> templates;
    467 
    468     Status ok;
    469 
    470     // Find all the normal entity templates first
    471     ok = vfs::ForEachFile(g_VFS, TEMPLATE_ROOT, AddToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE);
    472     WARN_IF_ERR(ok);
    473 
    474     if (includeActors)
    475     {
    476         // Add all the actors too
    477         ok = vfs::ForEachFile(g_VFS, ACTOR_ROOT, AddActorToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE);
    478         WARN_IF_ERR(ok);
    479     }
    480 
    481     return templates;
     231    ETemplatesType templatesType = includeActors ? ALL_TEMPLATES : SIMULATION_TEMPLATES;
     232    return m_templateLoader.FindTemplates("", true, templatesType);
    482233}
    483234
    484235/**
     
    494245    }
    495246    return entities;
    496247}
    497 
    498 void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse)
    499 {
    500     // We only want to include components which are necessary (for the visual previewing of an entity)
    501     // and safe (i.e. won't do anything that affects the synchronised simulation state), so additions
    502     // to this list should be carefully considered
    503     std::set<std::string> permittedComponentTypes;
    504     permittedComponentTypes.insert("Identity");
    505     permittedComponentTypes.insert("Ownership");
    506     permittedComponentTypes.insert("Position");
    507     permittedComponentTypes.insert("VisualActor");
    508     permittedComponentTypes.insert("Footprint");
    509     permittedComponentTypes.insert("Obstruction");
    510     permittedComponentTypes.insert("Decay");
    511     permittedComponentTypes.insert("BuildRestrictions");
    512 
    513     // Need these for the Actor Viewer:
    514     permittedComponentTypes.insert("Attack");
    515     permittedComponentTypes.insert("UnitMotion");
    516     permittedComponentTypes.insert("Sound");
    517 
    518     // (This set could be initialised once and reused, but it's not worth the effort)
    519 
    520     CParamNode::LoadXMLString(out, "<Entity/>");
    521     out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
    522 
    523     // Disable the Obstruction component (if there is one) so it doesn't affect pathfinding
    524     // (but can still be used for testing this entity for collisions against others)
    525     if (out.GetChild("Entity").GetChild("Obstruction").IsOk())
    526         CParamNode::LoadXMLString(out, "<Entity><Obstruction><Active>false</Active></Obstruction></Entity>");
    527 
    528     if (!corpse)
    529     {
    530         // Previews should not cast shadows
    531         if (out.GetChild("Entity").GetChild("VisualActor").IsOk())
    532             CParamNode::LoadXMLString(out, "<Entity><VisualActor><DisableShadows/></VisualActor></Entity>");
    533 
    534         // Previews should always be visible in fog-of-war/etc
    535         CParamNode::LoadXMLString(out, "<Entity><Vision><Range>0</Range><RetainInFog>false</RetainInFog><AlwaysVisible>true</AlwaysVisible></Vision></Entity>");
    536     }
    537 
    538     if (corpse)
    539     {
    540         // Corpses should include decay components and un-inactivate them
    541         if (out.GetChild("Entity").GetChild("Decay").IsOk())
    542             CParamNode::LoadXMLString(out, "<Entity><Decay><Inactive disable=''/></Decay></Entity>");
    543 
    544         // Corpses shouldn't display silhouettes (especially since they're often half underground)
    545         if (out.GetChild("Entity").GetChild("VisualActor").IsOk())
    546             CParamNode::LoadXMLString(out, "<Entity><VisualActor><SilhouetteDisplay>false</SilhouetteDisplay></VisualActor></Entity>");
    547 
    548         // Corpses should remain visible in fog-of-war
    549         CParamNode::LoadXMLString(out, "<Entity><Vision><Range>0</Range><RetainInFog>true</RetainInFog><AlwaysVisible>false</AlwaysVisible></Vision></Entity>");
    550     }
    551 }
    552 
    553 void CCmpTemplateManager::CopyFoundationSubset(CParamNode& out, const CParamNode& in)
    554 {
    555     // TODO: this is all kind of yucky and hard-coded; it'd be nice to have a more generic
    556     // extensible scriptable way to define these subsets
    557 
    558     std::set<std::string> permittedComponentTypes;
    559     permittedComponentTypes.insert("Ownership");
    560     permittedComponentTypes.insert("Position");
    561     permittedComponentTypes.insert("VisualActor");
    562     permittedComponentTypes.insert("Identity");
    563     permittedComponentTypes.insert("BuildRestrictions");
    564     permittedComponentTypes.insert("Obstruction");
    565     permittedComponentTypes.insert("Selectable");
    566     permittedComponentTypes.insert("Footprint");
    567     permittedComponentTypes.insert("Armour");
    568     permittedComponentTypes.insert("Health");
    569     permittedComponentTypes.insert("StatusBars");
    570     permittedComponentTypes.insert("OverlayRenderer");
    571     permittedComponentTypes.insert("Decay");
    572     permittedComponentTypes.insert("Cost");
    573     permittedComponentTypes.insert("Sound");
    574     permittedComponentTypes.insert("Vision");
    575     permittedComponentTypes.insert("AIProxy");
    576     permittedComponentTypes.insert("RallyPoint");
    577     permittedComponentTypes.insert("RallyPointRenderer");
    578 
    579     CParamNode::LoadXMLString(out, "<Entity/>");
    580     out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
    581 
    582     // Switch the actor to foundation mode
    583     CParamNode::LoadXMLString(out, "<Entity><VisualActor><Foundation/></VisualActor></Entity>");
    584 
    585     // Add the Foundation component, to deal with the construction process
    586     CParamNode::LoadXMLString(out, "<Entity><Foundation/></Entity>");
    587 
    588     // Initialise health to 1
    589     CParamNode::LoadXMLString(out, "<Entity><Health><Initial>1</Initial></Health></Entity>");
    590 
    591     // Foundations shouldn't initially block unit movement
    592     if (out.GetChild("Entity").GetChild("Obstruction").IsOk())
    593         CParamNode::LoadXMLString(out, "<Entity><Obstruction><DisableBlockMovement>true</DisableBlockMovement><DisableBlockPathfinding>true</DisableBlockPathfinding></Obstruction></Entity>");
    594 
    595     // Don't provide population bonuses yet (but still do take up population cost)
    596     if (out.GetChild("Entity").GetChild("Cost").IsOk())
    597         CParamNode::LoadXMLString(out, "<Entity><Cost><PopulationBonus>0</PopulationBonus></Cost></Entity>");
    598 
    599     // Foundations should be visible themselves in fog-of-war if their base template is,
    600     // but shouldn't have any vision range
    601     if (out.GetChild("Entity").GetChild("Vision").IsOk())
    602         CParamNode::LoadXMLString(out, "<Entity><Vision><Range>0</Range></Vision></Entity>");
    603 }
    604 
    605 void CCmpTemplateManager::CopyConstructionSubset(CParamNode& out, const CParamNode& in)
    606 {
    607     // Currently used for buildings rising during construction
    608     // Mostly serves to filter out components like Vision, UnitAI, etc.
    609     std::set<std::string> permittedComponentTypes;
    610     permittedComponentTypes.insert("Footprint");
    611     permittedComponentTypes.insert("Ownership");
    612     permittedComponentTypes.insert("Position");
    613     permittedComponentTypes.insert("VisualActor");
    614 
    615     CParamNode::LoadXMLString(out, "<Entity/>");
    616     out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
    617 }
    618 
    619 
    620 void CCmpTemplateManager::CopyResourceSubset(CParamNode& out, const CParamNode& in)
    621 {
    622     // Currently used for animals which die and leave a gatherable corpse.
    623     // Mostly serves to filter out components like Vision, UnitAI, etc.
    624     std::set<std::string> permittedComponentTypes;
    625     permittedComponentTypes.insert("Ownership");
    626     permittedComponentTypes.insert("Position");
    627     permittedComponentTypes.insert("VisualActor");
    628     permittedComponentTypes.insert("Identity");
    629     permittedComponentTypes.insert("Obstruction");
    630     permittedComponentTypes.insert("Minimap");
    631     permittedComponentTypes.insert("ResourceSupply");
    632     permittedComponentTypes.insert("Selectable");
    633     permittedComponentTypes.insert("Footprint");
    634     permittedComponentTypes.insert("StatusBars");
    635     permittedComponentTypes.insert("OverlayRenderer");
    636     permittedComponentTypes.insert("Sound");
    637     permittedComponentTypes.insert("AIProxy");
    638 
    639     CParamNode::LoadXMLString(out, "<Entity/>");
    640     out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
    641 }