Ticket #1589: template_manager.patch
File template_manager.patch, 39.8 KB (added by , 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 27 static const wchar_t TEMPLATE_ROOT[] = L"simulation/templates/"; 28 static const wchar_t ACTOR_ROOT[] = L"art/actors/"; 29 30 static CParamNode NULL_NODE(false); 31 32 33 bool 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 166 static 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 183 static 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 194 std::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 226 const 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 238 void 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 267 void 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 322 void 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 374 void 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 388 void 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 23 enum 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 */ 40 class CTemplateLoader 41 { 42 public: 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 58 private: 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) 201 3Wildfire Games.1 /* Copyright (C) 2014 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 22 22 23 23 #include "simulation2/MessageTypes.h" 24 24 25 #include "ps/TemplateLoader.h" 26 25 27 #include "lib/utf8.h" 26 28 #include "ps/CLogger.h" 27 #include "ps/Filesystem.h"28 29 #include "ps/XML/RelaxNG.h" 29 #include "ps/XML/Xeromyces.h"30 30 31 static const wchar_t TEMPLATE_ROOT[] = L"simulation/templates/";32 static const wchar_t ACTOR_ROOT[] = L"art/actors/";33 34 31 class CCmpTemplateManager : public ICmpTemplateManager 35 32 { 36 33 public: … … 136 133 virtual std::vector<entity_id_t> GetEntitiesUsingTemplate(std::string templateName); 137 134 138 135 private: 136 // Template loader 137 CTemplateLoader m_templateLoader; 138 139 139 // Entity template XML validator 140 140 RelaxNGValidator m_Validator; 141 141 … … 142 142 // Disable validation, for test cases 143 143 bool m_DisableValidation; 144 144 145 // Map from template name (XML filename or special |-separated string) to the most recently146 // 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 nicely148 // when hotloading broken files)149 std::map<std::string, CParamNode> m_TemplateFileData;150 151 145 // Map from template name to schema validation status. 152 146 // (Some files, e.g. inherited parent templates, may not be valid themselves but we still need to load 153 147 // them and use them; we only reject invalid templates that were requested directly by GetTemplate/etc) … … 157 151 // again for deserialization. 158 152 // TODO: should store player ID etc. 159 153 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 yet163 // 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 actor168 void ConstructTemplateActor(const std::string& actorName, CParamNode& out);169 170 // Copy the non-interactive components of an entity template (position, actor, etc) into171 // a new entity template172 void CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse);173 174 // Copy the components of an entity necessary for a construction foundation175 // (position, actor, armour, health, etc) into a new entity template176 void CopyFoundationSubset(CParamNode& out, const CParamNode& in);177 178 // Copy the components of an entity necessary for a non-foundation construction entity179 // into a new entity template180 void CopyConstructionSubset(CParamNode& out, const CParamNode& in);181 182 // Copy the components of an entity necessary for a gatherable resource183 // into a new entity template184 void CopyResourceSubset(CParamNode& out, const CParamNode& in);185 154 }; 186 155 187 156 REGISTER_COMPONENT_TYPE(TemplateManager) … … 201 170 202 171 const CParamNode* CCmpTemplateManager::GetTemplate(std::string templateName) 203 172 { 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()) 208 175 return NULL; 209 }210 176 211 177 if (!m_DisableValidation) 212 178 { … … 213 179 // Compute validity, if it's not computed before 214 180 if (m_TemplateSchemaValidity.find(templateName) == m_TemplateSchemaValidity.end()) 215 181 { 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()); 217 183 218 184 // Show error on the first failure to validate the template 219 185 if (!m_TemplateSchemaValidity[templateName]) … … 224 190 return NULL; 225 191 } 226 192 227 const CParamNode& templateRoot = m_TemplateFileData[templateName].GetChild("Entity");193 const CParamNode& templateRoot = fileData.GetChild("Entity"); 228 194 if (!templateRoot.IsOk()) 229 195 { 230 196 // The validator should never let this happen … … 237 203 238 204 const CParamNode* CCmpTemplateManager::GetTemplateWithoutValidation(std::string templateName) 239 205 { 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"); 248 207 if (!templateRoot.IsOk()) 249 208 return NULL; 250 209 … … 267 226 return it->second; 268 227 } 269 228 270 bool CCmpTemplateManager::LoadTemplateFile(const std::string& templateName, int depth)271 {272 // If this file was already loaded, we don't need to do anything273 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 crashing277 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 loaded294 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 template301 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 loaded309 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 template316 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 loaded324 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 template331 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 loaded339 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 template346 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 loaded354 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 template361 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 parents378 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 loaded385 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 parent394 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 necessary406 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 template414 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 Atlas422 "<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 extension437 VfsPath pathstem = pathname.ChangeExtension(L"");438 // Strip the root from the path439 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 editor442 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 path454 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 460 229 std::vector<std::string> CCmpTemplateManager::FindAllTemplates(bool includeActors) 461 230 { 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); 482 233 } 483 234 484 235 /** … … 494 245 } 495 246 return entities; 496 247 } 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 additions502 // to this list should be carefully considered503 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 pathfinding524 // (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 shadows531 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/etc535 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 them541 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-war549 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 generic556 // extensible scriptable way to define these subsets557 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 mode583 CParamNode::LoadXMLString(out, "<Entity><VisualActor><Foundation/></VisualActor></Entity>");584 585 // Add the Foundation component, to deal with the construction process586 CParamNode::LoadXMLString(out, "<Entity><Foundation/></Entity>");587 588 // Initialise health to 1589 CParamNode::LoadXMLString(out, "<Entity><Health><Initial>1</Initial></Health></Entity>");590 591 // Foundations shouldn't initially block unit movement592 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 range601 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 construction608 // 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 }