Live mod picker + in-process reload. Lists the @ folders actually present in the user's mods folder, lets you tick a set, and re-mounts the game with exactly that set — only from the main menu (re-mounting mid-mission would evict assets the simulation still references).
| 1052 | // in the user's mods folder, lets you tick a set, and re-mounts the game with |
| 1053 | // exactly that set — only from the main menu (re-mounting mid-mission would evict |
| 1054 | // assets the simulation still references). |
| 1055 | void DrawModsTab() |
| 1056 | { |
| 1057 | namespace fs = std::filesystem; |
| 1058 | const std::string modsRoot = GamePaths::Instance().ModsDir(); |
| 1059 | |
| 1060 | ImGui::TextUnformatted("Mods folder (scanned live):"); |
| 1061 | ImGui::TextWrapped("%s", modsRoot.c_str()); |
| 1062 | ImGui::Separator(); |
| 1063 | |
| 1064 | // Checkbox state persists across frames: modId ("@foo") -> checked. |
| 1065 | static std::map<std::string, bool> s_modChecked; |
| 1066 | |
| 1067 | // Live scan for @<mod> folders each frame. |
| 1068 | std::vector<std::string> mods; |
| 1069 | std::error_code ec; |
| 1070 | if (fs::is_directory(modsRoot, ec)) |
| 1071 | { |
| 1072 | for (const auto& entry : fs::directory_iterator(modsRoot, ec)) |
| 1073 | { |
| 1074 | std::error_code dirEc; |
| 1075 | if (!entry.is_directory(dirEc)) |
| 1076 | continue; |
| 1077 | const std::string name = entry.path().filename().string(); |
| 1078 | if (!name.empty() && name[0] == '@') |
| 1079 | mods.push_back(name); |
| 1080 | } |
| 1081 | } |
| 1082 | std::sort(mods.begin(), mods.end()); |
| 1083 | |
| 1084 | if (mods.empty()) |
| 1085 | ImGui::TextDisabled("(no @<mod> folders here yet — drop one in and it shows up)"); |
| 1086 | |
| 1087 | for (const std::string& modId : mods) |
| 1088 | { |
| 1089 | auto it = s_modChecked.find(modId); |
| 1090 | bool checked = (it != s_modChecked.end()) ? it->second : false; |
| 1091 | if (ImGui::Checkbox(modId.c_str(), &checked) || it == s_modChecked.end()) |
| 1092 | s_modChecked[modId] = checked; |
| 1093 | } |
| 1094 | |
| 1095 | // Build the mod path from the checked set — semicolon-separated absolute |
| 1096 | // mod-folder paths (empty string = base game only). modsRoot ends with a sep. |
| 1097 | std::string modPath; |
| 1098 | for (const std::string& modId : mods) |
| 1099 | { |
| 1100 | auto it = s_modChecked.find(modId); |
| 1101 | if (it != s_modChecked.end() && it->second) |
| 1102 | { |
| 1103 | if (!modPath.empty()) |
| 1104 | modPath += ';'; |
| 1105 | modPath += modsRoot + modId; |
| 1106 | } |
| 1107 | } |
| 1108 | |
| 1109 | ImGui::Spacing(); |
| 1110 | ImGui::Separator(); |
| 1111 | ImGui::TextWrapped("Apply set: %s", modPath.empty() ? "(none — base game only)" : modPath.c_str()); |
no test coverage detected