This commit is contained in:
AyeZeeBB committed 2026-10-11 19:26:25 -05:00
commit 8f2c456a39
12 files changed
+300 -48

No files matched your search

+75 -15
View File
@@ -120,6 +120,33 @@ bool deck_before(const std::string& a, const std::string& b) {
};
return place(a) != place(b) ? place(a) < place(b) : a < b;
}
// The decks a pack holds beside its own (mod_catalog.h), by name, in the menus' order.
std::vector<std::pair<std::string, fs::path>> decks_inside(const fs::path& mod) {
std::vector<std::pair<std::string, fs::path>> found;
std::error_code error;
for (fs::directory_iterator at(mod / L"decks", error), end; !error && at != end; at.increment(error)) {
if (!at->is_directory(error) || !fs::is_regular_file(at->path() / L"layout.toc", error)) continue;
auto name = lower(at->path().filename().string());
if (deck_name(name)) found.emplace_back(std::move(name), at->path());
}
std::sort(found.begin(), found.end(), [](const auto& a, const auto& b) { return deck_before(a.first, b.first); });
return found;
}
// Every deck in the Mods folder with the folder of the mod that brings it, in the menus' order,
// whatever mods.json says of that mod: the decks inside a pack, or else the one a folder is named for.
std::vector<std::pair<std::string, fs::path>> installed_decks() {
std::vector<std::pair<std::string, fs::path>> decks;
std::error_code error;
for (fs::directory_iterator at(engine_data_root() / L"Mods", error), end; !error && at != end; at.increment(error)) {
if (!at->is_directory(error)) continue;
auto inside = decks_inside(at->path());
for (auto& one : inside) decks.emplace_back(std::move(one.first), at->path());
if (!inside.empty()) continue;
if (auto deck = board_break_deck_of(at->path().filename().string()); !deck.empty()) decks.emplace_back(std::move(deck), at->path());
}
std::stable_sort(decks.begin(), decks.end(), [](const auto& a, const auto& b) { return deck_before(a.first, b.first); });
return decks;
}
// The choice made in this run, and what the last live apply gave the game (none since launch: the
// launch's catalogue says).
struct BoardBreakApplied {
@@ -133,13 +160,22 @@ std::optional<BoardBreakApplied> applied_board_break;
} // namespace
std::string board_break_deck_of(std::string_view mod) {
// Team-Board_Break_Wide and Board_Break_Wide are both the deck "wide".
constexpr std::string_view prefix = "board_break_";
auto name = lower(mod);
// A pack's deck is listed as <pack's folder>#<deck>; no folder has a # in its name.
if (const auto mark = name.rfind('#'); mark != std::string::npos) {
name.erase(0, mark + 1);
return deck_name(name) ? name : std::string{};
}
// Team-Board_Break_Wide and Board_Break_Wide are both the deck "wide".
if (const auto team = name.rfind('-'); team != std::string::npos) name.erase(0, team + 1);
if (!name.starts_with(prefix) || !deck_name(std::string_view(name).substr(prefix.size()))) return {};
return name.substr(prefix.size());
}
std::string board_break_pack_of(std::string_view mod) {
const auto mark = mod.rfind('#');
return mark == std::string_view::npos ? std::string{} : std::string(mod.substr(0, mark));
}
std::string wanted_board_break_deck() noexcept {
try {
{
@@ -178,23 +214,16 @@ bool save_board_break_deck(std::string_view deck) noexcept {
std::vector<std::filesystem::path> board_break_folders() noexcept {
std::vector<fs::path> found;
try {
std::vector<std::pair<std::string, fs::path>> decks;
std::error_code error;
for (fs::directory_iterator at(engine_data_root() / L"Mods", error), end; !error && at != end; at.increment(error))
if (at->is_directory(error))
if (auto deck = board_break_deck_of(at->path().filename().string()); !deck.empty()) decks.emplace_back(std::move(deck), at->path());
std::sort(decks.begin(), decks.end(), [](const auto& a, const auto& b) { return deck_before(a.first, b.first); });
for (auto& deck : decks) found.push_back(std::move(deck.second));
for (auto& deck : installed_decks())
if (std::find(found.begin(), found.end(), deck.second) == found.end()) found.push_back(std::move(deck.second));
} catch (...) {}
return found;
}
std::vector<std::string> installed_board_break_decks() noexcept {
std::vector<std::string> found;
try {
for (const auto& folder : board_break_folders()) {
auto deck = board_break_deck_of(folder.filename().string());
if (found.empty() || found.back() != deck) found.push_back(std::move(deck));
}
for (auto& deck : installed_decks())
if (found.empty() || found.back() != deck.first) found.push_back(std::move(deck.first));
} catch (...) {}
return found;
}
@@ -275,6 +304,37 @@ Catalog load_catalog(const std::filesystem::path& data_root, const MergeObserver
try {
for (const auto& name : list.missing)
result.notes.push_back("mods.json names a folder that is not present: " + name);
// Board Break: a deck pack holds several decks in one mod folder (mod_catalog.h). Each deck
// inside becomes a mod of its own for the run, listed after the pack as <folder>#<deck>, so
// that a pack's decks are chosen, set aside and switched like decks in folders of their own.
// It carries the pack's build stamp; mods.json and the mod list only know the pack. The
// pack's own layout is no deck here, whatever its folder is named: it is the deck a ReSkate
// from before deck packs breaks, and is set aside for the decks inside.
std::vector<std::string> packs;
for (std::size_t i = 0; i < list.entries.size(); ++i) {
auto inside = decks_inside(list.entries[i].mod.directory);
if (inside.empty()) continue;
const auto pack = list.entries[i].mod;
packs.push_back(pack.name);
const bool enabled = list.entries[i].enabled;
for (auto& [deck, directory] : inside) {
ModEntry part;
part.enabled = enabled;
part.mod.name = pack.name + "#" + deck;
part.mod.title = pack.title + " (" + deck + ")";
part.mod.author = pack.author;
part.mod.version = pack.version;
part.mod.directory = std::move(directory);
part.mod.provides_layout = true;
part.mod.studio_marker = pack.studio_marker;
part.mod.skate_sha256 = pack.skate_sha256;
part.mod.outdated = pack.outdated;
list.entries.insert(list.entries.begin() + static_cast<std::ptrdiff_t>(++i), std::move(part));
}
}
const auto deck_of = [&](const Mod& mod) {
return std::find(packs.begin(), packs.end(), mod.name) != packs.end() ? std::string{} : board_break_deck_of(mod.name);
};
// Board Break (Extension/BoardBreak/board_break.h). The deck that can break is a mod's own
// copy of the game's one deck model, and any mod that reshapes the deck replaces that same
// model: the one higher in the load order has it. So the deck the player chose goes first
@@ -287,7 +347,7 @@ Catalog load_catalog(const std::filesystem::path& data_root, const MergeObserver
{
const auto enabled_deck = [&](const std::string& deck) {
return std::find_if(list.entries.begin(), list.entries.end(), [&](const auto& entry) {
const auto found = board_break_deck_of(entry.mod.name);
const auto found = deck_of(entry.mod);
return entry.enabled && !found.empty() && (deck.empty() || found == deck);
});
};
@@ -298,7 +358,7 @@ Catalog load_catalog(const std::filesystem::path& data_root, const MergeObserver
std::rotate(list.entries.begin(), first, first + 1);
result.notes.push_back("Board Break: " + list.entries.front().mod.name + " loads first, so its deck is the one in the game");
for (auto entry = list.entries.begin() + 1; entry != list.entries.end(); ++entry) {
if (!entry->enabled || (board_break_deck_of(entry->mod.name).empty() && !changes_the_deck(entry->mod.directory))) continue;
if (!entry->enabled || (deck_of(entry->mod).empty() && !changes_the_deck(entry->mod.directory))) continue;
entry->enabled = false;
result.set_aside.push_back(entry->mod.name);
result.notes.push_back("Board Break: " + entry->mod.name + " brings a deck model too and is set aside for this run");
@@ -392,7 +452,7 @@ Catalog load_catalog(const std::filesystem::path& data_root, const MergeObserver
// Board Break's deck is the one in the game when its mod is among those that load and no mod
// above it brings a deck model of its own.
for (const auto& mod : result.mods) {
if (!(result.board_break_deck = board_break_deck_of(mod.name)).empty()) break;
if (!(result.board_break_deck = deck_of(mod)).empty()) break;
if (changes_the_deck(mod.directory)) break;
}
// Any Mods folder gets a patch, even an empty one or one of disabled
+21 -10
View File
@@ -49,22 +49,33 @@ struct Catalog {
};
// Board Break's decks (Extension/BoardBreak/board_break.h). The game has one deck model for every
// board, so each deck that can break is a content mod of its own that replaces it: any mod whose
// folder is named Board_Break_<Name> (also behind a team name, as a mod manager installs it:
// Team-Board_Break_<Name>). The name, in small letters, is what the deck goes by; the deck the
// player chose is the one that loads. These are the decks the feature was made with, in the order
// the menus list them, and the console also knows them by their place here (deck 1 is the first).
// Any other deck comes after them, by its name: a new shape needs no change to ReSkate.
// board, so each deck that can break is a copy of it in a content mod, and the deck the player
// chose is the one that loads. A deck comes in one of two ways:
// - a mod whose folder is named Board_Break_<Name> (also behind a team name, as a mod manager
// installs it: Team-Board_Break_<Name>) holds the deck <name>;
// - a deck pack, a mod of any name with a folder decks/<name>/ for each deck it brings, laid out
// like a mod folder (layout.toc, Win32/...). One package can bring every deck that way, where
// the launcher installs one mod folder per package. For the run each deck inside is a mod of
// its own, listed as <pack's folder>#<name>. The pack's own layout is not one of its decks,
// whatever the folder is named, and is set aside like any other deck model. It is what an
// older ReSkate loads from the pack: in a folder named Board_Break_<Name> the deck 2.0.6
// breaks, which knows a deck by its folder's name only; under another name the game's own.
// A deck goes by its name in small letters. These are the decks the feature was made with, in the
// order the menus list them, and the console also knows them by their place here (deck 1 is the
// first). Any other deck comes after them, by its name: a new shape needs no change to ReSkate.
inline constexpr std::array<std::string_view, 8> board_break_decks{"standard", "wide", "concave", "powell",
"slim", "egg", "square", "shaped"};
// The name of the deck a mod folder holds, or nothing for any other mod.
std::string board_break_deck_of(std::string_view mod_folder);
// The name of the deck a mod holds going by the mod's name (a folder's, or a pack's deck's), or
// nothing; and the folder name of the pack that one of a pack's decks is listed under, or nothing.
std::string board_break_deck_of(std::string_view mod);
std::string board_break_pack_of(std::string_view mod);
// The deck the player chose ("standard" when nothing was chosen): read whenever the mods are put
// together, at launch and at every live apply. Saving a choice makes it this run's at once.
std::string wanted_board_break_deck() noexcept;
bool save_board_break_deck(std::string_view deck) noexcept;
// The decks whose mod's folder is in Mods/, whatever mods.json says of them, in the menus' order,
// and those folders in the same order.
// The decks that are in the Mods folder, whatever mods.json says of their mods, in the menus'
// order; and the folders of the mods that bring them (a pack's once), for what a pack carries
// beside its decks.
std::vector<std::string> installed_board_break_decks() noexcept;
std::vector<std::filesystem::path> board_break_folders() noexcept;
// What the mods in effect give the game: the launch's catalogue, or the one the last live apply