Index: ps/trunk/source/tools/entconvert/entconvert.pl =================================================================== --- ps/trunk/source/tools/entconvert/entconvert.pl (revision 7351) +++ ps/trunk/source/tools/entconvert/entconvert.pl (revision 7352) @@ -1,247 +1,248 @@ use strict; use warnings; use File::Find; use XML::Simple; use Data::Dumper; my $dir = '../../../binaries/data/mods/public/entities'; my $dir2 = '../../../binaries/data/mods/public/simulation/templates'; my @xml; find({ wanted => sub { push @xml, $_ if /\.xml$/ and !/template_entity(|_full|_quasi)\.xml$/; }, no_chdir => 1 }, $dir); s~\Q$dir/~~ for @xml; my %xml = ('template_entity_full.xml' => 1, 'template_entity_quasi.xml' => 1); $xml{$_} = 1 for @xml; my (%dot_actor, %dot_inherit); for my $xml (@xml) { print "$xml\n"; my $name = $xml; $name =~ s/\.xml$//; my %opt = (KeyAttr => []); my $data = XMLin("$dir/$xml", %opt, ForceArray => 1); my $c = convert($name, $data); my $out = $c; # print "$out\n\n"; open my $fo, "> $dir2/$xml" or die $!; print $fo $out; } sub convert { my ($name, $data) = @_; #print Dumper $data if $name eq 'template_unit_infantry'; my $out = qq{\n}; my $i = " "; $out .= qq{{Parent}) { my $p = $data->{Parent}; $p = "units/$p" if $p =~ /^(celt|cart|hele|iber|pers|rome)_(cavalry|infantry)/; warn "Unknown parent $p\n" unless $xml{"$p.xml"}; $out .= qq{ parent="$p"}; $dot_inherit{$name}{$p} = 1; } $out .= qq{>\n}; my $civ; $civ = $1 if $name =~ /^units\/([a-z]{4})_/; my $needs_explicit_civ = ($civ and $data->{Parent} !~ /^${civ}_/); if ($data->{Traits}[0]{Id} or $needs_explicit_civ) { $out .= qq{$i\n}; $out .= qq{$i$i$civ\n} if $needs_explicit_civ; my @map = ( [Generic => 'GenericName'], [Specific => 'SpecificName'], [Icon => 'IconSheet'], [Icon_Cell => 'IconCell'], ); for my $m (@map) { $out .= qq{$i$i<$m->[1]>$data->{Traits}[0]{Id}[0]{$m->[0]}[0][1]>\n} if $data->{Traits}[0]{Id}[0]{$m->[0]}; } for my $k (keys %{$data->{Traits}[0]{Id}[0]}) { next if $k =~ /^(Civ)$/; # we do civ based on the filename instead, since it's more reliable next if $k =~ /^(Tooltip|History|Internal_Only|Classes|Rollover|Civ_Code)$/; # TODO: convert these somehow warn "Unrecognised field <$k>" unless grep $_->[0] eq $k, @map; } $out .= qq{$i\n}; } if ($name eq 'template_unit') { $out .= qq{$i\n}; $out .= qq{$i\n}; $out .= qq{$i$i1\n}; $out .= qq{$i\n}; } if ($data->{Traits}[0]{Population} or $data->{Traits}[0]{Creation}[0]{Resource}) { $out .= qq{$i\n}; $out .= qq{$i$i$data->{Traits}[0]{Population}[0]{Rem}[0]\n} if $data->{Traits}[0]{Population}[0]{Rem} and $data->{Traits}[0]{Population}[0]{Rem}[0] != 1; $out .= qq{$i$i$data->{Traits}[0]{Population}[0]{Add}[0]\n} if $data->{Traits}[0]{Population}[0]{Add}; if ($data->{Traits}[0]{Creation}[0]{Resource}) { $out .= qq{$i$i\n}; for (qw(Food Wood Stone Metal)) { $out .= qq{$i$i$i<\l$_>$data->{Traits}[0]{Creation}[0]{Resource}[0]{$_}[0]\n} if $data->{Traits}[0]{Creation}[0]{Resource}[0]{$_}[0]; } $out .= qq{$i$i\n}; } $out .= qq{$i\n}; } if ($data->{Traits}[0]{Supply} and $name =~ /template_gaia/) { $out .= qq{$i\n}; } if ($data->{Traits}[0]{Supply}) { $out .= qq{$i\n}; $out .= qq{$i$i$data->{Traits}[0]{Supply}[0]{Max}[0]\n}; $out .= qq{$i$i$data->{Traits}[0]{Supply}[0]{Type}[0]\n}; $out .= qq{$i$i$data->{Traits}[0]{Supply}[0]{SubType}[0]\n} if $data->{Traits}[0]{Supply}[0]{SubType}; $out .= qq{$i\n}; } if ($data->{Actions}[0]{Gather}) { $out .= qq{$i\n}; $out .= qq{$i$i$data->{Actions}[0]{Gather}[0]{Speed}[0]\n}; if ($data->{Actions}[0]{Gather}[0]{Resource}) { $out .= qq{$i$i\n}; my $r = $data->{Actions}[0]{Gather}[0]{Resource}[0]; for my $t (sort keys %$r) { if (ref $r->{$t}[0]) { for my $s (sort keys %{$r->{$t}[0]}) { $out .= qq{$i$i$i<\L$t.$s>$r->{$t}[0]{$s}[0]\n}; } } else { $out .= qq{$i$i$i<\L$t>$r->{$t}[0]\n}; } } $out .= qq{$i$i\n}; } $out .= qq{$i\n}; } if ($data->{Traits}[0]{Health}) { $out .= qq{$i\n}; + $out .= qq{$i$icorpse\n} if $name eq 'template_unit'; $out .= qq{$i$i$data->{Traits}[0]{Health}[0]{Max}[0]\n} if $data->{Traits}[0]{Health}[0]{Max}; $out .= qq{$i$i$data->{Traits}[0]{Health}[0]{RegenRate}[0]\n} if $data->{Traits}[0]{Health}[0]{RegenRate}; $out .= qq{$i\n}; } if ($data->{Traits}[0]{Armour}) { $out .= qq{$i\n}; for my $n (qw(Hack Pierce Crush)) { $out .= qq{$i$i<$n>$data->{Traits}[0]{Armour}[0]{$n}[0]\n} if $data->{Traits}[0]{Armour}[0]{$n}; } $out .= qq{$i\n}; } if ($data->{Actions}[0]{Move}) { $out .= qq{$i\n}; $out .= qq{$i$i$data->{Actions}[0]{Move}[0]{Speed}[0]\n} if $data->{Actions}[0]{Move}[0]{Speed}; $out .= qq{$i\n}; } die if $data->{Actions}[0]{Attack}[0]{Melee} and $data->{Actions}[0]{Attack}[0]{Ranged}; # only allow one at once my $attack = $data->{Actions}[0]{Attack}[0]{Melee} || $data->{Actions}[0]{Attack}[0]{Ranged}; if ($attack) { $out .= qq{$i\n}; for my $n (qw(Hack Pierce Crush Range MinRange ProjectileSpeed)) { $out .= qq{$i$i<$n>$attack->[0]{$n}[0]\n} if $attack->[0]{$n}; } if ($attack->[0]{Speed}) { my $s = $attack->[0]{Speed}[0]; # TODO: are these values sane? if ($s eq '1000') { $out .= qq{$i$i600\n}; $out .= qq{$i$i1000\n}; } elsif ($s eq '1500' or $s eq '1520' or $s eq '1510') { $out .= qq{$i$i900\n}; $out .= qq{$i$i1500\n}; } elsif ($s eq '2000') { $out .= qq{$i$i1200\n}; $out .= qq{$i$i2000\n}; } else { die $s; } } $out .= qq{$i\n}; } $dot_actor{$name} = $data->{Actor}; if ($data->{Actor}) { $out .= qq{$i\n}; $out .= qq{$i$i$data->{Actor}[0]\n}; $out .= qq{$i\n}; } if ($data->{Traits}[0]{Footprint}) { $out .= qq{$i\n}; if ($data->{Traits}[0]{Footprint}[0]{Radius}) { $out .= qq{$i$i\n}; } if ($data->{Traits}[0]{Footprint}[0]{Width}) { $out .= qq{$i$i\n}; } if ($data->{Traits}[0]{Footprint}[0]{Height}) { $out .= qq{$i$i$data->{Traits}[0]{Footprint}[0]{Height}[0]\n}; } $out .= qq{$i\n}; } if ($name =~ /^template_(structure|gaia)$/) { $out .= qq{$i\n}; } if ($name =~ /^template_structure_resource_field$/) { $out .= qq{$i\n}; } if ($data->{Actions}[0]{Create}[0]{List}[0]{StructCiv} or $data->{Actions}[0]{Create}[0]{List}[0]{StructMil}) { $out .= qq{$i\n}; $out .= qq{$i$i\n}; for (sort (keys %{$data->{Actions}[0]{Create}[0]{List}[0]{StructCiv}[0]}, keys %{$data->{Actions}[0]{Create}[0]{List}[0]{StructMil}[0]})) { my $n = "structures/" . ($civ || "{civ}") . "_" . (lc $_); $out .= qq{$i$i$i$n\n}; } $out .= qq{$i$i\n}; $out .= qq{$i\n}; } $out .= qq{\n}; return $out; } open my $dot, '> entities.dot' or die $!; print $dot < "$_";\n}; } for my $p (sort keys %dot_inherit) { for my $c (sort keys %{$dot_inherit{$p}}) { print $dot qq{"$p" -> "$c";\n}; } } print $dot "}\n"; Index: ps/trunk/source/simulation2/components/ICmpTemplateManager.h =================================================================== --- ps/trunk/source/simulation2/components/ICmpTemplateManager.h (revision 7351) +++ ps/trunk/source/simulation2/components/ICmpTemplateManager.h (revision 7352) @@ -1,92 +1,96 @@ /* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_ICMPTEMPLATEMANAGER #define INCLUDED_ICMPTEMPLATEMANAGER #include "simulation2/system/Interface.h" #include /** * Template manager: Handles the loading of entity template files for the initialisation * and deserialization of entity components. */ class ICmpTemplateManager : public IComponent { public: /** * Loads the template XML file identified by 'templateName' (including inheritance * from parent XML files), and applies the techs that are currently active for * player 'playerID', for use with a new entity 'ent'. * The returned CParamNode must not be used for any entities other than 'ent'. * * If templateName is of the form "actor|foo" then it will load a default * stationary entity template that uses actor "foo". (This is a convenience to * avoid the need for hundreds of tiny decorative-object entity templates.) * * If templateName is of the form "preview|foo" then it will load a template * based on entity template "foo" with the non-graphical components removed. * (This is for previewing construction/placement of units.) * + * If templateName is of the form "foundation|foo" then it will load a template + * based on entity template "foo" with various components removed and a few changed + * and added. (This is for constructing foundations of buildings.) + * * @return NULL on error */ virtual const CParamNode* LoadTemplate(entity_id_t ent, const std::wstring& templateName, int playerID) = 0; /** * Loads the template XML file identified by 'templateName' (including inheritance * from parent XML files). The templateName syntax is the same as LoadTemplate. * * @return NULL on error */ virtual const CParamNode* GetTemplate(std::wstring templateName) = 0; /** * Returns the template most recently specified for the entity 'ent'. * Used during deserialization. * * @return NULL on error */ virtual const CParamNode* LoadLatestTemplate(entity_id_t ent) = 0; /** * Returns the name of the template most recently specified for the entity 'ent'. */ virtual std::wstring GetCurrentTemplateName(entity_id_t ent) = 0; /** * Returns a list of strings that could be validly passed as @c templateName to LoadTemplate. * (This includes "actor|foo" etc names). * Intended for use by the map editor. This is likely to be quite slow. */ virtual std::vector FindAllTemplates() = 0; /* * TODO: * When an entity changes template (e.g. upgrades) or player ownership, it * should call some Reload(ent, templateName, playerID) function to load its new template. * When a player researches new techs, it should call Reload(playerID). * When a file changes on disk, something should call Reload(templateName). * * Reloading should happen by sending a message to affected components (containing * their new CParamNode), then automatically updating this.template of scripted components. */ DECLARE_INTERFACE_TYPE(TemplateManager) }; #endif // INCLUDED_ICMPTEMPLATEMANAGER Index: ps/trunk/source/simulation2/components/CCmpTemplateManager.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpTemplateManager.cpp (revision 7351) +++ ps/trunk/source/simulation2/components/CCmpTemplateManager.cpp (revision 7352) @@ -1,341 +1,393 @@ /* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "simulation2/system/Component.h" #include "ICmpTemplateManager.h" #include "simulation2/MessageTypes.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/XML/Xeromyces.h" static const wchar_t TEMPLATE_ROOT[] = L"simulation/templates/"; static const wchar_t ACTOR_ROOT[] = L"art/actors/"; class CCmpTemplateManager : public ICmpTemplateManager { public: static void ClassInit(CComponentManager& componentManager) { componentManager.SubscribeGloballyToMessageType(MT_Destroy); } DEFAULT_COMPONENT_ALLOCATOR(TemplateManager) virtual void Init(const CSimContext& UNUSED(context), const CParamNode& UNUSED(paramNode)) { } virtual void Deinit(const CSimContext& UNUSED(context)) { } virtual void Serialize(ISerializer& serialize) { // TODO: refactor the common bits of this? also need nicer debug output serialize.NumberU32_Unbounded("num entities", (u32)m_LatestTemplates.size()); std::map::const_iterator it = m_LatestTemplates.begin(); for (; it != m_LatestTemplates.end(); ++it) { serialize.NumberU32_Unbounded("id", it->first); serialize.String("template", it->second, 0, 256); } // TODO: will need to serialize techs too, because we need to be giving out // template data before other components (like the tech components) have been deserialized } virtual void Deserialize(const CSimContext& UNUSED(context), const CParamNode& UNUSED(paramNode), IDeserializer& deserialize) { u32 numEntities; deserialize.NumberU32_Unbounded(numEntities); for (u32 i = 0; i < numEntities; ++i) { entity_id_t ent; std::wstring templateName; deserialize.NumberU32_Unbounded(ent); deserialize.String(templateName, 0, 256); m_LatestTemplates[ent] = templateName; } } virtual void HandleMessage(const CSimContext& UNUSED(context), const CMessage& msg, bool UNUSED(global)) { switch (msg.GetType()) { case MT_Destroy: { const CMessageDestroy& msgData = static_cast (msg); // Clean up m_LatestTemplates so it doesn't record any data for destroyed entities m_LatestTemplates.erase(msgData.entity); break; } } } virtual const CParamNode* LoadTemplate(entity_id_t ent, const std::wstring& templateName, int playerID); virtual const CParamNode* GetTemplate(std::wstring templateName); virtual const CParamNode* LoadLatestTemplate(entity_id_t ent); virtual std::wstring GetCurrentTemplateName(entity_id_t ent); virtual std::vector FindAllTemplates(); private: // Map from template name (XML filename or special |-separated string) to the most recently // loaded valid template data. // (Failed loads won't remove valid entries under the same name, so we behave more nicely // when hotloading broken files) std::map m_TemplateFileData; // Remember the template used by each entity, so we can return them // again for deserialization. // TODO: should store player ID etc. std::map m_LatestTemplates; // (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) bool LoadTemplateFile(const std::wstring& templateName, int depth); // Constructs a standard static-decorative-object template for the given actor void ConstructTemplateActor(const std::wstring& actorName, CParamNode& out); // Copy the non-interactive components of an entity template (position, actor, etc) into // a new entity template void CopyPreviewSubset(CParamNode& out, const CParamNode& in); + + // Copy the components of an entity necessary for a construction foundation + // (position, actor, armour, health, etc) into a new entity template + void CopyFoundationSubset(CParamNode& out, const CParamNode& in); }; REGISTER_COMPONENT_TYPE(TemplateManager) const CParamNode* CCmpTemplateManager::LoadTemplate(entity_id_t ent, const std::wstring& templateName, int UNUSED(playerID)) { m_LatestTemplates[ent] = templateName; const CParamNode* templateRoot = GetTemplate(templateName); if (!templateRoot) return NULL; // TODO: Eventually we need to support techs in here, and return a different template per playerID return templateRoot; } const CParamNode* CCmpTemplateManager::GetTemplate(std::wstring templateName) { // Load the template if necessary if (!LoadTemplateFile(templateName, 0)) { LOGERROR(L"Failed to load entity template '%ls'", templateName.c_str()); return NULL; } const CParamNode& templateRoot = m_TemplateFileData[templateName].GetChild("Entity"); if (!templateRoot.IsOk()) { LOGERROR(L"Invalid root element in entity template '%ls'", templateName.c_str()); return NULL; } // TODO: the template ought to be validated with some schema, so we don't // need to nicely report errors like invalid root elements here return &templateRoot; } const CParamNode* CCmpTemplateManager::LoadLatestTemplate(entity_id_t ent) { std::map::const_iterator it = m_LatestTemplates.find(ent); if (it == m_LatestTemplates.end()) return NULL; return LoadTemplate(ent, it->second, -1); } std::wstring CCmpTemplateManager::GetCurrentTemplateName(entity_id_t ent) { std::map::const_iterator it = m_LatestTemplates.find(ent); if (it == m_LatestTemplates.end()) return L""; return it->second; } bool CCmpTemplateManager::LoadTemplateFile(const std::wstring& templateName, int depth) { // If this file was already loaded, we don't need to do anything if (m_TemplateFileData.find(templateName) != m_TemplateFileData.end()) return true; // Handle infinite loops more gracefully than running out of stack space and crashing if (depth > 100) { LOGERROR(L"Probable infinite inheritance loop in entity template '%ls'", templateName.c_str()); return false; } // Handle special case "actor|foo" if (templateName.find(L"actor|") == 0) { ConstructTemplateActor(templateName.substr(6), m_TemplateFileData[templateName]); return true; } // Handle special case "preview|foo" if (templateName.find(L"preview|") == 0) { // Load the base entity template, if it wasn't already loaded std::wstring baseName = templateName.substr(8); if (!LoadTemplateFile(baseName, depth+1)) { LOGERROR(L"Failed to load entity template '%ls'", baseName.c_str()); return NULL; } // Copy a subset to the requested template CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]); return true; } + // Handle special case "foundation|foo" + if (templateName.find(L"foundation|") == 0) + { + // Load the base entity template, if it wasn't already loaded + std::wstring baseName = templateName.substr(11); + if (!LoadTemplateFile(baseName, depth+1)) + { + LOGERROR(L"Failed to load entity template '%ls'", baseName.c_str()); + return NULL; + } + // Copy a subset to the requested template + CopyFoundationSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]); + return true; + } + // Normal case: templateName is an XML file: VfsPath path = VfsPath(TEMPLATE_ROOT) / (templateName + L".xml"); CXeromyces xero; PSRETURN ok = xero.Load(path); if (ok != PSRETURN_OK) return false; // (Xeromyces already logged an error with the full filename) int attr_parent = xero.GetAttributeID("parent"); utf16string parentStr = xero.GetRoot().GetAttributes().GetNamedItem(attr_parent); if (!parentStr.empty()) { std::wstring parentName(parentStr.begin(), parentStr.end()); // To prevent needless complexity in template design, we don't allow |-separated strings as parents if (parentName.find('|') != parentName.npos) { LOGERROR(L"Invalid parent '%ls' in entity template '%ls'", parentName.c_str(), templateName.c_str()); return false; } // Ensure the parent is loaded if (!LoadTemplateFile(parentName, depth+1)) { LOGERROR(L"Failed to load parent '%ls' of entity template '%ls'", 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); return true; } void CCmpTemplateManager::ConstructTemplateActor(const std::wstring& actorName, CParamNode& out) { std::string name = utf8_from_wstring(CParamNode::EscapeXMLString(actorName)); std::string xml = "" "" "" "upright" "0" "false" "" "" "" + name + "" "" ""; out.LoadXMLString(out, xml.c_str()); } static LibError AddToTemplates(const VfsPath& pathname, const FileInfo& UNUSED(fileInfo), const uintptr_t cbData) { std::vector& templates = *(std::vector*)cbData; // Strip the .xml extension VfsPath pathstem = change_extension(pathname, L""); // Strip the root from the path std::wstring 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; templates.push_back(name); return INFO::OK; } static LibError AddActorToTemplates(const VfsPath& pathname, const FileInfo& UNUSED(fileInfo), const uintptr_t cbData) { std::vector& templates = *(std::vector*)cbData; // Strip the root from the path std::wstring name = pathname.string().substr(ARRAY_SIZE(ACTOR_ROOT)-1); templates.push_back(L"actor|" + name); return INFO::OK; } std::vector CCmpTemplateManager::FindAllTemplates() { // TODO: eventually this should probably read all the template files and look for flags to // determine which should be displayed in the editor (and in what categories etc); for now we'll // just return all the files std::vector templates; LibError ok; // Find all the normal entity templates first ok = fs_util::ForEachFile(g_VFS, TEMPLATE_ROOT, AddToTemplates, (uintptr_t)&templates, L"*.xml", fs_util::DIR_RECURSIVE); WARN_ERR(ok); // Add all the actors too ok = fs_util::ForEachFile(g_VFS, ACTOR_ROOT, AddActorToTemplates, (uintptr_t)&templates, L"*.xml", fs_util::DIR_RECURSIVE); WARN_ERR(ok); return templates; } void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& in) { // We only want to include components which are necessary (for the visual previewing of an entity) // and safe (i.e. won't do anything that affects the synchronised simulation state), so additions // to this list should be carefully considered std::set permittedComponentTypes; permittedComponentTypes.insert("Ownership"); permittedComponentTypes.insert("Position"); permittedComponentTypes.insert("VisualActor"); // (This could be initialised once and reused, but it's not worth the effort) CParamNode::LoadXMLString(out, ""); out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes); // In the future, we might want to add some extra flags to certain components, to indicate they're // running in 'preview' mode and should not e.g. register with global managers } + +void CCmpTemplateManager::CopyFoundationSubset(CParamNode& out, const CParamNode& in) +{ + // TODO: this is all kind of yucky and hard-coded; it'd be nice to have a more generic + // extensible scriptable way to define these subsets + + std::set permittedComponentTypes; + permittedComponentTypes.insert("Ownership"); + permittedComponentTypes.insert("Position"); + permittedComponentTypes.insert("Identity"); + permittedComponentTypes.insert("Obstruction"); + permittedComponentTypes.insert("Selectable"); + permittedComponentTypes.insert("Footprint"); + permittedComponentTypes.insert("Armour"); + permittedComponentTypes.insert("Health"); + permittedComponentTypes.insert("Cost"); + + CParamNode::LoadXMLString(out, ""); + out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes); + + // TODO: the foundation shouldn't be considered an obstruction by default, until construction has + // really started, to prevent players abusing it to block their opponents + + // TODO: Use the appropriate actor + CParamNode::LoadXMLString(out, "structures/fndn_4x4.xml"); + + // Add the Foundation component, to deal with the construction process + CParamNode::LoadXMLString(out, ""); + + // Initialise health to 1 + CParamNode::LoadXMLString(out, "1"); +} + Index: ps/trunk/binaries/data/mods/public/gui/session_new/session.xml =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session_new/session.xml (revision 7351) +++ ps/trunk/binaries/data/mods/public/gui/session_new/session.xml (revision 7352) @@ -1,236 +1,239 @@