show outdated mods in the UI + make outdated Geode ver count for that too

This commit is contained in:
HJfod 2024-11-12 01:03:19 +02:00
parent f5f336532f
commit 09fa872781
12 changed files with 134 additions and 66 deletions

View file

@ -46,6 +46,21 @@ namespace geode {
Type type;
std::variant<std::filesystem::path, ModMetadata, Mod*> cause;
std::string message;
bool isSuggestion() const {
return
type == LoadProblem::Type::Recommendation ||
type == LoadProblem::Type::Suggestion;
}
bool isOutdated() const {
return
type == LoadProblem::Type::UnsupportedVersion ||
type == LoadProblem::Type::NeedsNewerGeodeVersion ||
type == LoadProblem::Type::UnsupportedGeodeVersion;
}
bool isProblem() const {
return !isSuggestion() && !isOutdated();
}
};
class LoaderImpl;

View file

@ -446,7 +446,16 @@ namespace geode {
bool isLoggingEnabled() const;
void setLoggingEnabled(bool enabled);
bool targetsOutdatedGDVersion() const;
/**
* If this mod is built for an outdated GD or Geode version, returns the
* `LoadProblem` describing the situation. Otherwise `nullopt` if the
* mod is made for the correct version of the game and Geode
*/
std::optional<LoadProblem> targetsOutdatedVersion() const;
/**
* @note Make sure to also call `targetsOutdatedVersion` if you want to
* make sure the mod is actually loadable
*/
bool hasLoadProblems() const;
std::vector<LoadProblem> getAllProblems() const;
std::vector<LoadProblem> getProblems() const;

View file

@ -46,7 +46,7 @@ void crashlog::printMods(std::stringstream& stream) {
mod->isCurrentlyLoading() ? "o"sv :
mod->isEnabled() ? "x"sv :
mod->hasLoadProblems() ? "!"sv : // thank you for this bug report
mod->targetsOutdatedGDVersion() ? "*"sv : // thank you very much for this bug report
mod->targetsOutdatedVersion() ? "*"sv : // thank you very much for this bug report
mod->shouldLoad() ? "~"sv :
" "sv,
mod->getVersion().toVString(), mod->getID()

View file

@ -71,11 +71,7 @@ std::vector<LoadProblem> Loader::getAllProblems() const {
std::vector<LoadProblem> Loader::getLoadProblems() const {
std::vector<LoadProblem> result;
for (auto problem : this->getAllProblems()) {
if (
problem.type != LoadProblem::Type::Recommendation &&
problem.type != LoadProblem::Type::Suggestion &&
problem.type != LoadProblem::Type::UnsupportedVersion
) {
if (problem.isProblem()) {
result.push_back(problem);
}
}
@ -84,7 +80,7 @@ std::vector<LoadProblem> Loader::getLoadProblems() const {
std::vector<LoadProblem> Loader::getOutdated() const {
std::vector<LoadProblem> result;
for (auto problem : this->getAllProblems()) {
if (problem.type == LoadProblem::Type::UnsupportedVersion) {
if (problem.isOutdated()) {
result.push_back(problem);
}
}
@ -93,10 +89,7 @@ std::vector<LoadProblem> Loader::getOutdated() const {
std::vector<LoadProblem> Loader::getRecommendations() const {
std::vector<LoadProblem> result;
for (auto problem : this->getAllProblems()) {
if (
problem.type == LoadProblem::Type::Recommendation ||
problem.type == LoadProblem::Type::Suggestion
) {
if (problem.isSuggestion()) {
result.push_back(problem);
}
}

View file

@ -384,6 +384,37 @@ void Loader::Impl::buildModGraph() {
}
void Loader::Impl::loadModGraph(Mod* node, bool early) {
// Check version first, as it's not worth trying to load a mod with an
// invalid target version
// Also this makes it so that when GD updates, outdated mods get shown as
// "Outdated" in the UI instead of "Missing Dependencies"
auto res = node->getMetadata().checkGameVersion();
if (!res) {
this->addProblem({
LoadProblem::Type::UnsupportedVersion,
node,
res.unwrapErr()
});
log::error("{}", res.unwrapErr());
log::popNest();
return;
}
if (!this->isModVersionSupported(node->getMetadata().getGeodeVersion())) {
this->addProblem({
node->getMetadata().getGeodeVersion() > this->getVersion() ? LoadProblem::Type::NeedsNewerGeodeVersion : LoadProblem::Type::UnsupportedGeodeVersion,
node,
fmt::format(
"Geode version {}\nis required to run this mod\n(installed: {})",
node->getMetadata().getGeodeVersion().toVString(),
this->getVersion().toVString()
)
});
log::error("Unsupported Geode version: {}", node->getMetadata().getGeodeVersion());
log::popNest();
return;
}
if (node->hasUnresolvedDependencies()) {
log::debug("{} {} has unresolved dependencies", node->getID(), node->getVersion());
return;
@ -444,35 +475,6 @@ void Loader::Impl::loadModGraph(Mod* node, bool early) {
log::popNest();
return;
}
auto res = node->getMetadata().checkGameVersion();
if (!res) {
this->addProblem({
LoadProblem::Type::UnsupportedVersion,
node,
res.unwrapErr()
});
log::error("{}", res.unwrapErr());
m_refreshingModCount -= 1;
log::popNest();
return;
}
if (!this->isModVersionSupported(node->getMetadata().getGeodeVersion())) {
this->addProblem({
node->getMetadata().getGeodeVersion() > this->getVersion() ? LoadProblem::Type::NeedsNewerGeodeVersion : LoadProblem::Type::UnsupportedGeodeVersion,
node,
fmt::format(
"Geode version {}\nis required to run this mod\n(installed: {})",
node->getMetadata().getGeodeVersion().toVString(),
this->getVersion().toVString()
)
});
log::error("Unsupported Geode version: {}", node->getMetadata().getGeodeVersion());
m_refreshingModCount -= 1;
log::popNest();
return;
}
}
if (early) {
@ -524,6 +526,10 @@ void Loader::Impl::findProblems() {
log::debug("{} is not enabled", id);
continue;
}
if (mod->targetsOutdatedVersion()) {
log::debug("{} is outdated", id);
continue;
}
log::debug("{}", id);
log::pushNest();

View file

@ -255,13 +255,13 @@ bool Mod::hasSavedValue(std::string_view key) {
bool Mod::hasLoadProblems() const {
return m_impl->hasLoadProblems();
}
bool Mod::targetsOutdatedGDVersion() const {
std::optional<LoadProblem> Mod::targetsOutdatedVersion() const {
for (auto problem : this->getAllProblems()) {
if (problem.type == LoadProblem::Type::UnsupportedVersion) {
return true;
if (problem.isOutdated()) {
return problem;
}
}
return false;
return std::nullopt;
}
std::vector<LoadProblem> Mod::getAllProblems() const {
return m_impl->getProblems();
@ -269,11 +269,7 @@ std::vector<LoadProblem> Mod::getAllProblems() const {
std::vector<LoadProblem> Mod::getProblems() const {
std::vector<LoadProblem> result;
for (auto problem : this->getAllProblems()) {
if (
problem.type != LoadProblem::Type::Recommendation &&
problem.type != LoadProblem::Type::Suggestion &&
problem.type != LoadProblem::Type::UnsupportedVersion
) {
if (problem.isProblem()) {
result.push_back(problem);
}
}
@ -282,10 +278,7 @@ std::vector<LoadProblem> Mod::getProblems() const {
std::vector<LoadProblem> Mod::getRecommendations() const {
std::vector<LoadProblem> result;
for (auto problem : this->getAllProblems()) {
if (
problem.type == LoadProblem::Type::Recommendation ||
problem.type == LoadProblem::Type::Suggestion
) {
if (problem.isSuggestion()) {
result.push_back(problem);
}
}

View file

@ -706,11 +706,7 @@ bool Mod::Impl::isCurrentlyLoading() const {
bool Mod::Impl::hasLoadProblems() const {
for (auto const& problem : m_problems) {
if (
problem.type != LoadProblem::Type::Recommendation &&
problem.type != LoadProblem::Type::Suggestion &&
problem.type != LoadProblem::Type::UnsupportedVersion
) {
if (problem.isProblem()) {
return true;
}
}

View file

@ -12,6 +12,8 @@ $on_mod(Loaded) {
ColorProvider::get()->define("mod-list-version-label-updates-available"_spr, ccc3(88, 202, 255));
ColorProvider::get()->define("mod-list-restart-required-label"_spr, ccc3(153, 245, 245));
ColorProvider::get()->define("mod-list-restart-required-label-bg"_spr, ccc3(123, 156, 163));
ColorProvider::get()->define("mod-list-outdated-label"_spr, ccc3(245, 153, 245));
ColorProvider::get()->define("mod-list-outdated-label-bg"_spr, ccc3(156, 123, 163));
ColorProvider::get()->define("mod-list-search-bg"_spr, { 83, 65, 109, 255 });
ColorProvider::get()->define("mod-list-updates-available-bg"_spr, { 139, 89, 173, 255 });
ColorProvider::get()->define("mod-list-updates-available-bg-2"_spr, { 45, 110, 222, 255 });

View file

@ -95,6 +95,17 @@ bool ModItem::init(ModSource&& source) {
m_restartRequiredLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(std::nullopt, .75f));
m_infoContainer->addChild(m_restartRequiredLabel);
m_outdatedLabel = createTagLabel(
fmt::format("Outdated (GD {})", m_source.getMetadata().getGameVersion().value_or("*")),
{
to3B(ColorProvider::get()->color("mod-list-outdated-label"_spr)),
to3B(ColorProvider::get()->color("mod-list-outdated-label-bg"_spr))
}
);
m_outdatedLabel->setID("outdated-label");
m_outdatedLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(std::nullopt, .75f));
m_infoContainer->addChild(m_outdatedLabel);
m_downloadBarContainer = CCNode::create();
m_downloadBarContainer->setID("download-bar-container");
m_downloadBarContainer->setContentSize({ 320, 30 });
@ -185,7 +196,7 @@ bool ModItem::init(ModSource&& source) {
m_viewMenu->addChild(m_enableToggle);
m_viewMenu->updateLayout();
}
if (mod->hasLoadProblems() || mod->targetsOutdatedGDVersion()) {
if (mod->hasLoadProblems() || mod->targetsOutdatedVersion()) {
auto viewErrorSpr = createGeodeCircleButton(
CCSprite::createWithSpriteFrameName("exclamation.png"_spr), 1.f,
CircleBaseSize::Small
@ -342,7 +353,6 @@ void ModItem::updateState() {
m_downloadBarContainer->setVisible(false);
m_downloadWaiting->setVisible(false);
}
m_infoContainer->updateLayout();
// Set default colors based on source to start off with
// (possibly overriding later based on state)
@ -410,16 +420,22 @@ void ModItem::updateState() {
m_titleContainer->updateLayout();
// If there were problems, tint the BG red
m_outdatedLabel->setVisible(false);
if (m_source.asMod()) {
if (m_source.asMod()->hasLoadProblems()) {
m_bg->setColor("mod-list-errors-found"_cc3b);
m_bg->setOpacity(isGeodeTheme() ? 25 : 90);
}
if (m_source.asMod()->targetsOutdatedGDVersion()) {
m_bg->setOpacity(isGeodeTheme() ? 0 : 0);
if (m_source.asMod()->targetsOutdatedVersion()) {
m_bg->setColor("mod-list-outdated-label"_cc3b);
m_bg->setOpacity(isGeodeTheme() ? 25 : 90);
m_outdatedLabel->setVisible(true);
m_developers->setVisible(false);
}
}
m_infoContainer->updateLayout();
// Highlight item via BG if it wants to restart for extra UI attention
if (wantsRestart) {
m_bg->setColor("mod-list-restart-required-label"_cc3b);
@ -541,8 +557,39 @@ void ModItem::onView(CCObject*) {
}
void ModItem::onViewError(CCObject*) {
if (auto mod = m_source.asMod()) {
if (auto problem = mod->targetsOutdatedVersion()) {
std::string issue;
std::string howToFix;
switch (problem->type) {
default:
case LoadProblem::Type::UnsupportedVersion: {
issue = fmt::format("<cy>{}</c>", problem->message);
howToFix = "wait for the developer to <cj>release an update to "
"the mod</c> that supports the newer version.";
} break;
case LoadProblem::Type::UnsupportedGeodeVersion: {
issue = "This mod is made for a <cp>newer version of Geode</c>.";
howToFix = "<cp>update Geode</c> by enabling <co>Automatic Updates</c> "
"or redownloading it from the Geode website.";
} break;
case LoadProblem::Type::NeedsNewerGeodeVersion: {
issue = "This mod is made for an <cy>older version of Geode</c>.";
howToFix = "wait for the developer to <cj>release an update to "
"the mod</c> that supports the newer version.";
} break;
}
FLAlertLayer::create(
"Outdated",
fmt::format("{} Please {}", issue, howToFix),
"OK"
)->show();
}
else {
ModErrorPopup::create(mod)->show();
}
}
}
void ModItem::onEnable(CCObject*) {
if (auto mod = m_source.asMod()) {

View file

@ -25,6 +25,7 @@ protected:
CCNode* m_recommendedBy;
CCLabelBMFont* m_developerLabel;
ButtonSprite* m_restartRequiredLabel;
ButtonSprite* m_outdatedLabel;
CCNode* m_downloadWaiting;
CCNode* m_downloadBarContainer;
Slider* m_downloadBar;

View file

@ -13,7 +13,7 @@ bool InstalledModsQuery::preCheck(ModSource const& src) const {
}
// If only errors requested, only show mods with errors (duh)
if (type == InstalledModListType::OnlyOutdated) {
return src.asMod()->targetsOutdatedGDVersion();
return src.asMod()->targetsOutdatedVersion().has_value();
}
if (type == InstalledModListType::OnlyErrors) {
return src.asMod()->hasLoadProblems();

View file

@ -230,7 +230,13 @@ void filterModsWithLocalQuery(ModListSource::ProvidedMods& mods, Query const& qu
if (a.second != b.second) {
return a.second > b.second;
}
// Sort secondarily alphabetically
// Make sure outdated mods are always last by default
auto aIsOutdated = a.first.getMetadata().checkGameVersion().isErr();
auto bIsOutdated = b.first.getMetadata().checkGameVersion().isErr();
if (aIsOutdated != bIsOutdated) {
return !aIsOutdated;
}
// Fallback sort alphabetically
return utils::string::caseInsensitiveCompare(
a.first.getMetadata().getName(),
b.first.getMetadata().getName()