diff options
author | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2025-01-20 10:26:22 +0100 |
---|---|---|
committer | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2025-01-20 10:51:24 +0100 |
commit | db9b9f316d7bdcb9504092908bb18e82fc21de2f (patch) | |
tree | 3f7bb1577c5210a9523fd240556cf840665f3efb /src/configuration.cpp | |
parent | 0d1024b64155a05f45f247ad57d0f444db01c1e2 (diff) | |
download | mana-db9b9f316d7bdcb9504092908bb18e82fc21de2f.tar.gz mana-db9b9f316d7bdcb9504092908bb18e82fc21de2f.tar.bz2 mana-db9b9f316d7bdcb9504092908bb18e82fc21de2f.tar.xz mana-db9b9f316d7bdcb9504092908bb18e82fc21de2f.zip |
Made client config statically typed
This makes accessing the config values much faster, since it no longer
needs to do a lookup nor string conversion, which means we could remove
some needless copying of the values.
Overall it makes it easier to find out where settings are used and it
puts the defaults along with the declaration.
Options with default values are no longer saved to the config file. This
does not include unrecognized options, which are kept around to provide
some compatibility with older clients.
While most basic options have kept the same name, more complicated
settings like window geometry, shortcuts, outfits, etc. now have their
own XML elements. Older clients will ignore these and erase them when
saving the configuration.
Diffstat (limited to 'src/configuration.cpp')
-rw-r--r-- | src/configuration.cpp | 451 |
1 files changed, 370 insertions, 81 deletions
diff --git a/src/configuration.cpp b/src/configuration.cpp index 5fc4eabd..ead228c4 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -1,7 +1,7 @@ /* * The Mana Client * Copyright (C) 2004-2009 The Mana World Development Team - * Copyright (C) 2009-2012 The Mana Developers + * Copyright (C) 2009-2024 The Mana Developers * * This file is part of The Mana Client. * @@ -33,16 +33,6 @@ void ConfigurationObject::setValue(const std::string &key, mOptions[key] = value; } -void Configuration::setValue(const std::string &key, const std::string &value) -{ - ConfigurationObject::setValue(key, value); - - // Notify listeners - Event event(Event::ConfigOptionChanged); - event.setString("option", key); - event.trigger(Event::ConfigChannel); -} - std::string ConfigurationObject::getValue(const std::string &key, const std::string &deflt) const { @@ -70,19 +60,8 @@ double ConfigurationObject::getValue(const std::string &key, return iter != mOptions.end() ? atof(iter->second.c_str()) : deflt; } -void ConfigurationObject::deleteList(std::list<ConfigurationObject*> &list) -{ - for (auto element : list) - delete element; - - list.clear(); -} - void ConfigurationObject::clear() { - for (auto &[_, list] : mContainerOptions) - deleteList(list); - mOptions.clear(); } @@ -212,33 +191,13 @@ void ConfigurationObject::initFromXML(XML::Node parent_node) for (auto node : parent_node.children()) { - if (node.name() == "list") + if (node.name() == "option") { - // List option handling. std::string name = node.getProperty("name", std::string()); - for (auto subnode : node.children()) - { - if (subnode.name() == name) - { - auto *cobj = new ConfigurationObject; - - cobj->initFromXML(subnode); // Recurse - - mContainerOptions[name].push_back(cobj); - } - } - - } - else if (node.name() == "option") - { - // Single option handling. - std::string name = node.getProperty("name", std::string()); - std::string value = node.getProperty("value", std::string()); - if (!name.empty()) - mOptions[name] = value; - } // Otherwise ignore + mOptions[name] = node.getProperty("value", std::string()); + } } } @@ -251,15 +210,15 @@ void Configuration::init(const std::string &filename, bool useResManager) else mConfigPath = filename; - if (!doc.rootNode()) + XML::Node rootNode = doc.rootNode(); + + if (!rootNode) { logger->log("Couldn't open configuration file: %s", filename.c_str()); return; } - XML::Node rootNode = doc.rootNode(); - - if (!rootNode || rootNode.name() != "configuration") + if (rootNode.name() != "configuration") { logger->log("Warning: No configuration file (%s)", filename.c_str()); return; @@ -268,57 +227,387 @@ void Configuration::init(const std::string &filename, bool useResManager) initFromXML(rootNode); } -void ConfigurationObject::writeToXML(XML::Writer &writer) const + +template<typename T> +struct Option +{ + Option(const char *name, const T &value, const T &defaultValue) + : name(name), value(value), defaultValue(defaultValue) + {} + + const char *name; + const T &value; + const T &defaultValue; +}; + +template<typename T> +static void serialize(XML::Writer &writer, const Option<T> &option) +{ + if (option.value == option.defaultValue) + return; + + writer.startElement("option"); + writer.addAttribute("name", option.name); + writer.addAttribute("value", option.value); + writer.endElement(); +} + +static void serialize(XML::Writer &writer, const ItemShortcutEntry &itemShortcut) +{ + writer.startElement("itemshortcut"); + writer.addAttribute("index", itemShortcut.index); + writer.addAttribute("id", itemShortcut.itemId); + writer.endElement(); +} + +static void serialize(XML::Writer &writer, const EmoteShortcutEntry &emoteShortcut) +{ + writer.startElement("emoteshortcut"); + writer.addAttribute("index", emoteShortcut.index); + writer.addAttribute("id", emoteShortcut.emoteId); + writer.endElement(); +} + +static void serialize(XML::Writer &writer, const Outfit &outfit) +{ + writer.startElement("outfit"); + writer.addAttribute("index", outfit.index); + writer.addAttribute("items", outfit.items); + writer.addAttribute("unequip", outfit.unequip); + writer.endElement(); +} + +static void serialize(XML::Writer &writer, const UserColor &color) +{ + if (!color.color.empty()) + writer.addAttribute("color", color.color); + + writer.addAttribute("gradient", color.gradient); + + if (color.delay) + writer.addAttribute("delay", *color.delay); +} + +static void serialize(XML::Writer &writer, const WindowState &state) { - for (auto &[name, value] : mOptions) + if (state.x) writer.addAttribute("x", *state.x); + if (state.y) writer.addAttribute("y", *state.y); + if (state.width) writer.addAttribute("width", *state.width); + if (state.height) writer.addAttribute("height", *state.height); + if (state.visible) writer.addAttribute("visible", *state.visible); + if (state.sticky) writer.addAttribute("sticky", *state.sticky); +} + +static const char *serverTypeToString(ServerType type) +{ + switch (type) { - writer.startElement("option"); - writer.addAttribute("name", name); - writer.addAttribute("value", value); - writer.endElement(); + case ServerType::TMWATHENA: + return "TmwAthena"; + case ServerType::MANASERV: + return "ManaServ"; + default: + return ""; } +} - for (auto &[name, list] : mContainerOptions) - { - writer.startElement("list"); - writer.addAttribute("name", name); +static void serialize(XML::Writer &writer, const ServerInfo &server) +{ + writer.startElement("server"); - // Recurse on all elements - for (auto element : list) - { - writer.startElement(name.c_str()); - element->writeToXML(writer); - writer.endElement(); - } + writer.addAttribute("name", server.name); + writer.addAttribute("type", serverTypeToString(server.type)); + + writer.startElement("connection"); + writer.addAttribute("hostname", server.hostname); + writer.addAttribute("port", server.port); + writer.endElement(); // connection + + if (!server.description.empty()) + { + writer.startElement("description"); + writer.writeText(server.description); + writer.endElement(); + } + if (!server.persistentIp) + { + writer.startElement("persistentIp"); + writer.writeText(server.persistentIp ? "1" : "0"); writer.endElement(); } + + writer.endElement(); // server +} + +template<typename T> +void serdeOptions(T option) +{ + option("OverlayDetail", &Config::overlayDetail); + option("speechBubblecolor", &Config::speechBubblecolor); + option("speechBubbleAlpha", &Config::speechBubbleAlpha); + option("speech", &Config::speech); + option("visiblenames", &Config::visibleNames); + option("showgender", &Config::showGender); + option("showMonstersTakedDamage", &Config::showMonstersTakedDamage); + option("showWarps", &Config::showWarps); + option("particleMaxCount", &Config::particleMaxCount); + option("particleFastPhysics", &Config::particleFastPhysics); + option("particleEmitterSkip", &Config::particleEmitterSkip); + option("particleeffects", &Config::particleEffects); + option("logToStandardOut", &Config::logToStandardOut); + option("opengl", &Config::opengl); + option("vsync", &Config::vsync); + option("windowmode", &Config::windowMode); + option("screenwidth", &Config::screenWidth); + option("screenheight", &Config::screenHeight); + option("scale", &Config::scale); + option("sound", &Config::sound); + option("sfxVolume", &Config::sfxVolume); + option("notificationsVolume", &Config::notificationsVolume); + option("musicVolume", &Config::musicVolume); + option("fpslimit", &Config::fpsLimit); + + option("remember", &Config::remember); + option("username", &Config::username); + option("lastCharacter", &Config::lastCharacter); + option("updatehost", &Config::updatehost); + option("screenshotDirectory", &Config::screenshotDirectory); + option("screenshotDirectorySuffix", &Config::screenshotDirectorySuffix); + option("useScreenshotDirectorySuffix", &Config::useScreenshotDirectorySuffix); + + option("EnableSync", &Config::enableSync); + + option("joystickEnabled", &Config::joystickEnabled); + option("upTolerance", &Config::upTolerance); + option("downTolerance", &Config::downTolerance); + option("leftTolerance", &Config::leftTolerance); + option("rightTolerance", &Config::rightTolerance); + + option("logNpcInGui", &Config::logNpcInGui); + option("download-music", &Config::downloadMusic); + option("guialpha", &Config::guiAlpha); + option("ChatLogLength", &Config::chatLogLength); + option("enableChatLog", &Config::enableChatLog); + option("whispertab", &Config::whisperTab); + option("customcursor", &Config::customCursor); + option("showownname", &Config::showOwnName); + option("showpickupparticle", &Config::showPickupParticle); + option("showpickupchat", &Config::showPickupChat); + option("showMinimap", &Config::showMinimap); + option("fontSize", &Config::fontSize); + option("ReturnToggles", &Config::returnTogglesChat); + option("ScrollLaziness", &Config::scrollLaziness); + option("ScrollRadius", &Config::scrollRadius); + option("ScrollCenterOffsetX", &Config::scrollCenterOffsetX); + option("ScrollCenterOffsetY", &Config::scrollCenterOffsetY); + option("onlineServerList", &Config::onlineServerList); + option("theme", &Config::theme); + option("disableTransparency", &Config::disableTransparency); + + option("persistent-player-list", &Config::persistentPlayerList); + option("player-ignore-strategy", &Config::playerIgnoreStrategy); + option("default-player-permissions", &Config::defaultPlayerPermissions); } -void Configuration::write() +void serialize(XML::Writer &writer, const Config &config) { - // Do not attempt to write to file that cannot be opened for writing - FILE *testFile = fopen(mConfigPath.c_str(), "w"); - if (!testFile) + const Config defaults; + auto serializeOption = [&](const char *name, auto member) { + serialize(writer, Option { name, config.*member, defaults.*member }); + }; + + writer.startElement("configuration"); + + serdeOptions(serializeOption); + + for (const auto &[name, value] : config.unknownOptions) + serialize(writer, Option { name.c_str(), value, std::string() }); + + for (const auto &[action, key] : config.keys) { - logger->log("Configuration::write() couldn't open %s for writing", - mConfigPath.c_str()); - return; + writer.startElement("key"); + writer.addAttribute("action", action); + writer.addAttribute("key", key); + writer.endElement(); } - fclose(testFile); + for (auto &itemShortcut : config.itemShortcuts) + serialize(writer, itemShortcut); + for (auto &emoteShortcut : config.emoteShortcuts) + serialize(writer, emoteShortcut); - XML::Writer writer(mConfigPath); + for (auto &outfit : config.outfits) + serialize(writer, outfit); - if (!writer.isValid()) + for (auto &[type, color] : config.colors) { - logger->log("Configuration::write() error while creating writer"); - return; + writer.startElement("color"); + writer.addAttribute("type", type); + + serialize(writer, color); + + writer.endElement(); } - logger->log("Configuration::write() writing configuration..."); + for (const auto &[name, state] : config.windows) + { + writer.startElement("window"); + writer.addAttribute("name", name); - writer.startElement("configuration"); - writeToXML(writer); + serialize(writer, state); + + writer.endElement(); // window + } + + for (const auto &server : config.servers) + { + if (server.save && server.isValid()) + serialize(writer, server); + } + + for (const auto &[name, relation] : config.players) + { + writer.startElement("player"); + writer.addAttribute("name", name); + writer.addAttribute("relation", static_cast<int>(relation)); + writer.endElement(); + } + + writer.endElement(); // configuration +} + +void deserialize(XML::Node node, ItemShortcutEntry &itemShortcut) +{ + node.attribute("index", itemShortcut.index); + node.attribute("id", itemShortcut.itemId); +} + +void deserialize(XML::Node node, EmoteShortcutEntry &emoteShortcut) +{ + node.attribute("index", emoteShortcut.index); + node.attribute("id", emoteShortcut.emoteId); +} + +void deserialize(XML::Node node, Outfit &outfit) +{ + node.attribute("index", outfit.index); + node.attribute("items", outfit.items); + node.attribute("unequip", outfit.unequip); +} + +void deserialize(XML::Node node, UserColor &color) +{ + node.attribute("color", color.color); + node.attribute("gradient", color.gradient); + node.attribute("delay", color.delay); +} + +void deserialize(XML::Node node, WindowState &state) +{ + node.attribute("x", state.x); + node.attribute("y", state.y); + node.attribute("width", state.width); + node.attribute("height", state.height); + node.attribute("visible", state.visible); + node.attribute("sticky", state.sticky); +} + +void deserialize(XML::Node node, ServerInfo &server) +{ + node.attribute("name", server.name); + + std::string type; + node.attribute("type", type); + server.type = ServerInfo::parseType(type); + + for (auto node : node.children()) { + if (node.name() == "connection") { + node.attribute("hostname", server.hostname); + node.attribute("port", server.port); + } else if (node.name() == "description") { + server.description = node.textContent(); + } else if (node.name() == "persistentIp") { + const std::string value { node.textContent() }; + server.persistentIp = getBoolFromString(value, server.persistentIp); + } + } +} + +void deserialize(XML::Node node, Config &config) +{ + std::map<std::string, std::string> options; + + for (auto node : node.children()) { + if (node.name() == "option") { + std::string name; + if (!node.attribute("name", name)) + continue; + node.attribute("value", options[name]); + } else if (node.name() == "list") { + // Backwards compatibility for old configuration files + for (auto node : node.children()) { + if (node.name() == "player") { + std::string playerName; + PlayerRelation relation = PlayerRelation::NEUTRAL; + + for (auto node : node.children()) { + if (node.name() == "option") { + std::string optionName; + + if (node.attribute("name", optionName)) { + if (optionName == "name") + node.attribute("value", playerName); + else if (optionName == "relation") + node.attribute("value", relation); + } + } + } + + if (!playerName.empty()) + config.players[playerName] = relation; + } + } + } else if (node.name() == "key") { + std::string action; + node.attribute("action", action); + if (!action.empty()) + node.attribute("key", config.keys[action]); + } else if (node.name() == "itemshortcut") { + deserialize(node, config.itemShortcuts.emplace_back()); + } else if (node.name() == "emoteshortcut") { + deserialize(node, config.emoteShortcuts.emplace_back()); + } else if (node.name() == "outfit") { + deserialize(node, config.outfits.emplace_back()); + } else if (node.name() == "color") { + std::string type; + node.attribute("type", type); + deserialize(node, config.colors[type]); + } else if (node.name() == "window") { + std::string name; + node.attribute("name", name); + deserialize(node, config.windows[name]); + } else if (node.name() == "player") { + std::string name; + node.attribute("name", name); + if (!name.empty()) + node.attribute("relation", config.players[name]); + } else if (node.name() == "server") { + deserialize(node, config.servers.emplace_back()); + } + } + + auto deserializeOption = [&](const char *name, auto member) { + auto it = options.find(name); + if (it == options.end()) + return; + + fromString(it->second.data(), config.*member); + options.erase(it); + }; + + serdeOptions(deserializeOption); + + config.unknownOptions = std::move(options); } |