Changeset 25223

Timestamp:
Apr 9, 2021, 5:43:50 PM (3 years ago)
Author:
wraitii
Message:

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

Location:
ps/trunk
Files:
2 added
2 deleted
22 edited
1 moved

Legend:

Unmodified
Added
Removed
  • ps/trunk/binaries/data/mods/public/simulation/templates/mixins/hoplite.xml

    r25221 r25223  
    1 <?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit_hero_infantry_spearman">
     1<?xml version="1.0" encoding="utf-8"?>
     2<Entity>
    33  <Identity>
    44    <Formations datatype="tokens">
  • ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml

    r25174 r25223  
    11<?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit">
     2<Entity parent="template_unit">
    33  <Attack>
    44    <Capture>
     
    1717    </Slaughter>
    1818  </Attack>
    19   <Builder>
    20     <Rate>1.0</Rate>
    21     <Entities datatype="tokens">
    22       structures/{civ}/civil_centre
    23       structures/{civ}/crannog
    24       structures/{civ}/military_colony
    25       structures/{civ}/house
    26       structures/{civ}/apartment
    27       structures/{civ}/storehouse
    28       structures/{civ}/farmstead
    29       structures/{civ}/field
    30       structures/{civ}/corral
    31       structures/{civ}/dock
    32       structures/{civ}/barracks
    33       structures/{civ}/stable
    34       structures/{civ}/elephant_stable
    35       structures/{civ}/arsenal
    36       structures/{civ}/forge
    37       structures/{civ}/temple
    38       structures/{civ}/market
    39       structures/{civ}/outpost
    40       structures/{civ}/sentry_tower
    41       structures/{civ}/defense_tower
    42       structures/{civ}/fortress
    43       structures/wallset_palisade
    44       structures/{civ}/wallset_siege
    45       structures/{civ}/wallset_stone
    46       structures/{civ}/theater
    47       structures/{civ}/wonder
    48     </Entities>
    49   </Builder>
    5019  <Cost>
    5120    <BuildTime>12</BuildTime>
     
    6332    <GenericName>Infantry</GenericName>
    6433    <Classes datatype="tokens">Human CitizenSoldier</Classes>
    65     <VisibleClasses datatype="tokens">Citizen Builder Worker Soldier Infantry</VisibleClasses>
     34    <VisibleClasses datatype="tokens">Citizen Worker Soldier Infantry</VisibleClasses>
    6635    <Rank>Basic</Rank>
    6736  </Identity>
  • ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_support_female_citizen.xml

    r25174 r25223  
    11<?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit_support">
     2<Entity parent="template_unit_support">
    33  <Attack>
    44    <Melee>
     
    1919    </Slaughter>
    2020  </Attack>
    21   <Builder>
    22     <Rate>1.0</Rate>
    23     <Entities datatype="tokens">
    24       structures/{civ}/civil_centre
    25       structures/{civ}/crannog
    26       structures/{civ}/military_colony
    27       structures/{civ}/house
    28       structures/{civ}/apartment
    29       structures/{civ}/storehouse
    30       structures/{civ}/farmstead
    31       structures/{civ}/field
    32       structures/{civ}/corral
    33       structures/{civ}/dock
    34       structures/{civ}/barracks
    35       structures/{civ}/stable
    36       structures/{civ}/elephant_stable
    37       structures/{civ}/arsenal
    38       structures/{civ}/forge
    39       structures/{civ}/temple
    40       structures/{civ}/market
    41       structures/{civ}/outpost
    42       structures/{civ}/sentry_tower
    43       structures/{civ}/defense_tower
    44       structures/{civ}/fortress
    45       structures/wallset_palisade
    46       structures/{civ}/wallset_siege
    47       structures/{civ}/wallset_stone
    48       structures/{civ}/theater
    49       structures/{civ}/wonder
    50     </Entities>
    51   </Builder>
    5221  <Cost>
    5322    <BuildTime>9</BuildTime>
     
    6433    <SelectionGroupName>template_unit_support_female_citizen</SelectionGroupName>
    6534    <Classes datatype="tokens">FemaleCitizen</Classes>
    66     <VisibleClasses datatype="tokens">Citizen Builder Worker</VisibleClasses>
     35    <VisibleClasses datatype="tokens">Citizen Worker</VisibleClasses>
    6736    <Formations disable=""/>
    6837  </Identity>
  • ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_support_slave.xml

    r25174 r25223  
    11<?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit_support">
     2<Entity parent="template_unit_support">
    33  <Builder>
    44    <Rate>0.5</Rate>
    5     <Entities datatype="tokens">
    6       structures/{civ}/civil_centre
    7       structures/{civ}/crannog
    8       structures/{civ}/military_colony
    9       structures/{civ}/house
    10       structures/{civ}/apartment
    11       structures/{civ}/storehouse
    12       structures/{civ}/farmstead
    13       structures/{civ}/field
    14       structures/{civ}/corral
    15       structures/{civ}/dock
    16       structures/{civ}/barracks
    17       structures/{civ}/stable
    18       structures/{civ}/elephant_stable
    19       structures/{civ}/arsenal
    20       structures/{civ}/forge
    21       structures/{civ}/temple
    22       structures/{civ}/market
    23       structures/{civ}/outpost
    24       structures/{civ}/sentry_tower
    25       structures/{civ}/defense_tower
    26       structures/{civ}/fortress
    27       structures/wallset_palisade
    28       structures/{civ}/wallset_siege
    29       structures/{civ}/wallset_stone
    30       structures/{civ}/theater
    31       structures/{civ}/wonder
    32     </Entities>
    335  </Builder>
    346  <Cost>
     
    4719    <SelectionGroupName>template_unit_support_slave</SelectionGroupName>
    4820    <Tooltip>Gatherer with a finite life span. Bonused at mining and lumbering.</Tooltip>
    49     <VisibleClasses datatype="tokens">Builder Worker Slave</VisibleClasses>
     21    <VisibleClasses datatype="tokens">Worker Slave</VisibleClasses>
    5022  </Identity>
    5123  <Loot>
  • ps/trunk/binaries/data/mods/public/simulation/templates/units/athen/champion_infantry.xml

    r24216 r25223  
    11<?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit_champion_infantry_spearman_hoplite">
     2<Entity parent="">
    33  <Identity>
    44    <Civ>athen</Civ>
  • ps/trunk/binaries/data/mods/public/simulation/templates/units/athen/hero_pericles.xml

    r24216 r25223  
    11<?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit_hero_infantry_spearman_hoplite">
     2<Entity parent="">
    33  <Auras datatype="tokens">
    44    units/heroes/athen_hero_pericles_1
  • ps/trunk/binaries/data/mods/public/simulation/templates/units/athen/infantry_spearman_b.xml

    r24687 r25223  
    11<?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit_infantry_melee_spearman_hoplite">
     2<Entity parent="">
    33  <Builder>
    44    <Entities datatype="tokens">
  • ps/trunk/binaries/data/mods/public/simulation/templates/units/cart/champion_infantry.xml

    r24216 r25223  
    11<?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit_champion_infantry_spearman_hoplite">
     2<Entity parent="">
    33  <Identity>
    44    <Civ>cart</Civ>
  • ps/trunk/binaries/data/mods/public/simulation/templates/units/cart/infantry_spearman_b.xml

    r24216 r25223  
    11<?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit_infantry_melee_spearman_hoplite">
     2<Entity parent="">
    33  <Builder>
    44    <Entities datatype="tokens">
  • ps/trunk/binaries/data/mods/public/simulation/templates/units/mace/champion_infantry_spearman.xml

    r24657 r25223  
    11<?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit_champion_infantry_spearman_hoplite">
     2<Entity parent="">
    33  <Identity>
    44    <Civ>mace</Civ>
  • ps/trunk/binaries/data/mods/public/simulation/templates/units/pers/kardakes_hoplite.xml

    r24216 r25223  
    11<?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit_champion_infantry_spearman_hoplite">
     2<Entity parent="">
    33  <Identity>
    44    <Civ>pers</Civ>
  • ps/trunk/binaries/data/mods/public/simulation/templates/units/ptol/infantry_spearman_merc_b.xml

    r24330 r25223  
    11<?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit_infantry_melee_spearman_hoplite">
     2<Entity parent="">
    33  <Builder>
    44    <Entities datatype="tokens">
  • ps/trunk/binaries/data/mods/public/simulation/templates/units/samnite_spearman.xml

    r22824 r25223  
    11<?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit_champion_infantry_spearman_hoplite">
     2<Entity parent="">
    33  <Identity>
    44    <VisibleClasses datatype="tokens">Mercenary</VisibleClasses>
  • ps/trunk/binaries/data/mods/public/simulation/templates/units/sele/infantry_spearman_b.xml

    r24687 r25223  
    11<?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit_infantry_melee_spearman_hoplite">
     2<Entity parent="">
    33  <Identity>
    44    <Civ>sele</Civ>
  • ps/trunk/binaries/data/mods/public/simulation/templates/units/spart/champion_infantry_spear.xml

    r24216 r25223  
    11<?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit_champion_infantry_spearman_hoplite">
     2<Entity parent="">
    33  <Identity>
    44    <Civ>spart</Civ>
  • ps/trunk/binaries/data/mods/public/simulation/templates/units/spart/hero_agis.xml

    r24474 r25223  
    11<?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit_hero_infantry_spearman_hoplite">
     2<Entity parent="">
    33  <Identity>
    44    <Civ>spart</Civ>
  • ps/trunk/binaries/data/mods/public/simulation/templates/units/spart/hero_leonidas.xml

    r24216 r25223  
    11<?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit_hero_infantry_spearman_hoplite">
     2<Entity parent="">
    33  <Auras datatype="tokens">
    44    units/heroes/spart_hero_leonidas
  • ps/trunk/binaries/data/mods/public/simulation/templates/units/spart/infantry_spearman_b.xml

    r24687 r25223  
    11<?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit_infantry_melee_spearman_hoplite">
     2<Entity parent="">
    33  <Builder>
    44    <Entities datatype="tokens">
  • ps/trunk/binaries/data/mods/public/simulation/templates/units/thebes_sacred_band_hoplitai.xml

    r21975 r25223  
    11<?xml version="1.0" encoding="utf-8"?>
    2 <Entity parent="template_unit_champion_infantry_spearman_hoplite">
     2<Entity parent="">
    33  <Identity>
    44    <Lang>greek</Lang>
  • ps/trunk/source/ps/TemplateLoader.cpp

    r23704 r25223  
    1 /* Copyright (C) 2020 Wildfire Games.
     1/* Copyright (C) 202 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
     
    3030static CParamNode NULL_NODE(false);
    3131
    32 bool CTemplateLoader::LoadTemplateFile(const std::string& templateName, int depth)
     32bool CTemplateLoader::LoadTemplateFile(, int depth)
    3333{
    34     // If this file was already loaded, we don't need to do anything
    35     if (m_TemplateFileData.find(templateName) != m_TemplateFileData.end())
     34    // Handle special case "actor|foo", which does not load 'foo' at all, just uses the name.
     35    if (templateName.compare(0, 6, "actor|") == 0)
     36    {
     37        ConstructTemplateActor(templateName.substr(6), node);
    3638        return true;
    37 
     39    }
    3840    // Handle infinite loops more gracefully than running out of stack space and crashing
    3941    if (depth > 100)
    4042    {
    41         LOGERROR("Probable infinite inheritance loop in entity template '%s'", templateName.c_str());
     43        LOGERROR("Probable infinite inheritance loop in entity template '%s'", ));
    4244        return false;
    4345    }
    4446
    45     // Handle special case "actor|foo"
    46     if (templateName.find("actor|") == 0)
     47   
     48    if ()
    4749    {
    48         ConstructTemplateActor(templateName.substr(6), m_TemplateFileData[templateName]);
     50        // 'foo|bar' pattern: 'bar' is treated as the parent of 'foo'.
     51        if (!LoadTemplateFile(node, templateName.substr(pos + 1), false, depth + 1))
     52            return false;
     53        if (!LoadTemplateFile(node, templateName.substr(0, pos), true, depth + 1))
     54            return false;
    4955        return true;
    5056    }
    5157
    52     // Handle special case "bar|foo"
    53     size_t pos = templateName.find_first_of('|');
    54     if (pos != std::string::npos)
    55     {
    56         std::string prefix = templateName.substr(0, pos);
    57         std::string baseName = templateName.substr(pos+1);
     58    // Load the data we need to apply on the node. This data may contain special modifiers,
     59    // such as filters, merges, multiplying the parent values, etc. Applying it to paramnode is destructive.
     60    // Find the XML file to load - by default, this assumes the files reside in 'special/filter'.
     61    // If not found there, it will be searched for in 'mixins/', then from the root.
     62    // The reason for this order is that filters are used at runtime, mixins at load time.
     63    std::wstring wtempName = wstring_from_utf8(std::string(templateName) + ".xml");
     64    VfsPath path = VfsPath(TEMPLATE_ROOT) / L"special" / L"filter" / wtempName;
     65    if (!VfsFileExists(path))
     66        path = VfsPath(TEMPLATE_ROOT) / L"mixins" / wtempName;
     67    if (!VfsFileExists(path))
     68        path = VfsPath(TEMPLATE_ROOT) / wtempName;
    5869
    59         if (!LoadTemplateFile(baseName, depth+1))
    60         {
    61             LOGERROR("Failed to load entity template '%s'", baseName.c_str());
    62             return false;
    63         }
    64 
    65         VfsPath path = VfsPath(TEMPLATE_ROOT) / L"special" / L"filter" / wstring_from_utf8(prefix + ".xml");
    66         if (!VfsFileExists(path))
    67         {
    68             LOGERROR("Invalid subset '%s'", prefix.c_str());
    69             return false;
    70         }
    71 
    72         CXeromyces xero;
    73         PSRETURN ok = xero.Load(g_VFS, path);
    74         if (ok != PSRETURN_OK)
    75             return false; // (Xeromyces already logged an error with the full filename)
    76 
    77         m_TemplateFileData[templateName] = m_TemplateFileData[baseName];
    78         CParamNode::LoadXML(m_TemplateFileData[templateName], xero, path.string().c_str());
    79         return true;
    80     }
    81 
    82     // Normal case: templateName is an XML file:
    83 
    84     VfsPath path = VfsPath(TEMPLATE_ROOT) / wstring_from_utf8(templateName + ".xml");
    8570    CXeromyces xero;
    8671    PSRETURN ok = xero.Load(g_VFS, path);
     
    8873        return false; // (Xeromyces already logged an error with the full filename)
    8974
     75
    9076    int attr_parent = xero.GetAttributeID("parent");
    9177    CStr parentName = xero.GetRoot().GetAttributes().GetNamedItem(attr_parent);
    92     if (!parentName.empty())
    93     {
    94         // To prevent needless complexity in template design, we don't allow |-separated strings as parents
    95         if (parentName.find('|') != parentName.npos)
    96         {
    97             LOGERROR("Invalid parent '%s' in entity template '%s'", parentName.c_str(), templateName.c_str());
    98             return false;
    99         }
     78    if (!parentName.empty() && !LoadTemplateFile(node, parentName, compositing, depth + 1))
     79        return false;
    10080
    101         // Ensure the parent is loaded
    102         if (!LoadTemplateFile(parentName, depth+1))
    103         {
    104             LOGERROR("Failed to load parent '%s' of entity template '%s'", parentName.c_str(), templateName.c_str());
    105             return false;
    106         }
    107 
    108         CParamNode& parentData = m_TemplateFileData[parentName];
    109 
    110         // Initialise this template with its parent
    111         m_TemplateFileData[templateName] = parentData;
    112     }
    113 
    114     // Load the new file into the template data (overriding parent values)
    115     CParamNode::LoadXML(m_TemplateFileData[templateName], xero, wstring_from_utf8(templateName).c_str());
    116 
     81    // Load the new file into the template data (overriding parent values).
     82    // TODO: error handling.
     83    CParamNode::LoadXML(node, xero);
    11784    return true;
    11885}
     
    12592    VfsPath pathstem = pathname.ChangeExtension(L"");
    12693    // Strip the root from the path
    127     std::wstring name = pathstem.string().substr(ARRAY_SIZE(TEMPLATE_ROOT)-1);
     94    std::wstring name = pathstem.string().substr(ARRAY_SIZE(TEMPLATE_ROOT)-1);
    12895
    12996    // We want to ignore template_*.xml templates, since they should never be built in the editor
     
    13198        return INFO::OK;
    13299
    133     if (name.substr(0, 8) == L"special/")
     100    // Also ignore some subfolders.
     101    if (name.substr(0, 8) == L"special/" || name.substr(0, 7) == L"mixins/")
    134102        return INFO::OK;
    135103
     
    179147const CParamNode& CTemplateLoader::GetTemplateFileData(const std::string& templateName)
    180148{
    181     // Load the template if necessary
    182     if (!LoadTemplateFile(templateName, 0))
     149    if (std::unordered_map<std::string, CParamNode>::const_iterator it = m_TemplateFileData.find(templateName); it != m_TemplateFileData.end())
     150        return it->second;
     151
     152    CParamNode ret;
     153    if (!LoadTemplateFile(ret, templateName, false, 0))
    183154    {
    184155        LOGERROR("Failed to load entity template '%s'", templateName.c_str());
    185156        return NULL_NODE;
    186157    }
    187 
    188     return m_TemplateFileData[templateName];
     158    return m_TemplateFileData.insert_or_assign(templateName, ret).first->second;
    189159}
    190160
    191 void CTemplateLoader::ConstructTemplateActor(const std::string& actorName, CParamNode& out)
     161void CTemplateLoader::ConstructTemplateActor( actorName, CParamNode& out)
    192162{
    193163    // Copy the actor template
     
    195165
    196166    // Initialize the actor's name and make it an Atlas selectable entity.
    197     std::wstring actorNameW = wstring_from_utf8(actorName);
    198     std::string name = utf8_from_wstring(CParamNode::EscapeXMLString(actorNameW));
    199     std::string xml = "<Entity>"
    200                           "<VisualActor><Actor>" + name + "</Actor><ActorOnly/></VisualActor>"
    201                           // Arbitrary-sized Footprint definition to make actors' selection outlines show up in Atlas.
    202                           "<Footprint><Circle radius='2.0'/><Height>1.0</Height></Footprint>"
    203                           "<Selectable>"
    204                               "<EditorOnly/>"
    205                               "<Overlay><Texture><MainTexture>128x128/ellipse.png</MainTexture><MainTextureMask>128x128/ellipse_mask.png</MainTextureMask></Texture></Overlay>"
    206                           "</Selectable>"
    207                       "</Entity>";
    208 
    209     CParamNode::LoadXMLString(out, xml.c_str(), actorNameW.c_str());
     167    std::(actorName);
     168    std::);
     169    s = "<Entity>"
     170               e + "</Actor><ActorOnly/></VisualActor>"
     171  // Arbitrary-sized Footprint definition to make actors' selection outlines show up in Atlas.
     172      "<Footprint><Circle radius='2.0'/><Height>1.0</Height></Footprint>"
     173                 "<Selectable>"
     174                   "<EditorOnly/>"
     175                   "<Overlay><Texture><MainTexture>128x128/ellipse.png</MainTexture><MainTextureMask>128x128/ellipse_mask.png</MainTextureMask></Texture></Overlay>"
     176                 "</Selectable>"
     177               "</Entity>";
     178    // We'll assume that actorName is valid XML, otherwise this will fail and report the error anyways.
     179    CParamNode::LoadXMLString(out, .c_str(), actorNameW.c_str());
    210180}
  • ps/trunk/source/ps/TemplateLoader.h

    r20519 r25223  
    1 /* Copyright (C) 2017 Wildfire Games.
     1/* Copyright (C) 20 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
     
    2020
    2121#include "simulation2/system/ParamNode.h"
     22
     23
     24
    2225
    2326enum ETemplatesType
     
    7073     * and saves into m_TemplateFileData. Also loads any parents that are not yet
    7174     * loaded. Returns false on error.
    72      * @param templateName XML filename to load (not a |-separated string)
     75     * @param templateName - XML filename to load (may be a |-separated string)
     76     * @param compositing - whether this template is an intermediary layer in a |-separated string.
     77     * @param depth - the current recursion depth.
    7378     */
    74     bool LoadTemplateFile(const std::string& templateName, int depth);
     79    bool LoadTemplateFile(, int depth);
    7580
    7681    /**
    7782     * Constructs a standard static-decorative-object template for the given actor
    7883     */
    79     void ConstructTemplateActor(const std::string& actorName, CParamNode& out);
     84    void ConstructTemplateActor( actorName, CParamNode& out);
    8085
    8186    /**
     
    8590     * when hotloading broken files)
    8691     */
    87     std::map<std::string, CParamNode> m_TemplateFileData;
     92    std::map<std::string, CParamNode> m_TemplateFileData;
    8893};
    8994
  • ps/trunk/source/tools/entity/Entity.pm

    r22096 r25223  
    1313{
    1414    my ($vfspath, $mod) = @_;
    15     my $fn = "$vfsroot/$mod/simulation/templates/$vfspath.xml";
     15    my $fn = "$vfsroot/$mod/simulation/templates/special/filter/$vfspath.xml";
     16    if (not -e $fn) {
     17        $fn = "$vfsroot/$mod/simulation/templates/mixins/$vfspath.xml";
     18    }
     19    if (not -e $fn) {
     20        $fn = "$vfsroot/$mod/simulation/templates/$vfspath.xml";
     21    }
    1622    return $fn;
    1723}
     
    137143sub load_inherited
    138144{
    139     my ($vfspath, $mods) = @_;
     145    my ($vfspath, $mods, $base) = @_;
     146    if ($vfspath =~ /\|/) {
     147        my @paths = split(/\|/, $vfspath, 2);
     148        $base = load_inherited($paths[1], $mods, $base);
     149        $base = load_inherited($paths[0], $mods, $base);
     150        return $base
     151    }
    140152    my $main_mod = get_main_mod($vfspath, $mods);
    141153    my $layer = load_xml($vfspath, get_file($vfspath, $main_mod));
    142154
    143155    if ($layer->{Entity}{'@parent'}) {
    144         my $parent = load_inherited($layer->{Entity}{'@parent'}{' content'}, $mods);
     156        my $parent = load_inherited($layer->{Entity}{'@parent'}{' content'}, $mods);
    145157        apply_layer($parent->{Entity}, $layer->{Entity});
    146158        return $parent;
    147159    } else {
    148         return $layer;
     160        if (not $base) {
     161            return $layer;
     162        }
     163        else {
     164            apply_layer($base->{Entity}, $layer->{Entity});
     165            return $base
     166        }
    149167    }
    150168}
  • ps/trunk/source/tools/entity/checkrefs.pl

    r25022 r25223  
    131131        my $ent = Entity::load_inherited($f, "$mod_list_string");
    132132
    133         push @deps, [ $path, "simulation/templates/" . $ent->{Entity}{'@parent'}{' content'} . ".xml" ] if $ent->{Entity}{'@parent'};
     133        if ($ent->{Entity}{'@parent'})
     134        {
     135            my @parents = split(/\|/, $ent->{Entity}{'@parent'}{' content'});
     136            for my $parentPath (@parents)
     137            {
     138                push @deps, [ $path, "simulation/templates/" . $parentPath . ".xml" ];
     139            }
     140        }
    134141
    135142        if ($f !~ /^template_/)
     
    639646    for my $f (sort keys %revdeps)
    640647    {
     648
     649
     650
     651
     652
    641653        next if exists $files{$f};
    642654        warn "Missing file '$f' referenced by: " . (join ', ', map "'$_'", map vfs_to_relative_to_mods($_), sort @{$revdeps{$f}}) . "\n";
Note: See TracChangeset for help on using the changeset viewer.