mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 17:27:19 +02:00
Map downloader - A host or dedicated server now tells joiners which Thunderstore package its map comes from (the Mods folder name plus manifest.json). - A player without the map gets a card: icon, name, author, version and description, with DOWNLOAD and CANCEL. It works with mouse, keyboard and controller. - Only packages listed as Maps in the ReSkate community are offered, and never deprecated, rejected or NSFW ones. - On yes the package is downloaded, installed into Mods and applied as a live mod, then the session loads the map. A small card on the right shows the progress, so the player can keep skating. - The player stays connected while it downloads: the host gives a player who says they are fetching the map up to 45 minutes instead of the usual load time. - When the server changes map with the player in it, the card says so. - Protocol version 47: map package on map_offer and world_state, and a "fetching the map" flag on map_request and world_ready. Joining - The pause-menu server browser shows a CONNECTING card over the page while a join is under way, and a COULDN'T CONNECT card with the reason when it does not get in. - The session and voice tabs no longer show before the player is in. - Leaving without the map (declined or cancelled download) no longer counts towards the failed-join limit, on servers and player hosts. Input - A card that asks something holds the game's input until the buttons are released. - Steam's overlay hooks XInput in front of ReSkate and answers the game itself, so a controller reached the game behind menus. ReSkate now hooks that hook as well. Mod merging - Mods are read on several threads, at launch and for live merges. A live merge runs below the game's priority. - The paid-cosmetics check reads the game's files and the mods on several threads. - A live merge reuses the paid-cosmetics verdicts it already has and reports its progress and timings. Launcher - NSFW mods and their categories are not shown in the mod browser.
91 lines
5.8 KiB
C++
91 lines
5.8 KiB
C++
// One Thunderstore package looked up by name (Engine/Vfs/thunderstore_package.h): the name a
|
|
// host gives, and what Thunderstore's answer has to say before the game downloads it.
|
|
#include "Engine/Vfs/thunderstore_package.h"
|
|
|
|
#include <iostream>
|
|
#include <string>
|
|
|
|
using namespace dingosdk;
|
|
namespace package = thunderstore_package;
|
|
|
|
namespace {
|
|
int failures = 0;
|
|
void check(bool condition, const char* what) {
|
|
if (condition) return;
|
|
std::cerr << "FAIL: " << what << '\n';
|
|
++failures;
|
|
}
|
|
|
|
// Thunderstore's answer for a package, as /api/experimental/package/<owner>/<name>/ gives it.
|
|
std::string answer(std::string_view owner, std::string_view name, std::string_view version, std::string_view community,
|
|
std::string_view category, std::string_view review, bool deprecated = false) {
|
|
return std::string("{\"namespace\":\"") + std::string(owner) + "\",\"name\":\"" + std::string(name) +
|
|
"\",\"full_name\":\"x\",\"is_deprecated\":" + (deprecated ? "true" : "false") +
|
|
",\"latest\":{\"version_number\":\"" + std::string(version) +
|
|
"\",\"icon\":\"https://gcdn.thunderstore.io/live/repository/icons/x.png\",\"description\":\"A plaza\\nby the sea\",\"download_url\":\"https://elsewhere.example/steal.zip\",\"is_active\":true},\"community_listings\":[{\"has_nsfw_content\":false,"
|
|
"\"categories\":[\"" + std::string(category) + "\"],\"community\":\"" + std::string(community) +
|
|
"\",\"review_status\":\"" + std::string(review) + "\"}]}";
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
{
|
|
const auto plain = package::parse_name("Sandos-Vancouver_Plaza");
|
|
check(plain && plain->owner == "Sandos" && plain->name == "Vancouver_Plaza" && plain->version.empty() &&
|
|
plain->folder() == "Sandos-Vancouver_Plaza", "a package name read");
|
|
const auto versioned = package::parse_name("Sandos-Vancouver_Plaza-2.0.0");
|
|
check(versioned && versioned->version == "2.0.0" && versioned->folder() == "Sandos-Vancouver_Plaza", "and with its version");
|
|
for (const char* bad : {"", "Sandos", "Sandos-", "-Plaza", "San dos-Plaza", "Sandos-Plaza-2.0", "Sandos-Plaza-2.0.0.1", "Sandos-Plaza-two",
|
|
"../x-y", "Sandos-Plaza/..", "Sandos-Pla.za", "a-b-1.2.3-4"})
|
|
check(!package::parse_name(bad), bad);
|
|
const auto name = *versioned;
|
|
check(package::details_url(name) == L"https://thunderstore.io/api/experimental/package/Sandos/Vancouver_Plaza/", "where it is asked about");
|
|
check(package::download_url(name, "2.0.0") == L"https://thunderstore.io/package/download/Sandos/Vancouver_Plaza/2.0.0/",
|
|
"where it is downloaded from");
|
|
}
|
|
{
|
|
const package::Name newest{"Sandos", "Vancouver_Plaza", ""}, pinned{"Sandos", "Vancouver_Plaza", "1.4.0"};
|
|
const auto listed = answer("Sandos", "Vancouver_Plaza", "2.0.0", "reskate", "Maps", "unreviewed");
|
|
auto choice = package::choose(listed, newest, "reskate");
|
|
check(choice.ok && choice.version == "2.0.0" && choice.newest == "2.0.0", "a listed map is taken, its newest version");
|
|
check(choice.description == "A plaza by the sea", "with what it says of itself, on one line");
|
|
check(choice.icon == "https://gcdn.thunderstore.io/live/repository/icons/x.png", "and its icon, from Thunderstore's own host");
|
|
{
|
|
auto elsewhere = listed;
|
|
const std::string host = "gcdn.thunderstore.io";
|
|
elsewhere.replace(elsewhere.find(host), host.size(), "thunderstore.io.example.com");
|
|
const auto other = package::choose(elsewhere, newest, "reskate");
|
|
check(other.ok && other.icon.empty(), "an icon anywhere else is not fetched");
|
|
}
|
|
choice = package::choose(listed, pinned, "reskate");
|
|
check(choice.ok && choice.version == "1.4.0" && choice.newest == "2.0.0", "or the version the host has, with the newest to fall back on");
|
|
check(package::choose(answer("sandos", "vancouver_plaza", "2.0.0", "ReSkate", "maps", "approved"), newest, "reskate").ok,
|
|
"names compare whatever their case");
|
|
const auto refused = [&](const std::string& text, const char* what) {
|
|
const auto result = package::choose(text, newest, "reskate");
|
|
check(!result.ok && !result.reason.empty() && result.version.empty(), what);
|
|
};
|
|
refused(answer("Someone", "Else", "2.0.0", "reskate", "Maps", "unreviewed"), "another package's answer is refused");
|
|
refused(answer("Sandos", "Vancouver_Plaza", "2.0.0", "reskate", "Cosmetics", "unreviewed"), "a package that is not a map is refused");
|
|
refused(answer("Sandos", "Vancouver_Plaza", "2.0.0", "othergame", "Maps", "unreviewed"), "a package of another community is refused");
|
|
refused(answer("Sandos", "Vancouver_Plaza", "2.0.0", "reskate", "Maps", "rejected"), "a package the moderators rejected is refused");
|
|
refused(answer("Sandos", "Vancouver_Plaza", "2.0.0", "reskate", "Maps", "unreviewed", true), "a deprecated package is refused");
|
|
refused(answer("Sandos", "Vancouver_Plaza", "latest", "reskate", "Maps", "unreviewed"), "a version that is not one is refused");
|
|
{
|
|
auto marked = listed;
|
|
const std::string off = "\"has_nsfw_content\":false";
|
|
marked.replace(marked.find(off), off.size(), "\"has_nsfw_content\":true");
|
|
refused(marked, "a package marked NSFW is refused");
|
|
}
|
|
refused("{\"namespace\":\"Sandos\",\"name\":\"Vancouver_Plaza\"}", "an answer with no listing is refused");
|
|
refused("not json", "an answer that is not JSON is refused");
|
|
refused("[]", "an answer that is not an object is refused");
|
|
}
|
|
if (failures) {
|
|
std::cerr << failures << " failure(s)\n";
|
|
return 1;
|
|
}
|
|
std::cout << "thunderstore package tests passed\n";
|
|
return 0;
|
|
}
|