This commit is contained in:
sfence 2024-05-16 06:59:48 +02:00 committed by GitHub
commit 59ca4a839b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 96 additions and 12 deletions

View File

@ -63,6 +63,8 @@ The game directory can contain the following files:
* `name`: (Deprecated) same as title.
* `description`: Short description to be shown in the content tab.
See [Translating content meta](#translating-content-meta).
* `first_mod`: Use this to specify the mod that must be loaded before any other mod.
* `last_mod`: Use this to specify the mod that must be loaded after all other mods
* `allowed_mapgens = <comma-separated mapgens>`
e.g. `allowed_mapgens = v5,v6,flat`
Mapgens not in this list are removed from the list of mapgens for the

View File

@ -1,2 +1,4 @@
title = Development Test
description = Testing environment to help with testing the engine features of Minetest. It can also be helpful in mod development.
first_mod = first_mod
last_mod = last_mod

View File

@ -0,0 +1 @@
-- Nothing to do here, loading order is tested in C++ unittests.

View File

@ -0,0 +1,2 @@
name = first_mod
description = Mod which should be loaded before every other mod.

View File

@ -0,0 +1 @@
-- Nothing to do here, loading order is tested in C++ unittests.

View File

@ -0,0 +1,2 @@
name = last_mod
description = Mod which should be loaded as last mod.

View File

@ -116,6 +116,9 @@ void ModConfiguration::addGameMods(const SubgameSpec &gamespec)
std::string game_virtual_path;
game_virtual_path.append("games/").append(gamespec.id).append("/mods");
addModsInPath(gamespec.gamemods_path, game_virtual_path);
m_first_mod = gamespec.first_mod;
m_last_mod = gamespec.last_mod;
}
void ModConfiguration::addModsFromConfig(
@ -221,10 +224,19 @@ void ModConfiguration::checkConflictsAndDeps()
void ModConfiguration::resolveDependencies()
{
// Step 1: Compile a list of the mod names we're working with
// Compile a list of the mod names we're working with
std::set<std::string> modnames;
for (const ModSpec &mod : m_unsatisfied_mods) {
modnames.insert(mod.name);
std::optional<ModSpec> first_mod_spec, last_mod_spec;
for (ModSpec &mod : m_unsatisfied_mods) {
if (m_first_mod.has_value() && mod.name == *m_first_mod) {
first_mod_spec = mod;
} else if (m_last_mod.has_value() && mod.name == *m_last_mod) {
// only non optional depends have to be check for last mod
mod.unsatisfied_depends = mod.depends;
last_mod_spec = mod;
} else {
modnames.insert(mod.name);
}
}
// Step 1.5 (optional): shuffle unsatisfied mods so non declared depends get found by their devs
@ -236,7 +248,13 @@ void ModConfiguration::resolveDependencies()
);
}
// Step 2: get dependencies (including optional dependencies)
// Check for presence of first and last mod
if (m_first_mod.has_value() && !first_mod_spec.has_value())
throw ModError("The mod specified as first by the game was not found.");
if (m_last_mod.has_value() && !last_mod_spec.has_value())
throw ModError("The mod specified as last by the game was not found.");
// Get dependencies (including optional dependencies)
// of each mod, split mods into satisfied and unsatisfied
std::vector<ModSpec> satisfied;
std::list<ModSpec> unsatisfied;
@ -247,14 +265,27 @@ void ModConfiguration::resolveDependencies()
if (modnames.count(optdep) != 0)
mod.unsatisfied_depends.insert(optdep);
}
if (last_mod_spec.has_value() && mod.unsatisfied_depends.count(last_mod_spec->name) != 0) {
throw ModError("It is not allowed to have mod selected as last by the game in dependencies.");
}
// 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
} else {
unsatisfied.push_back(mod);
}
}
// Step 3: mods without unmet dependencies can be appended to
// Check and add first mod
if (first_mod_spec.has_value()) {
// dependencies are not allowed for first mod
if (!first_mod_spec->depends.empty() || !first_mod_spec->optdepends.empty())
throw ModError("Mod selected as first by the game is not allowed to have dependencies.");
satisfied.push_back(*first_mod_spec);
}
// Mods without unmet dependencies can be appended to
// the sorted list.
while (!satisfied.empty()) {
ModSpec mod = satisfied.back();
@ -270,8 +301,18 @@ void ModConfiguration::resolveDependencies()
++it;
}
}
if (last_mod_spec.has_value())
last_mod_spec->unsatisfied_depends.erase(mod.name);
}
// Step 4: write back list of unsatisfied mods
// Write back list of unsatisfied mods
m_unsatisfied_mods.assign(unsatisfied.begin(), unsatisfied.end());
// Check and add last mod
if (last_mod_spec.has_value()) {
if (!last_mod_spec->unsatisfied_depends.empty())
throw ModError("Mod selected as last by the game has an unsatisfied dependency.");
m_sorted_mods.push_back(*last_mod_spec);
}
}

View File

@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once
#include "mods.h"
#include <optional>
/**
@ -83,10 +84,16 @@ public:
/**
* Call this function once all mods have been added
*
* @param first_mod First mod to be loaded
* @param last_mod Last mod to be loaded
*/
void checkConflictsAndDeps();
private:
std::optional<std::string> m_first_mod;
std::optional<std::string> m_last_mod;
std::vector<ModSpec> m_sorted_mods;
/**

View File

@ -170,6 +170,20 @@ SubgameSpec findSubgame(const std::string &id)
if (conf.exists("release"))
game_release = conf.getS32("release");
std::optional<std::string> first_mod;
if (conf.exists("first_mod")) {
first_mod = conf.get("first_mod");
if (first_mod->empty())
first_mod = std::nullopt;
}
std::optional<std::string> last_mod;
if (conf.exists("last_mod")) {
last_mod = conf.get("last_mod");
if (last_mod->empty())
last_mod = std::nullopt;
}
std::string menuicon_path;
#ifndef SERVER
menuicon_path = getImagePath(
@ -177,7 +191,7 @@ SubgameSpec findSubgame(const std::string &id)
#endif
SubgameSpec spec(id, game_path, gamemod_path, mods_paths, game_title,
menuicon_path, game_author, game_release);
menuicon_path, game_author, game_release, first_mod, last_mod);
if (conf.exists("name") && !conf.exists("title"))
spec.deprecation_msgs.push_back("\"name\" setting in game.conf is deprecated, please use \"title\" instead");

View File

@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <set>
#include <unordered_map>
#include <vector>
#include <optional>
class Settings;
@ -32,6 +33,8 @@ struct SubgameSpec
std::string title;
std::string author;
int release;
std::optional<std::string> first_mod;
std::optional<std::string> last_mod;
std::string path;
std::string gamemods_path;
@ -49,10 +52,16 @@ struct SubgameSpec
const std::unordered_map<std::string, std::string> &addon_mods_paths = {},
const std::string &title = "",
const std::string &menuicon_path = "",
const std::string &author = "", int release = 0) :
const std::string &author = "", int release = 0,
const std::optional<std::string> &first_mod = std::nullopt,
const std::optional<std::string> &last_mod = std::nullopt) :
id(id),
title(title), author(author), release(release), path(path),
gamemods_path(gamemods_path), addon_mods_paths(addon_mods_paths),
title(title), author(author), release(release),
first_mod(first_mod),
last_mod(last_mod),
path(path),
gamemods_path(gamemods_path),
addon_mods_paths(addon_mods_paths),
menuicon_path(menuicon_path)
{
}

View File

@ -139,6 +139,9 @@ void TestServerModManager::testGetMods()
UASSERTEQ(bool, default_found, true);
UASSERTEQ(bool, test_mod_found, true);
UASSERT(mods.front().name == "first_mod");
UASSERT(mods.back().name == "last_mod");
}
void TestServerModManager::testGetModspec()