mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 23:17:06 +02:00
Map updates - A map the game already has, being replaced by the host's version, could load as soon as the new files were in Mods, while the mods were still being applied: it ended on "This map is not loading". The session now holds the load while a map is downloading, installing or applying, as it waits for one that is missing. - A player whose version of a map is later than the host's keeps it and loads. Only an older one is replaced by the host's. Versions are compared number by number (1.10.0 is later than 1.9.2). Dedicated servers - maps.latest_versions (off by default): the server asks Thunderstore for the newest version of each of its custom maps, at start and every maps.latest_check_minutes (5-1440, default 60), and writes it into the mod's manifest.json. Nothing is downloaded: the server reads only a map's level list and version, and players, who are held to the server's version, fetch it as they join. The map being played waits until nobody is on it. A folder that is not a Thunderstore package is not touched, and the level list is left as it is. - Console: maps update, maps latest on|off, maps latest every <minutes>. Overlay - Vote card: the pass mark is always in the middle of the bar. Green grows over the left half up to the yes votes needed and over the right half past them, whatever share the vote needs. - The "Staff" tag is "ReSkate Admin", shimmering from #2500FF to #B2F9FF. No protocol change. Tests cover the version comparison and the manifest write; the Thunderstore lookup was checked against the site. The map update flow, the vote bar and the tag were not seen in game, and the server setting has not run on a live server. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
100 lines
4.5 KiB
C++
100 lines
4.5 KiB
C++
// Writing a map mod's newest version into its manifest.json (server_map_update.h), without the
|
|
// network.
|
|
#include "Server/server_map_update.h"
|
|
#include "Engine/Core/Json/json.h"
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
|
|
namespace fs = std::filesystem;
|
|
using namespace dingosdk;
|
|
using namespace dingosdk::server;
|
|
|
|
namespace {
|
|
int failures{};
|
|
void check(bool ok, const std::string &what) {
|
|
if (ok) return;
|
|
++failures;
|
|
std::cerr << "FAIL: " << what << '\n';
|
|
}
|
|
std::string read(const fs::path &file) {
|
|
std::ifstream in(file, std::ios::binary);
|
|
std::stringstream text;
|
|
text << in.rdbuf();
|
|
return text.str();
|
|
}
|
|
void write(const fs::path &file, const std::string &text) {
|
|
fs::create_directories(file.parent_path());
|
|
std::ofstream(file, std::ios::binary) << text;
|
|
}
|
|
|
|
// With a Thunderstore package's folder name as its argument: asks Thunderstore about it and
|
|
// says what it would do, changing nothing (the map counts as being played). By hand only.
|
|
int look_up(const std::string &folder) {
|
|
const auto mods = fs::temp_directory_path() / "reskate-server-map-lookup" / "Mods";
|
|
std::error_code ignored;
|
|
fs::remove_all(mods.parent_path(), ignored);
|
|
write(mods / folder / "manifest.json", "{\"version_number\": \"0.0.0\"}");
|
|
write(mods / folder / "reskate-levels.json", "{\"levels\": []}");
|
|
for (const auto &update : update_maps(mods, folder))
|
|
std::cout << update.folder << ": installed " << update.from << ", newest " << (update.to.empty() ? "?" : update.to)
|
|
<< (update.waiting ? " (would be written)" : "") << (update.problem.empty() ? "" : " - " + update.problem) << std::endl;
|
|
fs::remove_all(mods.parent_path(), ignored);
|
|
return 0;
|
|
}
|
|
} // namespace
|
|
|
|
int main(int argc, char **argv) {
|
|
if (argc > 1) return look_up(argv[1]);
|
|
const auto mods = fs::temp_directory_path() / "reskate-server-map-update-tests" / "Mods";
|
|
std::error_code ignored;
|
|
fs::remove_all(mods.parent_path(), ignored);
|
|
try {
|
|
// The version changes and the rest of the manifest is kept.
|
|
const auto map = mods / "Team-Map";
|
|
write(map / "manifest.json", "{\"name\": \"Map\", \"version_number\": \"1.0.0\", \"dependencies\": [\"A-B-1.0.0\"], \"website_url\": \"\"}");
|
|
write(map / "reskate-levels.json", "{\"levels\": [1]}");
|
|
check(map_version(map) == "1.0.0", "A manifest's version was not read");
|
|
set_map_version(map, "1.2.0");
|
|
const auto after = Json::parse(read(map / "manifest.json"));
|
|
check(map_version(map) == "1.2.0", "The new version is not in the manifest");
|
|
check(after.value("name", "") == "Map" && after.contains("dependencies") && after.at("dependencies").size() == 1 && after.contains("website_url"),
|
|
"Writing the version lost something else of the manifest");
|
|
check(read(map / "reskate-levels.json") == "{\"levels\": [1]}", "Writing the version touched the level list");
|
|
check(!fs::exists(map / "manifest.json.new"), "The manifest's working file was left behind");
|
|
|
|
// A folder with only a level list gets a manifest with the version alone.
|
|
const auto lite = mods / "Team-Lite";
|
|
write(lite / "reskate-levels.json", "{\"levels\": []}");
|
|
check(map_version(lite).empty(), "A folder without a manifest has a version");
|
|
set_map_version(lite, "3.0.0");
|
|
check(map_version(lite) == "3.0.0", "A folder without a manifest did not get one with its version");
|
|
|
|
// A manifest that is not what it should be is left as it is.
|
|
const auto odd = mods / "Team-Odd";
|
|
write(odd / "manifest.json", "[1, 2, 3]");
|
|
bool threw{};
|
|
try {
|
|
set_map_version(odd, "9.9.9");
|
|
} catch (const std::exception &) {
|
|
threw = true;
|
|
}
|
|
check(threw && read(odd / "manifest.json") == "[1, 2, 3]", "A manifest that is not an object was written over");
|
|
write(odd / "manifest.json", "{ not json");
|
|
threw = false;
|
|
try {
|
|
set_map_version(odd, "9.9.9");
|
|
} catch (const std::exception &) {
|
|
threw = true;
|
|
}
|
|
check(threw && read(odd / "manifest.json") == "{ not json", "A manifest that cannot be read was written over");
|
|
} catch (const std::exception &e) {
|
|
check(false, std::string("The test stopped: ") + e.what());
|
|
}
|
|
fs::remove_all(mods.parent_path(), ignored);
|
|
if (!failures) std::cout << "Server map update tests passed.\n";
|
|
return failures ? 1 : 0;
|
|
}
|