2010-01-09 20:20:14 +01:00
|
|
|
/* 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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "precompiled.h"
|
|
|
|
|
|
|
|
#include "simulation2/system/Component.h"
|
|
|
|
#include "ICmpTemplateManager.h"
|
|
|
|
|
2010-03-07 21:14:30 +01:00
|
|
|
#include "simulation2/MessageTypes.h"
|
|
|
|
|
2010-03-01 15:55:34 +01:00
|
|
|
#include "lib/utf8.h"
|
2010-01-09 20:20:14 +01:00
|
|
|
#include "ps/CLogger.h"
|
|
|
|
#include "ps/Filesystem.h"
|
2010-04-14 19:22:32 +02:00
|
|
|
#include "ps/XML/RelaxNG.h"
|
2010-01-09 20:20:14 +01:00
|
|
|
#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:
|
2010-03-07 21:14:30 +01:00
|
|
|
static void ClassInit(CComponentManager& componentManager)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2010-03-07 21:14:30 +01:00
|
|
|
componentManager.SubscribeGloballyToMessageType(MT_Destroy);
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFAULT_COMPONENT_ALLOCATOR(TemplateManager)
|
|
|
|
|
2010-04-23 18:09:03 +02:00
|
|
|
static std::string GetSchema()
|
|
|
|
{
|
|
|
|
return "<a:component type='system'/><empty/>";
|
|
|
|
}
|
|
|
|
|
2010-04-14 19:22:32 +02:00
|
|
|
virtual void Init(const CSimContext& context, const CParamNode& UNUSED(paramNode))
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2010-07-21 18:04:17 +02:00
|
|
|
m_DisableValidation = false;
|
|
|
|
|
2010-04-14 19:22:32 +02:00
|
|
|
m_Validator.LoadGrammar(context.GetComponentManager().GenerateSchema());
|
|
|
|
// TODO: handle errors loading the grammar here?
|
|
|
|
// TODO: support hotloading changes to the grammar
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual void Deinit(const CSimContext& UNUSED(context))
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void Serialize(ISerializer& serialize)
|
|
|
|
{
|
2010-05-23 01:02:07 +02:00
|
|
|
size_t count = 0;
|
|
|
|
|
2010-05-25 20:17:12 +02:00
|
|
|
for (std::map<entity_id_t, std::string>::const_iterator it = m_LatestTemplates.begin(); it != m_LatestTemplates.end(); ++it)
|
2010-05-23 01:02:07 +02:00
|
|
|
{
|
|
|
|
if (ENTITY_IS_LOCAL(it->first))
|
|
|
|
continue;
|
|
|
|
++count;
|
|
|
|
}
|
|
|
|
serialize.NumberU32_Unbounded("num entities", (u32)count);
|
|
|
|
|
2010-05-25 20:17:12 +02:00
|
|
|
for (std::map<entity_id_t, std::string>::const_iterator it = m_LatestTemplates.begin(); it != m_LatestTemplates.end(); ++it)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2010-05-23 01:02:07 +02:00
|
|
|
if (ENTITY_IS_LOCAL(it->first))
|
|
|
|
continue;
|
2010-01-09 20:20:14 +01:00
|
|
|
serialize.NumberU32_Unbounded("id", it->first);
|
2010-05-25 20:17:12 +02:00
|
|
|
serialize.StringASCII("template", it->second, 0, 256);
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
2010-05-25 20:17:12 +02:00
|
|
|
// TODO: maybe we should do some kind of interning thing instead of printing so many strings?
|
2010-01-09 20:20:14 +01:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2010-04-14 19:22:32 +02:00
|
|
|
virtual void Deserialize(const CSimContext& context, const CParamNode& paramNode, IDeserializer& deserialize)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2010-04-14 19:22:32 +02:00
|
|
|
Init(context, paramNode);
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
u32 numEntities;
|
2010-09-17 19:53:26 +02:00
|
|
|
deserialize.NumberU32_Unbounded("num entities", numEntities);
|
2010-01-09 20:20:14 +01:00
|
|
|
for (u32 i = 0; i < numEntities; ++i)
|
|
|
|
{
|
|
|
|
entity_id_t ent;
|
2010-05-25 20:17:12 +02:00
|
|
|
std::string templateName;
|
2010-09-17 19:53:26 +02:00
|
|
|
deserialize.NumberU32_Unbounded("id", ent);
|
|
|
|
deserialize.StringASCII("template", templateName, 0, 256);
|
2010-01-09 20:20:14 +01:00
|
|
|
m_LatestTemplates[ent] = templateName;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-03-07 21:14:30 +01:00
|
|
|
virtual void HandleMessage(const CSimContext& UNUSED(context), const CMessage& msg, bool UNUSED(global))
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2010-03-07 21:14:30 +01:00
|
|
|
switch (msg.GetType())
|
|
|
|
{
|
|
|
|
case MT_Destroy:
|
|
|
|
{
|
|
|
|
const CMessageDestroy& msgData = static_cast<const CMessageDestroy&> (msg);
|
|
|
|
|
|
|
|
// Clean up m_LatestTemplates so it doesn't record any data for destroyed entities
|
|
|
|
m_LatestTemplates.erase(msgData.entity);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
2010-07-21 18:04:17 +02:00
|
|
|
virtual void DisableValidation()
|
|
|
|
{
|
|
|
|
m_DisableValidation = true;
|
|
|
|
}
|
|
|
|
|
2010-05-25 20:17:12 +02:00
|
|
|
virtual const CParamNode* LoadTemplate(entity_id_t ent, const std::string& templateName, int playerID);
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2010-05-25 20:17:12 +02:00
|
|
|
virtual const CParamNode* GetTemplate(std::string templateName);
|
2010-01-26 00:43:58 +01:00
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
virtual const CParamNode* LoadLatestTemplate(entity_id_t ent);
|
|
|
|
|
2010-05-25 20:17:12 +02:00
|
|
|
virtual std::string GetCurrentTemplateName(entity_id_t ent);
|
2010-01-09 20:20:14 +01:00
|
|
|
|
|
|
|
virtual std::vector<std::wstring> FindAllTemplates();
|
|
|
|
|
|
|
|
private:
|
2010-04-14 19:22:32 +02:00
|
|
|
// Entity template XML validator
|
|
|
|
RelaxNGValidator m_Validator;
|
|
|
|
|
2010-07-21 18:04:17 +02:00
|
|
|
// Disable validation, for test cases
|
|
|
|
bool m_DisableValidation;
|
|
|
|
|
2010-01-14 21:36:29 +01:00
|
|
|
// Map from template name (XML filename or special |-separated string) to the most recently
|
2010-04-14 19:22:32 +02:00
|
|
|
// loaded non-broken template data. This includes files that will fail schema validation.
|
|
|
|
// (Failed loads won't remove existing entries under the same name, so we behave more nicely
|
2010-01-14 21:36:29 +01:00
|
|
|
// when hotloading broken files)
|
2010-05-25 20:17:12 +02:00
|
|
|
std::map<std::string, CParamNode> m_TemplateFileData;
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2010-04-14 19:22:32 +02:00
|
|
|
// Map from template name to schema validation status.
|
|
|
|
// (Some files, e.g. inherited parent templates, may not be valid themselves but we still need to load
|
|
|
|
// them and use them; we only reject invalid templates that were requested directly by GetTemplate/etc)
|
2010-05-25 20:17:12 +02:00
|
|
|
std::map<std::string, bool> m_TemplateSchemaValidity;
|
2010-04-14 19:22:32 +02:00
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
// Remember the template used by each entity, so we can return them
|
|
|
|
// again for deserialization.
|
|
|
|
// TODO: should store player ID etc.
|
2010-05-25 20:17:12 +02:00
|
|
|
std::map<entity_id_t, std::string> m_LatestTemplates;
|
2010-01-09 20:20:14 +01:00
|
|
|
|
|
|
|
// (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.
|
2010-01-14 21:36:29 +01:00
|
|
|
// @param templateName XML filename to load (not a |-separated string)
|
2010-05-25 20:17:12 +02:00
|
|
|
bool LoadTemplateFile(const std::string& templateName, int depth);
|
2010-01-09 20:20:14 +01:00
|
|
|
|
|
|
|
// Constructs a standard static-decorative-object template for the given actor
|
2010-05-25 20:17:12 +02:00
|
|
|
void ConstructTemplateActor(const std::string& actorName, CParamNode& out);
|
2010-01-14 21:36:29 +01:00
|
|
|
|
|
|
|
// Copy the non-interactive components of an entity template (position, actor, etc) into
|
|
|
|
// a new entity template
|
2010-08-01 19:38:01 +02:00
|
|
|
void CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse);
|
2010-03-12 22:41:40 +01:00
|
|
|
|
|
|
|
// 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);
|
2010-01-09 20:20:14 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
REGISTER_COMPONENT_TYPE(TemplateManager)
|
|
|
|
|
2010-05-25 20:17:12 +02:00
|
|
|
const CParamNode* CCmpTemplateManager::LoadTemplate(entity_id_t ent, const std::string& templateName, int UNUSED(playerID))
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
|
|
|
m_LatestTemplates[ent] = templateName;
|
|
|
|
|
2010-01-26 00:43:58 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2010-05-25 20:17:12 +02:00
|
|
|
const CParamNode* CCmpTemplateManager::GetTemplate(std::string templateName)
|
2010-01-26 00:43:58 +01:00
|
|
|
{
|
2010-01-14 21:36:29 +01:00
|
|
|
// Load the template if necessary
|
|
|
|
if (!LoadTemplateFile(templateName, 0))
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2010-05-25 20:17:12 +02:00
|
|
|
LOGERROR(L"Failed to load entity template '%hs'", templateName.c_str());
|
2010-01-14 21:36:29 +01:00
|
|
|
return NULL;
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
2010-07-21 18:04:17 +02:00
|
|
|
if (!m_DisableValidation)
|
|
|
|
{
|
|
|
|
// Compute validity, if it's not computed before
|
|
|
|
if (m_TemplateSchemaValidity.find(templateName) == m_TemplateSchemaValidity.end())
|
|
|
|
m_TemplateSchemaValidity[templateName] = m_Validator.Validate(CStrW(templateName), m_TemplateFileData[templateName].ToXML());
|
|
|
|
// Refuse to return invalid templates
|
|
|
|
if (!m_TemplateSchemaValidity[templateName])
|
|
|
|
return NULL;
|
|
|
|
}
|
2010-04-14 19:22:32 +02:00
|
|
|
|
2010-02-03 00:01:17 +01:00
|
|
|
const CParamNode& templateRoot = m_TemplateFileData[templateName].GetChild("Entity");
|
|
|
|
if (!templateRoot.IsOk())
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2010-07-21 18:04:17 +02:00
|
|
|
// The validator should never let this happen
|
2010-05-25 20:17:12 +02:00
|
|
|
LOGERROR(L"Invalid root element in entity template '%hs'", templateName.c_str());
|
2010-01-09 20:20:14 +01:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2010-02-03 00:01:17 +01:00
|
|
|
return &templateRoot;
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const CParamNode* CCmpTemplateManager::LoadLatestTemplate(entity_id_t ent)
|
|
|
|
{
|
2010-05-25 20:17:12 +02:00
|
|
|
std::map<entity_id_t, std::string>::const_iterator it = m_LatestTemplates.find(ent);
|
2010-01-09 20:20:14 +01:00
|
|
|
if (it == m_LatestTemplates.end())
|
|
|
|
return NULL;
|
|
|
|
return LoadTemplate(ent, it->second, -1);
|
|
|
|
}
|
|
|
|
|
2010-05-25 20:17:12 +02:00
|
|
|
std::string CCmpTemplateManager::GetCurrentTemplateName(entity_id_t ent)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2010-05-25 20:17:12 +02:00
|
|
|
std::map<entity_id_t, std::string>::const_iterator it = m_LatestTemplates.find(ent);
|
2010-01-09 20:20:14 +01:00
|
|
|
if (it == m_LatestTemplates.end())
|
2010-05-25 20:17:12 +02:00
|
|
|
return "";
|
2010-01-09 20:20:14 +01:00
|
|
|
return it->second;
|
|
|
|
}
|
|
|
|
|
2010-05-25 20:17:12 +02:00
|
|
|
bool CCmpTemplateManager::LoadTemplateFile(const std::string& templateName, int depth)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2010-01-14 21:36:29 +01:00
|
|
|
// If this file was already loaded, we don't need to do anything
|
|
|
|
if (m_TemplateFileData.find(templateName) != m_TemplateFileData.end())
|
|
|
|
return true;
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
// Handle infinite loops more gracefully than running out of stack space and crashing
|
|
|
|
if (depth > 100)
|
|
|
|
{
|
2010-05-25 20:17:12 +02:00
|
|
|
LOGERROR(L"Probable infinite inheritance loop in entity template '%hs'", templateName.c_str());
|
2010-01-09 20:20:14 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2010-01-14 21:36:29 +01:00
|
|
|
// Handle special case "actor|foo"
|
2010-05-25 20:17:12 +02:00
|
|
|
if (templateName.find("actor|") == 0)
|
2010-01-14 21:36:29 +01:00
|
|
|
{
|
|
|
|
ConstructTemplateActor(templateName.substr(6), m_TemplateFileData[templateName]);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle special case "preview|foo"
|
2010-05-25 20:17:12 +02:00
|
|
|
if (templateName.find("preview|") == 0)
|
2010-01-14 21:36:29 +01:00
|
|
|
{
|
|
|
|
// Load the base entity template, if it wasn't already loaded
|
2010-05-25 20:17:12 +02:00
|
|
|
std::string baseName = templateName.substr(8);
|
2010-01-14 21:36:29 +01:00
|
|
|
if (!LoadTemplateFile(baseName, depth+1))
|
|
|
|
{
|
2010-05-25 20:17:12 +02:00
|
|
|
LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
|
2010-03-29 12:22:34 +02:00
|
|
|
return false;
|
2010-01-14 21:36:29 +01:00
|
|
|
}
|
|
|
|
// Copy a subset to the requested template
|
2010-08-01 19:38:01 +02:00
|
|
|
CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], false);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle special case "corpse|foo"
|
|
|
|
if (templateName.find("corpse|") == 0)
|
|
|
|
{
|
|
|
|
// Load the base entity template, if it wasn't already loaded
|
|
|
|
std::string baseName = templateName.substr(7);
|
|
|
|
if (!LoadTemplateFile(baseName, depth+1))
|
|
|
|
{
|
|
|
|
LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Copy a subset to the requested template
|
|
|
|
CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], true);
|
2010-01-14 21:36:29 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-03-12 22:41:40 +01:00
|
|
|
// Handle special case "foundation|foo"
|
2010-05-25 20:17:12 +02:00
|
|
|
if (templateName.find("foundation|") == 0)
|
2010-03-12 22:41:40 +01:00
|
|
|
{
|
|
|
|
// Load the base entity template, if it wasn't already loaded
|
2010-05-25 20:17:12 +02:00
|
|
|
std::string baseName = templateName.substr(11);
|
2010-03-12 22:41:40 +01:00
|
|
|
if (!LoadTemplateFile(baseName, depth+1))
|
|
|
|
{
|
2010-05-25 20:17:12 +02:00
|
|
|
LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
|
2010-03-29 12:22:34 +02:00
|
|
|
return false;
|
2010-03-12 22:41:40 +01:00
|
|
|
}
|
|
|
|
// Copy a subset to the requested template
|
|
|
|
CopyFoundationSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-01-14 21:36:29 +01:00
|
|
|
// Normal case: templateName is an XML file:
|
|
|
|
|
2010-05-25 20:17:12 +02:00
|
|
|
VfsPath path = VfsPath(TEMPLATE_ROOT) / (std::wstring)CStrW(templateName + ".xml");
|
2010-01-09 20:20:14 +01:00
|
|
|
CXeromyces xero;
|
2010-07-04 12:15:53 +02:00
|
|
|
PSRETURN ok = xero.Load(g_VFS, path);
|
2010-01-09 20:20:14 +01:00
|
|
|
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())
|
|
|
|
{
|
2010-05-25 20:17:12 +02:00
|
|
|
std::string parentName(parentStr.begin(), parentStr.end());
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2010-01-14 21:36:29 +01:00
|
|
|
// To prevent needless complexity in template design, we don't allow |-separated strings as parents
|
|
|
|
if (parentName.find('|') != parentName.npos)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2010-05-25 20:17:12 +02:00
|
|
|
LOGERROR(L"Invalid parent '%hs' in entity template '%hs'", parentName.c_str(), templateName.c_str());
|
2010-01-14 21:36:29 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the parent is loaded
|
|
|
|
if (!LoadTemplateFile(parentName, depth+1))
|
|
|
|
{
|
2010-05-25 20:17:12 +02:00
|
|
|
LOGERROR(L"Failed to load parent '%hs' of entity template '%hs'", parentName.c_str(), templateName.c_str());
|
2010-01-14 21:36:29 +01:00
|
|
|
return false;
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2010-05-25 20:17:12 +02:00
|
|
|
void CCmpTemplateManager::ConstructTemplateActor(const std::string& actorName, CParamNode& out)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2010-05-25 20:17:12 +02:00
|
|
|
std::string name = utf8_from_wstring(CParamNode::EscapeXMLString(CStrW(actorName)));
|
2010-01-09 20:20:14 +01:00
|
|
|
std::string xml = "<?xml version='1.0' encoding='utf-8'?>"
|
|
|
|
"<Entity>"
|
|
|
|
"<Position>"
|
|
|
|
"<Anchor>upright</Anchor>"
|
|
|
|
"<Altitude>0</Altitude>"
|
|
|
|
"<Floating>false</Floating>"
|
|
|
|
"</Position>"
|
|
|
|
"<VisualActor>"
|
|
|
|
"<Actor>" + name + "</Actor>"
|
|
|
|
"</VisualActor>"
|
|
|
|
"</Entity>";
|
|
|
|
|
|
|
|
out.LoadXMLString(out, xml.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
static LibError AddToTemplates(const VfsPath& pathname, const FileInfo& UNUSED(fileInfo), const uintptr_t cbData)
|
|
|
|
{
|
|
|
|
std::vector<std::wstring>& templates = *(std::vector<std::wstring>*)cbData;
|
|
|
|
|
2010-01-14 21:36:29 +01:00
|
|
|
// Strip the .xml extension
|
|
|
|
VfsPath pathstem = change_extension(pathname, L"");
|
2010-01-09 20:20:14 +01:00
|
|
|
// Strip the root from the path
|
2010-01-14 21:36:29 +01:00
|
|
|
std::wstring name = pathstem.string().substr(ARRAY_SIZE(TEMPLATE_ROOT)-1);
|
2010-01-09 20:20:14 +01:00
|
|
|
|
|
|
|
// 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<std::wstring>& templates = *(std::vector<std::wstring>*)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<std::wstring> 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<std::wstring> 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;
|
|
|
|
}
|
2010-01-14 21:36:29 +01:00
|
|
|
|
2010-08-01 19:38:01 +02:00
|
|
|
void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse)
|
2010-01-14 21:36:29 +01:00
|
|
|
{
|
|
|
|
// 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<std::string> permittedComponentTypes;
|
2010-01-22 21:03:14 +01:00
|
|
|
permittedComponentTypes.insert("Ownership");
|
2010-01-14 21:36:29 +01:00
|
|
|
permittedComponentTypes.insert("Position");
|
|
|
|
permittedComponentTypes.insert("VisualActor");
|
2010-03-18 00:02:40 +01:00
|
|
|
permittedComponentTypes.insert("Footprint");
|
|
|
|
permittedComponentTypes.insert("Obstruction");
|
2010-08-01 19:38:01 +02:00
|
|
|
permittedComponentTypes.insert("Decay");
|
2010-06-05 02:49:14 +02:00
|
|
|
|
|
|
|
// Need these for the Actor Viewer:
|
|
|
|
permittedComponentTypes.insert("Attack");
|
|
|
|
permittedComponentTypes.insert("UnitMotion");
|
|
|
|
permittedComponentTypes.insert("Sound");
|
|
|
|
|
|
|
|
// (This set could be initialised once and reused, but it's not worth the effort)
|
2010-01-14 21:36:29 +01:00
|
|
|
|
|
|
|
CParamNode::LoadXMLString(out, "<Entity/>");
|
|
|
|
out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
|
2010-03-18 00:02:40 +01:00
|
|
|
|
|
|
|
// Disable the Obstruction component (if there is one) so it doesn't affect pathfinding
|
2010-04-23 18:09:03 +02:00
|
|
|
// (but can still be used for testing this entity for collisions against others)
|
2010-03-18 00:02:40 +01:00
|
|
|
if (out.GetChild("Entity").GetChild("Obstruction").IsOk())
|
|
|
|
CParamNode::LoadXMLString(out, "<Entity><Obstruction><Inactive/></Obstruction></Entity>");
|
2010-08-01 19:38:01 +02:00
|
|
|
|
2010-09-23 14:13:13 +02:00
|
|
|
if (!corpse)
|
|
|
|
{
|
|
|
|
// Previews should always be visible in fog-of-war
|
|
|
|
CParamNode::LoadXMLString(out, "<Entity><Vision><Range>0</Range><RetainInFog>true</RetainInFog></Vision></Entity>");
|
|
|
|
}
|
|
|
|
|
2010-08-01 19:38:01 +02:00
|
|
|
if (corpse)
|
|
|
|
{
|
2010-09-23 14:13:13 +02:00
|
|
|
// Corpses should include decay components and un-inactivate them
|
2010-08-01 19:38:01 +02:00
|
|
|
if (out.GetChild("Entity").GetChild("Decay").IsOk())
|
|
|
|
CParamNode::LoadXMLString(out, "<Entity><Decay><Inactive disable=''/></Decay></Entity>");
|
|
|
|
}
|
2010-01-14 21:36:29 +01:00
|
|
|
}
|
2010-03-12 22:41:40 +01:00
|
|
|
|
|
|
|
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<std::string> permittedComponentTypes;
|
|
|
|
permittedComponentTypes.insert("Ownership");
|
|
|
|
permittedComponentTypes.insert("Position");
|
2010-03-12 23:28:51 +01:00
|
|
|
permittedComponentTypes.insert("VisualActor");
|
2010-03-12 22:41:40 +01:00
|
|
|
permittedComponentTypes.insert("Identity");
|
|
|
|
permittedComponentTypes.insert("Obstruction");
|
|
|
|
permittedComponentTypes.insert("Selectable");
|
|
|
|
permittedComponentTypes.insert("Footprint");
|
|
|
|
permittedComponentTypes.insert("Armour");
|
|
|
|
permittedComponentTypes.insert("Health");
|
2010-10-03 02:30:43 +02:00
|
|
|
permittedComponentTypes.insert("StatusBars");
|
|
|
|
permittedComponentTypes.insert("OverlayRenderer");
|
2010-08-01 19:38:01 +02:00
|
|
|
permittedComponentTypes.insert("Decay");
|
2010-03-12 22:41:40 +01:00
|
|
|
permittedComponentTypes.insert("Cost");
|
2010-08-10 03:25:24 +02:00
|
|
|
permittedComponentTypes.insert("Sound");
|
2010-09-23 14:13:13 +02:00
|
|
|
permittedComponentTypes.insert("Vision");
|
2010-03-12 22:41:40 +01:00
|
|
|
|
|
|
|
CParamNode::LoadXMLString(out, "<Entity/>");
|
|
|
|
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
|
|
|
|
|
2010-03-12 23:28:51 +01:00
|
|
|
// Switch the actor to foundation mode
|
|
|
|
CParamNode::LoadXMLString(out, "<Entity><VisualActor><Foundation/></VisualActor></Entity>");
|
2010-03-12 22:41:40 +01:00
|
|
|
|
|
|
|
// Add the Foundation component, to deal with the construction process
|
|
|
|
CParamNode::LoadXMLString(out, "<Entity><Foundation/></Entity>");
|
|
|
|
|
|
|
|
// Initialise health to 1
|
|
|
|
CParamNode::LoadXMLString(out, "<Entity><Health><Initial>1</Initial></Health></Entity>");
|
2010-07-05 20:28:19 +02:00
|
|
|
|
|
|
|
// Don't provide population bonuses yet (but still do take up population cost)
|
|
|
|
if (out.GetChild("Entity").GetChild("Cost").IsOk())
|
2010-08-22 12:09:01 +02:00
|
|
|
CParamNode::LoadXMLString(out, "<Entity><Cost><PopulationBonus>0</PopulationBonus></Cost></Entity>");
|
2010-09-23 14:13:13 +02:00
|
|
|
|
|
|
|
// Foundations should be visible themselves in fog-of-war if their base template is,
|
|
|
|
// but shouldn't have any vision range
|
|
|
|
if (out.GetChild("Entity").GetChild("Vision").IsOk())
|
|
|
|
CParamNode::LoadXMLString(out, "<Entity><Vision><Range>0</Range></Vision></Entity>");
|
2010-03-12 22:41:40 +01:00
|
|
|
}
|