1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00

Mapgen selection and init code

This commit is contained in:
Xeno333 2025-06-10 18:00:45 -05:00
parent aba2b6638e
commit f9bad58d77
14 changed files with 211 additions and 59 deletions

View file

@ -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),

View file

@ -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
│   └── <custom data and code>
└── 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).

View file

@ -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

1
mapgens/mapgens_here.txt Normal file
View file

@ -0,0 +1 @@
Put lua mapgens in this directory.

View file

@ -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<std::string, std::string> &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<ModSpec> 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<ModSpec> 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<std::string, std::string> &modPaths)

View file

@ -66,6 +66,15 @@ public:
void addModsFromConfig(const std::string &settings_path,
const std::unordered_map<std::string, std::string> &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<std::string, std::string> &mapgenPaths);
/**
* Call this function once all mods have been added
*/

View file

@ -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<int (*)(int)>(&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<int (*)(int)>(&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<std::string> 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<int (*)(int)>(&std::isspace)), dep.end());
for (const auto &dependency : str_split(dep, ',')) {
spec.depends.insert(dependency);
}
}
for (auto &dependency : dependencies) {
std::unordered_set<char> 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<int (*)(int)>(&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<std::string> 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<char> symbols;
if (parseDependsString(dependency, symbols)) {
if (symbols.count('?') != 0) {
spec.optdepends.insert(dependency);
} else {
spec.depends.insert(dependency);
}
}
}
}

View file

@ -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,

View file

@ -81,7 +81,8 @@ std::string getSubgamePathEnv()
static SubgameSpec getSubgameSpec(const std::string &game_id,
const std::string &game_path,
const std::unordered_map<std::string, std::string> &mods_paths)
const std::unordered_map<std::string, std::string> &mods_paths,
const std::unordered_map<std::string, std::string> &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<std::string, std::string> mods_paths;
std::unordered_map<std::string, std::string> 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<WorldSpec> 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");

View file

@ -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<std::string, std::string> addon_mods_paths;
std::unordered_map<std::string, std::string> mapgen_paths;
// For logging purposes
std::vector<const char *> 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<std::string, std::string> &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<WorldSpec> 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);

View file

@ -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<ModSpec> 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);

View file

@ -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

View file

@ -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());
}

View file

@ -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();
}