Files
ReSkate/Extension/Customization/item_origins.cpp
T
AyeZeeBB ab8c9dc6ae Customization: a mod's item card shows the mod, with its icon, inside the game's item card
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.
2026-10-17 12:04:38 -05:00

92 lines
3.4 KiB
C++

#include "item_origins.h"
#include "Engine/Core/Platform/path_text.h"
#include "Engine/Vfs/content_catalogs.h"
#include "Engine/Vfs/mod_catalog.h"
#include "Engine/Vfs/mod_item_sources.h"
#include <Windows.h>
#include <algorithm>
#include <cctype>
#include <map>
#include <mutex>
namespace dingosdk::item_origins {
namespace {
namespace fs = std::filesystem;
fs::path utf8_path(std::string_view text) { return fs::path(std::u8string(text.begin(), text.end())); }
std::string folded(std::string_view text) {
std::string result(text);
for (auto& c : result) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
return result;
}
struct Table {
std::mutex mutex;
std::map<std::string, std::shared_ptr<const Origin>, std::less<>> by_key;
fs::file_time_type written{};
std::uintmax_t size{};
bool read{};
ULONGLONG next_look{};
};
Table& table() { static auto* value = new Table; return *value; } // process lifetime
void refresh(Table& t) {
const auto now = GetTickCount64();
if (t.read && now < t.next_look) return;
t.next_look = now + 2000;
const auto mods_root = mods::catalog().root;
const auto file = mods_root / mods::generated_folder / mods::item_sources_file;
std::error_code error;
const auto written = fs::last_write_time(file, error);
const auto size = error ? 0 : fs::file_size(file, error);
if (error) {
if (t.read && t.by_key.empty()) return;
t.by_key.clear();
t.read = true;
return;
}
if (t.read && written == t.written && size == t.size) return;
t.written = written;
t.size = size;
t.read = true;
t.by_key.clear();
// The first that lists an item is the one the game has (highest priority first).
for (const auto& source : mods::read_item_sources(file)) {
auto origin = std::make_shared<Origin>();
origin->title = source.title.empty() ? source.mod : source.title;
std::replace(origin->title.begin(), origin->title.end(), '_', ' ');
origin->author = source.author;
origin->version = source.version;
origin->name = mods::item_source_name(source);
origin->icon = mods_root / utf8_path(source.folder) / L"icon.png";
// A file from before the version and folder were recorded: the mod as the launch read it.
if (source.folder.empty() || source.version.empty()) {
const auto& loaded = mods::catalog().mods;
if (const auto mod = std::ranges::find(loaded, source.mod, &mods::Mod::name); mod != loaded.end()) {
if (origin->version.empty()) origin->version = mod->version;
if (source.folder.empty()) origin->icon = mod->directory / L"icon.png";
}
}
origin->items = source.keys.size();
const std::shared_ptr<const Origin> shared = std::move(origin);
for (const auto& key : source.keys) t.by_key.emplace(key, shared);
}
}
} // namespace
std::shared_ptr<const Origin> find(std::string_view key) {
try {
auto& t = table();
std::lock_guard lock(t.mutex);
refresh(t);
const auto found = t.by_key.find(folded(key));
return found == t.by_key.end() ? nullptr : found->second;
} catch (...) {
return nullptr;
}
}
bool game_item(std::string_view key) {
const auto& game = content_cache::catalogs();
return game.available && game.items.contains(folded(key));
}
} // namespace dingosdk::item_origins