mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-23 17:35:57 +02:00
A mod's item (or one of the game's own a mod changes) keeps three empty description lines, so the game's item card, which grows upward from a fixed bottom edge, always has room in the same place; the overlay draws the mod there: its icon (decoded off the render thread into a fourth texture slot, the last 32 kept), a MOD or CHANGED BY MOD tag, its name, author, version and how many items it has. The card is placed as the game places its own, measured at many window sizes: by the smaller of its scales, a 21:9 frame centred on wider screens (the search bar follows it too). The warning sign the game's focus script gives any description is taken off that notice each tick while it shows the reserved lines. The merge's item list records each mod's version and folder (an older list falls back to the mods loaded at launch). The item grids now run every client tick instead of at the 500 ms customization pace, follow the highlight between their 40 ms passes, and let go of a grid as soon as its screen closes, so the card, the search bar and favorites keep up with the player.
76 lines
2.8 KiB
C++
76 lines
2.8 KiB
C++
#include "mod_item_sources.h"
|
|
|
|
#include "Engine/Core/Json/json.h"
|
|
#include "Engine/Core/Platform/path_text.h"
|
|
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <iterator>
|
|
#include <stdexcept>
|
|
|
|
namespace dingosdk::mods {
|
|
namespace fs = std::filesystem;
|
|
|
|
std::vector<ItemSource> read_item_sources(const fs::path& file) noexcept {
|
|
std::vector<ItemSource> sources;
|
|
try {
|
|
std::ifstream input(file, std::ios::binary);
|
|
if (!input) return {};
|
|
const std::string text{std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>()};
|
|
const auto root = Json::parse(text, {64u << 20, 32, 1u << 24});
|
|
const auto& mods = root.at("mods");
|
|
for (std::size_t i = 0; i < mods.size(); ++i) {
|
|
const auto& entry = mods.at(i);
|
|
ItemSource source{entry.at("mod").string(), entry.value("title", std::string()), entry.value("author", std::string()),
|
|
entry.value("version", std::string()), entry.value("folder", std::string()), {}};
|
|
const auto& keys = entry.at("items");
|
|
for (std::size_t k = 0; k < keys.size(); ++k) source.keys.push_back(keys.at(k).string());
|
|
sources.push_back(std::move(source));
|
|
}
|
|
} catch (...) {
|
|
return {}; // an unreadable file only means no card says where its item is from
|
|
}
|
|
return sources;
|
|
}
|
|
|
|
void write_item_sources(const fs::path& file, const std::vector<ItemSource>& sources) {
|
|
auto mods = Json::array();
|
|
for (const auto& source : sources) {
|
|
auto entry = Json::object();
|
|
entry["mod"] = source.mod;
|
|
entry["title"] = source.title;
|
|
entry["author"] = source.author;
|
|
entry["version"] = source.version;
|
|
entry["folder"] = source.folder;
|
|
entry["items"] = Json(source.keys);
|
|
mods.push_back(std::move(entry));
|
|
}
|
|
auto root = Json::object();
|
|
root["schema"] = 1u;
|
|
root["mods"] = std::move(mods);
|
|
const auto text = root.dump(1);
|
|
// Whole or not at all: the game may read it while a merge runs beside it.
|
|
auto temporary = file;
|
|
temporary += L".new";
|
|
{
|
|
std::ofstream output(temporary, std::ios::binary | std::ios::trunc);
|
|
output.write(text.data(), static_cast<std::streamsize>(text.size()));
|
|
if (!output.flush()) throw std::runtime_error("Cannot write " + path_utf8(temporary));
|
|
}
|
|
std::error_code error;
|
|
fs::rename(temporary, file, error);
|
|
if (error) {
|
|
fs::remove(temporary, error);
|
|
throw std::runtime_error("Cannot write " + path_utf8(file));
|
|
}
|
|
}
|
|
|
|
std::string item_source_name(const ItemSource& source) {
|
|
std::string name = source.title.empty() ? source.mod : source.title;
|
|
std::replace(name.begin(), name.end(), '_', ' ');
|
|
if (!source.author.empty()) name += " by " + source.author;
|
|
return name;
|
|
}
|
|
|
|
} // namespace dingosdk::mods
|