mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-27 18:37:09 +01:00
Mod profiles: an option to keep other mods on when loading a profile
Loading a profile switches every installed mod it does not list off. With "Keep my other mods on when loading a profile" (saved in profiles.json), a profile switches its own mods on and leaves the rest as they are, so a shared profile (a pack of mods) can be added to a player's own set. A profile then counts as in use when its own mods match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
45346b122c
commit
076c693e64
4 files changed
+50
-15
No files matched your search
@@ -79,6 +79,21 @@ void applying() {
|
||||
check(profiles::in_use(have, after) && !profiles::in_use(have, list), "In use does not follow which mods are on");
|
||||
}
|
||||
|
||||
// "Keep my other mods on": a profile adds its mods to the player's own, and is in use when its own mods match.
|
||||
void keeping_others() {
|
||||
const auto list = installed({{"Team-A", "1.0.0", true}, {"Team-B", "2.1.0", false}, {"Local Mod", "wip", true}});
|
||||
const profiles::Profile pack{"Pack", {{"Team-B", "2.1.0", true}}};
|
||||
const auto applied = profiles::apply(pack, list, true);
|
||||
check(order(applied.entries) == std::vector<std::string>{"Team-B+", "Team-A+", "Local Mod+"},
|
||||
"Keeping other mods switched a mod the profile does not name off");
|
||||
check(order(profiles::apply(pack, list).entries) == std::vector<std::string>{"Team-B+", "Team-A-", "Local Mod-"},
|
||||
"Without keeping other mods, the mods outside the profile were not switched off");
|
||||
auto after = list;
|
||||
after.entries = applied.entries;
|
||||
check(profiles::in_use(pack, after, true) && !profiles::in_use(pack, after), "In use with kept mods does not follow the profile's own mods");
|
||||
check(!profiles::in_use(pack, list, true), "A profile whose mod is off counts as in use");
|
||||
}
|
||||
|
||||
void storage(const std::filesystem::path& root) {
|
||||
std::filesystem::remove_all(root);
|
||||
std::filesystem::create_directories(root);
|
||||
@@ -86,9 +101,11 @@ void storage(const std::filesystem::path& root) {
|
||||
profiles::Profiles set;
|
||||
set.list = {{"Street", {{"Team-A", "1.0.0", true}, {"Team-B", "2.0.0", false}}}, {"Empty", {}}};
|
||||
set.active = "Street";
|
||||
set.keep_others = true;
|
||||
profiles::save(root, set);
|
||||
const auto read = profiles::load(root);
|
||||
check(read.active == "Street" && read.list == set.list, "Profiles did not survive saving and loading");
|
||||
check(read.keep_others, "Keeping other mods did not survive saving and loading");
|
||||
std::ofstream(root / profiles::profiles_file) << R"({"active":"Gone","profiles":[{"name":"ok","mods":[{"name":"../x"},{"name":"Fine"}]},)"
|
||||
R"({"name":"bad/name"},{"name":"ok"},7]})";
|
||||
const auto loose = profiles::load(root);
|
||||
@@ -149,6 +166,7 @@ int wmain(int argc, wchar_t** argv) {
|
||||
}
|
||||
names();
|
||||
applying();
|
||||
keeping_others();
|
||||
storage(std::filesystem::path(argv[1]) / L"profiles");
|
||||
exporting(std::filesystem::path(argv[1]) / L"profiles");
|
||||
codes();
|
||||
|
||||
@@ -68,7 +68,7 @@ std::vector<std::string> unobtainable(const ModsPanel& panel, const std::vector<
|
||||
|
||||
// Writes mods.json as the profile has it. False, with the reason shown, when that failed.
|
||||
bool write_order(ModsPanel& panel, const profiles::Profile& profile, std::vector<profiles::ProfileMod>& missing) {
|
||||
auto applied = profiles::apply(profile, panel.list);
|
||||
auto applied = profiles::apply(profile, panel.list, panel.profiles.keep_others);
|
||||
try {
|
||||
mods::save_mod_order(panel.root, applied.entries);
|
||||
} catch (const std::exception& failure) {
|
||||
@@ -122,7 +122,7 @@ void add_profile(ModsPanel& panel, profiles::Profile profile, std::string_view v
|
||||
profile.name = panel.profiles.free_name(profiles::clean_profile_name(profile.name));
|
||||
const auto name = profile.name;
|
||||
const auto count = profile.mods.size();
|
||||
const auto missing = profiles::apply(profile, panel.list).missing.size();
|
||||
const auto missing = profiles::apply(profile, panel.list, panel.profiles.keep_others).missing.size();
|
||||
panel.profiles.list.push_back(std::move(profile));
|
||||
if (!store(panel)) {
|
||||
panel.profiles.list.pop_back();
|
||||
@@ -319,10 +319,14 @@ void profiles_page(const Fonts& fonts, ModsPanel& panel, HWND window, float heig
|
||||
|
||||
const profiles::Profile* using_now = nullptr;
|
||||
for (const auto& profile : panel.profiles.list)
|
||||
if (!using_now && profiles::in_use(profile, panel.list)) using_now = &profile;
|
||||
if (!using_now && profiles::in_use(profile, panel.list, panel.profiles.keep_others)) using_now = &profile;
|
||||
page_banner(fonts, "PROFILES", nullptr,
|
||||
"A profile remembers which of your mods are on and in what order. Every mod stays installed once; loading a "
|
||||
"profile switches the rest off. Share one as a code or a file, with ReSkate or r2modman.",
|
||||
panel.profiles.keep_others
|
||||
? "A profile remembers which of your mods are on and in what order. Every mod stays installed once; loading a "
|
||||
"profile switches its mods on and leaves your other mods as they are. Share one as a code or a file, with "
|
||||
"ReSkate or r2modman."
|
||||
: "A profile remembers which of your mods are on and in what order. Every mod stays installed once; loading a "
|
||||
"profile switches the rest off. Share one as a code or a file, with ReSkate or r2modman.",
|
||||
using_now ? "IN USE: " + using_now->name : "NO PROFILE IN USE", using_now ? color::good : color::tile_grey,
|
||||
std::format("{} of {} saved", panel.profiles.list.size(), profiles::max_profiles));
|
||||
|
||||
@@ -390,7 +394,13 @@ void profiles_page(const Fonts& fonts, ModsPanel& panel, HWND window, float heig
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(start.x, start.y + tall + S(12)));
|
||||
ImGui::Dummy(ImVec2(0, 0));
|
||||
ImGui::BeginDisabled(busy);
|
||||
if (ImGui::Checkbox("Keep my other mods on when loading a profile", &panel.profiles.keep_others)) store(panel);
|
||||
ImGui::EndDisabled();
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
||||
ImGui::SetTooltip("On: a profile switches its own mods on and leaves the rest as they are.\n"
|
||||
"Off: a profile switches every mod it does not list off.");
|
||||
ImGui::Dummy(ImVec2(0, S(4)));
|
||||
}
|
||||
|
||||
// ------------------------------------------------ the profiles, a card each
|
||||
@@ -408,8 +418,8 @@ void profiles_page(const Fonts& fonts, ModsPanel& panel, HWND window, float heig
|
||||
const auto& profile = panel.profiles.list[index];
|
||||
ImGui::PushID(static_cast<int>(index));
|
||||
const auto on = std::count_if(profile.mods.begin(), profile.mods.end(), [](const auto& mod) { return mod.enabled; });
|
||||
const auto missing = profiles::apply(profile, panel.list).missing.size();
|
||||
const bool current = profiles::in_use(profile, panel.list);
|
||||
const auto missing = profiles::apply(profile, panel.list, panel.profiles.keep_others).missing.size();
|
||||
const bool current = profiles::in_use(profile, panel.list, panel.profiles.keep_others);
|
||||
const bool last = !current && profile.name == panel.profiles.active;
|
||||
std::vector<std::string> names;
|
||||
for (const auto& mod : profile.mods)
|
||||
|
||||
@@ -228,6 +228,7 @@ Profiles load(const fs::path& mods_root) noexcept {
|
||||
}
|
||||
profiles.active = root.value("active", "");
|
||||
if (!profiles.find(profiles.active)) profiles.active.clear();
|
||||
profiles.keep_others = root.value("keep_others", false);
|
||||
} catch (...) {
|
||||
profiles = {};
|
||||
}
|
||||
@@ -252,6 +253,7 @@ void save(const fs::path& mods_root, const Profiles& profiles) {
|
||||
}
|
||||
Json root = Json::object();
|
||||
root["active"] = profiles.active;
|
||||
root["keep_others"] = profiles.keep_others;
|
||||
root["profiles"] = std::move(list);
|
||||
const auto target = mods_root / profiles_file;
|
||||
auto temporary = target;
|
||||
@@ -280,7 +282,7 @@ Profile capture(std::string name, const mods::ModList& list) {
|
||||
return profile;
|
||||
}
|
||||
|
||||
Applied apply(const Profile& profile, const mods::ModList& list) {
|
||||
Applied apply(const Profile& profile, const mods::ModList& list, bool keep_others) {
|
||||
Applied result;
|
||||
std::map<std::string, const mods::ModEntry*> installed;
|
||||
for (const auto& entry : list.entries) installed.emplace(folded(entry.mod.name), &entry);
|
||||
@@ -299,13 +301,13 @@ Applied apply(const Profile& profile, const mods::ModList& list) {
|
||||
for (const auto& entry : list.entries)
|
||||
if (!used.contains(folded(entry.mod.name))) {
|
||||
result.entries.push_back(entry);
|
||||
result.entries.back().enabled = false;
|
||||
if (!keep_others) result.entries.back().enabled = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool in_use(const Profile& profile, const mods::ModList& list) {
|
||||
const auto applied = apply(profile, list);
|
||||
bool in_use(const Profile& profile, const mods::ModList& list, bool keep_others) {
|
||||
const auto applied = apply(profile, list, keep_others);
|
||||
if (!applied.missing.empty()) return false;
|
||||
const auto enabled = [](const std::vector<mods::ModEntry>& entries) {
|
||||
std::vector<std::string> names;
|
||||
|
||||
@@ -34,6 +34,9 @@ struct Profile {
|
||||
struct Profiles {
|
||||
std::string active; // the profile last loaded or saved; empty for none
|
||||
std::vector<Profile> list;
|
||||
// Loading a profile leaves the mods it does not name as they are, instead of switching them off:
|
||||
// a profile then adds its mods to the player's own.
|
||||
bool keep_others{};
|
||||
|
||||
Profile* find(std::string_view name);
|
||||
// `wanted`, or `wanted (2)`, `wanted (3)`... when a profile already has that name.
|
||||
@@ -59,14 +62,16 @@ Profile capture(std::string name, const mods::ModList& list);
|
||||
|
||||
struct Applied {
|
||||
// What mods.json should hold: the profile's installed mods in its order with its
|
||||
// switches, then every installed mod it does not name, switched off.
|
||||
// switches, then every installed mod it does not name, switched off (or left as it is
|
||||
// with keep_others).
|
||||
std::vector<mods::ModEntry> entries;
|
||||
// The mods it switches on that are not installed, to be downloaded.
|
||||
std::vector<ProfileMod> missing;
|
||||
};
|
||||
Applied apply(const Profile& profile, const mods::ModList& list);
|
||||
Applied apply(const Profile& profile, const mods::ModList& list, bool keep_others = false);
|
||||
// True when loading `profile` would change nothing: the same mods on, in the same order.
|
||||
bool in_use(const Profile& profile, const mods::ModList& list);
|
||||
// With keep_others only the profile's own mods count.
|
||||
bool in_use(const Profile& profile, const mods::ModList& list, bool keep_others = false);
|
||||
|
||||
// export.r2x. parse_export throws a readable message for text that is not one.
|
||||
std::string export_text(const Profile& profile);
|
||||
|
||||
Reference in new issue
Block a user