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