mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 15:07:06 +02:00
Launcher: mod profiles, sharing them by code or file, and Thunderstore dependencies
The Mods page has a third tab, PROFILES. A profile is a saved list of which mods are on, in what order and at what version; every mod stays installed once, in the one Mods folder. Loading a profile rewrites mods.json, switches off what it does not name, and downloads from Thunderstore what it names that is missing. Profiles live in Mods/profiles.json. A profile travels the way r2modman's do, so the two managers read each other's: export.r2x inside a .r2z file, and a code on Thunderstore's profile service. Config files in an r2modman profile are ignored. Installing or updating a Thunderstore package now brings the packages it depends on, needed ones first, leaving out any already installed at a new enough version. A mod pack is a package that is nothing else. A kick, by an admin or by a passed vote, used to keep the player out until the server restarted. "kick_minutes" in the players section of the config (1-1440; 0, the default, is still until the restart) and the console command "kick-time <minutes>|restart" now set how long it lasts. The player is told how long when they are kicked, and how many minutes are left if they try to join early. A kick keeps the time it was given, so changing the setting is for later kicks. Bans are unchanged.
This commit is contained in:
1 parent
242ac11814
commit
2e186b9839
24 files changed
+1488
-38
No files matched your search
@@ -68,6 +68,7 @@ std::optional<Version> parse_version(const Json& row) {
|
||||
version.date_created = text(row, "date_created");
|
||||
version.downloads = count(row, "downloads");
|
||||
version.file_size = count(row, "file_size");
|
||||
version.dependencies = strings(row, "dependencies");
|
||||
if (version.number.empty() || !https(version.download_url)) return std::nullopt;
|
||||
if (!https(version.icon)) version.icon.clear();
|
||||
return version;
|
||||
@@ -135,6 +136,63 @@ std::wstring community_page(std::string_view community) {
|
||||
return widen(std::string(site) + "/c/" + std::string(community) + "/");
|
||||
}
|
||||
|
||||
Package Package::at_version(std::string_view number) const {
|
||||
auto result = *this;
|
||||
const auto found = std::find_if(result.versions.begin(), result.versions.end(),
|
||||
[&](const Version& version) { return version.number == number; });
|
||||
if (found != result.versions.end()) std::rotate(result.versions.begin(), found, found + 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<std::string, std::string> split_dependency(std::string_view text) {
|
||||
const auto dash = text.rfind('-');
|
||||
if (dash == std::string_view::npos || dash == 0) return {std::string(text), {}};
|
||||
const auto version = text.substr(dash + 1);
|
||||
const bool numbered = !version.empty() && std::all_of(version.begin(), version.end(),
|
||||
[](char ch) { return (ch >= '0' && ch <= '9') || ch == '.'; }) && version.find('.') != std::string_view::npos;
|
||||
if (!numbered) return {std::string(text), {}};
|
||||
return {std::string(text.substr(0, dash)), std::string(version)};
|
||||
}
|
||||
|
||||
const Package* find_package(std::span<const Package> listing, std::string_view full_name) {
|
||||
for (const auto& package : listing)
|
||||
if (package.full_name == full_name) return &package;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<Package> with_dependencies(const Package& package, std::span<const Package> listing,
|
||||
const std::map<std::string, std::string, std::less<>>& have,
|
||||
std::vector<std::string>* unknown) {
|
||||
std::vector<Package> order;
|
||||
std::vector<std::string> visiting;
|
||||
const auto seen = [&](std::string_view name) {
|
||||
return std::find(visiting.begin(), visiting.end(), name) != visiting.end() ||
|
||||
std::any_of(order.begin(), order.end(), [&](const Package& done) { return done.full_name == name; });
|
||||
};
|
||||
// Depth first, so a package comes after what it needs. `visiting` breaks a cycle.
|
||||
const auto visit = [&](const auto& self, const Package& current) -> void {
|
||||
visiting.push_back(current.full_name);
|
||||
for (const auto& text : current.latest().dependencies) {
|
||||
const auto [name, version] = split_dependency(text);
|
||||
if (seen(name)) continue;
|
||||
const auto installed = have.find(folder_for(name));
|
||||
if (installed != have.end() && (version.empty() || compare_versions(installed->second, version) >= 0)) continue;
|
||||
const auto* needed = find_package(listing, name);
|
||||
if (!needed) {
|
||||
if (unknown && std::find(unknown->begin(), unknown->end(), name) == unknown->end()) unknown->push_back(name);
|
||||
continue;
|
||||
}
|
||||
// The newest is installed unless only an older one will do, which no listing says:
|
||||
// a dependency names the oldest version that works.
|
||||
self(self, *needed);
|
||||
}
|
||||
visiting.pop_back();
|
||||
order.push_back(current);
|
||||
};
|
||||
visit(visit, package);
|
||||
return order;
|
||||
}
|
||||
|
||||
std::string Package::title() const {
|
||||
auto result = name;
|
||||
std::replace(result.begin(), result.end(), '_', ' ');
|
||||
|
||||
Reference in new issue
Block a user