diff --git a/builtin/mainmenu/dlg_create_world.lua b/builtin/mainmenu/dlg_create_world.lua index 27fe68050..39d6f3c42 100644 --- a/builtin/mainmenu/dlg_create_world.lua +++ b/builtin/mainmenu/dlg_create_world.lua @@ -80,8 +80,17 @@ local mgv6_biomes = { local function create_world_formspec(dialogdata) - local current_mg = dialogdata.mg + local current_mapgen = dialogdata.mg local mapgens = core.get_mapgen_names() + local lua_mapgens = core.get_lua_mapgen_descriptions() + for k, v in pairs(lua_mapgens) do + mapgens[#mapgens+1] = k + end + + local current_mapgen_internal = dialogdata.mg + if lua_mapgens[current_mapgen_internal] then + current_mapgen_internal = "singlenode" + end local flags = dialogdata.flags @@ -137,7 +146,7 @@ local function create_world_formspec(dialogdata) if not first_mg then first_mg = v end - if current_mg == v then + if current_mapgen == v then selindex = i end i = i + 1 @@ -145,7 +154,7 @@ local function create_world_formspec(dialogdata) end if not selindex then selindex = 1 - current_mg = first_mg + current_mapgen = first_mg end mglist = mglist:sub(1, -2) end @@ -254,7 +263,7 @@ local function create_world_formspec(dialogdata) local str_flags, str_spflags local label_flags, label_spflags = "", "" y = y + 0.3 - str_flags, y = mg_main_flags(current_mg, y) + str_flags, y = mg_main_flags(current_mapgen_internal, y) if str_flags ~= "" then label_flags = "label[0,"..y_start..";" .. fgettext("Mapgen flags") .. "]" y_start = y + 0.4 @@ -262,7 +271,7 @@ local function create_world_formspec(dialogdata) y_start = 0.0 end y = y_start + 0.3 - str_spflags = mg_specific_flags(current_mg, y) + str_spflags = mg_specific_flags(current_mapgen_internal, y) if str_spflags ~= "" then label_spflags = "label[0,"..y_start..";" .. fgettext("Mapgen-specific flags") .. "]" end @@ -370,11 +379,20 @@ local function create_world_buttonhandler(this, fields) if message == nil then this.data.seed = fields["te_seed"] or "" this.data.mg = fields["dd_mapgen"] + local mapgen_internal = this.data.mg + local mapgen = nil + + local lua_mapgens = core.get_lua_mapgen_descriptions() + if lua_mapgens[this.data.mg] then + mapgen_internal = "singlenode" + mapgen = this.data.mg + end -- actual names as used by engine local settings = { fixed_map_seed = this.data.seed, - mg_name = this.data.mg, + mg_name = mapgen_internal, + lua_mapgen = mapgen, mg_flags = table_to_flags(this.data.flags.main), mgv5_spflags = table_to_flags(this.data.flags.v5), mgv6_spflags = table_to_flags(this.data.flags.v6), diff --git a/doc/lua_api.md b/doc/lua_api.md index ca3a5fee7..20f18366e 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -403,6 +403,40 @@ Any mod can redefine `experimental:tnt` by using the name when registering it. For this to work correctly, that mod must have `experimental` as a dependency. +Lua-defined Mapgens +==== + +Except what is refrenced here, lua-defined mapgens are identical to mods. + +mapgen load path +------------- + +Paths are relative to the directories listed in the [Paths] section above. +* `mapgens/` + +Mapgen directory structure +----------------------- + + mapgens + ├── mapgenname + │   ├── mapgen.conf + │   ├── init.lua + │   └── + └── another + +### mapgen.conf + +A `Settings` file that provides meta information about the mapgen. + +* `name`: The mapgen name. Allows Luanti to determine the mapgen name even if the + folder is wrongly named. +* `title`: A human-readable title to address the mapgen. See [Translating content meta](#translating-content-meta). +* `description`: Description of mapgen. See [Translating content meta](#translating-content-meta). +* `author`: The author's ContentDB username. +* `release`: Ignore this: Should only ever be set by ContentDB, as it is an + internal ID used to track versions. +* `textdomain`: Textdomain used to translate title and description. Defaults to mapgen. + See [Translating content meta](#translating-content-meta). diff --git a/doc/menu_lua_api.md b/doc/menu_lua_api.md index 56b51f3b9..ead7695b4 100644 --- a/doc/menu_lua_api.md +++ b/doc/menu_lua_api.md @@ -109,6 +109,8 @@ of manually putting one, as different OSs use different delimiters. E.g. * `handle:stop()` or `core.sound_stop(handle)` * `core.get_mapgen_names([include_hidden=false])` -> table of map generator algorithms registered in the core (possible in async calls) +* `core.get_lua_mapgen_descriptions()` -> map of `[mapgen_name] = mapgen_description` as listed + in `mapgen.conf`. * `core.get_cache_path()` -> path of cache * `core.get_temp_path([param])` (possible in async calls) * `param`=true: returns path to a newly created temporary file diff --git a/mapgens/mapgens_here.txt b/mapgens/mapgens_here.txt new file mode 100644 index 000000000..cac417e4b --- /dev/null +++ b/mapgens/mapgens_here.txt @@ -0,0 +1 @@ +Put lua mapgens in this directory. diff --git a/src/content/mod_configuration.cpp b/src/content/mod_configuration.cpp index bcbdd89ea..054952547 100644 --- a/src/content/mod_configuration.cpp +++ b/src/content/mod_configuration.cpp @@ -112,6 +112,39 @@ void ModConfiguration::addGameMods(const SubgameSpec &gamespec) m_last_mod = gamespec.last_mod; } +void ModConfiguration::addMapgenFromConfig( + const std::string &settings_path, + const std::unordered_map &mapgenPaths) +{ + Settings conf; + conf.readConfigFile(settings_path.c_str()); + + if (!conf.exists("mapgen")) + return; + + const std::string mapgen = conf.get("mapgen"); + + // List of enabled mapgens + std::vector mapgen_mod; + + /* + * Iterate through all installed mapgens + * + * If the mod is enabled, add it to `mapgen_mod` and break + */ + for (const auto &mapgenPath : mapgenPaths) { + std::vector addon_mods_in_path = flattenMods(getModsInPath(mapgenPath.second, mapgenPath.first)); + for (const auto &mod : addon_mods_in_path) { + if (mod.name == mapgen) { + mapgen_mod.push_back(mod); + break; + } + } + } + + addMods(mapgen_mod); +} + void ModConfiguration::addModsFromConfig( const std::string &settings_path, const std::unordered_map &modPaths) diff --git a/src/content/mod_configuration.h b/src/content/mod_configuration.h index 17436ef9a..4c7cd8cae 100644 --- a/src/content/mod_configuration.h +++ b/src/content/mod_configuration.h @@ -66,6 +66,15 @@ public: void addModsFromConfig(const std::string &settings_path, const std::unordered_map &modPaths); + /** + * Adds mods specified by a world.mt config + * + * @param settings_path Path to world.mt + * @param mapgenPaths Map from virtual name to mapgen path + */ + void addMapgenFromConfig(const std::string &settings_path, + const std::unordered_map &mapgenPaths); + /** * Call this function once all mods have been added */ diff --git a/src/content/mods.cpp b/src/content/mods.cpp index 333f1d24d..386356d6f 100644 --- a/src/content/mods.cpp +++ b/src/content/mods.cpp @@ -76,7 +76,12 @@ bool parseModContents(ModSpec &spec) Settings info; - info.readConfigFile((spec.path + DIR_DELIM + "mod.conf").c_str()); + if (fs::IsFile(spec.path + DIR_DELIM + "mapgen.conf")) { + info.readConfigFile((spec.path + DIR_DELIM + "mapgen.conf").c_str()); + spec.is_mapgen = true; + } + else + info.readConfigFile((spec.path + DIR_DELIM + "mod.conf").c_str()); if (info.exists("name")) spec.name = info.get("name"); @@ -89,50 +94,52 @@ bool parseModContents(ModSpec &spec) if (info.exists("release")) spec.release = info.getS32("release"); - // Attempt to load dependencies from mod.conf - bool mod_conf_has_depends = false; - if (info.exists("depends")) { - mod_conf_has_depends = true; - std::string dep = info.get("depends"); - dep.erase(std::remove_if(dep.begin(), dep.end(), - static_cast(&std::isspace)), dep.end()); - for (const auto &dependency : str_split(dep, ',')) { - spec.depends.insert(dependency); - } - } - - if (info.exists("optional_depends")) { - mod_conf_has_depends = true; - std::string dep = info.get("optional_depends"); - dep.erase(std::remove_if(dep.begin(), dep.end(), - static_cast(&std::isspace)), dep.end()); - for (const auto &dependency : str_split(dep, ',')) { - spec.optdepends.insert(dependency); - } - } - - // Fallback to depends.txt - if (!mod_conf_has_depends) { - std::vector dependencies; - - std::ifstream is((spec.path + DIR_DELIM + "depends.txt").c_str()); - - if (is.good()) - spec.deprecation_msgs.push_back("depends.txt is deprecated, please use mod.conf instead."); - - while (is.good()) { - std::string dep; - std::getline(is, dep); - dependencies.push_back(dep); + // Attempt to load dependencies from mod.conf if not mapgen + if (!spec.is_mapgen) { + bool mod_conf_has_depends = false; + if (info.exists("depends")) { + mod_conf_has_depends = true; + std::string dep = info.get("depends"); + dep.erase(std::remove_if(dep.begin(), dep.end(), + static_cast(&std::isspace)), dep.end()); + for (const auto &dependency : str_split(dep, ',')) { + spec.depends.insert(dependency); + } } - for (auto &dependency : dependencies) { - std::unordered_set symbols; - if (parseDependsString(dependency, symbols)) { - if (symbols.count('?') != 0) { - spec.optdepends.insert(dependency); - } else { - spec.depends.insert(dependency); + if (info.exists("optional_depends")) { + mod_conf_has_depends = true; + std::string dep = info.get("optional_depends"); + dep.erase(std::remove_if(dep.begin(), dep.end(), + static_cast(&std::isspace)), dep.end()); + for (const auto &dependency : str_split(dep, ',')) { + spec.optdepends.insert(dependency); + } + } + + // Fallback to depends.txt + if (!mod_conf_has_depends) { + std::vector dependencies; + + std::ifstream is((spec.path + DIR_DELIM + "depends.txt").c_str()); + + if (is.good()) + spec.deprecation_msgs.push_back("depends.txt is deprecated, please use mod.conf instead."); + + while (is.good()) { + std::string dep; + std::getline(is, dep); + dependencies.push_back(dep); + } + + for (auto &dependency : dependencies) { + std::unordered_set symbols; + if (parseDependsString(dependency, symbols)) { + if (symbols.count('?') != 0) { + spec.optdepends.insert(dependency); + } else { + spec.depends.insert(dependency); + } } } } diff --git a/src/content/mods.h b/src/content/mods.h index fc98d9298..b3c3894f9 100644 --- a/src/content/mods.h +++ b/src/content/mods.h @@ -37,6 +37,9 @@ struct ModSpec bool part_of_modpack = false; bool is_modpack = false; + // lua-defined mapgen + bool is_mapgen = false; + /** * A constructed canonical path to represent this mod's location. * This intended to be used as an identifier for a modpath that tolerates file movement, diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp index e9e3a72d8..0610d16b0 100644 --- a/src/content/subgames.cpp +++ b/src/content/subgames.cpp @@ -81,7 +81,8 @@ std::string getSubgamePathEnv() static SubgameSpec getSubgameSpec(const std::string &game_id, const std::string &game_path, - const std::unordered_map &mods_paths) + const std::unordered_map &mods_paths, + const std::unordered_map &mapgen_paths) { const auto gamemods_path = game_path + DIR_DELIM + "mods"; // Get meta @@ -113,8 +114,12 @@ static SubgameSpec getSubgameSpec(const std::string &game_id, if (conf.exists("last_mod")) last_mod = conf.get("last_mod"); + std::string mapgen; + if (conf.exists("mapgen")) + mapgen = conf.get("mapgen"); + SubgameSpec spec(game_id, game_path, gamemods_path, mods_paths, game_title, - game_author, game_release, first_mod, last_mod); + game_author, game_release, first_mod, last_mod, mapgen, mapgen_paths); if (conf.exists("name") && !conf.exists("title")) spec.deprecation_msgs.push_back("\"name\" setting in game.conf is deprecated, please use \"title\" instead"); @@ -167,6 +172,14 @@ SubgameSpec findSubgame(const std::string &id) // Find mod directories std::unordered_map mods_paths; + std::unordered_map mapgen_paths; + + // Find mapgen directories + mapgen_paths["mapgens"] = user + DIR_DELIM + "mapgens"; + if (!user_game && user != share) + mapgen_paths["mapgens_share"] = share + DIR_DELIM + "mapgens"; + + // Find mod directories mods_paths["mods"] = user + DIR_DELIM + "mods"; if (!user_game && user != share) mods_paths["share"] = share + DIR_DELIM + "mods"; @@ -175,7 +188,7 @@ SubgameSpec findSubgame(const std::string &id) mods_paths[fs::AbsolutePath(mod_path)] = mod_path; } - return getSubgameSpec(id, game_path, mods_paths); + return getSubgameSpec(id, game_path, mods_paths, mapgen_paths); } SubgameSpec findWorldSubgame(const std::string &world_path) @@ -184,7 +197,7 @@ SubgameSpec findWorldSubgame(const std::string &world_path) // See if world contains an embedded game; if so, use it. std::string world_gamepath = world_path + DIR_DELIM + "game"; if (fs::PathExists(world_gamepath)) - return getSubgameSpec(world_gameid, world_gamepath, {}); + return getSubgameSpec(world_gameid, world_gamepath, {}, {}); return findSubgame(world_gameid); } @@ -330,7 +343,7 @@ std::vector getAvailableWorlds() } void loadGameConfAndInitWorld(const std::string &path, const std::string &name, - const SubgameSpec &gamespec, bool create_world) + const SubgameSpec &gamespec, bool create_world, const std::string &lua_mapgen) { std::string final_path = path; @@ -380,6 +393,9 @@ void loadGameConfAndInitWorld(const std::string &path, const std::string &name, } conf.set("backend", backend); + if (lua_mapgen != "") + conf.set("mapgen", lua_mapgen); + conf.set("player_backend", "sqlite3"); conf.set("auth_backend", "sqlite3"); conf.set("mod_storage_backend", "sqlite3"); diff --git a/src/content/subgames.h b/src/content/subgames.h index 1884ce578..85209933e 100644 --- a/src/content/subgames.h +++ b/src/content/subgames.h @@ -19,6 +19,7 @@ struct SubgameSpec int release; std::string first_mod; // "" <=> no mod std::string last_mod; // "" <=> no mod + std::string lua_mapgen; // "" <=> use internal std::string path; std::string gamemods_path; @@ -26,6 +27,7 @@ struct SubgameSpec * Map from virtual path to mods path */ std::unordered_map addon_mods_paths; + std::unordered_map mapgen_paths; // For logging purposes std::vector deprecation_msgs; @@ -36,14 +38,18 @@ struct SubgameSpec const std::string &title = "", const std::string &author = "", int release = 0, const std::string &first_mod = "", - const std::string &last_mod = "") : + const std::string &last_mod = "", + const std::string &lua_mapgen = "", + const std::unordered_map &mapgen_paths = {}) : id(id), title(title), author(author), release(release), first_mod(first_mod), last_mod(last_mod), + lua_mapgen(lua_mapgen), path(path), gamemods_path(gamemods_path), - addon_mods_paths(addon_mods_paths) + addon_mods_paths(addon_mods_paths), + mapgen_paths(mapgen_paths) { } @@ -88,4 +94,4 @@ std::vector getAvailableWorlds(); // loads the subgame's config and creates world directory // and world.mt if they don't exist void loadGameConfAndInitWorld(const std::string &path, const std::string &name, - const SubgameSpec &gamespec, bool create_world); + const SubgameSpec &gamespec, bool create_world, const std::string &lua_mapgen); diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 4b878ee1c..61798038a 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -593,7 +593,10 @@ int ModApiMainMenu::l_create_world(lua_State *L) // Create world if it doesn't exist try { - loadGameConfAndInitWorld(path, name, *game_it, true); + std::string lua_mapgen = ""; + if (use_settings.find("lua_mapgen") != use_settings.end()) + lua_mapgen = use_settings["lua_mapgen"]; + loadGameConfAndInitWorld(path, name, *game_it, true, lua_mapgen); lua_pushnil(L); } catch (const BaseException &e) { auto err = std::string("Failed to initialize world: ") + e.what(); @@ -660,6 +663,21 @@ int ModApiMainMenu::l_get_mapgen_names(lua_State *L) return 1; } +/******************************************************************************/ +int ModApiMainMenu::l_get_lua_mapgen_descriptions(lua_State *L) +{ + std::vector addon_mods_in_path = flattenMods(getModsInPath(porting::path_share + DIR_DELIM + "mapgens" + DIR_DELIM, "mapgen/")); + + lua_newtable(L); + for (const auto &mod : addon_mods_in_path) { + if (mod.is_mapgen) { + lua_pushstring(L, mod.desc.c_str()); + lua_setfield(L, -2, mod.name.c_str()); + } + } + + return 1; +} /******************************************************************************/ int ModApiMainMenu::l_get_user_path(lua_State *L) @@ -1059,6 +1077,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(set_background); API_FCT(set_topleft_text); API_FCT(get_mapgen_names); + API_FCT(get_lua_mapgen_descriptions); API_FCT(get_user_path); API_FCT(get_modpath); API_FCT(get_modpaths); @@ -1100,6 +1119,7 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top) API_FCT(get_worlds); API_FCT(get_games); API_FCT(get_mapgen_names); + API_FCT(get_lua_mapgen_descriptions); API_FCT(get_user_path); API_FCT(get_modpath); API_FCT(get_modpaths); diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index fc2d90af8..b6e27f0b2 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -51,6 +51,8 @@ private: static int l_get_mapgen_names(lua_State *L); + static int l_get_lua_mapgen_descriptions(lua_State *L); + static int l_get_language(lua_State *L); //packages diff --git a/src/server.cpp b/src/server.cpp index 89bba75fb..5cbad550d 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -447,7 +447,7 @@ void Server::init() try { loadGameConfAndInitWorld(m_path_world, fs::GetFilenameFromPath(m_path_world.c_str()), - m_gamespec, false); + m_gamespec, false, ""); } catch (const BaseException &e) { throw ServerError(std::string("Failed to initialize world: ") + e.what()); } diff --git a/src/server/mods.cpp b/src/server/mods.cpp index e2d5debba..2347b2b1a 100644 --- a/src/server/mods.cpp +++ b/src/server/mods.cpp @@ -23,6 +23,7 @@ ServerModManager::ServerModManager(const std::string &worldpath, SubgameSpec gam // Load normal mods std::string worldmt = worldpath + DIR_DELIM + "world.mt"; + configuration.addMapgenFromConfig(worldmt, gamespec.mapgen_paths); configuration.addModsFromConfig(worldmt, gamespec.addon_mods_paths); configuration.checkConflictsAndDeps(); }