diff --git a/code/ryzom/client/src/continent_manager.cpp b/code/ryzom/client/src/continent_manager.cpp index fe9067a4f..56a11cf63 100644 --- a/code/ryzom/client/src/continent_manager.cpp +++ b/code/ryzom/client/src/continent_manager.cpp @@ -488,8 +488,177 @@ CContinent *CContinentManager::get(const std::string &contName) return NULL; } -void CContinentManager::serialUserLandMarks(NLMISC::IStream &f) +void CContinentManager::writeTo(xmlNodePtr node) const { + // + // + // + // ... + // + for(TContinents::const_iterator it = _Continents.begin(); it != _Continents.end(); ++it) + { + std::string name = it->first; + xmlNodePtr contNode = xmlNewChild(node, NULL, (const xmlChar*)"landmarks", NULL); + xmlSetProp(contNode, (const xmlChar*)"continent", (const xmlChar*)name.c_str()); + xmlSetProp(contNode, (const xmlChar*)"type", (const xmlChar*)"user"); + + if (it->second && it->second->UserLandMarks.size() > 0) + { + for(uint i = 0; i< it->second->UserLandMarks.size(); ++i) + { + const CUserLandMark& lm = it->second->UserLandMarks[i]; + + xmlNodePtr lmNode = xmlNewChild(contNode, NULL, (const xmlChar*)"landmark", NULL); + xmlSetProp(lmNode, (const xmlChar*)"type", (const xmlChar*)toString("%d", (uint32)lm.Type).c_str()); + xmlSetProp(lmNode, (const xmlChar*)"x", (const xmlChar*)toString("%.2f", lm.Pos.x).c_str()); + xmlSetProp(lmNode, (const xmlChar*)"y", (const xmlChar*)toString("%.2f", lm.Pos.y).c_str()); + + // sanitize ascii control chars + // libxml will encode other special chars itself + std::string title = lm.Title.toUtf8(); + for(uint i = 0; i< title.size(); i++) + { + if (title[i] < ' ' && title[i] != '\n' && title[i] != '\t') + { + title[i] = '?'; + } + } + xmlSetProp(lmNode, (const xmlChar*)"title", (const xmlChar*)title.c_str()); + } + } + } +} + +void CContinentManager::readFrom(xmlNodePtr node) +{ + CXMLAutoPtr prop; + + // + // + // ... + // + std::string continent; + prop = xmlGetProp(node, (xmlChar*)"continent"); + if (!prop) + { + nlwarning("Ignore landmarks group 'continent' attribute."); + return; + } + continent = (const char*)prop; + + TContinents::iterator itContinent = _Continents.find(continent); + if (itContinent == _Continents.end() || !itContinent->second) + { + nlwarning("Ignore landmarks group with unknown 'continent' '%s'", continent.c_str()); + return; + } + + std::string lmtype; + prop = xmlGetProp(node, (xmlChar*)"type"); + if (!prop) + { + nlwarning("Ignore landmarks group without 'type' attribute."); + return; + } + lmtype = toLower((const char*)prop); + if (lmtype != "user") + { + nlwarning("Ignore landmarks group with type '%s', expected 'user'.", lmtype.c_str()); + return; + } + + node = node->children; + while(node) + { + if (stricmp((char*)node->name, "landmark") != 0) + { + nlwarning("Ignore invalid node '%s' under landmarks group", (const char*)node->name); + + node = node->next; + continue; + } + + bool add = true; + CUserLandMark lm; + + prop = xmlGetProp(node, (xmlChar*)"type"); + if (prop) + fromString((const char*)prop, lm.Type); + else + nlwarning("Using default value for landmark type"); + + prop = xmlGetProp(node, (xmlChar*)"x"); + if (prop) + { + fromString((const char*)prop, lm.Pos.x); + } + else + { + nlwarning("Landmark missing 'x' attribute"); + add = false; + } + + prop = xmlGetProp(node, (xmlChar*)"y"); + if (prop) + { + fromString((const char*)prop, lm.Pos.y); + } + else + { + nlwarning("Landmark missing 'y' attribute"); + add = false; + } + + prop = xmlGetProp(node, (xmlChar*)"title"); + if (prop) + { + lm.Title.fromUtf8((const char*)prop); + } + else + { + nlwarning("Landmark missing 'title' attribute"); + add = false; + } + + if (add) + { + // before adding, check for duplicate + // duplicates might be read from .icfg before .xml is read + add = true; + for(uint i = 0; i< itContinent->second->UserLandMarks.size(); ++i) + { + const CUserLandMark& test = itContinent->second->UserLandMarks[i]; + uint xdiff = abs(test.Pos.x - lm.Pos.x) * 100; + uint ydiff = abs(test.Pos.y - lm.Pos.y) * 100; + if (xdiff == 0 && ydiff == 0) + { + add = false; + break; + } + } + + if (add) + { + itContinent->second->UserLandMarks.push_back(lm); + } + else + { + nlwarning("Ignore landmark with duplicate pos (continent:'%s', x:%.2f, y:%.2f, type:%d, title:'%s')", continent.c_str(), lm.Pos.x, lm.Pos.y, (uint8)lm.Type, lm.Title.toUtf8().c_str()); + } + } + else + { + nlwarning("Landmark not added"); + } + + node = node->next; + } +} + +uint32 CContinentManager::serialUserLandMarks(NLMISC::IStream &f) +{ + uint32 totalLandmarks = 0; + f.serialVersion(1); if (!f.isReading()) { @@ -502,6 +671,7 @@ void CContinentManager::serialUserLandMarks(NLMISC::IStream &f) if (it->second) { f.serialCont(it->second->UserLandMarks); + totalLandmarks += it->second->UserLandMarks.size(); } else { @@ -522,6 +692,7 @@ void CContinentManager::serialUserLandMarks(NLMISC::IStream &f) if (it != _Continents.end() && it->second) { f.serialCont(it->second->UserLandMarks); + totalLandmarks += it->second->UserLandMarks.size(); } else { @@ -530,6 +701,8 @@ void CContinentManager::serialUserLandMarks(NLMISC::IStream &f) } } } + + return totalLandmarks; } diff --git a/code/ryzom/client/src/continent_manager.h b/code/ryzom/client/src/continent_manager.h index c51498967..1a0641627 100644 --- a/code/ryzom/client/src/continent_manager.h +++ b/code/ryzom/client/src/continent_manager.h @@ -109,8 +109,13 @@ public: const std::string &getCurrentContinentSelectName(); + // load / save all user landmarks in xml format + void writeTo(xmlNodePtr node) const; + void readFrom(xmlNodePtr node); + // load / saves all user landMarks - void serialUserLandMarks(NLMISC::IStream &f); + // \return number of landmarks loaded or saved + uint32 serialUserLandMarks(NLMISC::IStream &f); // rebuild visible landmarks on current map void updateUserLandMarks(); diff --git a/code/ryzom/client/src/far_tp.cpp b/code/ryzom/client/src/far_tp.cpp index 577477ea2..92a8bbe3a 100644 --- a/code/ryzom/client/src/far_tp.cpp +++ b/code/ryzom/client/src/far_tp.cpp @@ -1298,6 +1298,7 @@ void CFarTP::sendReady() pIM->loadKeys(); CWidgetManager::getInstance()->hideAllWindows(); pIM->loadInterfaceConfig(); + pIM->loadLandmarks(); } else { diff --git a/code/ryzom/client/src/interface_v3/group_map.cpp b/code/ryzom/client/src/interface_v3/group_map.cpp index 9de74bcc7..28eb5e1e3 100644 --- a/code/ryzom/client/src/interface_v3/group_map.cpp +++ b/code/ryzom/client/src/interface_v3/group_map.cpp @@ -2715,7 +2715,7 @@ CCtrlButton *CGroupMap::addUserLandMark(const NLMISC::CVector2f &pos, const ucst addLandMark(_UserLM, pos, title, getUserLandMarkOptions((uint32)_CurContinent->UserLandMarks.size() - 1)); // Save the config file each time a user landmark is created - CInterfaceManager::getInstance()->saveConfig(); + CInterfaceManager::getInstance()->saveLandmarks(); return _UserLM.back(); } diff --git a/code/ryzom/client/src/interface_v3/interface_manager.cpp b/code/ryzom/client/src/interface_v3/interface_manager.cpp index 7684accd8..bc606f939 100644 --- a/code/ryzom/client/src/interface_v3/interface_manager.cpp +++ b/code/ryzom/client/src/interface_v3/interface_manager.cpp @@ -458,6 +458,7 @@ CInterfaceManager::CInterfaceManager() parser->addModule( "command", new CCommandParser() ); parser->addModule( "key", new CKeyParser() ); parser->addModule( "macro", new CMacroParser() ); + parser->addModule( "landmarks", new CLandmarkParser() ); parser->setCacheUIParsing( ClientCfg.CacheUIParsing ); CViewRenderer::setDriver( Driver ); @@ -982,6 +983,9 @@ void CInterfaceManager::initInGame() // Interface config loadInterfaceConfig(); + //Load user landmarks + loadLandmarks(); + // Must do extra init for people interaction after load PeopleInterraction.initAfterLoad(); @@ -1217,6 +1221,22 @@ void CInterfaceManager::configureQuitDialogBox() } } +// ------------------------------------------------------------------------------------------------ +// +std::string CInterfaceManager::getSaveFileName(const std::string &module, const std::string &ext, bool useShared) const +{ + string filename = "save/" + module + "_" + PlayerSelectedFileName + "." + ext; + if (useShared && !CFile::fileExists(filename)) + { + string sharedFile = "save/shared_" + module + "." + ext; + if (CFile::fileExists(sharedFile)) + { + return sharedFile; + } + } + return filename; +} + // ------------------------------------------------------------------------------------------------ void CInterfaceManager::loadKeys() { @@ -1228,26 +1248,19 @@ void CInterfaceManager::loadKeys() vector xmlFilesToParse; // Does the keys file exist ? - string userKeyFileName = "save/keys_"+PlayerSelectedFileName+".xml"; + string userKeyFileName = getSaveFileName("keys", "xml"); if (CFile::fileExists(userKeyFileName) && CFile::getFileSize(userKeyFileName) > 0) { // Load the user key file xmlFilesToParse.push_back (userKeyFileName); } - else - { - string filename = "save/shared_keys.xml"; - if(CFile::fileExists(filename) && CFile::getFileSize(filename) > 0) - { - xmlFilesToParse.push_back(filename); - } - } + // Load the default key (but don't replace existings bounds, see keys.xml "key_def_no_replace") xmlFilesToParse.push_back ("keys.xml"); if (!parseInterface (xmlFilesToParse, true)) { - badXMLParseMessageBox(); + createFileBackup("Error loading keys", userKeyFileName); } _KeysLoaded = true; @@ -1260,10 +1273,7 @@ void CInterfaceManager::loadInterfaceConfig() if (ClientCfg.R2EDEnabled) // in R2ED mode the CEditor class deals with it return; - string filename = "save/interface_" + PlayerSelectedFileName + ".icfg"; - if (!CFile::fileExists(filename)) - filename = "save/shared_interface.icfg"; - + string filename = getSaveFileName("interface", "icfg"); loadConfig(filename); // Invalidate coords of changed groups _ConfigLoaded = true; @@ -1680,6 +1690,7 @@ bool CInterfaceManager::loadConfig (const string &filename) uint32 nNbMode; CInterfaceConfig ic; bool lastInGameScreenResLoaded= false; + uint32 nbLandmarks = 0; try { sint ver = f.serialVersion(ICFG_STREAM_VERSION); @@ -1775,7 +1786,7 @@ bool CInterfaceManager::loadConfig (const string &filename) } // Load user landmarks - ContinentMngr.serialUserLandMarks(f); + nbLandmarks = ContinentMngr.serialUserLandMarks(f); CCDBNodeLeaf *pNL = NLGUI::CDBManager::getInstance()->getDbProp( "SERVER:INTERFACES:NB_BONUS_LANDMARKS" ); if ( pNL ) @@ -1809,10 +1820,9 @@ bool CInterfaceManager::loadConfig (const string &filename) catch(const NLMISC::EStream &) { f.close(); - string sFileNameBackup = sFileName+"backup"; - if (CFile::fileExists(sFileNameBackup)) - CFile::deleteFile(sFileNameBackup); - CFile::moveFile(sFileNameBackup, sFileName); + + createFileBackup("Config loading failed", sFileName); + nlwarning("Config loading failed : restore default"); vector v; if (!ClientCfg.R2EDEnabled) @@ -1823,6 +1833,35 @@ bool CInterfaceManager::loadConfig (const string &filename) } f.close(); + if (nbLandmarks > 0) + { + // use copy for backup so on save proper shared/player icfg file is used + createFileBackup("Landmarks will be migrated to xml", sFileName, true); + + // if icfg is interface_player.icfg, then landmarks must also be loaded/saved to player file + if (nlstricmp(sFileName.substr(0, 12), "save/shared_") != 0) + { + string lmfile = getSaveFileName("landmarks", "xml", false); + if (!CFile::fileExists(lmfile)) + { + // create placeholder player landmarks file so saveLandmarks will use it + // even if shared landmarks file exists + COFile f; + if (f.open(lmfile, false, false, true)) + { + std::string xml; + xml = "\n"; + f.serialBuffer((uint8 *)xml.c_str(), xml.size()); + f.close(); + } + } + } + + // merge .icfg landmarks with landmarks.xml + loadLandmarks(); + saveLandmarks(); + } + // *** If saved resolution is different from the current one setuped, must fix positions in _Modes if(lastInGameScreenResLoaded) { @@ -1877,6 +1916,88 @@ public: } }; +bool CInterfaceManager::saveLandmarks(bool verbose) const +{ + bool ret = true; + + if (!ClientCfg.R2EDEnabled) + { + uint8 currMode = getMode(); + + string filename = getSaveFileName("landmarks", "xml"); + if (verbose) CInterfaceManager::getInstance()->displaySystemInfo("Saving " + filename); + ret = saveLandmarks(filename); + } + + return ret; +} + +bool CInterfaceManager::loadLandmarks() +{ + // Does the keys file exist ? + string filename = getSaveFileName("landmarks", "xml"); + + CIFile f; + string sFileName; + sFileName = NLMISC::CPath::lookup (filename, false); + if (sFileName.empty() || !f.open(sFileName)) + return false; + + bool ret = false; + vector xmlFilesToParse; + xmlFilesToParse.push_back (filename); + + //ContinentMngr.serialUserLandMarks(node); + if (!parseInterface (xmlFilesToParse, true)) + { + f.close(); + + createFileBackup("Error while loading landmarks", filename); + + ret = false; + } + + return ret; +} + +bool CInterfaceManager::saveLandmarks(const std::string &filename) const +{ + nlinfo( "Saving landmarks : %s", filename.c_str() ); + + bool ret = false; + try + { + COFile f; + + // using temporary file, so no f.close() unless its a success + if (f.open(filename, false, false, true)) + { + COXml xmlStream; + xmlStream.init (&f); + + xmlDocPtr doc = xmlStream.getDocument (); + xmlNodePtr node = xmlNewDocNode(doc, NULL, (const xmlChar*)"interface_config", NULL); + xmlDocSetRootElement (doc, node); + + ContinentMngr.writeTo(node); + + // Flush the stream + xmlStream.flush(); + + // Close the stream + f.close (); + + ret = true; + } + } + catch (const Exception &e) + { + nlwarning ("Error while writing the file %s : %s.", filename.c_str(), e.what ()); + } + + return ret; +} + // ------------------------------------------------------------------------------------------------ // bool CInterfaceManager::saveConfig (bool verbose) @@ -1887,9 +2008,7 @@ bool CInterfaceManager::saveConfig (bool verbose) { uint8 currMode = getMode(); - string filename = "save/interface_" + PlayerSelectedFileName + ".icfg"; - if (!CFile::fileExists(filename) && CFile::fileExists("save/shared_interface.icfg")) - filename = "save/shared_interface.icfg"; + string filename = getSaveFileName("interface", "icfg"); if (verbose) CInterfaceManager::getInstance()->displaySystemInfo("Saving " + filename); ret = saveConfig(filename); @@ -2004,8 +2123,13 @@ bool CInterfaceManager::saveConfig (const string &filename) CTaskBarManager *pTBM= CTaskBarManager::getInstance(); pTBM->serial(f); - // Save user landmarks - ContinentMngr.serialUserLandMarks(f); + //ContinentMngr.serialUserLandMarks(f); + // empty landmarks block for compatibility + { + f.serialVersion(1); + uint32 numCont = 0; + f.serial(numCont); + } // Info Windows position. CInterfaceHelp::serialInfoWindows(f); @@ -2972,6 +3096,7 @@ class CAHSaveUI : public IActionHandler { CInterfaceManager::getInstance()->saveKeys(true); CInterfaceManager::getInstance()->saveConfig(true); + CInterfaceManager::getInstance()->saveLandmarks(true); } }; REGISTER_ACTION_HANDLER (CAHSaveUI, "save_ui"); @@ -4135,3 +4260,40 @@ bool CInterfaceManager::parseTokens(ucstring& ucstr) ucstr = str; return true;; } + +std::string CInterfaceManager::getNextBackupName(std::string filename) +{ + std::string ts = getTimestampHuman("%Y-%m-%d"); + + if (!ts.empty()) + { + std::string::size_type pos = filename.find_last_of('.'); + if (pos == std::string::npos) + filename = filename + "_" + ts + "_"; + else + filename = filename.substr(0, pos) + "_" + ts + "_" + filename.substr(pos); + } + + // filename_YYYY-MM-DD_000.ext + return CFile::findNewFile(filename); +} + +void CInterfaceManager::createFileBackup(const std::string &message, const std::string &filename, bool useCopy) +{ + std::string backupName = getNextBackupName(filename); + nlwarning("%s: '%s'.", message.c_str(), filename.c_str()); + if (!backupName.empty()) + { + if (useCopy) + { + nlwarning("Backup copy saved as '%s'", backupName.c_str()); + CFile::copyFile(backupName, filename); + } + else + { + nlwarning("File renamed to '%s'", backupName.c_str()); + CFile::moveFile(backupName, filename); + } + } +} + diff --git a/code/ryzom/client/src/interface_v3/interface_manager.h b/code/ryzom/client/src/interface_v3/interface_manager.h index 652d692b3..488dbe0a5 100644 --- a/code/ryzom/client/src/interface_v3/interface_manager.h +++ b/code/ryzom/client/src/interface_v3/interface_manager.h @@ -203,6 +203,19 @@ public: /// Load a set of xml files bool parseInterface (const std::vector &xmlFileNames, bool reload, bool isFilename = true); + /// return new filename that can be used to backup original file + std::string getNextBackupName(std::string filename); + /// copy/rename filename for backup and show error in log + void createFileBackup(const std::string &message, const std::string &filename, bool useCopy = false); + + /// select player/shared file name from 'save' folder' + std::string getSaveFileName(const std::string &module, const std::string &ext, bool useShared = true) const; + + /// Load / save user landmarks in .xml format + bool loadLandmarks (); + bool saveLandmarks (bool verbose = false) const; + bool saveLandmarks (const std::string &filename) const; + // Load/Save position, size, etc.. of windows bool loadConfig (const std::string &filename); // Save config to default location, if verbose is true, display message in game sysinfo diff --git a/code/ryzom/client/src/interface_v3/parser_modules.cpp b/code/ryzom/client/src/interface_v3/parser_modules.cpp index b540900a4..2d2c7cb60 100644 --- a/code/ryzom/client/src/interface_v3/parser_modules.cpp +++ b/code/ryzom/client/src/interface_v3/parser_modules.cpp @@ -24,6 +24,7 @@ #include "../commands.h" #include "interface_3d_scene.h" #include "nel/misc/i_xml.h" +#include "../continent_manager.h" using namespace NLMISC; @@ -31,6 +32,8 @@ using namespace NLMISC; #include "../client_cfg.h" #endif +extern CContinentManager ContinentMngr; + CIF3DSceneParser::CIF3DSceneParser() { parsingStage |= ( Resolved | GroupChildren ); @@ -529,3 +532,21 @@ bool CMacroParser::parse( xmlNodePtr cur, NLGUI::CInterfaceGroup *parentGroup ) return true; } +CLandmarkParser::CLandmarkParser() +{ + parsingStage |= Unresolved; +} + +CLandmarkParser::~CLandmarkParser() +{ +} + +bool CLandmarkParser::parse( xmlNodePtr cur, NLGUI::CInterfaceGroup *parentGroup ) +{ + H_AUTO(parseLandmark) + + ContinentMngr.readFrom(cur); + + return true; +} + diff --git a/code/ryzom/client/src/interface_v3/parser_modules.h b/code/ryzom/client/src/interface_v3/parser_modules.h index 9a2bf76b9..f816f8d39 100644 --- a/code/ryzom/client/src/interface_v3/parser_modules.h +++ b/code/ryzom/client/src/interface_v3/parser_modules.h @@ -76,4 +76,13 @@ public: bool parse( xmlNodePtr cur, CInterfaceGroup *parentGroup ); }; +class CLandmarkParser : public CInterfaceParser::IParserModule +{ +public: + CLandmarkParser(); + ~CLandmarkParser(); + + bool parse( xmlNodePtr cur, CInterfaceGroup *parentGroup ); +}; + #endif