From: Loïc Blot Date: Fri, 27 Jan 2017 06:41:10 +0000 (+0100) Subject: [CSM] implement client side mod loading (#5123) X-Git-Tag: 0.4.16~396 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=92b45b2a189b703fc7cfc8ddbc09a7ad563a13bc;p=oweals%2Fminetest.git [CSM] implement client side mod loading (#5123) * client side mods are located in clientmods/ * move builtin/preview.lua to clientmods/preview/init.lua as a preview mod * refactor ModConfiguration class to work properly with client and server using child objects * move some Server constructor mod load code to ModConfiguration to reduce code duplication between client and server * remove mods.{cpp,h} unused functions * use UNORDERED_SET instead of std::set in some modspec storages --- diff --git a/CMakeLists.txt b/CMakeLists.txt index d2568a9ae..7fe950c81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -152,6 +152,7 @@ endif() install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/builtin" DESTINATION "${SHAREDIR}") install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/client" DESTINATION "${SHAREDIR}") +install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/clientmods" DESTINATION "${SHAREDIR}") install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/games" DESTINATION "${SHAREDIR}" PATTERN ".git*" EXCLUDE) if(BUILD_CLIENT) diff --git a/builtin/client/chatcommands.lua b/builtin/client/chatcommands.lua index b49c222ef..43b4d9a72 100644 --- a/builtin/client/chatcommands.lua +++ b/builtin/client/chatcommands.lua @@ -5,24 +5,24 @@ core.register_on_sending_chat_messages(function(message) if not (message:sub(1,1) == "/") then return false end - + core.display_chat_message("issued command: " .. message) - + local cmd, param = string.match(message, "^/([^ ]+) *(.*)") if not param then param = "" end - + local cmd_def = core.registered_chatcommands[cmd] - + if cmd_def then core.set_last_run_mod(cmd_def.mod_origin) - local success, message = cmd_def.func(param) + local _, message = cmd_def.func(param) if message then core.display_chat_message(message) end return true end - + return false -end) \ No newline at end of file +end) diff --git a/builtin/client/init.lua b/builtin/client/init.lua index b204ee5e6..592274540 100644 --- a/builtin/client/init.lua +++ b/builtin/client/init.lua @@ -7,7 +7,6 @@ dofile(clientpath .. "register.lua") dofile(commonpath .. "after.lua") dofile(commonpath .. "chatcommands.lua") dofile(clientpath .. "chatcommands.lua") -dofile(clientpath .. "preview.lua") core.register_on_death(function() core.display_chat_message("You died.") diff --git a/builtin/client/preview.lua b/builtin/client/preview.lua deleted file mode 100644 index 4c01d665f..000000000 --- a/builtin/client/preview.lua +++ /dev/null @@ -1,42 +0,0 @@ --- This is an example function to ensure it's working properly, should be removed before merge -core.register_on_shutdown(function() - print("[PREVIEW] shutdown client") -end) - --- This is an example function to ensure it's working properly, should be removed before merge -core.register_on_receiving_chat_messages(function(message) - print("[PREVIEW] Received message " .. message) - return false -end) - --- This is an example function to ensure it's working properly, should be removed before merge -core.register_on_sending_chat_messages(function(message) - print("[PREVIEW] Sending message " .. message) - return false -end) - --- This is an example function to ensure it's working properly, should be removed before merge -core.register_on_hp_modification(function(hp) - print("[PREVIEW] HP modified " .. hp) -end) - --- This is an example function to ensure it's working properly, should be removed before merge -core.register_on_damage_taken(function(hp) - print("[PREVIEW] Damage taken " .. hp) -end) - --- This is an example function to ensure it's working properly, should be removed before merge -core.register_globalstep(function(dtime) - -- print("[PREVIEW] globalstep " .. dtime) -end) - --- This is an example function to ensure it's working properly, should be removed before merge -core.register_chatcommand("dump", { - func = function(param) - return true, dump(_G) - end, -}) - -core.after(2, function() - print("After 2") -end) diff --git a/clientmods/preview/init.lua b/clientmods/preview/init.lua new file mode 100644 index 000000000..4c01d665f --- /dev/null +++ b/clientmods/preview/init.lua @@ -0,0 +1,42 @@ +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_on_shutdown(function() + print("[PREVIEW] shutdown client") +end) + +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_on_receiving_chat_messages(function(message) + print("[PREVIEW] Received message " .. message) + return false +end) + +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_on_sending_chat_messages(function(message) + print("[PREVIEW] Sending message " .. message) + return false +end) + +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_on_hp_modification(function(hp) + print("[PREVIEW] HP modified " .. hp) +end) + +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_on_damage_taken(function(hp) + print("[PREVIEW] Damage taken " .. hp) +end) + +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_globalstep(function(dtime) + -- print("[PREVIEW] globalstep " .. dtime) +end) + +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_chatcommand("dump", { + func = function(param) + return true, dump(_G) + end, +}) + +core.after(2, function() + print("After 2") +end) diff --git a/src/client.cpp b/src/client.cpp index 3b8074252..4bb63fef1 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -268,14 +268,50 @@ Client::Client( void Client::initMods() { - std::string script_path = getBuiltinLuaPath() + DIR_DELIM "init.lua"; + m_script->loadMod(getBuiltinLuaPath() + DIR_DELIM "init.lua", BUILTIN_MOD_NAME); + + ClientModConfiguration modconf(getClientModsLuaPath()); + std::vector mods = modconf.getMods(); + std::vector unsatisfied_mods = modconf.getUnsatisfiedMods(); + // complain about mods with unsatisfied dependencies + if (!modconf.isConsistent()) { + modconf.printUnsatisfiedModsError(); + } + + // Print mods + infostream << "Client Loading mods: "; + for (std::vector::const_iterator i = mods.begin(); + i != mods.end(); ++i) { + infostream << (*i).name << " "; + } + + infostream << std::endl; + // Load and run "mod" scripts + for (std::vector::const_iterator it = mods.begin(); + it != mods.end(); ++it) { + const ModSpec &mod = *it; + if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) { + throw ModError("Error loading mod \"" + mod.name + + "\": Mod name does not follow naming conventions: " + "Only chararacters [a-z0-9_] are allowed."); + } + std::string script_path = mod.path + DIR_DELIM + "init.lua"; + infostream << " [" << padStringRight(mod.name, 12) << "] [\"" + << script_path << "\"]" << std::endl; + m_script->loadMod(script_path, mod.name); + } +} - m_script->loadMod(script_path, BUILTIN_MOD_NAME); +const std::string &Client::getBuiltinLuaPath() +{ + static const std::string builtin_dir = porting::path_share + DIR_DELIM + "builtin"; + return builtin_dir; } -const std::string Client::getBuiltinLuaPath() +const std::string &Client::getClientModsLuaPath() { - return porting::path_share + DIR_DELIM + "builtin"; + static const std::string clientmods_dir = porting::path_share + DIR_DELIM + "clientmods"; + return clientmods_dir; } const std::vector& Client::getMods() const diff --git a/src/client.h b/src/client.h index d170f9a07..9b7130268 100644 --- a/src/client.h +++ b/src/client.h @@ -433,7 +433,8 @@ public: ClientEnvironment& getEnv() { return m_env; } ITextureSource *tsrc() { return getTextureSource(); } ISoundManager *sound() { return getSoundManager(); } - static const std::string getBuiltinLuaPath(); + static const std::string &getBuiltinLuaPath(); + static const std::string &getClientModsLuaPath(); virtual const std::vector &getMods() const; virtual const ModSpec* getModSpec(const std::string &modname) const; diff --git a/src/mods.cpp b/src/mods.cpp index bae9a42d3..5a7dc6dca 100644 --- a/src/mods.cpp +++ b/src/mods.cpp @@ -25,6 +25,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "subgame.h" #include "settings.h" #include "convert_json.h" +#include "exceptions.h" +#include "porting.h" static bool parseDependsLine(std::istream &is, std::string &dep, std::set &symbols) @@ -107,28 +109,6 @@ std::map getModsInPath(std::string path, bool part_of_modp return result; } -std::map flattenModTree(std::map mods) -{ - std::map result; - for(std::map::iterator it = mods.begin(); - it != mods.end(); ++it) - { - ModSpec mod = (*it).second; - if(mod.is_modpack) - { - std::map content = - flattenModTree(mod.modpack_content); - result.insert(content.begin(),content.end()); - result.insert(std::make_pair(mod.name,mod)); - } - else //not a modpack - { - result.insert(std::make_pair(mod.name,mod)); - } - } - return result; -} - std::vector flattenMods(std::map mods) { std::vector result; @@ -151,78 +131,32 @@ std::vector flattenMods(std::map mods) return result; } -ModConfiguration::ModConfiguration(std::string worldpath) +ModConfiguration::ModConfiguration(const std::string &worldpath): + m_unsatisfied_mods(), + m_sorted_mods(), + m_name_conflicts() { - SubgameSpec gamespec = findWorldSubgame(worldpath); - - // Add all game mods and all world mods - addModsInPath(gamespec.gamemods_path); - addModsInPath(worldpath + DIR_DELIM + "worldmods"); - - // check world.mt file for mods explicitely declared to be - // loaded or not by a load_mod_ = ... line. - std::string worldmt = worldpath+DIR_DELIM+"world.mt"; - Settings worldmt_settings; - worldmt_settings.readConfigFile(worldmt.c_str()); - std::vector names = worldmt_settings.getNames(); - std::set include_mod_names; - for(std::vector::iterator it = names.begin(); - it != names.end(); ++it) - { - std::string name = *it; - // for backwards compatibility: exclude only mods which are - // explicitely excluded. if mod is not mentioned at all, it is - // enabled. So by default, all installed mods are enabled. - if (name.compare(0,9,"load_mod_") == 0 && - worldmt_settings.getBool(name)) - { - include_mod_names.insert(name.substr(9)); - } - } - - // Collect all mods that are also in include_mod_names - std::vector addon_mods; - for(std::set::const_iterator it_path = gamespec.addon_mods_paths.begin(); - it_path != gamespec.addon_mods_paths.end(); ++it_path) - { - std::vector addon_mods_in_path = flattenMods(getModsInPath(*it_path)); - for(std::vector::iterator it = addon_mods_in_path.begin(); - it != addon_mods_in_path.end(); ++it) - { - ModSpec& mod = *it; - if(include_mod_names.count(mod.name) != 0) - addon_mods.push_back(mod); - else - worldmt_settings.setBool("load_mod_" + mod.name, false); - } - } - worldmt_settings.updateConfigFile(worldmt.c_str()); - - addMods(addon_mods); +} - // report on name conflicts - if(!m_name_conflicts.empty()){ - std::string s = "Unresolved name conflicts for mods "; - for(std::set::const_iterator it = m_name_conflicts.begin(); - it != m_name_conflicts.end(); ++it) - { - if(it != m_name_conflicts.begin()) s += ", "; - s += std::string("\"") + (*it) + "\""; - } - s += "."; - throw ModError(s); +void ModConfiguration::printUnsatisfiedModsError() const +{ + for (std::vector::const_iterator it = m_unsatisfied_mods.begin(); + it != m_unsatisfied_mods.end(); ++it) { + ModSpec mod = *it; + errorstream << "mod \"" << mod.name << "\" has unsatisfied dependencies: "; + for (UNORDERED_SET::iterator dep_it = mod.unsatisfied_depends.begin(); + dep_it != mod.unsatisfied_depends.end(); ++dep_it) + errorstream << " \"" << *dep_it << "\""; + errorstream << std::endl; } - - // get the mods in order - resolveDependencies(); } -void ModConfiguration::addModsInPath(std::string path) +void ModConfiguration::addModsInPath(const std::string &path) { addMods(flattenMods(getModsInPath(path))); } -void ModConfiguration::addMods(std::vector new_mods) +void ModConfiguration::addMods(const std::vector &new_mods) { // Maintain a map of all existing m_unsatisfied_mods. // Keys are mod names and values are indices into m_unsatisfied_mods. @@ -240,8 +174,8 @@ void ModConfiguration::addMods(std::vector new_mods) std::set seen_this_iteration; - for(std::vector::const_iterator it = new_mods.begin(); - it != new_mods.end(); ++it){ + for (std::vector::const_iterator it = new_mods.begin(); + it != new_mods.end(); ++it) { const ModSpec &mod = *it; if(mod.part_of_modpack != (bool)want_from_modpack) continue; @@ -280,6 +214,24 @@ void ModConfiguration::addMods(std::vector new_mods) } } +void ModConfiguration::checkConflictsAndDeps() +{ + // report on name conflicts + if (!m_name_conflicts.empty()) { + std::string s = "Unresolved name conflicts for mods "; + for (UNORDERED_SET::const_iterator it = m_name_conflicts.begin(); + it != m_name_conflicts.end(); ++it) { + if (it != m_name_conflicts.begin()) s += ", "; + s += std::string("\"") + (*it) + "\""; + } + s += "."; + throw ModError(s); + } + + // get the mods in order + resolveDependencies(); +} + void ModConfiguration::resolveDependencies() { // Step 1: Compile a list of the mod names we're working with @@ -293,19 +245,19 @@ void ModConfiguration::resolveDependencies() // of each mod, split mods into satisfied and unsatisfied std::list satisfied; std::list unsatisfied; - for(std::vector::iterator it = m_unsatisfied_mods.begin(); - it != m_unsatisfied_mods.end(); ++it){ + for (std::vector::iterator it = m_unsatisfied_mods.begin(); + it != m_unsatisfied_mods.end(); ++it) { ModSpec mod = *it; mod.unsatisfied_depends = mod.depends; // check which optional dependencies actually exist - for(std::set::iterator it_optdep = mod.optdepends.begin(); - it_optdep != mod.optdepends.end(); ++it_optdep){ + for (UNORDERED_SET::iterator it_optdep = mod.optdepends.begin(); + it_optdep != mod.optdepends.end(); ++it_optdep) { std::string optdep = *it_optdep; - if(modnames.count(optdep) != 0) + if (modnames.count(optdep) != 0) mod.unsatisfied_depends.insert(optdep); } // if a mod has no depends it is initially satisfied - if(mod.unsatisfied_depends.empty()) + if (mod.unsatisfied_depends.empty()) satisfied.push_back(mod); else unsatisfied.push_back(mod); @@ -335,6 +287,65 @@ void ModConfiguration::resolveDependencies() m_unsatisfied_mods.assign(unsatisfied.begin(), unsatisfied.end()); } +ServerModConfiguration::ServerModConfiguration(const std::string &worldpath): + ModConfiguration(worldpath) +{ + SubgameSpec gamespec = findWorldSubgame(worldpath); + + // Add all game mods and all world mods + addModsInPath(gamespec.gamemods_path); + addModsInPath(worldpath + DIR_DELIM + "worldmods"); + + // check world.mt file for mods explicitely declared to be + // loaded or not by a load_mod_ = ... line. + std::string worldmt = worldpath+DIR_DELIM+"world.mt"; + Settings worldmt_settings; + worldmt_settings.readConfigFile(worldmt.c_str()); + std::vector names = worldmt_settings.getNames(); + std::set include_mod_names; + for (std::vector::const_iterator it = names.begin(); + it != names.end(); ++it) { + std::string name = *it; + // for backwards compatibility: exclude only mods which are + // explicitely excluded. if mod is not mentioned at all, it is + // enabled. So by default, all installed mods are enabled. + if (name.compare(0,9,"load_mod_") == 0 && + worldmt_settings.getBool(name)) { + include_mod_names.insert(name.substr(9)); + } + } + + // Collect all mods that are also in include_mod_names + std::vector addon_mods; + for (std::set::const_iterator it_path = gamespec.addon_mods_paths.begin(); + it_path != gamespec.addon_mods_paths.end(); ++it_path) { + std::vector addon_mods_in_path = flattenMods(getModsInPath(*it_path)); + for (std::vector::const_iterator it = addon_mods_in_path.begin(); + it != addon_mods_in_path.end(); ++it) { + const ModSpec& mod = *it; + if (include_mod_names.count(mod.name) != 0) + addon_mods.push_back(mod); + else + worldmt_settings.setBool("load_mod_" + mod.name, false); + } + } + worldmt_settings.updateConfigFile(worldmt.c_str()); + + addMods(addon_mods); + + checkConflictsAndDeps(); +} + +#ifndef SERVER +ClientModConfiguration::ClientModConfiguration(const std::string &path): + ModConfiguration(path) +{ + addModsInPath(path); + addModsInPath(porting::path_user + DIR_DELIM + "clientmods"); + checkConflictsAndDeps(); +} +#endif + #if USE_CURL Json::Value getModstoreUrl(std::string url) { diff --git a/src/mods.h b/src/mods.h index 61af5e5d1..c9bd51d99 100644 --- a/src/mods.h +++ b/src/mods.h @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include "util/cpp11_container.h" #include "config.h" #include "metadata.h" @@ -37,9 +38,9 @@ struct ModSpec std::string name; std::string path; //if normal mod: - std::set depends; - std::set optdepends; - std::set unsatisfied_depends; + UNORDERED_SET depends; + UNORDERED_SET optdepends; + UNORDERED_SET unsatisfied_depends; bool part_of_modpack; bool is_modpack; @@ -62,12 +63,6 @@ void parseModContents(ModSpec &mod); std::map getModsInPath(std::string path, bool part_of_modpack = false); -// If failed, returned modspec has name=="" -ModSpec findCommonMod(const std::string &modname); - -// expands modpack contents, but does not replace them. -std::map flattenModTree(std::map mods); - // replaces modpack Modspecs with their content std::vector flattenMods(std::map mods); @@ -77,17 +72,8 @@ std::vector flattenMods(std::map mods); class ModConfiguration { public: - ModConfiguration(): - m_unsatisfied_mods(), - m_sorted_mods(), - m_name_conflicts() - {} - - - ModConfiguration(std::string worldpath); - // checks if all dependencies are fullfilled. - bool isConsistent() + bool isConsistent() const { return m_unsatisfied_mods.empty(); } @@ -97,19 +83,24 @@ public: return m_sorted_mods; } - std::vector getUnsatisfiedMods() + const std::vector &getUnsatisfiedMods() const { return m_unsatisfied_mods; } -private: + void printUnsatisfiedModsError() const; + +protected: + ModConfiguration(const std::string &worldpath); // adds all mods in the given path. used for games, modpacks // and world-specific mods (worldmods-folders) - void addModsInPath(std::string path); + void addModsInPath(const std::string &path); // adds all mods in the set. - void addMods(std::vector new_mods); + void addMods(const std::vector &new_mods); + void checkConflictsAndDeps(); +private: // move mods from m_unsatisfied_mods to m_sorted_mods // in an order that satisfies dependencies void resolveDependencies(); @@ -132,10 +123,28 @@ private: // 1. game mod in modpack; 2. game mod; // 3. world mod in modpack; 4. world mod; // 5. addon mod in modpack; 6. addon mod. - std::set m_name_conflicts; + UNORDERED_SET m_name_conflicts; + + // Deleted default constructor + ModConfiguration() {} }; +class ServerModConfiguration: public ModConfiguration +{ +public: + ServerModConfiguration(const std::string &worldpath); + +}; + +#ifndef SERVER +class ClientModConfiguration: public ModConfiguration +{ +public: + ClientModConfiguration(const std::string &path); +}; +#endif + #if USE_CURL Json::Value getModstoreUrl(std::string url); #else diff --git a/src/server.cpp b/src/server.cpp index 3adbf40cc..dd6c9a418 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -218,20 +218,12 @@ Server::Server( std::string ban_path = m_path_world + DIR_DELIM "ipban.txt"; m_banmanager = new BanManager(ban_path); - ModConfiguration modconf(m_path_world); + ServerModConfiguration modconf(m_path_world); m_mods = modconf.getMods(); std::vector unsatisfied_mods = modconf.getUnsatisfiedMods(); // complain about mods with unsatisfied dependencies - if(!modconf.isConsistent()) { - for(std::vector::iterator it = unsatisfied_mods.begin(); - it != unsatisfied_mods.end(); ++it) { - ModSpec mod = *it; - errorstream << "mod \"" << mod.name << "\" has unsatisfied dependencies: "; - for(std::set::iterator dep_it = mod.unsatisfied_depends.begin(); - dep_it != mod.unsatisfied_depends.end(); ++dep_it) - errorstream << " \"" << *dep_it << "\""; - errorstream << std::endl; - } + if (!modconf.isConsistent()) { + modconf.printUnsatisfiedModsError(); } Settings worldmt_settings; @@ -271,20 +263,17 @@ Server::Server( m_script = new ServerScripting(this); - std::string script_path = getBuiltinLuaPath() + DIR_DELIM "init.lua"; - - m_script->loadMod(script_path, BUILTIN_MOD_NAME); + m_script->loadMod(getBuiltinLuaPath() + DIR_DELIM "init.lua", BUILTIN_MOD_NAME); // Print mods infostream << "Server: Loading mods: "; - for(std::vector::iterator i = m_mods.begin(); + for (std::vector::const_iterator i = m_mods.begin(); i != m_mods.end(); ++i) { - const ModSpec &mod = *i; - infostream << mod.name << " "; + infostream << (*i).name << " "; } infostream << std::endl; // Load and run "mod" scripts - for (std::vector::iterator it = m_mods.begin(); + for (std::vector::const_iterator it = m_mods.begin(); it != m_mods.end(); ++it) { const ModSpec &mod = *it; if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) {