mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-14 19:15:05 -05:00
Compare commits
14 commits
4ba8751c68
...
acad3d2a8d
Author | SHA1 | Date | |
---|---|---|---|
|
acad3d2a8d | ||
|
09fa872781 | ||
|
d415c949d0 | ||
|
c9e97af18a | ||
|
9d6b2954e5 | ||
|
bcb856a302 | ||
|
f5f336532f | ||
|
324883140a | ||
|
b1ab3eb373 | ||
|
6db3084062 | ||
|
673317d3cb | ||
|
9c1f48ee64 | ||
|
2940de38bc | ||
|
0c469b98fe |
21 changed files with 394 additions and 84 deletions
|
@ -189,13 +189,10 @@ include(cmake/Platform.cmake)
|
||||||
include(cmake/GeodeFile.cmake)
|
include(cmake/GeodeFile.cmake)
|
||||||
|
|
||||||
if (NOT DEFINED GEODE_GD_VERSION)
|
if (NOT DEFINED GEODE_GD_VERSION)
|
||||||
if (GEODE_TARGET_PLATFORM STREQUAL "Win64" OR GEODE_TARGET_PLATFORM STREQUAL "MacOS")
|
# this is incorrect!
|
||||||
set(GEODE_GD_VERSION 2.2073)
|
# Android and iOS are on 2.2074 so please fix when 2.2074 releases for desktop :(
|
||||||
set(GEODE_COMP_GD_VERSION 22073)
|
set(GEODE_GD_VERSION 2.2073)
|
||||||
else()
|
set(GEODE_COMP_GD_VERSION 22073)
|
||||||
set(GEODE_GD_VERSION 2.206)
|
|
||||||
set(GEODE_COMP_GD_VERSION 22060)
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_compile_definitions(
|
target_compile_definitions(
|
||||||
|
@ -241,8 +238,8 @@ if (ANDROID)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(MAT_JSON_AS_INTERFACE ON)
|
set(MAT_JSON_AS_INTERFACE ON)
|
||||||
CPMAddPackage("gh:geode-sdk/result@1.1.1")
|
CPMAddPackage("gh:geode-sdk/result@1.2.0")
|
||||||
CPMAddPackage("gh:geode-sdk/json@3.0.1")
|
CPMAddPackage("gh:geode-sdk/json@3.0.2")
|
||||||
CPMAddPackage("gh:fmtlib/fmt#11.0.2")
|
CPMAddPackage("gh:fmtlib/fmt#11.0.2")
|
||||||
|
|
||||||
target_compile_definitions(${PROJECT_NAME} INTERFACE MAT_JSON_DYNAMIC=1)
|
target_compile_definitions(${PROJECT_NAME} INTERFACE MAT_JSON_DYNAMIC=1)
|
||||||
|
@ -272,7 +269,7 @@ if (DEFINED GEODE_TULIPHOOK_REPO_PATH)
|
||||||
message(STATUS "Using ${GEODE_TULIPHOOK_REPO_PATH} for TulipHook")
|
message(STATUS "Using ${GEODE_TULIPHOOK_REPO_PATH} for TulipHook")
|
||||||
add_subdirectory(${GEODE_TULIPHOOK_REPO_PATH} ${GEODE_TULIPHOOK_REPO_PATH}/build)
|
add_subdirectory(${GEODE_TULIPHOOK_REPO_PATH} ${GEODE_TULIPHOOK_REPO_PATH}/build)
|
||||||
else()
|
else()
|
||||||
CPMAddPackage("gh:geode-sdk/TulipHook#4e5d607")
|
CPMAddPackage("gh:geode-sdk/TulipHook@2.4.0")
|
||||||
endif()
|
endif()
|
||||||
set(CMAKE_WARN_DEPRECATED ON CACHE BOOL "" FORCE)
|
set(CMAKE_WARN_DEPRECATED ON CACHE BOOL "" FORCE)
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,21 @@ namespace geode {
|
||||||
Type type;
|
Type type;
|
||||||
std::variant<std::filesystem::path, ModMetadata, Mod*> cause;
|
std::variant<std::filesystem::path, ModMetadata, Mod*> cause;
|
||||||
std::string message;
|
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;
|
class LoaderImpl;
|
||||||
|
|
|
@ -446,7 +446,16 @@ namespace geode {
|
||||||
bool isLoggingEnabled() const;
|
bool isLoggingEnabled() const;
|
||||||
void setLoggingEnabled(bool enabled);
|
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;
|
bool hasLoadProblems() const;
|
||||||
std::vector<LoadProblem> getAllProblems() const;
|
std::vector<LoadProblem> getAllProblems() const;
|
||||||
std::vector<LoadProblem> getProblems() const;
|
std::vector<LoadProblem> getProblems() const;
|
||||||
|
|
|
@ -104,8 +104,36 @@
|
||||||
} \
|
} \
|
||||||
} while (0);
|
} while (0);
|
||||||
|
|
||||||
namespace geode::modifier {
|
namespace geode {
|
||||||
|
class Priority {
|
||||||
|
public:
|
||||||
|
static inline constexpr int32_t First = -3000;
|
||||||
|
static inline constexpr int32_t VeryEarly = -2000;
|
||||||
|
static inline constexpr int32_t Early = -1000;
|
||||||
|
static inline constexpr int32_t Normal = 0;
|
||||||
|
static inline constexpr int32_t Late = 1000;
|
||||||
|
static inline constexpr int32_t VeryLate = 2000;
|
||||||
|
static inline constexpr int32_t Last = 3000;
|
||||||
|
|
||||||
|
static inline constexpr int32_t FirstPre = First;
|
||||||
|
static inline constexpr int32_t VeryEarlyPre = VeryEarly;
|
||||||
|
static inline constexpr int32_t EarlyPre = Early;
|
||||||
|
static inline constexpr int32_t NormalPre = Normal;
|
||||||
|
static inline constexpr int32_t LatePre = Late;
|
||||||
|
static inline constexpr int32_t VeryLatePre = VeryLate;
|
||||||
|
static inline constexpr int32_t LastPre = Last;
|
||||||
|
|
||||||
|
static inline constexpr int32_t FirstPost = Last;
|
||||||
|
static inline constexpr int32_t VeryEarlyPost = VeryLate;
|
||||||
|
static inline constexpr int32_t EarlyPost = Late;
|
||||||
|
static inline constexpr int32_t NormalPost = Normal;
|
||||||
|
static inline constexpr int32_t LatePost = Early;
|
||||||
|
static inline constexpr int32_t VeryLatePost = VeryEarly;
|
||||||
|
static inline constexpr int32_t LastPost = First;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace geode::modifier {
|
||||||
template <class Derived, class Base>
|
template <class Derived, class Base>
|
||||||
class ModifyDerive;
|
class ModifyDerive;
|
||||||
|
|
||||||
|
@ -114,6 +142,9 @@ namespace geode::modifier {
|
||||||
public:
|
public:
|
||||||
std::map<std::string, std::shared_ptr<Hook>> m_hooks;
|
std::map<std::string, std::shared_ptr<Hook>> m_hooks;
|
||||||
|
|
||||||
|
/// @brief Get a hook by name
|
||||||
|
/// @param name The name of the hook to get
|
||||||
|
/// @returns Ok if the hook was found, Err if the hook was not found
|
||||||
Result<Hook*> getHook(std::string const& name) {
|
Result<Hook*> getHook(std::string const& name) {
|
||||||
if (m_hooks.find(name) == m_hooks.end()) {
|
if (m_hooks.find(name) == m_hooks.end()) {
|
||||||
return Err("Hook not in this modify");
|
return Err("Hook not in this modify");
|
||||||
|
@ -121,15 +152,128 @@ namespace geode::modifier {
|
||||||
return Ok(m_hooks[name].get());
|
return Ok(m_hooks[name].get());
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<> setHookPriority(std::string const& name, int32_t priority) {
|
/// @brief Set the priority of a hook
|
||||||
auto res = this->getHook(name);
|
/// @param name The name of the hook to set the priority of
|
||||||
if (!res) {
|
/// @param priority The priority to set the hook to
|
||||||
return Err(res.unwrapErr());
|
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
|
||||||
}
|
Result<> setHookPriority(std::string const& name, int32_t priority = Priority::Normal) {
|
||||||
res.unwrap()->setPriority(priority);
|
GEODE_UNWRAP_INTO(auto hook, this->getHook(name));
|
||||||
|
hook->setPriority(priority);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief Set the priority of a hook
|
||||||
|
/// @param name The name of the hook to set the priority of
|
||||||
|
/// @param priority The priority to set the hook to
|
||||||
|
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
|
||||||
|
Result<> setHookPriorityPre(std::string const& name, int32_t priority = Priority::Normal) {
|
||||||
|
return this->setHookPriority(name, priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Set the priority of a hook
|
||||||
|
/// @param name The name of the hook to set the priority of
|
||||||
|
/// @param priority The priority to set the hook to
|
||||||
|
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
|
||||||
|
Result<> setHookPriorityPost(std::string const& name, int32_t priority = Priority::Normal) {
|
||||||
|
return this->setHookPriority(name, -priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Set the priority of a hook to be after another hook in different mods
|
||||||
|
/// @param name The name of the hook to set the priority of
|
||||||
|
/// @param after The mod ids of the mods to set the priority after
|
||||||
|
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
|
||||||
|
template<class... C> requires (std::is_convertible_v<C, std::string> && ...)
|
||||||
|
Result<> setHookPriorityAfter(std::string const& name, C&&... after) {
|
||||||
|
GEODE_UNWRAP_INTO(auto hook, this->getHook(name));
|
||||||
|
([&](){
|
||||||
|
auto mod = Loader::get()->getInstalledMod(after);
|
||||||
|
if (!mod) return;
|
||||||
|
auto hooks = mod->getHooks();
|
||||||
|
auto func = [=](){
|
||||||
|
for (auto modHook : hooks) {
|
||||||
|
if (modHook->getAddress() != hook->getAddress()) continue;
|
||||||
|
auto priority = hook->getPriority();
|
||||||
|
if (priority < modHook->getPriority()) {
|
||||||
|
hook->setPriority(modHook->getPriority() + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (Loader::get()->isModLoaded(mod)) {
|
||||||
|
func();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
new EventListener(func, ModStateFilter(mod, ModEventType::Loaded));
|
||||||
|
}
|
||||||
|
} (), ...);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Set the priority of a hook to be before another hook in different mods
|
||||||
|
/// @param name The name of the hook to set the priority of
|
||||||
|
/// @param before The mod ids of the mods to set the priority before
|
||||||
|
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
|
||||||
|
template<class... C> requires (std::is_convertible_v<C, std::string> && ...)
|
||||||
|
Result<> setHookPriorityBefore(std::string const& name, C&&... before) {
|
||||||
|
GEODE_UNWRAP_INTO(auto hook, this->getHook(name));
|
||||||
|
([&](){
|
||||||
|
auto mod = Loader::get()->getInstalledMod(before);
|
||||||
|
if (!mod) return;
|
||||||
|
auto hooks = mod->getHooks();
|
||||||
|
auto func = [=](){
|
||||||
|
for (auto modHook : hooks) {
|
||||||
|
if (modHook->getAddress() != hook->getAddress()) continue;
|
||||||
|
auto priority = hook->getPriority();
|
||||||
|
if (priority > modHook->getPriority()) {
|
||||||
|
hook->setPriority(modHook->getPriority() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (Loader::get()->isModLoaded(mod)) {
|
||||||
|
func();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
new EventListener(func, ModStateFilter(mod, ModEventType::Loaded));
|
||||||
|
}
|
||||||
|
} (), ...);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Set the priority of a hook to be after another hook in different mods
|
||||||
|
/// @param name The name of the hook to set the priority of
|
||||||
|
/// @param after The mod ids of the mods to set the priority after
|
||||||
|
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
|
||||||
|
template<class... C> requires (std::is_convertible_v<C, std::string> && ...)
|
||||||
|
Result<> setHookPriorityAfterPre(std::string const& name, C&&... after) {
|
||||||
|
return this->setHookPriorityAfter(name, Priority::NormalPre, std::forward<C>(after)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Set the priority of a hook to be before another hook in different mods
|
||||||
|
/// @param name The name of the hook to set the priority of
|
||||||
|
/// @param before The mod ids of the mods to set the priority before
|
||||||
|
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
|
||||||
|
template<class... C> requires (std::is_convertible_v<C, std::string> && ...)
|
||||||
|
Result<> setHookPriorityBeforePre(std::string const& name, C&&... before) {
|
||||||
|
return this->setHookPriorityBefore(name, Priority::NormalPre, std::forward<C>(before)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Set the priority of a hook to be after another hook in different mods
|
||||||
|
/// @param name The name of the hook to set the priority of
|
||||||
|
/// @param after The mod ids of the mods to set the priority after
|
||||||
|
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
|
||||||
|
template<class... C> requires (std::is_convertible_v<C, std::string> && ...)
|
||||||
|
Result<> setHookPriorityAfterPost(std::string const& name, C&&... after) {
|
||||||
|
return this->setHookPriorityBefore(name, Priority::NormalPost, std::forward<C>(after)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Set the priority of a hook to be before another hook in different mods
|
||||||
|
/// @param name The name of the hook to set the priority of
|
||||||
|
/// @param before The mod ids of the mods to set the priority before
|
||||||
|
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
|
||||||
|
template<class... C> requires (std::is_convertible_v<C, std::string> && ...)
|
||||||
|
Result<> setHookPriorityBeforePost(std::string const& name, C&&... before) {
|
||||||
|
return this->setHookPriorityAfter(name, Priority::NormalPost, std::forward<C>(before)...);
|
||||||
|
}
|
||||||
|
|
||||||
// unordered_map<handles> idea
|
// unordered_map<handles> idea
|
||||||
ModifyBase() {
|
ModifyBase() {
|
||||||
struct EboCheck : ModifyDerived::Base {
|
struct EboCheck : ModifyDerived::Base {
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
|
#pragma once
|
||||||
|
#include <Geode/modify/Modify.hpp>
|
||||||
#include "cocos.hpp"
|
#include "cocos.hpp"
|
||||||
|
|
||||||
namespace geode::node_ids {
|
namespace geode::node_ids {
|
||||||
using namespace cocos2d;
|
using namespace cocos2d;
|
||||||
|
|
||||||
static constexpr int32_t GEODE_ID_PRIORITY = 0x100000;
|
static constexpr int32_t GEODE_ID_PRIORITY = Priority::VeryEarlyPost;
|
||||||
|
|
||||||
template <class T = CCNode>
|
template <class T = CCNode>
|
||||||
requires std::is_base_of_v<CCNode, T>
|
requires std::is_base_of_v<CCNode, T>
|
||||||
|
|
|
@ -751,6 +751,93 @@ namespace geode {
|
||||||
this->listen(std::move(onResult), [](auto const&) {}, [] {});
|
this->listen(std::move(onResult), [](auto const&) {}, [] {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Task that listens to this Task and maps the values using
|
||||||
|
* the provided function. The new Task will only start when this Task finishes.
|
||||||
|
* @param mapper Function that makes a new task given the finished value of this task.
|
||||||
|
* The function signature should be `Task<NewType, NewProgress>(T*)`, and it will be executed
|
||||||
|
* on the main thread.
|
||||||
|
* @param name The name of the Task; used for debugging.
|
||||||
|
* @return The new Task that will run when this Task finishes.
|
||||||
|
* @note Progress from this task is not sent through, only progress from the new task is.
|
||||||
|
*/
|
||||||
|
template <std::invocable<T*> Mapper>
|
||||||
|
auto chain(Mapper mapper, std::string_view name = "<Chained Task>") const -> decltype(mapper(std::declval<T*>())) {
|
||||||
|
using NewTask = decltype(mapper(std::declval<T*>()));
|
||||||
|
using NewType = typename NewTask::Value;
|
||||||
|
using NewProgress = typename NewTask::Progress;
|
||||||
|
|
||||||
|
std::unique_lock<std::recursive_mutex> lock(m_handle->m_mutex);
|
||||||
|
|
||||||
|
if (m_handle->m_status == Status::Cancelled) {
|
||||||
|
// if the current task has been cancelled already, make an immediate cancelled task
|
||||||
|
return NewTask::cancelled();
|
||||||
|
}
|
||||||
|
else if (m_handle->m_status == Status::Finished) {
|
||||||
|
// if the current task is already done, we can just call the mapper directly
|
||||||
|
return mapper(&*m_handle->m_resultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, make a wrapper task that waits for the current task to finish,
|
||||||
|
// and then runs the mapper on the result. this new task will also wait for the task
|
||||||
|
// created by the mapper to finish, and will just forward the values through.
|
||||||
|
// do this because we cant really change the handle of the task we already returned
|
||||||
|
|
||||||
|
NewTask task = NewTask::Handle::create(fmt::format("{} <- {}", name, m_handle->m_name));
|
||||||
|
|
||||||
|
task.m_handle->m_extraData = std::make_unique<typename NewTask::Handle::ExtraData>(
|
||||||
|
// make the first event listener that waits for the current task
|
||||||
|
static_cast<void*>(new EventListener<Task>(
|
||||||
|
[handle = std::weak_ptr(task.m_handle), mapper = std::move(mapper)](Event* event) mutable {
|
||||||
|
if (auto v = event->getValue()) {
|
||||||
|
auto newInnerTask = mapper(v);
|
||||||
|
// this is scary.. but it doesn't seem to crash lol
|
||||||
|
handle.lock()->m_extraData = std::make_unique<typename NewTask::Handle::ExtraData>(
|
||||||
|
// make the second event listener that waits for the mapper's task
|
||||||
|
// and just forwards everything through
|
||||||
|
static_cast<void*>(new EventListener<NewTask>(
|
||||||
|
[handle](Event* event) mutable {
|
||||||
|
if (auto v = event->getValue()) {
|
||||||
|
NewTask::finish(handle.lock(), std::move(*v));
|
||||||
|
}
|
||||||
|
else if (auto p = event->getProgress()) {
|
||||||
|
NewTask::progress(handle.lock(), std::move(*p));
|
||||||
|
}
|
||||||
|
else if (event->isCancelled()) {
|
||||||
|
NewTask::cancel(handle.lock());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
std::move(newInnerTask)
|
||||||
|
)),
|
||||||
|
+[](void* ptr) {
|
||||||
|
delete static_cast<EventListener<NewTask>*>(ptr);
|
||||||
|
},
|
||||||
|
+[](void* ptr) {
|
||||||
|
static_cast<EventListener<NewTask>*>(ptr)->getFilter().cancel();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (auto p = event->getProgress()) {
|
||||||
|
// no guarantee P and NewProgress are compatible
|
||||||
|
// nor does it seem like the intended behavior?
|
||||||
|
// TODO: maybe add a mapper for progress?
|
||||||
|
}
|
||||||
|
else if (event->isCancelled()) {
|
||||||
|
NewTask::cancel(handle.lock());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
*this
|
||||||
|
)),
|
||||||
|
+[](void* ptr) {
|
||||||
|
delete static_cast<EventListener<Task>*>(ptr);
|
||||||
|
},
|
||||||
|
+[](void* ptr) {
|
||||||
|
static_cast<EventListener<Task>*>(ptr)->getFilter().cancel();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
ListenerResult handle(std::function<Callback> fn, Event* e) {
|
ListenerResult handle(std::function<Callback> fn, Event* e) {
|
||||||
if (e->m_handle == m_handle && (!e->m_for || e->m_for == m_listener)) {
|
if (e->m_handle == m_handle && (!e->m_for || e->m_for == m_listener)) {
|
||||||
fn(e);
|
fn(e);
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -75,6 +75,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
|
||||||
|
|
||||||
this->fixSocialMenu();
|
this->fixSocialMenu();
|
||||||
|
|
||||||
|
//this code doesnt run have fun figuring out why idc enough
|
||||||
if (auto node = this->getChildByID("settings-gamepad-icon")) {
|
if (auto node = this->getChildByID("settings-gamepad-icon")) {
|
||||||
node->setPositionX(
|
node->setPositionX(
|
||||||
bottomMenu->getChildByID("settings-button")->getPositionX() + winSize.width / 2
|
bottomMenu->getChildByID("settings-button")->getPositionX() + winSize.width / 2
|
||||||
|
|
|
@ -47,6 +47,11 @@ $register_ids(MenuLayer) {
|
||||||
|
|
||||||
setIDSafe<CCLabelBMFont>(this, labelOffset++, "player-username");
|
setIDSafe<CCLabelBMFont>(this, labelOffset++, "player-username");
|
||||||
|
|
||||||
|
if(auto node = this->getChildByID("settings-gamepad-icon")) {
|
||||||
|
// hide it until someone figures out how to bind the positioning to the actual button
|
||||||
|
node->setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
// main menu
|
// main menu
|
||||||
if (auto menu = this->getChildByType<CCMenu>(0)) {
|
if (auto menu = this->getChildByType<CCMenu>(0)) {
|
||||||
menu->setID("main-menu");
|
menu->setID("main-menu");
|
||||||
|
|
|
@ -46,7 +46,7 @@ void crashlog::printMods(std::stringstream& stream) {
|
||||||
mod->isCurrentlyLoading() ? "o"sv :
|
mod->isCurrentlyLoading() ? "o"sv :
|
||||||
mod->isEnabled() ? "x"sv :
|
mod->isEnabled() ? "x"sv :
|
||||||
mod->hasLoadProblems() ? "!"sv : // thank you for this bug report
|
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 :
|
mod->shouldLoad() ? "~"sv :
|
||||||
" "sv,
|
" "sv,
|
||||||
mod->getVersion().toVString(), mod->getID()
|
mod->getVersion().toVString(), mod->getID()
|
||||||
|
|
|
@ -71,11 +71,7 @@ std::vector<LoadProblem> Loader::getAllProblems() const {
|
||||||
std::vector<LoadProblem> Loader::getLoadProblems() const {
|
std::vector<LoadProblem> Loader::getLoadProblems() const {
|
||||||
std::vector<LoadProblem> result;
|
std::vector<LoadProblem> result;
|
||||||
for (auto problem : this->getAllProblems()) {
|
for (auto problem : this->getAllProblems()) {
|
||||||
if (
|
if (problem.isProblem()) {
|
||||||
problem.type != LoadProblem::Type::Recommendation &&
|
|
||||||
problem.type != LoadProblem::Type::Suggestion &&
|
|
||||||
problem.type != LoadProblem::Type::UnsupportedVersion
|
|
||||||
) {
|
|
||||||
result.push_back(problem);
|
result.push_back(problem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,7 +80,7 @@ std::vector<LoadProblem> Loader::getLoadProblems() const {
|
||||||
std::vector<LoadProblem> Loader::getOutdated() const {
|
std::vector<LoadProblem> Loader::getOutdated() const {
|
||||||
std::vector<LoadProblem> result;
|
std::vector<LoadProblem> result;
|
||||||
for (auto problem : this->getAllProblems()) {
|
for (auto problem : this->getAllProblems()) {
|
||||||
if (problem.type == LoadProblem::Type::UnsupportedVersion) {
|
if (problem.isOutdated()) {
|
||||||
result.push_back(problem);
|
result.push_back(problem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,10 +89,7 @@ std::vector<LoadProblem> Loader::getOutdated() const {
|
||||||
std::vector<LoadProblem> Loader::getRecommendations() const {
|
std::vector<LoadProblem> Loader::getRecommendations() const {
|
||||||
std::vector<LoadProblem> result;
|
std::vector<LoadProblem> result;
|
||||||
for (auto problem : this->getAllProblems()) {
|
for (auto problem : this->getAllProblems()) {
|
||||||
if (
|
if (problem.isSuggestion()) {
|
||||||
problem.type == LoadProblem::Type::Recommendation ||
|
|
||||||
problem.type == LoadProblem::Type::Suggestion
|
|
||||||
) {
|
|
||||||
result.push_back(problem);
|
result.push_back(problem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,11 @@ Loader::Impl::~Impl() = default;
|
||||||
// Initialization
|
// Initialization
|
||||||
|
|
||||||
bool Loader::Impl::isForwardCompatMode() {
|
bool Loader::Impl::isForwardCompatMode() {
|
||||||
|
#ifdef GEODE_IS_ANDROID
|
||||||
|
// forward compat mode doesn't really make sense on android
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!m_forwardCompatMode.has_value()) {
|
if (!m_forwardCompatMode.has_value()) {
|
||||||
m_forwardCompatMode = !this->getGameVersion().empty() &&
|
m_forwardCompatMode = !this->getGameVersion().empty() &&
|
||||||
this->getGameVersion() != GEODE_STR(GEODE_GD_VERSION);
|
this->getGameVersion() != GEODE_STR(GEODE_GD_VERSION);
|
||||||
|
@ -384,6 +389,37 @@ void Loader::Impl::buildModGraph() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Loader::Impl::loadModGraph(Mod* node, bool early) {
|
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()) {
|
if (node->hasUnresolvedDependencies()) {
|
||||||
log::debug("{} {} has unresolved dependencies", node->getID(), node->getVersion());
|
log::debug("{} {} has unresolved dependencies", node->getID(), node->getVersion());
|
||||||
return;
|
return;
|
||||||
|
@ -444,35 +480,6 @@ void Loader::Impl::loadModGraph(Mod* node, bool early) {
|
||||||
log::popNest();
|
log::popNest();
|
||||||
return;
|
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) {
|
if (early) {
|
||||||
|
@ -524,6 +531,10 @@ void Loader::Impl::findProblems() {
|
||||||
log::debug("{} is not enabled", id);
|
log::debug("{} is not enabled", id);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (mod->targetsOutdatedVersion()) {
|
||||||
|
log::debug("{} is outdated", id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
log::debug("{}", id);
|
log::debug("{}", id);
|
||||||
log::pushNest();
|
log::pushNest();
|
||||||
|
|
||||||
|
|
|
@ -255,13 +255,13 @@ bool Mod::hasSavedValue(std::string_view key) {
|
||||||
bool Mod::hasLoadProblems() const {
|
bool Mod::hasLoadProblems() const {
|
||||||
return m_impl->hasLoadProblems();
|
return m_impl->hasLoadProblems();
|
||||||
}
|
}
|
||||||
bool Mod::targetsOutdatedGDVersion() const {
|
std::optional<LoadProblem> Mod::targetsOutdatedVersion() const {
|
||||||
for (auto problem : this->getAllProblems()) {
|
for (auto problem : this->getAllProblems()) {
|
||||||
if (problem.type == LoadProblem::Type::UnsupportedVersion) {
|
if (problem.isOutdated()) {
|
||||||
return true;
|
return problem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
std::vector<LoadProblem> Mod::getAllProblems() const {
|
std::vector<LoadProblem> Mod::getAllProblems() const {
|
||||||
return m_impl->getProblems();
|
return m_impl->getProblems();
|
||||||
|
@ -269,11 +269,7 @@ std::vector<LoadProblem> Mod::getAllProblems() const {
|
||||||
std::vector<LoadProblem> Mod::getProblems() const {
|
std::vector<LoadProblem> Mod::getProblems() const {
|
||||||
std::vector<LoadProblem> result;
|
std::vector<LoadProblem> result;
|
||||||
for (auto problem : this->getAllProblems()) {
|
for (auto problem : this->getAllProblems()) {
|
||||||
if (
|
if (problem.isProblem()) {
|
||||||
problem.type != LoadProblem::Type::Recommendation &&
|
|
||||||
problem.type != LoadProblem::Type::Suggestion &&
|
|
||||||
problem.type != LoadProblem::Type::UnsupportedVersion
|
|
||||||
) {
|
|
||||||
result.push_back(problem);
|
result.push_back(problem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -282,10 +278,7 @@ std::vector<LoadProblem> Mod::getProblems() const {
|
||||||
std::vector<LoadProblem> Mod::getRecommendations() const {
|
std::vector<LoadProblem> Mod::getRecommendations() const {
|
||||||
std::vector<LoadProblem> result;
|
std::vector<LoadProblem> result;
|
||||||
for (auto problem : this->getAllProblems()) {
|
for (auto problem : this->getAllProblems()) {
|
||||||
if (
|
if (problem.isSuggestion()) {
|
||||||
problem.type == LoadProblem::Type::Recommendation ||
|
|
||||||
problem.type == LoadProblem::Type::Suggestion
|
|
||||||
) {
|
|
||||||
result.push_back(problem);
|
result.push_back(problem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -706,11 +706,7 @@ bool Mod::Impl::isCurrentlyLoading() const {
|
||||||
|
|
||||||
bool Mod::Impl::hasLoadProblems() const {
|
bool Mod::Impl::hasLoadProblems() const {
|
||||||
for (auto const& problem : m_problems) {
|
for (auto const& problem : m_problems) {
|
||||||
if (
|
if (problem.isProblem()) {
|
||||||
problem.type != LoadProblem::Type::Recommendation &&
|
|
||||||
problem.type != LoadProblem::Type::Suggestion &&
|
|
||||||
problem.type != LoadProblem::Type::UnsupportedVersion
|
|
||||||
) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ std::string Loader::Impl::getGameVersion() {
|
||||||
case 37: m_gdVersion = "2.200"; break;
|
case 37: m_gdVersion = "2.200"; break;
|
||||||
case 38: m_gdVersion = "2.205"; break;
|
case 38: m_gdVersion = "2.205"; break;
|
||||||
case 39: m_gdVersion = "2.206"; break;
|
case 39: m_gdVersion = "2.206"; break;
|
||||||
|
case 40: m_gdVersion = "2.2074"; break;
|
||||||
default: m_gdVersion = std::to_string(version_code);
|
default: m_gdVersion = std::to_string(version_code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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-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"_spr, ccc3(153, 245, 245));
|
||||||
ColorProvider::get()->define("mod-list-restart-required-label-bg"_spr, ccc3(123, 156, 163));
|
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-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"_spr, { 139, 89, 173, 255 });
|
||||||
ColorProvider::get()->define("mod-list-updates-available-bg-2"_spr, { 45, 110, 222, 255 });
|
ColorProvider::get()->define("mod-list-updates-available-bg-2"_spr, { 45, 110, 222, 255 });
|
||||||
|
|
|
@ -95,6 +95,17 @@ bool ModItem::init(ModSource&& source) {
|
||||||
m_restartRequiredLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(std::nullopt, .75f));
|
m_restartRequiredLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(std::nullopt, .75f));
|
||||||
m_infoContainer->addChild(m_restartRequiredLabel);
|
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 = CCNode::create();
|
||||||
m_downloadBarContainer->setID("download-bar-container");
|
m_downloadBarContainer->setID("download-bar-container");
|
||||||
m_downloadBarContainer->setContentSize({ 320, 30 });
|
m_downloadBarContainer->setContentSize({ 320, 30 });
|
||||||
|
@ -185,7 +196,7 @@ bool ModItem::init(ModSource&& source) {
|
||||||
m_viewMenu->addChild(m_enableToggle);
|
m_viewMenu->addChild(m_enableToggle);
|
||||||
m_viewMenu->updateLayout();
|
m_viewMenu->updateLayout();
|
||||||
}
|
}
|
||||||
if (mod->hasLoadProblems() || mod->targetsOutdatedGDVersion()) {
|
if (mod->hasLoadProblems() || mod->targetsOutdatedVersion()) {
|
||||||
auto viewErrorSpr = createGeodeCircleButton(
|
auto viewErrorSpr = createGeodeCircleButton(
|
||||||
CCSprite::createWithSpriteFrameName("exclamation.png"_spr), 1.f,
|
CCSprite::createWithSpriteFrameName("exclamation.png"_spr), 1.f,
|
||||||
CircleBaseSize::Small
|
CircleBaseSize::Small
|
||||||
|
@ -342,7 +353,6 @@ void ModItem::updateState() {
|
||||||
m_downloadBarContainer->setVisible(false);
|
m_downloadBarContainer->setVisible(false);
|
||||||
m_downloadWaiting->setVisible(false);
|
m_downloadWaiting->setVisible(false);
|
||||||
}
|
}
|
||||||
m_infoContainer->updateLayout();
|
|
||||||
|
|
||||||
// Set default colors based on source to start off with
|
// Set default colors based on source to start off with
|
||||||
// (possibly overriding later based on state)
|
// (possibly overriding later based on state)
|
||||||
|
@ -410,16 +420,22 @@ void ModItem::updateState() {
|
||||||
m_titleContainer->updateLayout();
|
m_titleContainer->updateLayout();
|
||||||
|
|
||||||
// If there were problems, tint the BG red
|
// If there were problems, tint the BG red
|
||||||
|
m_outdatedLabel->setVisible(false);
|
||||||
if (m_source.asMod()) {
|
if (m_source.asMod()) {
|
||||||
if (m_source.asMod()->hasLoadProblems()) {
|
if (m_source.asMod()->hasLoadProblems()) {
|
||||||
m_bg->setColor("mod-list-errors-found"_cc3b);
|
m_bg->setColor("mod-list-errors-found"_cc3b);
|
||||||
m_bg->setOpacity(isGeodeTheme() ? 25 : 90);
|
m_bg->setOpacity(isGeodeTheme() ? 25 : 90);
|
||||||
}
|
}
|
||||||
if (m_source.asMod()->targetsOutdatedGDVersion()) {
|
if (m_source.asMod()->targetsOutdatedVersion()) {
|
||||||
m_bg->setOpacity(isGeodeTheme() ? 0 : 0);
|
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
|
// Highlight item via BG if it wants to restart for extra UI attention
|
||||||
if (wantsRestart) {
|
if (wantsRestart) {
|
||||||
m_bg->setColor("mod-list-restart-required-label"_cc3b);
|
m_bg->setColor("mod-list-restart-required-label"_cc3b);
|
||||||
|
@ -541,7 +557,38 @@ void ModItem::onView(CCObject*) {
|
||||||
}
|
}
|
||||||
void ModItem::onViewError(CCObject*) {
|
void ModItem::onViewError(CCObject*) {
|
||||||
if (auto mod = m_source.asMod()) {
|
if (auto mod = m_source.asMod()) {
|
||||||
ModErrorPopup::create(mod)->show();
|
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*) {
|
void ModItem::onEnable(CCObject*) {
|
||||||
|
|
|
@ -25,6 +25,7 @@ protected:
|
||||||
CCNode* m_recommendedBy;
|
CCNode* m_recommendedBy;
|
||||||
CCLabelBMFont* m_developerLabel;
|
CCLabelBMFont* m_developerLabel;
|
||||||
ButtonSprite* m_restartRequiredLabel;
|
ButtonSprite* m_restartRequiredLabel;
|
||||||
|
ButtonSprite* m_outdatedLabel;
|
||||||
CCNode* m_downloadWaiting;
|
CCNode* m_downloadWaiting;
|
||||||
CCNode* m_downloadBarContainer;
|
CCNode* m_downloadBarContainer;
|
||||||
Slider* m_downloadBar;
|
Slider* m_downloadBar;
|
||||||
|
|
|
@ -13,7 +13,7 @@ bool InstalledModsQuery::preCheck(ModSource const& src) const {
|
||||||
}
|
}
|
||||||
// If only errors requested, only show mods with errors (duh)
|
// If only errors requested, only show mods with errors (duh)
|
||||||
if (type == InstalledModListType::OnlyOutdated) {
|
if (type == InstalledModListType::OnlyOutdated) {
|
||||||
return src.asMod()->targetsOutdatedGDVersion();
|
return src.asMod()->targetsOutdatedVersion().has_value();
|
||||||
}
|
}
|
||||||
if (type == InstalledModListType::OnlyErrors) {
|
if (type == InstalledModListType::OnlyErrors) {
|
||||||
return src.asMod()->hasLoadProblems();
|
return src.asMod()->hasLoadProblems();
|
||||||
|
|
|
@ -230,7 +230,13 @@ void filterModsWithLocalQuery(ModListSource::ProvidedMods& mods, Query const& qu
|
||||||
if (a.second != b.second) {
|
if (a.second != b.second) {
|
||||||
return 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(
|
return utils::string::caseInsensitiveCompare(
|
||||||
a.first.getMetadata().getName(),
|
a.first.getMetadata().getName(),
|
||||||
b.first.getMetadata().getName()
|
b.first.getMetadata().getName()
|
||||||
|
|
Loading…
Reference in a new issue