std::vector<std::string> CTemplateLoader::FindTemplates(const std::string& path, bool includeSubdirectories, ETemplatesType templatesType) { std::vector<std::string> templates; Status ok; VfsPath templatePath; if (templatesType == SIMULATION_TEMPLATES || templatesType == ALL_TEMPLATES) { templatePath = VfsPath(TEMPLATE_ROOT) / path; if (includeSubdirectories) ok = vfs::ForEachFile(g_VFS, templatePath, AddToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE); else ok = vfs::ForEachFile(g_VFS, templatePath, AddToTemplates, (uintptr_t)&templates, L"*.xml"); WARN_IF_ERR(ok); } if (templatesType == ACTOR_TEMPLATES || templatesType == ALL_TEMPLATES) { templatePath = VfsPath(ACTOR_ROOT) / path; if (includeSubdirectories) ok = vfs::ForEachFile(g_VFS, templatePath, AddActorToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE); else ok = vfs::ForEachFile(g_VFS, templatePath, AddActorToTemplates, (uintptr_t)&templates, L"*.xml"); WARN_IF_ERR(ok); } if (templatesType != SIMULATION_TEMPLATES && templatesType != ACTOR_TEMPLATES && templatesType != ALL_TEMPLATES) LOGERROR("Undefined template type (valid: all, simulation, actor)"); return templates; }
void CGUIManager::LoadPage(SGUIPage& page) { // If we're hotloading then try to grab some data from the previous page CScriptValRooted hotloadData; if (page.gui) m_ScriptInterface.CallFunction(OBJECT_TO_JSVAL(page.gui->GetScriptObject()), "getHotloadData", hotloadData); page.inputs.clear(); page.gui.reset(new CGUI()); page.gui->Initialize(); VfsPath path = VfsPath("gui") / page.name; page.inputs.insert(path); CXeromyces xero; if (xero.Load(g_VFS, path) != PSRETURN_OK) // Fail silently (Xeromyces reported the error) return; int elmt_page = xero.GetElementID("page"); int elmt_include = xero.GetElementID("include"); XMBElement root = xero.GetRoot(); if (root.GetNodeName() != elmt_page) { LOGERROR(L"GUI page '%ls' must have root element <page>", page.name.c_str()); return; } XERO_ITER_EL(root, node) { if (node.GetNodeName() != elmt_include) { LOGERROR(L"GUI page '%ls' must only have <include> elements inside <page>", page.name.c_str()); continue; } CStrW name (node.GetText().FromUTF8()); TIMER(name.c_str()); VfsPath path = VfsPath("gui") / name; page.gui->LoadXmlFile(path, page.inputs); } // Remember this GUI page, in case the scripts call FindObjectByName shared_ptr<CGUI> oldGUI = m_CurrentGUI; m_CurrentGUI = page.gui; page.gui->SendEventToAll("load"); // Call the init() function if (!m_ScriptInterface.CallFunctionVoid(OBJECT_TO_JSVAL(page.gui->GetScriptObject()), "init", page.initData, hotloadData)) { LOGERROR(L"GUI page '%ls': Failed to call init() function", page.name.c_str()); } m_CurrentGUI = oldGUI; }
CObjectBase* CObjectManager::FindObjectBase(const CStrW& objectname) { ENSURE(!objectname.empty()); // See if the base type has been loaded yet: std::map<CStrW, CObjectBase*>::iterator it = m_ObjectBases.find(objectname); if (it != m_ObjectBases.end()) return it->second; // Not already loaded, so try to load it: CObjectBase* obj = new CObjectBase(*this); VfsPath pathname = VfsPath("art/actors/") / objectname; if (obj->Load(pathname)) { m_ObjectBases[objectname] = obj; return obj; } else delete obj; LOGERROR(L"CObjectManager::FindObjectBase(): Cannot find object '%ls'", objectname.c_str()); return 0; }
/** * Initializes the game world with the attributes provided. **/ void CWorld::RegisterInit(const CStrW& mapFile, const CScriptValRooted& settings, int playerID) { // Load the map, if one was specified if (mapFile.length()) { VfsPath mapfilename = VfsPath(mapFile).ChangeExtension(L".pmp"); CMapReader* reader = 0; try { reader = new CMapReader; CTriggerManager* pTriggerManager = NULL; reader->LoadMap(mapfilename, settings, m_Terrain, CRenderer::IsInitialised() ? g_Renderer.GetWaterManager() : NULL, CRenderer::IsInitialised() ? g_Renderer.GetSkyManager() : NULL, &g_LightEnv, m_pGame->GetView(), m_pGame->GetView() ? m_pGame->GetView()->GetCinema() : NULL, pTriggerManager, CRenderer::IsInitialised() ? &g_Renderer.GetPostprocManager() : NULL, m_pGame->GetSimulation2(), &m_pGame->GetSimulation2()->GetSimContext(), playerID, false); // fails immediately, or registers for delay loading } catch (PSERROR_File& err) { delete reader; LOGERROR(L"Failed to load map %ls: %hs", mapfilename.string().c_str(), err.what()); throw PSERROR_Game_World_MapLoadFailed("Failed to load map.\nCheck application log for details."); } } }
CScriptVal CComponentManager::ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, std::wstring filePath, std::wstring fileName) { CComponentManager* componentManager = static_cast<CComponentManager*> (pCxPrivate->pCBData); VfsPath path = VfsPath(filePath) / fileName; return componentManager->GetScriptInterface().ReadJSONFile(path).get(); }
CScriptVal CComponentManager::ReadJSONFile(void* cbdata, std::wstring filePath, std::wstring fileName) { CComponentManager* componentManager = static_cast<CComponentManager*> (cbdata); VfsPath path = VfsPath(filePath) / fileName; return componentManager->GetScriptInterface().ReadJSONFile(path).get(); }
VfsPath h_filename(const Handle h) { // don't require type check: should be usable for any handle, // even if the caller doesn't know its type. HDATA* hd; if(h_data_tag(h, hd) != INFO::OK) return VfsPath(); return hd->pathname; }
JS::Value CComponentManager::ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filePath, const std::wstring& fileName) { CComponentManager* componentManager = static_cast<CComponentManager*> (pCxPrivate->pCBData); JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); JSAutoRequest rq(cx); VfsPath path = VfsPath(filePath) / fileName; JS::RootedValue out(cx); componentManager->GetScriptInterface().ReadJSONFile(path, &out); return out; }
VfsPath CCacheLoader::LooseCachePath(const VfsPath& sourcePath, const MD5& initialHash, u32 version) { CFileInfo fileInfo; if (m_VFS->GetFileInfo(sourcePath, &fileInfo) < 0) { debug_warn(L"source file disappeared"); // this should never happen return VfsPath(); } u64 mtime = (u64)fileInfo.MTime() & ~1; // skip lowest bit, since zip and FAT don't preserve it u64 size = (u64)fileInfo.Size(); // Construct a hash of the file data and settings. MD5 hash = initialHash; hash.Update((const u8*)&mtime, sizeof(mtime)); hash.Update((const u8*)&size, sizeof(size)); hash.Update((const u8*)&version, sizeof(version)); // these are local cached files, so we don't care about endianness etc // Use a short prefix of the full hash (we don't need high collision-resistance), // converted to hex u8 digest[MD5::DIGESTSIZE]; hash.Final(digest); std::wstringstream digestPrefix; digestPrefix << std::hex; for (size_t i = 0; i < 8; ++i) digestPrefix << std::setfill(L'0') << std::setw(2) << (int)digest[i]; // Get the mod path OsPath path; m_VFS->GetRealPath(sourcePath, path); // Construct the final path return VfsPath("cache") / path_name_only(path.BeforeCommon(sourcePath).Parent().string().c_str()) / sourcePath.ChangeExtension(sourcePath.Extension().string() + L"." + digestPrefix.str() + m_FileExtension); }
bool CColladaManager::GenerateCachedFile(const VfsPath& sourcePath, FileType type, VfsPath& archiveCachePath) { std::wstring extn; switch (type) { case PMD: extn = L".pmd"; break; case PSA: extn = L".psa"; break; // no other alternatives } CCacheLoader cacheLoader(m_VFS, extn); archiveCachePath = cacheLoader.ArchiveCachePath(sourcePath); return m->Convert(sourcePath, VfsPath("cache") / archiveCachePath, type); }
std::vector<std::string> CComponentManager::Script_FindJSONFiles(void* UNUSED(cbdata), std::wstring subPath, bool recursive) { FindJSONFilesCallbackData cbData; cbData.path = VfsPath(L"simulation/data/" + subPath + L"/"); int dir_flags = 0; if (recursive) { dir_flags = vfs::DIR_RECURSIVE; } // Find all simulation/data/{subPath}/*.json recursively Status ret = vfs::ForEachFile(g_VFS, cbData.path, FindJSONFilesCallback, (uintptr_t)&cbData, L"*.json", dir_flags); if (ret != INFO::OK) { // Some error reading directory wchar_t error[200]; LOGERROR(L"Error reading directory '%ls': %ls", cbData.path.string().c_str(), StatusDescription(ret, error, ARRAY_SIZE(error))); } return cbData.templates; }
CMaterial CMaterialManager::LoadMaterial(const VfsPath& pathname) { if (pathname.empty()) return CMaterial(); std::map<VfsPath, CMaterial>::iterator iter = m_Materials.find(pathname); if (iter != m_Materials.end()) return iter->second; CXeromyces xeroFile; if (xeroFile.Load(g_VFS, pathname, "material") != PSRETURN_OK) return CMaterial(); #define EL(x) int el_##x = xeroFile.GetElementID(#x) #define AT(x) int at_##x = xeroFile.GetAttributeID(#x) EL(alpha_blending); EL(alternative); EL(define); EL(shader); EL(uniform); EL(renderquery); EL(required_texture); EL(conditional_define); AT(effect); AT(if); AT(define); AT(quality); AT(material); AT(name); AT(value); AT(type); AT(min); AT(max); AT(conf); #undef AT #undef EL CMaterial material; XMBElement root = xeroFile.GetRoot(); CPreprocessorWrapper preprocessor; preprocessor.AddDefine("CFG_FORCE_ALPHATEST", g_Renderer.m_Options.m_ForceAlphaTest ? "1" : "0"); CVector4D vec(qualityLevel,0,0,0); material.AddStaticUniform("qualityLevel", vec); XERO_ITER_EL(root, node) { int token = node.GetNodeName(); XMBAttributeList attrs = node.GetAttributes(); if (token == el_alternative) { CStr cond = attrs.GetNamedItem(at_if); if (cond.empty() || !preprocessor.TestConditional(cond)) { cond = attrs.GetNamedItem(at_quality); if (cond.empty()) continue; else { if (cond.ToFloat() <= qualityLevel) continue; } } material = LoadMaterial(VfsPath("art/materials") / attrs.GetNamedItem(at_material).FromUTF8()); break; } else if (token == el_alpha_blending) { material.SetUsesAlphaBlending(true); } else if (token == el_shader) { material.SetShaderEffect(attrs.GetNamedItem(at_effect)); } else if (token == el_define) { material.AddShaderDefine(CStrIntern(attrs.GetNamedItem(at_name)), CStrIntern(attrs.GetNamedItem(at_value))); } else if (token == el_conditional_define) { std::vector<float> args; CStr type = attrs.GetNamedItem(at_type).c_str(); int typeID = -1; if (type == CStr("draw_range")) { typeID = DCOND_DISTANCE; float valmin = -1.0f; float valmax = -1.0f; CStr conf = attrs.GetNamedItem(at_conf); if (!conf.empty()) { CFG_GET_VAL("materialmgr." + conf + ".min", valmin); CFG_GET_VAL("materialmgr." + conf + ".max", valmax); } else { CStr dmin = attrs.GetNamedItem(at_min); if (!dmin.empty()) valmin = attrs.GetNamedItem(at_min).ToFloat(); CStr dmax = attrs.GetNamedItem(at_max); if (!dmax.empty()) valmax = attrs.GetNamedItem(at_max).ToFloat(); } args.push_back(valmin); args.push_back(valmax); if (valmin >= 0.0f) { std::stringstream sstr; sstr << valmin; material.AddShaderDefine(CStrIntern(conf + "_MIN"), CStrIntern(sstr.str())); } if (valmax >= 0.0f) { std::stringstream sstr; sstr << valmax; material.AddShaderDefine(CStrIntern(conf + "_MAX"), CStrIntern(sstr.str())); } } material.AddConditionalDefine(attrs.GetNamedItem(at_name).c_str(), attrs.GetNamedItem(at_value).c_str(), typeID, args); } else if (token == el_uniform) { std::stringstream str(attrs.GetNamedItem(at_value)); CVector4D vec; str >> vec.X >> vec.Y >> vec.Z >> vec.W; material.AddStaticUniform(attrs.GetNamedItem(at_name).c_str(), vec); }
VfsPath CColladaManager::GetLoadableFilename(const VfsPath& pathnameNoExtension, FileType type) { std::wstring extn; switch (type) { case PMD: extn = L".pmd"; break; case PSA: extn = L".psa"; break; // no other alternatives } /* If there is a .dae file: * Calculate a hash to identify it. * Look for a cached .pmd file matching that hash. * If it exists, load it. Else, convert the .dae into .pmd and load it. Otherwise, if there is a (non-cache) .pmd file: * Load it. Else, fail. The hash calculation ought to be fast, since normally (during development) the .dae file will exist but won't have changed recently and so the cache would be used. Hence, just hash the file's size, mtime, and the converter version number (so updates of the converter can cause regeneration of .pmds) instead of the file's actual contents. TODO (maybe): The .dae -> .pmd conversion may fail (e.g. if the .dae is invalid or unsupported), but it may take a long time to start the conversion then realise it's not going to work. That will delay the loading of the game every time, which is annoying, so maybe it should cache the error message until the .dae is updated and fixed. (Alternatively, avoid having that many broken .daes in the game.) */ // (TODO: the comments and variable names say "pmd" but actually they can // be "psa" too.) VfsPath dae(pathnameNoExtension.ChangeExtension(L".dae")); if (! VfsFileExists(dae)) { // No .dae - got to use the .pmd, assuming there is one return pathnameNoExtension.ChangeExtension(extn); } // There is a .dae - see if there's an up-to-date cached copy FileInfo fileInfo; if (g_VFS->GetFileInfo(dae, &fileInfo) < 0) { // This shouldn't occur for any sensible reasons LOGERROR(L"Failed to stat DAE file '%ls'", dae.string().c_str()); return VfsPath(); } // Build a struct of all the data we want to hash. // (Use ints and not time_t/off_t because we don't care about overflow // but do care about the fields not being 64-bit aligned) // (Remove the lowest bit of mtime because some things round it to a // resolution of 2 seconds) #pragma pack(push, 1) struct { int version; int mtime; int size; } hashSource = { COLLADA_CONVERTER_VERSION, (int)fileInfo.MTime() & ~1, (int)fileInfo.Size() }; cassert(sizeof(hashSource) == sizeof(int) * 3); // no padding, because that would be bad #pragma pack(pop) // Calculate the hash, convert to hex u32 hash = fnv_hash(static_cast<void*>(&hashSource), sizeof(hashSource)); wchar_t hashString[9]; swprintf_s(hashString, ARRAY_SIZE(hashString), L"%08x", hash); std::wstring extension(L"_"); extension += hashString; extension += extn; // realDaePath_ is "[..]/mods/whatever/art/meshes/whatever.dae" OsPath realDaePath_; Status ret = g_VFS->GetRealPath(dae, realDaePath_); ENSURE(ret == INFO::OK); wchar_t realDaeBuf[PATH_MAX]; wcscpy_s(realDaeBuf, ARRAY_SIZE(realDaeBuf), realDaePath_.string().c_str()); std::replace(realDaeBuf, realDaeBuf+ARRAY_SIZE(realDaeBuf), '\\', '/'); const wchar_t* realDaePath = wcsstr(realDaeBuf, L"mods/"); // cachedPmdVfsPath is "cache/mods/whatever/art/meshes/whatever_{hash}.pmd" VfsPath cachedPmdVfsPath = VfsPath("cache") / realDaePath; cachedPmdVfsPath = cachedPmdVfsPath.ChangeExtension(extension); // If it's not in the cache, we'll have to create it first if (! VfsFileExists(cachedPmdVfsPath)) { if (! m->Convert(dae, cachedPmdVfsPath, type)) return L""; // failed to convert } return cachedPmdVfsPath; }
void CGUIManager::LoadPage(SGUIPage& page) { // If we're hotloading then try to grab some data from the previous page shared_ptr<ScriptInterface::StructuredClone> hotloadData; if (page.gui) { shared_ptr<ScriptInterface> scriptInterface = page.gui->GetScriptInterface(); JSContext* cx = scriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedValue global(cx, scriptInterface->GetGlobalObject()); JS::RootedValue hotloadDataVal(cx); scriptInterface->CallFunction(global, "getHotloadData", &hotloadDataVal); hotloadData = scriptInterface->WriteStructuredClone(hotloadDataVal); } page.inputs.clear(); page.gui.reset(new CGUI(m_ScriptRuntime)); page.gui->Initialize(); VfsPath path = VfsPath("gui") / page.name; page.inputs.insert(path); CXeromyces xero; if (xero.Load(g_VFS, path, "gui_page") != PSRETURN_OK) // Fail silently (Xeromyces reported the error) return; int elmt_page = xero.GetElementID("page"); int elmt_include = xero.GetElementID("include"); XMBElement root = xero.GetRoot(); if (root.GetNodeName() != elmt_page) { LOGERROR("GUI page '%s' must have root element <page>", utf8_from_wstring(page.name)); return; } XERO_ITER_EL(root, node) { if (node.GetNodeName() != elmt_include) { LOGERROR("GUI page '%s' must only have <include> elements inside <page>", utf8_from_wstring(page.name)); continue; } std::string name = node.GetText(); CStrW nameW (node.GetText().FromUTF8()); PROFILE2("load gui xml"); PROFILE2_ATTR("name: %s", name.c_str()); TIMER(nameW.c_str()); if (name.back() == '/') { VfsPath directory = VfsPath("gui") / nameW; VfsPaths pathnames; vfs::GetPathnames(g_VFS, directory, L"*.xml", pathnames); for (const VfsPath& path : pathnames) page.gui->LoadXmlFile(path, page.inputs); } else { VfsPath path = VfsPath("gui") / nameW; page.gui->LoadXmlFile(path, page.inputs); } } // Remember this GUI page, in case the scripts call FindObjectByName shared_ptr<CGUI> oldGUI = m_CurrentGUI; m_CurrentGUI = page.gui; page.gui->SendEventToAll("load"); shared_ptr<ScriptInterface> scriptInterface = page.gui->GetScriptInterface(); JSContext* cx = scriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedValue initDataVal(cx); JS::RootedValue hotloadDataVal(cx); JS::RootedValue global(cx, scriptInterface->GetGlobalObject()); if (page.initData) scriptInterface->ReadStructuredClone(page.initData, &initDataVal); if (hotloadData) scriptInterface->ReadStructuredClone(hotloadData, &hotloadDataVal); // Call the init() function if (!scriptInterface->CallFunctionVoid( global, "init", initDataVal, hotloadDataVal) ) { LOGERROR("GUI page '%s': Failed to call init() function", utf8_from_wstring(page.name)); } m_CurrentGUI = oldGUI; }
void CSimulation2Impl::Update(int turnLength, const std::vector<SimulationCommand>& commands) { PROFILE3("sim update"); PROFILE2_ATTR("turn %d", (int)m_TurnNumber); fixed turnLengthFixed = fixed::FromInt(turnLength) / 1000; /* * In serialization test mode, we save the original (primary) simulation state before each turn update. * We run the update, then load the saved state into a secondary context. * We serialize that again and compare to the original serialization (to check that * serialize->deserialize->serialize is equivalent to serialize). * Then we run the update on the secondary context, and check that its new serialized * state matches the primary context after the update (to check that the simulation doesn't depend * on anything that's not serialized). */ const bool serializationTestDebugDump = false; // set true to save human-readable state dumps before an error is detected, for debugging (but slow) const bool serializationTestHash = true; // set true to save and compare hash of state SerializationTestState primaryStateBefore; if (m_EnableSerializationTest) { ENSURE(m_ComponentManager.SerializeState(primaryStateBefore.state)); if (serializationTestDebugDump) ENSURE(m_ComponentManager.DumpDebugState(primaryStateBefore.debug, false)); if (serializationTestHash) ENSURE(m_ComponentManager.ComputeStateHash(primaryStateBefore.hash, false)); } UpdateComponents(m_SimContext, turnLengthFixed, commands); if (m_EnableSerializationTest) { // Initialise the secondary simulation CTerrain secondaryTerrain; CSimContext secondaryContext; secondaryContext.m_Terrain = &secondaryTerrain; CComponentManager secondaryComponentManager(secondaryContext, m_ComponentManager.GetScriptInterface().GetRuntime()); secondaryComponentManager.LoadComponentTypes(); ENSURE(LoadDefaultScripts(secondaryComponentManager, NULL)); ResetComponentState(secondaryComponentManager, false, false); // Load the map into the secondary simulation LDR_BeginRegistering(); CMapReader* mapReader = new CMapReader; // automatically deletes itself // TODO: this duplicates CWorld::RegisterInit and could probably be cleaned up a bit std::string mapType; m_ComponentManager.GetScriptInterface().GetProperty(m_InitAttributes.get(), "mapType", mapType); if (mapType == "random") { // TODO: support random map scripts debug_warn(L"Serialization test mode only supports scenarios"); } else { std::wstring mapFile; m_ComponentManager.GetScriptInterface().GetProperty(m_InitAttributes.get(), "map", mapFile); VfsPath mapfilename = VfsPath(mapFile).ChangeExtension(L".pmp"); mapReader->LoadMap(mapfilename, CScriptValRooted(), &secondaryTerrain, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &secondaryContext, INVALID_PLAYER, true); // throws exception on failure } LDR_EndRegistering(); ENSURE(LDR_NonprogressiveLoad() == INFO::OK); ENSURE(secondaryComponentManager.DeserializeState(primaryStateBefore.state)); SerializationTestState secondaryStateBefore; ENSURE(secondaryComponentManager.SerializeState(secondaryStateBefore.state)); if (serializationTestDebugDump) ENSURE(secondaryComponentManager.DumpDebugState(secondaryStateBefore.debug, false)); if (serializationTestHash) ENSURE(secondaryComponentManager.ComputeStateHash(secondaryStateBefore.hash, false)); if (primaryStateBefore.state.str() != secondaryStateBefore.state.str() || primaryStateBefore.hash != secondaryStateBefore.hash) { ReportSerializationFailure(&primaryStateBefore, NULL, &secondaryStateBefore, NULL); } SerializationTestState primaryStateAfter; ENSURE(m_ComponentManager.SerializeState(primaryStateAfter.state)); if (serializationTestHash) ENSURE(m_ComponentManager.ComputeStateHash(primaryStateAfter.hash, false)); UpdateComponents(secondaryContext, turnLengthFixed, CloneCommandsFromOtherContext(m_ComponentManager.GetScriptInterface(), secondaryComponentManager.GetScriptInterface(), commands)); SerializationTestState secondaryStateAfter; ENSURE(secondaryComponentManager.SerializeState(secondaryStateAfter.state)); if (serializationTestHash) ENSURE(secondaryComponentManager.ComputeStateHash(secondaryStateAfter.hash, false)); if (primaryStateAfter.state.str() != secondaryStateAfter.state.str() || primaryStateAfter.hash != secondaryStateAfter.hash) { // Only do the (slow) dumping now we know we're going to need to report it ENSURE(m_ComponentManager.DumpDebugState(primaryStateAfter.debug, false)); ENSURE(secondaryComponentManager.DumpDebugState(secondaryStateAfter.debug, false)); ReportSerializationFailure(&primaryStateBefore, &primaryStateAfter, &secondaryStateBefore, &secondaryStateAfter); } } // if (m_TurnNumber == 0) // m_ComponentManager.GetScriptInterface().DumpHeap(); // Run the GC occasionally // (TODO: we ought to schedule this for a frame where we're not // running the sim update, to spread the load) if (m_TurnNumber % 1 == 0) m_ComponentManager.GetScriptInterface().MaybeIncrementalRuntimeGC(); if (m_EnableOOSLog) DumpState(); // Start computing AI for the next turn CmpPtr<ICmpAIManager> cmpAIManager(m_SimContext, SYSTEM_ENTITY); if (cmpAIManager) cmpAIManager->StartComputation(); ++m_TurnNumber; }
/////////////////////////////////////////////////////////////////// // Load all sky textures void SkyManager::LoadSkyTextures() { for (size_t i = 0; i < ARRAY_SIZE(m_SkyTexture); ++i) { VfsPath path = VfsPath("art/textures/skies") / m_SkySet / (Path::String(s_imageNames[i])+L".dds"); CTextureProperties textureProps(path); textureProps.SetWrap(GL_CLAMP_TO_EDGE); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); texture->Prefetch(); m_SkyTexture[i] = texture; } glGenTextures(1, &m_SkyCubeMap); glBindTexture(GL_TEXTURE_CUBE_MAP, m_SkyCubeMap); int types[] = { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y }; const wchar_t* images[numTextures+1] = { L"front", L"back", L"right", L"left", L"top", L"top" }; for (size_t i = 0; i < numTextures+1; ++i) { VfsPath path = VfsPath("art/textures/skies") / m_SkySet / (Path::String(images[i])+L".dds"); shared_ptr<u8> file; size_t fileSize; g_VFS->LoadFile(path, file, fileSize); Tex tex; tex_decode(file, fileSize, &tex); tex_transform_to(&tex, (tex.flags | TEX_BOTTOM_UP | TEX_ALPHA) & ~(TEX_DXT | TEX_MIPMAPS)); u8* data = tex_get_data(&tex); if (types[i] == GL_TEXTURE_CUBE_MAP_NEGATIVE_Y || types[i] == GL_TEXTURE_CUBE_MAP_POSITIVE_Y) { std::vector<u8> rotated(tex.dataSize); for (size_t y = 0; y < tex.h; ++y) { for (size_t x = 0; x < tex.w; ++x) { size_t invx = y, invy = tex.w-x-1; rotated[(y*tex.w + x) * 4 + 0] = data[(invy*tex.w + invx) * 4 + 0]; rotated[(y*tex.w + x) * 4 + 1] = data[(invy*tex.w + invx) * 4 + 1]; rotated[(y*tex.w + x) * 4 + 2] = data[(invy*tex.w + invx) * 4 + 2]; rotated[(y*tex.w + x) * 4 + 3] = data[(invy*tex.w + invx) * 4 + 3]; } } glTexImage2D(types[i], 0, GL_RGB, tex.w, tex.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, &rotated[0]); } else { glTexImage2D(types[i], 0, GL_RGB, tex.w, tex.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); } tex_free(&tex); } glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D, 0); }
bool CTemplateLoader::TemplateExists(const std::string& templateName) const { size_t pos = templateName.rfind('|'); std::string baseName(pos != std::string::npos ? templateName.substr(pos+1) : templateName); return VfsFileExists(VfsPath(TEMPLATE_ROOT) / wstring_from_utf8(baseName + ".xml")); }
void CGUI::Xeromyces_ReadImage(XMBElement Element, CXeromyces* pFile, CGUISprite &parent) { // Image object we're adding SGUIImage* Image = new SGUIImage; // Set defaults to "0 0 100% 100%" Image->m_TextureSize = CClientArea(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100)); Image->m_Size = CClientArea(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100)); // TODO Gee: Setup defaults here (or maybe they are in the SGUIImage ctor) // // Read Attributes // // Now we can iterate all attributes and store XMBAttributeList attributes = Element.GetAttributes(); for (int i=0; i<attributes.Count; ++i) { XMBAttribute attr = attributes.Item(i); CStr attr_name (pFile->GetAttributeString(attr.Name)); CStrW attr_value (attr.Value.FromUTF8()); if (attr_name == "texture") { Image->m_TextureName = VfsPath("art/textures/ui") / attr_value; } else if (attr_name == "size") { CClientArea ca; if (!GUI<CClientArea>::ParseString(attr_value, ca)) LOGERROR(L"GUI: Error parsing '%hs' (\"%ls\")", attr_name.c_str(), attr_value.c_str()); else Image->m_Size = ca; } else if (attr_name == "texture_size") { CClientArea ca; if (!GUI<CClientArea>::ParseString(attr_value, ca)) LOGERROR(L"GUI: Error parsing '%hs' (\"%ls\")", attr_name.c_str(), attr_value.c_str()); else Image->m_TextureSize = ca; } else if (attr_name == "real_texture_placement") { CRect rect; if (!GUI<CRect>::ParseString(attr_value, rect)) LOGERROR(L"GUI: Error parsing '%hs' (\"%ls\")", attr_name.c_str(), attr_value.c_str()); else Image->m_TexturePlacementInFile = rect; } else if (attr_name == "cell_size") { CSize size; if (!GUI<CSize>::ParseString(attr_value, size)) LOGERROR(L"GUI: Error parsing '%hs' (\"%ls\")", attr_name.c_str(), attr_value.c_str()); else Image->m_CellSize = size; } else if (attr_name == "fixed_h_aspect_ratio") { float val; if (!GUI<float>::ParseString(attr_value, val)) LOGERROR(L"GUI: Error parsing '%hs' (\"%ls\")", attr_name.c_str(), attr_value.c_str()); else Image->m_FixedHAspectRatio = val; } else if (attr_name == "round_coordinates") { bool b; if (!GUI<bool>::ParseString(attr_value, b)) LOGERROR(L"GUI: Error parsing '%hs' (\"%ls\")", attr_name.c_str(), attr_value.c_str()); else Image->m_RoundCoordinates = b; } else if (attr_name == "wrap_mode") { if (attr_value == L"repeat") Image->m_WrapMode = GL_REPEAT; else if (attr_value == L"mirrored_repeat") Image->m_WrapMode = GL_MIRRORED_REPEAT; else if (attr_value == L"clamp_to_edge") Image->m_WrapMode = GL_CLAMP_TO_EDGE; else LOGERROR(L"GUI: Error parsing '%hs' (\"%ls\")", attr_name.c_str(), attr_value.c_str()); } else if (attr_name == "z_level") { float z_level; if (!GUI<float>::ParseString(attr_value, z_level)) LOGERROR(L"GUI: Error parsing '%hs' (\"%ls\")", attr_name.c_str(), attr_value.c_str()); else Image->m_DeltaZ = z_level/100.f; } else if (attr_name == "backcolor") { CColor color; if (!GUI<CColor>::ParseString(attr_value, color)) LOGERROR(L"GUI: Error parsing '%hs' (\"%ls\")", attr_name.c_str(), attr_value.c_str()); else Image->m_BackColor = color; } else if (attr_name == "bordercolor") { CColor color; if (!GUI<CColor>::ParseString(attr_value, color)) LOGERROR(L"GUI: Error parsing '%hs' (\"%ls\")", attr_name.c_str(), attr_value.c_str()); else Image->m_BorderColor = color; } else if (attr_name == "border") { bool b; if (!GUI<bool>::ParseString(attr_value, b)) LOGERROR(L"GUI: Error parsing '%hs' (\"%ls\")", attr_name.c_str(), attr_value.c_str()); else Image->m_Border = b; } else { debug_warn(L"Invalid data - DTD shouldn't allow this"); } } // Look for effects XMBElementList children = Element.GetChildNodes(); for (int i=0; i<children.Count; ++i) { XMBElement child = children.Item(i); CStr ElementName (pFile->GetElementString(child.GetNodeName())); if (ElementName == "effect") { if (Image->m_Effects) { LOGERROR(L"GUI <image> must not have more than one <effect>"); } else { Image->m_Effects = new SGUIImageEffects; Xeromyces_ReadEffects(child, pFile, *Image->m_Effects); } } else { debug_warn(L"Invalid data - DTD shouldn't allow this"); } } // // Input // parent.AddImage(Image); }
std::vector<std::string> CTemplateLoader::FindPlaceableTemplates(const std::string& path, bool includeSubdirectories, ETemplatesType templatesType, ScriptInterface& scriptInterface) { JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); std::vector<std::string> templates; Status ok; VfsPath templatePath; if (templatesType == SIMULATION_TEMPLATES || templatesType == ALL_TEMPLATES) { JS::RootedValue placeablesFilter(cx); scriptInterface.ReadJSONFile("simulation/data/placeablesFilter.json", &placeablesFilter); JS::RootedObject folders(cx); if (scriptInterface.GetProperty(placeablesFilter, "templates", &folders)) { if (!(JS_IsArrayObject(cx, folders))) { LOGERROR("FindPlaceableTemplates: Argument must be an array!"); return templates; } u32 length; if (!JS_GetArrayLength(cx, folders, &length)) { LOGERROR("FindPlaceableTemplates: Failed to get array length!"); return templates; } templatePath = VfsPath(TEMPLATE_ROOT) / path; //I have every object inside, just run for each for (u32 i=0; i<length; ++i) { JS::RootedValue val(cx); if (!JS_GetElement(cx, folders, i, &val)) { LOGERROR("FindPlaceableTemplates: Failed to read array element!"); return templates; } std::string directoryPath; std::wstring fileFilter; scriptInterface.GetProperty(val, "directory", directoryPath); scriptInterface.GetProperty(val, "file", fileFilter); VfsPaths filenames; if (vfs::GetPathnames(g_VFS, templatePath / (directoryPath + "/"), fileFilter.c_str(), filenames) != INFO::OK) continue; for (const VfsPath& filename : filenames) { // Strip the .xml extension VfsPath pathstem = filename.ChangeExtension(L""); // Strip the root from the path std::wstring name = pathstem.string().substr(ARRAY_SIZE(TEMPLATE_ROOT) - 1); templates.emplace_back(name.begin(), name.end()); } } } } if (templatesType == ACTOR_TEMPLATES || templatesType == ALL_TEMPLATES) { templatePath = VfsPath(ACTOR_ROOT) / path; if (includeSubdirectories) ok = vfs::ForEachFile(g_VFS, templatePath, AddActorToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE); else ok = vfs::ForEachFile(g_VFS, templatePath, AddActorToTemplates, (uintptr_t)&templates, L"*.xml"); WARN_IF_ERR(ok); } if (templatesType != SIMULATION_TEMPLATES && templatesType != ACTOR_TEMPLATES && templatesType != ALL_TEMPLATES) LOGERROR("Undefined template type (valid: all, simulation, actor)"); return templates; }
void CArchiveBuilder::Build(const OsPath& archive, bool compress) { // By default we disable zip compression because it significantly hurts download // size for releases (which re-compress all files with better compression // algorithms) - it's probably most important currently to optimise for // download size rather than install size or startup performance. // (See http://trac.wildfiregames.com/ticket/671) const bool noDeflate = !compress; PIArchiveWriter writer = CreateArchiveWriter_Zip(archive, noDeflate); // Use CTextureManager instead of CTextureConverter directly, // so it can deal with all the loading of settings.xml files CTextureManager textureManager(m_VFS, true, true); CColladaManager colladaManager(m_VFS); CXeromyces xero; for (size_t i = 0; i < m_Files.size(); ++i) { Status ret; const VfsPath path = m_Files[i]; OsPath realPath; ret = m_VFS->GetRealPath(path, realPath); ENSURE(ret == INFO::OK); // Compress textures and store the new cached version instead of the original if ((boost::algorithm::starts_with(path.string(), L"art/textures/") || boost::algorithm::starts_with(path.string(), L"fonts/") ) && tex_is_known_extension(path) && // Skip some subdirectories where the engine doesn't use CTextureManager yet: !boost::algorithm::starts_with(path.string(), L"art/textures/cursors/") && !boost::algorithm::starts_with(path.string(), L"art/textures/terrain/alphamaps/") ) { VfsPath cachedPath; debug_printf(L"Converting texture %ls\n", realPath.string().c_str()); bool ok = textureManager.GenerateCachedTexture(path, cachedPath); ENSURE(ok); OsPath cachedRealPath; ret = m_VFS->GetRealPath(VfsPath("cache")/cachedPath, cachedRealPath); ENSURE(ret == INFO::OK); writer->AddFile(cachedRealPath, cachedPath); // We don't want to store the original file too (since it's a // large waste of space), so skip to the next file continue; } // Convert DAE models and store the new cached version instead of the original if (path.Extension() == L".dae") { CColladaManager::FileType type; if (boost::algorithm::starts_with(path.string(), L"art/meshes/")) type = CColladaManager::PMD; else if (boost::algorithm::starts_with(path.string(), L"art/animation/")) type = CColladaManager::PSA; else { // Unknown type of DAE, just add to archive and continue writer->AddFile(realPath, path); continue; } VfsPath cachedPath; debug_printf(L"Converting model %ls\n", realPath.string().c_str()); bool ok = colladaManager.GenerateCachedFile(path, type, cachedPath); // The DAE might fail to convert for whatever reason, and in that case // it can't be used in the game, so we just exclude it // (alternatively we could throw release blocking errors on useless files) if (ok) { OsPath cachedRealPath; ret = m_VFS->GetRealPath(VfsPath("cache")/cachedPath, cachedRealPath); ENSURE(ret == INFO::OK); writer->AddFile(cachedRealPath, cachedPath); } // We don't want to store the original file too (since it's a // large waste of space), so skip to the next file continue; } debug_printf(L"Adding %ls\n", realPath.string().c_str()); writer->AddFile(realPath, path); // Also cache XMB versions of all XML files if (path.Extension() == L".xml") { VfsPath cachedPath; debug_printf(L"Converting XML file %ls\n", realPath.string().c_str()); bool ok = xero.GenerateCachedXMB(m_VFS, path, cachedPath); ENSURE(ok); OsPath cachedRealPath; ret = m_VFS->GetRealPath(VfsPath("cache")/cachedPath, cachedRealPath); ENSURE(ret == INFO::OK); writer->AddFile(cachedRealPath, cachedPath); } } }
void CSimulation2Impl::Update(int turnLength, const std::vector<SimulationCommand>& commands) { PROFILE3("sim update"); PROFILE2_ATTR("turn %d", (int)m_TurnNumber); fixed turnLengthFixed = fixed::FromInt(turnLength) / 1000; /* * In serialization test mode, we save the original (primary) simulation state before each turn update. * We run the update, then load the saved state into a secondary context. * We serialize that again and compare to the original serialization (to check that * serialize->deserialize->serialize is equivalent to serialize). * Then we run the update on the secondary context, and check that its new serialized * state matches the primary context after the update (to check that the simulation doesn't depend * on anything that's not serialized). */ const bool serializationTestDebugDump = false; // set true to save human-readable state dumps before an error is detected, for debugging (but slow) const bool serializationTestHash = true; // set true to save and compare hash of state SerializationTestState primaryStateBefore; ScriptInterface& scriptInterface = m_ComponentManager.GetScriptInterface(); if (m_EnableSerializationTest) { ENSURE(m_ComponentManager.SerializeState(primaryStateBefore.state)); if (serializationTestDebugDump) ENSURE(m_ComponentManager.DumpDebugState(primaryStateBefore.debug, false)); if (serializationTestHash) ENSURE(m_ComponentManager.ComputeStateHash(primaryStateBefore.hash, false)); } UpdateComponents(m_SimContext, turnLengthFixed, commands); if (m_EnableSerializationTest) { // Initialise the secondary simulation CTerrain secondaryTerrain; CSimContext secondaryContext; secondaryContext.m_Terrain = &secondaryTerrain; CComponentManager secondaryComponentManager(secondaryContext, scriptInterface.GetRuntime()); secondaryComponentManager.LoadComponentTypes(); std::set<VfsPath> secondaryLoadedScripts; ENSURE(LoadDefaultScripts(secondaryComponentManager, &secondaryLoadedScripts)); ResetComponentState(secondaryComponentManager, false, false); // Load the trigger scripts after we have loaded the simulation. { JSContext* cx2 = secondaryComponentManager.GetScriptInterface().GetContext(); JSAutoRequest rq2(cx2); JS::RootedValue mapSettingsCloned(cx2, secondaryComponentManager.GetScriptInterface().CloneValueFromOtherContext( scriptInterface, m_MapSettings)); ENSURE(LoadTriggerScripts(secondaryComponentManager, mapSettingsCloned, &secondaryLoadedScripts)); } // Load the map into the secondary simulation LDR_BeginRegistering(); CMapReader* mapReader = new CMapReader; // automatically deletes itself std::string mapType; scriptInterface.GetProperty(m_InitAttributes, "mapType", mapType); if (mapType == "random") { // TODO: support random map scripts debug_warn(L"Serialization test mode does not support random maps"); } else { std::wstring mapFile; scriptInterface.GetProperty(m_InitAttributes, "map", mapFile); VfsPath mapfilename = VfsPath(mapFile).ChangeExtension(L".pmp"); mapReader->LoadMap(mapfilename, scriptInterface.GetJSRuntime(), JS::UndefinedHandleValue, &secondaryTerrain, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &secondaryContext, INVALID_PLAYER, true); // throws exception on failure } LDR_EndRegistering(); ENSURE(LDR_NonprogressiveLoad() == INFO::OK); ENSURE(secondaryComponentManager.DeserializeState(primaryStateBefore.state)); SerializationTestState secondaryStateBefore; ENSURE(secondaryComponentManager.SerializeState(secondaryStateBefore.state)); if (serializationTestDebugDump) ENSURE(secondaryComponentManager.DumpDebugState(secondaryStateBefore.debug, false)); if (serializationTestHash) ENSURE(secondaryComponentManager.ComputeStateHash(secondaryStateBefore.hash, false)); if (primaryStateBefore.state.str() != secondaryStateBefore.state.str() || primaryStateBefore.hash != secondaryStateBefore.hash) { ReportSerializationFailure(&primaryStateBefore, NULL, &secondaryStateBefore, NULL); } SerializationTestState primaryStateAfter; ENSURE(m_ComponentManager.SerializeState(primaryStateAfter.state)); if (serializationTestHash) ENSURE(m_ComponentManager.ComputeStateHash(primaryStateAfter.hash, false)); UpdateComponents(secondaryContext, turnLengthFixed, CloneCommandsFromOtherContext(scriptInterface, secondaryComponentManager.GetScriptInterface(), commands)); SerializationTestState secondaryStateAfter; ENSURE(secondaryComponentManager.SerializeState(secondaryStateAfter.state)); if (serializationTestHash) ENSURE(secondaryComponentManager.ComputeStateHash(secondaryStateAfter.hash, false)); if (primaryStateAfter.state.str() != secondaryStateAfter.state.str() || primaryStateAfter.hash != secondaryStateAfter.hash) { // Only do the (slow) dumping now we know we're going to need to report it ENSURE(m_ComponentManager.DumpDebugState(primaryStateAfter.debug, false)); ENSURE(secondaryComponentManager.DumpDebugState(secondaryStateAfter.debug, false)); ReportSerializationFailure(&primaryStateBefore, &primaryStateAfter, &secondaryStateBefore, &secondaryStateAfter); } } // if (m_TurnNumber == 0) // m_ComponentManager.GetScriptInterface().DumpHeap(); // Run the GC occasionally // No delay because a lot of garbage accumulates in one turn and in non-visual replays there are // much more turns in the same time than in normal games. // Every 500 turns we run a shrinking GC, which decommits unused memory and frees all JIT code. // Based on testing, this seems to be a good compromise between memory usage and performance. // Also check the comment about gcPreserveCode in the ScriptInterface code and this forum topic: // http://www.wildfiregames.com/forum/index.php?showtopic=18466&p=300323 // // (TODO: we ought to schedule this for a frame where we're not // running the sim update, to spread the load) if (m_TurnNumber % 500 == 0) scriptInterface.GetRuntime()->ShrinkingGC(); else scriptInterface.GetRuntime()->MaybeIncrementalGC(0.0f); if (m_EnableOOSLog) DumpState(); // Start computing AI for the next turn CmpPtr<ICmpAIManager> cmpAIManager(m_SimContext, SYSTEM_ENTITY); if (cmpAIManager) cmpAIManager->StartComputation(); ++m_TurnNumber; }
void AddPlaylistItem(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring filename) { if ( CSoundManager* sndManager = (CSoundManager*)g_SoundManager ) sndManager->AddPlayListItem(VfsPath(filename)); }
bool CTemplateLoader::LoadTemplateFile(const std::string& 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("Probable infinite inheritance loop in entity template '%s'", templateName.c_str()); return false; } // Handle special case "actor|foo" if (templateName.find("actor|") == 0) { ConstructTemplateActor(templateName.substr(6), m_TemplateFileData[templateName]); return true; } // Handle special case "preview|foo" if (templateName.find("preview|") == 0) { // Load the base entity template, if it wasn't already loaded std::string baseName = templateName.substr(8); if (!LoadTemplateFile(baseName, depth+1)) { LOGERROR("Failed to load entity template '%s'", baseName.c_str()); return false; } // Copy a subset to the requested template 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("Failed to load entity template '%s'", baseName.c_str()); return false; } // Copy a subset to the requested template CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], true); return true; } // Handle special case "mirage|foo" if (templateName.find("mirage|") == 0) { // Load the base entity template, if it wasn't already loaded std::string baseName = templateName.substr(7); if (!LoadTemplateFile(baseName, depth+1)) { LOGERROR("Failed to load entity template '%s'", baseName.c_str()); return false; } // Copy a subset to the requested template CopyMirageSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]); return true; } // Handle special case "foundation|foo" if (templateName.find("foundation|") == 0) { // Load the base entity template, if it wasn't already loaded std::string baseName = templateName.substr(11); if (!LoadTemplateFile(baseName, depth+1)) { LOGERROR("Failed to load entity template '%s'", baseName.c_str()); return false; } // Copy a subset to the requested template CopyFoundationSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]); return true; } // Handle special case "construction|foo" if (templateName.find("construction|") == 0) { // Load the base entity template, if it wasn't already loaded std::string baseName = templateName.substr(13); if (!LoadTemplateFile(baseName, depth+1)) { LOGERROR("Failed to load entity template '%s'", baseName.c_str()); return false; } // Copy a subset to the requested template CopyConstructionSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]); return true; } // Handle special case "resource|foo" if (templateName.find("resource|") == 0) { // Load the base entity template, if it wasn't already loaded std::string baseName = templateName.substr(9); if (!LoadTemplateFile(baseName, depth+1)) { LOGERROR("Failed to load entity template '%s'", baseName.c_str()); return false; } // Copy a subset to the requested template CopyResourceSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]); return true; } // Normal case: templateName is an XML file: VfsPath path = VfsPath(TEMPLATE_ROOT) / wstring_from_utf8(templateName + ".xml"); CXeromyces xero; PSRETURN ok = xero.Load(g_VFS, path); if (ok != PSRETURN_OK) return false; // (Xeromyces already logged an error with the full filename) int attr_parent = xero.GetAttributeID("parent"); CStr parentName = xero.GetRoot().GetAttributes().GetNamedItem(attr_parent); if (!parentName.empty()) { // To prevent needless complexity in template design, we don't allow |-separated strings as parents if (parentName.find('|') != parentName.npos) { LOGERROR("Invalid parent '%s' in entity template '%s'", parentName.c_str(), templateName.c_str()); return false; } // Ensure the parent is loaded if (!LoadTemplateFile(parentName, depth+1)) { LOGERROR("Failed to load parent '%s' of entity template '%s'", 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, wstring_from_utf8(templateName).c_str()); return true; }