Compare commits

...

76 commits

Author SHA1 Message Date
Justin
46f70cf4a5
Merge 4a40835f71 into 47f0b555f3 2024-11-12 19:26:58 -05:00
mat
47f0b555f3
Merge pull request #1144 from geode-sdk/v4
Geode 4.0.0
2024-11-12 21:13:07 -03:00
mat
708e6dc7f2
disable macos build for now
Some checks failed
Build Binaries / Build Windows (push) Has been cancelled
Build Binaries / Build macOS (push) Has been cancelled
Build Binaries / Build Android (64-bit) (push) Has been cancelled
Build Binaries / Build Android (32-bit) (push) Has been cancelled
Build Binaries / Publish (push) Has been cancelled
2024-11-12 20:17:07 -03:00
Fleeym
f96ea5e727 fix: write it a bit better
Some checks are pending
Build Binaries / Build Windows (push) Waiting to run
Build Binaries / Build macOS (push) Waiting to run
Build Binaries / Build Android (64-bit) (push) Waiting to run
Build Binaries / Build Android (32-bit) (push) Waiting to run
Build Binaries / Publish (push) Blocked by required conditions
2024-11-13 00:24:01 +02:00
Fleeym
0b2fc66a89 fix: don't show outdated if wantsRestart 2024-11-13 00:20:49 +02:00
Fleeym
df2528c8a5 fix: fix ModItem only displaying outdated GD 2024-11-13 00:11:01 +02:00
Fleeym
03a4387c80 bump version to v4.0.0-alpha.1 2024-11-12 23:47:56 +02:00
HJfod
5c85b3c48d Merge branch 'v4' of https://github.com/geode-sdk/geode into v4 2024-11-12 23:05:10 +02:00
HJfod
123b3abff3 somes settings UI fixes 2024-11-12 23:05:03 +02:00
Cvolton
6d13f7837d
fix outdated geode error swap 2024-11-12 21:12:05 +01:00
Cvolton
9b95301472
also show the loading circle on the former progress bar condition 2024-11-12 20:37:47 +01:00
Cvolton
02845d967e
disable the modlist slider 2024-11-12 20:35:01 +01:00
Cvolton
2d66279243
limit geode mod list error to the frame 2024-11-12 20:17:01 +01:00
Cvolton
b662aac29d
update messageboxfix to 2.2074 2024-11-12 19:56:15 +01:00
matcool
c197e2913c update libraries 2024-11-12 14:39:08 -03:00
matcool
ab196b9adf allow sending progress from Task coroutines by using co_yield 2024-11-12 12:23:50 -03:00
Oleksandr Nemesh
0ee9aebdee
add 2.2074 timestamp (#1143)
Some checks are pending
Build Binaries / Build Windows (push) Waiting to run
Build Binaries / Build macOS (push) Waiting to run
Build Binaries / Build Android (64-bit) (push) Waiting to run
Build Binaries / Build Android (32-bit) (push) Waiting to run
Build Binaries / Publish (push) Blocked by required conditions
2024-11-12 12:53:15 +01:00
matcool
00cc510d5b fix typename 2024-11-12 02:41:35 -03:00
matcool
e61b2c0595 co_await support for geode Task
see comment at the bottom of the header for more information
2024-11-12 02:32:05 -03:00
HJfod
acad3d2a8d Merge branch 'v4' of https://github.com/geode-sdk/geode into v4
Some checks are pending
Build Binaries / Build Windows (push) Waiting to run
Build Binaries / Build macOS (push) Waiting to run
Build Binaries / Build Android (64-bit) (push) Waiting to run
Build Binaries / Build Android (32-bit) (push) Waiting to run
Build Binaries / Publish (push) Blocked by required conditions
2024-11-12 01:03:37 +02:00
HJfod
09fa872781 show outdated mods in the UI + make outdated Geode ver count for that too 2024-11-12 01:03:19 +02:00
Chloe
d415c949d0
add android gameversion to mappings 2024-11-11 15:53:26 -07:00
Chloe
c9e97af18a
disable forward compat mode on android 2024-11-11 15:53:02 -07:00
Chloe
9d6b2954e5
ok there 2024-11-11 15:21:34 -07:00
Chloe
bcb856a302
update android binaries 2024-11-11 15:14:32 -07:00
Cvolton
f5f336532f
fix the stupid misaligned controller button 2024-11-11 21:36:39 +01:00
matcool
324883140a add Task::chain 2024-11-11 16:43:38 -03:00
altalk23
b1ab3eb373 thank you mat 2024-11-11 22:43:04 +03:00
altalk23
6db3084062 priorities production ready (not tested) 2024-11-11 22:40:30 +03:00
altalk23
673317d3cb hook priority changes with pre/post and Priority class for static priority values 2024-11-11 22:38:03 +03:00
altalk23
9c1f48ee64 update tuliphook take 2 2024-11-11 21:37:25 +03:00
altalk23
2940de38bc update tuliphook 2024-11-11 21:33:34 +03:00
altalk23
0c469b98fe update json and result 2024-11-11 21:25:06 +03:00
matcool
4ba8751c68 update json to 3.0.1
Some checks are pending
Build Binaries / Build Windows (push) Waiting to run
Build Binaries / Build macOS (push) Waiting to run
Build Binaries / Build Android (64-bit) (push) Waiting to run
Build Binaries / Build Android (32-bit) (push) Waiting to run
Build Binaries / Publish (push) Blocked by required conditions
2024-11-10 19:28:19 -03:00
HJfod
a3beed16f5 silly alk revert 2024-11-11 00:25:16 +02:00
Cvolton
038788bf57
fix safe mode dialog randomly appearing 2024-11-10 20:17:44 +01:00
dankmeme01
76db1268bf change some const string refs to string views and change getID to return a ref 2024-11-10 17:47:09 +01:00
dankmeme01
084fea220e code cleanup: remove unnecessary const in function parameters
Some checks are pending
Build Binaries / Build Windows (push) Waiting to run
Build Binaries / Build macOS (push) Waiting to run
Build Binaries / Build Android (64-bit) (push) Waiting to run
Build Binaries / Build Android (32-bit) (push) Waiting to run
Build Binaries / Publish (push) Blocked by required conditions
2024-11-10 16:05:20 +01:00
matcool
deab3d2517 remove GEODE_STATIC_PTR and GEODE_STATIC_VAR 2024-11-10 10:38:59 -03:00
matcool
7b171c56c9 avoid collision with result's geode_concat macro 2024-11-10 10:14:46 -03:00
HJfod
12e8bbb6a2 split problems into load problems and outdated GD version 2024-11-10 12:55:56 +02:00
HJfod
c9afa75367 move mod list arrows a bit to not overlap 2024-11-10 12:20:55 +02:00
HJfod
cad670fb31 hide search toggle 2024-11-10 12:20:44 +02:00
HJfod
efb1fbf729 add option for single-page local mods list 2024-11-10 12:17:14 +02:00
HJfod
a3b306a4e0 turns out that path was case-insensitive now... 2024-11-10 11:59:14 +02:00
HJfod
9d4e6ba0e4 header fixes (i think this might make compiling without PCH possible?) 2024-11-10 11:51:49 +02:00
matcool
be9ba27ba4 only set gd version to 2.207 on desktop
Some checks are pending
Build Binaries / Build Windows (push) Waiting to run
Build Binaries / Build macOS (push) Waiting to run
Build Binaries / Build Android (64-bit) (push) Waiting to run
Build Binaries / Build Android (32-bit) (push) Waiting to run
Build Binaries / Publish (push) Blocked by required conditions
2024-11-10 01:28:57 -03:00
matcool
2b0970d341 bump version 2024-11-10 01:26:25 -03:00
matcool
4f837a0f8b Merge remote-tracking branch 'origin/2207' into v4 2024-11-10 00:43:28 -03:00
matcool
5b10a91d8c do a few v4 todos 2024-11-10 00:43:06 -03:00
matcool
31d7c9d099 fix mod descriptions (nasty bug)
Some checks are pending
Build Binaries / Build Windows (push) Waiting to run
Build Binaries / Build macOS (push) Waiting to run
Build Binaries / Build Android (64-bit) (push) Waiting to run
Build Binaries / Build Android (32-bit) (push) Waiting to run
Build Binaries / Publish (push) Blocked by required conditions
this is a very bad issue with the result library :( alk pls fix
2024-11-09 18:34:12 -03:00
Chloe
70be7c6061
how much will this break 2024-11-09 14:33:03 -07:00
Chloe
5eda75311d
fix macos build 2024-11-09 14:27:24 -07:00
matcool
13b7cfc488 actually now 2024-11-09 16:04:38 -03:00
Justin
39dc184b88
2.207 patch updates (#1140)
Some checks failed
Build Binaries / Build Windows (push) Has been cancelled
Build Binaries / Build macOS (push) Has been cancelled
Build Binaries / Build Android (64-bit) (push) Has been cancelled
Build Binaries / Build Android (32-bit) (push) Has been cancelled
Build Binaries / Publish (push) Has been cancelled
* Update gdTimestampMap.hpp

* Update CMakeLists.txt

* Add files via upload
2024-11-09 19:55:51 +01:00
matcool
e8ef9b79c8 fix build 2024-11-09 15:51:20 -03:00
matcool
9ed55c4e7b matjson 3/3, compiles but mod about.md dont load for some reason 2024-11-09 15:16:24 -03:00
Cvolton
af23cf81d3 fix cceglview 2024-11-09 16:55:39 +01:00
matcool
d1d34aab8f update cpm to 0.40.2 2024-11-09 12:31:54 -03:00
matcool
30f872cc87 Revert "add 2.207 support"
This reverts commit 8aa2c2283a.
2024-11-09 12:29:19 -03:00
altalk23
108721dd3f matjson 2/?
Some checks are pending
Build Binaries / Build Windows (push) Waiting to run
Build Binaries / Build macOS (push) Waiting to run
Build Binaries / Build Android (64-bit) (push) Waiting to run
Build Binaries / Build Android (32-bit) (push) Waiting to run
Build Binaries / Publish (push) Blocked by required conditions
2024-11-09 09:14:00 +03:00
altalk23
f2ec5fa3f8 matjson status 1/? 2024-11-09 09:05:21 +03:00
dankmeme01
4c4770bfd3 add 2.207 support 2024-11-09 02:13:17 +01:00
dankmeme01
8aa2c2283a add 2.207 support 2024-11-09 01:42:39 +01:00
Justin
4a40835f71
Change approach 2024-11-08 12:23:18 -05:00
Justin
74d0924bcb
Part 2: Geode SDK 2024-11-08 10:20:30 -05:00
altalk23
088eddbb7b c++ as well
Some checks failed
Build Binaries / Build Windows (push) Has been cancelled
Build Binaries / Build macOS (push) Has been cancelled
Build Binaries / Build Android (64-bit) (push) Has been cancelled
Build Binaries / Build Android (32-bit) (push) Has been cancelled
Build Binaries / Publish (push) Has been cancelled
2024-11-07 20:42:33 +03:00
altalk23
693fadd9bc i love clang sometimes 2024-11-07 20:41:05 +03:00
altalk23
37e5e9f00b update tuliphook 2024-11-07 20:29:37 +03:00
The Bearodactyl
12b70db212 add hashtag symbol to CommonFilter::Any (#1131) 2024-11-07 20:29:01 +03:00
Justin
842b342dc5 yeah... 2024-11-07 20:29:01 +03:00
altalk23
bdb0c99e17 update to new result 2024-11-04 23:24:20 +03:00
altalk23
8320cf6057 Alias V3 settings to default 2024-11-04 21:27:14 +03:00
altalk23
bed622243b remove minifunction 2024-11-04 20:42:09 +03:00
altalk23
985b3aedb5 removing deprecated things part 2 (compiles) 2024-11-04 20:28:38 +03:00
altalk23
50ab4ebed7 removing deprecated things, part 1 (does not compile) 2024-11-04 20:14:23 +03:00
154 changed files with 2124 additions and 11007 deletions

View file

@ -137,6 +137,7 @@ jobs:
build-mac:
name: Build macOS
runs-on: macos-latest
if: false
env:
SCCACHE_CACHE_MULTIARCH: 1

View file

@ -189,8 +189,8 @@ include(cmake/Platform.cmake)
include(cmake/GeodeFile.cmake)
if (NOT DEFINED GEODE_GD_VERSION)
set(GEODE_GD_VERSION 2.206)
set(GEODE_COMP_GD_VERSION 22060)
set(GEODE_GD_VERSION 2.2074)
set(GEODE_COMP_GD_VERSION 22074)
endif()
target_compile_definitions(
@ -236,8 +236,9 @@ if (ANDROID)
endif()
set(MAT_JSON_AS_INTERFACE ON)
CPMAddPackage("gh:geode-sdk/json#cda9807")
CPMAddPackage("gh:fmtlib/fmt#10.2.1")
CPMAddPackage("gh:geode-sdk/result@1.2.1")
CPMAddPackage("gh:geode-sdk/json@3.0.3")
CPMAddPackage("gh:fmtlib/fmt#11.0.2")
target_compile_definitions(${PROJECT_NAME} INTERFACE MAT_JSON_DYNAMIC=1)
@ -266,7 +267,7 @@ if (DEFINED GEODE_TULIPHOOK_REPO_PATH)
message(STATUS "Using ${GEODE_TULIPHOOK_REPO_PATH} for TulipHook")
add_subdirectory(${GEODE_TULIPHOOK_REPO_PATH} ${GEODE_TULIPHOOK_REPO_PATH}/build)
else()
CPMAddPackage("gh:geode-sdk/TulipHook#d1d9559")
CPMAddPackage("gh:geode-sdk/TulipHook@2.4.1")
endif()
set(CMAKE_WARN_DEPRECATED ON CACHE BOOL "" FORCE)
@ -296,7 +297,7 @@ target_include_directories(GeodeBindings PUBLIC
${GEODE_LOADER_PATH}/include/Geode/fmod
)
target_link_directories(GeodeBindings PUBLIC ${GEODE_LOADER_PATH}/include/link)
target_link_libraries(GeodeBindings PUBLIC fmt TulipHookInclude mat-json)
target_link_libraries(GeodeBindings PUBLIC fmt TulipHookInclude mat-json GeodeResult)
target_link_libraries(${PROJECT_NAME} INTERFACE GeodeBindings)
if (NOT EXISTS ${GEODE_BIN_PATH})

View file

@ -1 +1 @@
3.9.0
4.0.0-alpha.1

View file

@ -2,8 +2,8 @@
#
# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors
set(CPM_DOWNLOAD_VERSION 0.39.0)
set(CPM_HASH_SUM "66639bcac9dd2907b2918de466783554c1334446b9874e90d38e3778d404c2ef")
set(CPM_DOWNLOAD_VERSION 0.40.2)
set(CPM_HASH_SUM "c8cdc32c03816538ce22781ed72964dc864b2a34a310d3b7104812a5ca2d835d")
if(CPM_SOURCE_CACHE)
set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")

View file

@ -164,7 +164,27 @@ function(setup_geode_mod proname)
set(HAS_HEADERS Off)
endif()
if (HAS_HEADERS AND WIN32)
if (GEODE_BUNDLE_PDB AND WIN32 AND (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo"))
if (HAS_HEADERS)
add_custom_target(${proname}_PACKAGE ALL
DEPENDS ${proname} ${CMAKE_CURRENT_SOURCE_DIR}/mod.json
COMMAND ${GEODE_CLI} package new ${CMAKE_CURRENT_SOURCE_DIR}
--binary $<TARGET_FILE:${proname}> $<TARGET_LINKER_FILE:${proname}> $<TARGET_PDB_FILE:${proname}>
--output ${CMAKE_CURRENT_BINARY_DIR}/${MOD_ID}.geode
${INSTALL_ARG} ${PDB_ARG}
VERBATIM USES_TERMINAL
)
else()
add_custom_target(${proname}_PACKAGE ALL
DEPENDS ${proname} ${CMAKE_CURRENT_SOURCE_DIR}/mod.json
COMMAND ${GEODE_CLI} package new ${CMAKE_CURRENT_SOURCE_DIR}
--binary $<TARGET_FILE:${proname}> $<TARGET_PDB_FILE:${proname}>
--output ${CMAKE_CURRENT_BINARY_DIR}/${MOD_ID}.geode
${INSTALL_ARG} ${PDB_ARG}
VERBATIM USES_TERMINAL
)
endif()
elseif (HAS_HEADERS AND WIN32)
# this adds the .lib file on windows, which is needed for linking with the headers
add_custom_target(${proname}_PACKAGE ALL
DEPENDS ${proname} ${CMAKE_CURRENT_SOURCE_DIR}/mod.json

View file

@ -5,21 +5,10 @@
#include <Geode/platform/platform.hpp>
#include <variant>
#define GEODE_STATIC_PTR(type, name) \
static type* s_##name; \
inline type* name() { \
if (!s_##name) s_##name = new type(); \
return s_##name; \
}
#define GEODE_STATIC_VAR(type, name) \
inline type& name() { \
static type s_##name; \
return s_##name; \
}
#define GEODE_WRAPPER_CONCAT(x, y) x##y
#define GEODE_CONCAT(x, y) GEODE_WRAPPER_CONCAT(x, y)
#if !defined(GEODE_CONCAT)
#define GEODE_WRAPPER_CONCAT(x, y) x##y
#define GEODE_CONCAT(x, y) GEODE_WRAPPER_CONCAT(x, y)
#endif
#define GEODE_WRAPPER_STR(...) #__VA_ARGS__
#define GEODE_STR(...) GEODE_WRAPPER_STR(__VA_ARGS__)

View file

@ -6,7 +6,8 @@
#include "ui/EnterLayerEvent.hpp"
#include "ui/BasedButtonSprite.hpp"
#include "ui/IconButtonSprite.hpp"
#include "ui/InputNode.hpp"
#include "ui/Layout.hpp"
#include "ui/SpacerNode.hpp"
#include "ui/General.hpp"
#include "ui/ListView.hpp"
#include "ui/MDPopup.hpp"

View file

@ -1,7 +1,6 @@
#pragma once
#include "DefaultInclude.hpp"
#include "utils/Result.hpp"
#include "utils/VersionInfo.hpp"
#include "utils/ranges.hpp"
#include "utils/casts.hpp"
@ -12,5 +11,4 @@
#include "utils/permission.hpp"
#include "utils/general.hpp"
#include "utils/timer.hpp"
#include "utils/MiniFunction.hpp"
#include "utils/ObjcHook.hpp"

View file

@ -93,7 +93,7 @@ namespace gd {
bool empty() const;
bool operator==(string const& other) const;
bool operator==(std::string_view const other) const;
bool operator==(std::string_view other) const;
bool operator==(char const* other) const {
return *this == std::string_view(other);
}
@ -101,7 +101,7 @@ namespace gd {
return *this == std::string_view(other);
}
std::strong_ordering operator<=>(string const& other) const;
std::strong_ordering operator<=>(std::string_view const other) const;
std::strong_ordering operator<=>(std::string_view other) const;
std::strong_ordering operator<=>(char const* other) const {
return *this <=> std::string_view(other);
}

View file

@ -37,7 +37,6 @@
#include "../kazmath/include/kazmath/kazmath.h"
#include "../script_support/CCScriptSupport.h"
#include "../include/CCProtocols.h"
#include "Layout.hpp"
#include "../../loader/Event.hpp"
#include <Geode/utils/casts.hpp>
@ -45,6 +44,12 @@
#include <matjson.hpp>
#endif
namespace geode {
class Layout;
class LayoutOptions;
enum class Anchor;
}
NS_CC_BEGIN
class CCCamera;
@ -871,31 +876,19 @@ public:
private:
friend class geode::modifier::FieldContainer;
[[deprecated("Will be removed, it's an ABI break")]]
GEODE_DLL geode::modifier::FieldContainer* getFieldContainer();
GEODE_DLL geode::modifier::FieldContainer* getFieldContainer(char const* forClass);
GEODE_DLL void addEventListenerInternal(
std::string const& id,
geode::EventListenerProtocol* protocol
);
#ifdef GEODE_EXPORTING
[[deprecated("Will be removed, it's an ABI break")]]
GEODE_DLL std::optional<matjson::Value> getAttributeInternal(std::string const& attribute);
#endif
public:
#ifdef GEODE_EXPORTING
[[deprecated("Will be removed, it's an ABI break")]]
GEODE_DLL void setAttribute(std::string const& attribute, matjson::Value const& value);
#endif
public:
/**
* Get the string ID of this node
* @returns The ID, or an empty string if the node has no ID.
* @note Geode addition
*/
GEODE_DLL std::string getID();
GEODE_DLL const std::string& getID();
/**
* Set the string ID of this node. String IDs are a Geode addition
* that are much safer to use to get nodes than absolute indexes
@ -906,13 +899,23 @@ public:
*/
GEODE_DLL void setID(std::string const& id);
/**
* Set the string ID of this node. String IDs are a Geode addition
* that are much safer to use to get nodes than absolute indexes
* @param id The ID of the node, recommended to be in kebab case
* without any spaces or uppercase letters. If the node is added
* by a mod, use the _spr literal to append the mod ID to it
* @note Geode addition
*/
GEODE_DLL void setID(std::string&& id);
/**
* Get a child by its string ID
* @param id ID of the child
* @returns The child, or nullptr if none was found
* @note Geode addition
*/
GEODE_DLL CCNode* getChildByID(std::string const& id);
GEODE_DLL CCNode* getChildByID(std::string_view id);
/**
* Get a child by its string ID. Recursively searches all the children
@ -920,7 +923,7 @@ public:
* @returns The child, or nullptr if none was found
* @note Geode addition
*/
GEODE_DLL CCNode* getChildByIDRecursive(std::string const& id);
GEODE_DLL CCNode* getChildByIDRecursive(std::string_view id);
/**
* Get a child based on a query. Searches the child tree for a matching
@ -936,14 +939,14 @@ public:
* ->getChildByID("mod.id/epic-button")`
* @returns The first matching node, or nullptr if none was found
*/
GEODE_DLL CCNode* querySelector(std::string const& query);
GEODE_DLL CCNode* querySelector(std::string_view query);
/**
* Removes a child from the container by its ID.
* @param id The ID of the node
* @note Geode addition
*/
GEODE_DLL void removeChildByID(std::string const& id);
GEODE_DLL void removeChildByID(std::string_view id);
/**
* Add a child before a specified existing child
@ -990,13 +993,13 @@ public:
* CCLayers / CCMenus, this will change where the children are located
* @note Geode addition
*/
GEODE_DLL void setLayout(Layout* layout, bool apply = true, bool respectAnchor = true);
GEODE_DLL void setLayout(geode::Layout* layout, bool apply = true, bool respectAnchor = true);
/**
* Get the Layout for this node
* @returns The current layout, or nullptr if no layout is set
* @note Geode addition
*/
GEODE_DLL Layout* getLayout();
GEODE_DLL geode::Layout* getLayout();
/**
* Update the layout of this node using the current Layout. If no layout is
* set, nothing happens
@ -1011,13 +1014,13 @@ public:
* @param apply Whether to update the layout of the parent node
* @note Geode addition
*/
GEODE_DLL void setLayoutOptions(LayoutOptions* options, bool apply = true);
GEODE_DLL void setLayoutOptions(geode::LayoutOptions* options, bool apply = true);
/**
* Get the layout options for this node
* @returns The current layout options, or nullptr if no options are set
* @note Geode addition
*/
GEODE_DLL LayoutOptions* getLayoutOptions();
GEODE_DLL geode::LayoutOptions* getLayoutOptions();
/**
* Adds a child at an anchored position with an offset. The node is placed
* in its parent where the anchor specifies, and then the offset is used to
@ -1029,7 +1032,7 @@ public:
* if no other layout is already specified
* @note Geode addition
*/
GEODE_DLL void addChildAtPosition(CCNode* child, Anchor anchor, CCPoint const& offset = CCPointZero, bool useAnchorLayout = true);
GEODE_DLL void addChildAtPosition(CCNode* child, geode::Anchor anchor, CCPoint const& offset = CCPointZero, bool useAnchorLayout = true);
/**
* Adds a child at an anchored position with an offset. The node is placed
* in its parent where the anchor specifies, and then the offset is used to
@ -1044,7 +1047,7 @@ public:
*/
GEODE_DLL void addChildAtPosition(
CCNode* child,
Anchor anchor,
geode::Anchor anchor,
CCPoint const& offset,
CCPoint const& nodeAnchor,
bool useAnchorLayout = true
@ -1057,7 +1060,7 @@ public:
* @param offset Where to place the child relative to the anchor
* @note Geode addition
*/
GEODE_DLL void updateAnchoredPosition(Anchor anchor, CCPoint const& offset = CCPointZero);
GEODE_DLL void updateAnchoredPosition(geode::Anchor anchor, CCPoint const& offset = CCPointZero);
/**
* Updates the anchored position of a child. Requires the child to already
* have a parent; if the child already has AnchorLayoutOptions set, those
@ -1068,7 +1071,7 @@ public:
* @note Geode addition
*/
GEODE_DLL void updateAnchoredPosition(
Anchor anchor,
geode::Anchor anchor,
CCPoint const& offset,
CCPoint const& nodeAnchor
);
@ -1102,7 +1105,7 @@ public:
template <class Filter, class... Args>
geode::EventListenerProtocol* addEventListener(
std::string const& id,
geode::utils::MiniFunction<typename Filter::Callback> callback,
std::function<typename Filter::Callback> callback,
Args&&... args
) {
auto listener = new geode::EventListener<Filter>(
@ -1113,7 +1116,7 @@ public:
}
template <class Filter, class... Args>
geode::EventListenerProtocol* addEventListener(
geode::utils::MiniFunction<typename Filter::Callback> callback,
std::function<typename Filter::Callback> callback,
Args&&... args
) {
return this->addEventListener<Filter, Args...>(
@ -1923,7 +1926,7 @@ namespace geode {
std::string m_targetID;
public:
ListenerResult handle(utils::MiniFunction<Callback> fn, UserObjectSetEvent* event);
ListenerResult handle(std::function<Callback> fn, UserObjectSetEvent* event);
AttributeSetFilter(std::string const& id);
};

View file

@ -59,7 +59,6 @@ THE SOFTWARE.
// base_nodes
#include "../base_nodes/CCNode.h"
#include "../base_nodes/CCAtlasNode.h"
#include "../base_nodes/SpacerNode.hpp"
// cocoa
#include "../cocoa/CCAffineTransform.h"

View file

@ -142,7 +142,7 @@ public:
/**
* @note RobTop addition
*/
void toggleFullScreen(bool fullscreen, bool borderless);
void toggleFullScreen(bool fullscreen, bool borderless, bool fix);
/**
* @note RobTop addition
@ -185,6 +185,8 @@ public:
protected:
static CCEGLView* s_pEglView;
// @note unknown members here
uint8_t m_unkPad[8];
bool m_bCaptured;
// Robtop Removal
// HWND m_hWnd;
@ -203,6 +205,8 @@ protected:
int m_nRetinaFactor;
// @note RobTop Addition
bool m_bCursorHidden;
// @note may be before m_bCursorHidden
int m_unkSize4;
// Robtop Removal
// LPCWSTR m_menu;
// Robtop Removal
@ -225,6 +229,8 @@ public:
// @note RobTop Addition
bool m_bIsBorderless;
// @note RobTop Addition
bool m_bIsFix;
// @note RobTop Addition
bool m_bShouldHideCursor;
// @note RobTop Addition
bool m_bCursorLocked;

File diff suppressed because it is too large Load diff

View file

@ -53,7 +53,7 @@ namespace geode {
return dispatchPools()[m_id];
}
ListenerResult handle(utils::MiniFunction<Callback> fn, Ev* event) {
ListenerResult handle(std::function<Callback> fn, Ev* event) {
if (event->getID() == m_id) {
return std::apply(fn, event->getArgs());
}

View file

@ -1,7 +1,6 @@
#pragma once
#include "../utils/casts.hpp"
#include "../utils/MiniFunction.hpp"
#include <Geode/DefaultInclude.hpp>
#include <type_traits>
@ -52,6 +51,7 @@ namespace geode {
private:
static DefaultEventListenerPool* create();
DefaultEventListenerPool();
public:
bool add(EventListenerProtocol* listener) override;
@ -64,10 +64,7 @@ namespace geode {
friend class DispatchEvent;
template <class... Args>
friend class DispatchFilter;
// todo: make this private in Geode 4.0.0
DefaultEventListenerPool();
friend class DispatchFilter;
};
class GEODE_DLL EventListenerProtocol {
@ -103,7 +100,7 @@ namespace geode {
using Callback = ListenerResult(T*);
using Event = T;
ListenerResult handle(utils::MiniFunction<Callback> fn, T* e) {
ListenerResult handle(std::function<Callback> fn, T* e) {
return fn(e);
}
@ -156,7 +153,7 @@ namespace geode {
this->enable();
}
EventListener(utils::MiniFunction<Callback> fn, T filter = T())
EventListener(std::function<Callback> fn, T filter = T())
: m_callback(fn), m_filter(filter)
{
m_filter.setListener(this);
@ -193,10 +190,7 @@ namespace geode {
this->enable();
}
void bind(utils::MiniFunction<Callback> const& fn) {
m_callback = fn;
}
void bind(utils::MiniFunction<Callback>&& fn) {
void bind(std::function<Callback> fn) {
m_callback = fn;
}
@ -218,12 +212,12 @@ namespace geode {
return m_filter;
}
utils::MiniFunction<Callback>& getCallback() {
std::function<Callback>& getCallback() {
return m_callback;
}
protected:
utils::MiniFunction<Callback> m_callback = nullptr;
std::function<Callback> m_callback = nullptr;
T m_filter;
};

View file

@ -57,7 +57,7 @@ namespace geode::ipc {
std::string m_messageID;
public:
ListenerResult handle(utils::MiniFunction<Callback> fn, IPCEvent* event);
ListenerResult handle(std::function<Callback> fn, IPCEvent* event);
IPCFilter(
std::string const& modID,
std::string const& messageID

View file

@ -1,8 +1,7 @@
#pragma once
#include <filesystem>
#include "../utils/Result.hpp"
#include "../utils/MiniFunction.hpp"
#include <Geode/Result.hpp>
#include "Log.hpp"
#include "ModEvent.hpp"
#include "ModMetadata.hpp"
@ -15,7 +14,7 @@
#include <string_view>
namespace geode {
using ScheduledFunction = utils::MiniFunction<void()>;
using ScheduledFunction = std::function<void()>;
struct InvalidGeodeFile {
std::filesystem::path path;
@ -47,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;
@ -92,7 +106,8 @@ namespace geode {
Mod* getLoadedMod(std::string const& id) const;
std::vector<Mod*> getAllMods();
std::vector<LoadProblem> getAllProblems() const;
std::vector<LoadProblem> getProblems() const;
std::vector<LoadProblem> getLoadProblems() const;
std::vector<LoadProblem> getOutdated() const;
std::vector<LoadProblem> getRecommendations() const;
/**
@ -103,41 +118,36 @@ namespace geode {
* Returns whether the specified launch argument was passed in via the command line.
* @param name The argument name
*/
bool hasLaunchArgument(std::string_view const name) const;
bool hasLaunchArgument(std::string_view name) const;
/**
* Get a launch argument. These are passed into the game as command-line arguments
* with the format `--geode:arg-name=value`.
* @param name The argument name
* @return The value, if present
*/
std::optional<std::string> getLaunchArgument(std::string_view const name) const;
std::optional<std::string> getLaunchArgument(std::string_view name) const;
/**
* Get a launch argument flag. Returns whether the argument is present and its
* value is exactly `true`.
* @param name The argument name
*/
bool getLaunchFlag(std::string_view const name) const;
bool getLaunchFlag(std::string_view name) const;
/**
* Get and parse a launch argument value using the setting value system.
* @param name The argument name
*/
template <class T>
std::optional<T> parseLaunchArgument(std::string_view const name) const {
Result<T> parseLaunchArgument(std::string_view name) const {
auto str = this->getLaunchArgument(name);
if (!str.has_value()) {
return std::nullopt;
return Err(fmt::format("Launch argument '{}' not found", name));
}
std::string parseError;
auto jsonOpt = matjson::parse(str.value(), parseError);
if (!jsonOpt.has_value()) {
log::debug("Parsing launch argument '{}' failed: {}", name, parseError);
return std::nullopt;
auto jsonOpt = matjson::Value::parse(str.value());
if (jsonOpt.isErr()) {
return Err(fmt::format("Parsing launch argument '{}' failed: {}", name, jsonOpt.unwrapErr()));
}
auto value = jsonOpt.value();
if (!value.is<T>()) {
return std::nullopt;
}
return value.as<T>();
auto value = jsonOpt.unwrap();
return value.template as<T>();
}
void queueInMainThread(ScheduledFunction&& func);

View file

@ -65,12 +65,6 @@ namespace std::filesystem {
}
}
namespace matjson {
GEODE_INLINE GEODE_HIDDEN std::string format_as(matjson::Value const& value) {
return value.dump(matjson::NO_INDENTATION);
}
}
namespace geode {
class Mod;

View file

@ -2,7 +2,7 @@
#include "../DefaultInclude.hpp"
#include "../cocos/support/zip_support/ZipUtils.h"
#include "../utils/Result.hpp"
#include <Geode/Result.hpp>
#include "../utils/VersionInfo.hpp"
#include "../utils/general.hpp"
@ -10,7 +10,6 @@
#include "Hook.hpp"
#include "ModMetadata.hpp"
#include "Setting.hpp"
#include "SettingV3.hpp"
#include "Types.hpp"
#include "Loader.hpp"
@ -23,9 +22,7 @@
#include <unordered_map>
#include <vector>
namespace geode {
class SettingV3;
namespace geode {
template <class T>
struct HandleToSaved : public T {
Mod* m_mod;
@ -54,22 +51,6 @@ namespace geode {
return action == ModRequestedAction::Uninstall || action == ModRequestedAction::UninstallWithSaveData;
}
template <class T>
static consteval bool typeImplementsIsJSON() {
using namespace matjson;
if constexpr (requires(const Value& json) { Serialize<std::decay_t<T>>::is_json(json); })
return true;
if constexpr (std::is_same_v<T, Value>) return true;
if constexpr (std::is_same_v<T, Array>) return true;
if constexpr (std::is_same_v<T, Object>) return true;
if constexpr (std::is_constructible_v<std::string, T>) return true;
if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T>) return true;
if constexpr (std::is_same_v<T, bool>) return true;
if constexpr (std::is_same_v<T, std::nullptr_t>) return true;
return false;
}
GEODE_HIDDEN Mod* takeNextLoaderMod();
class ModImpl;
@ -108,8 +89,6 @@ namespace geode {
std::string getID() const;
std::string getName() const;
[[deprecated("Use Mod::getDevelopers instead")]]
std::string getDeveloper() const;
std::vector<std::string> getDevelopers() const;
std::optional<std::string> getDescription() const;
std::optional<std::string> getDetails() const;
@ -137,17 +116,6 @@ namespace geode {
std::vector<Mod*> getDependants() const;
#endif
/**
* Check if this Mod has updates available on the mods index. If
* you're using this for automatic update checking, use
* `openInfoPopup` or `openIndexPopup` from the `ui/GeodeUI.hpp`
* header to open the Mod's page to let the user install the update
* @returns The latest available version on the index if there are
* updates for this mod
*/
[[deprecated("Use Mod::checkUpdates instead; this function always returns nullopt")]]
std::optional<VersionInfo> hasAvailableUpdate() const;
using CheckUpdatesTask = Task<Result<std::optional<VersionInfo>, std::string>>;
/**
* Check if this Mod has updates available on the mods index. If
@ -187,52 +155,15 @@ namespace geode {
* declared in `mod.json`)
*/
std::vector<std::string> getSettingKeys() const;
bool hasSetting(std::string_view const key) const;
bool hasSetting(std::string_view key) const;
// todo in v4: remove these
[[deprecated("Use Mod::getSettingV3")]]
std::optional<Setting> getSettingDefinition(std::string_view const key) const;
[[deprecated("Use Mod::getSettingV3")]]
SettingValue* getSetting(std::string_view const key) const;
// todo in v4: possibly rename this to getSetting?
/**
* Get the definition of a setting, or null if the setting was not found,
* or if it's a custom setting that has not yet been registered using
* `Mod::registerCustomSettingType`
* @param key The key of the setting as defined in `mod.json`
*/
std::shared_ptr<SettingV3> getSettingV3(std::string_view const key) const;
/**
* Register a custom setting's value class. See Mod::addCustomSetting
* for a convenience wrapper that creates the value in-place to avoid
* code duplication. Also see
* [the tutorial page](https://docs.geode-sdk.org/mods/settings) for
* more information about custom settings
* @param key The setting's key
* @param value The SettingValue class that shall handle this setting
* @see addCustomSetting
*/
[[deprecated("Use Mod::registerCustomSettingType")]]
void registerCustomSetting(std::string_view const key, std::unique_ptr<SettingValue> value);
/**
* Register a custom setting's value class. The new SettingValue class
* will be created in-place using `std::make_unique`. See
* [the tutorial page](https://docs.geode-sdk.org/mods/settings) for
* more information about custom settings
* @param key The setting's key
* @param value The value of the custom setting
* @example
* $on_mod(Loaded) {
* Mod::get()->addCustomSetting<MySettingValue>("setting-key", DEFAULT_VALUE);
* }
*/
template <class T, class V>
[[deprecated("Use Mod::registerCustomSettingType")]]
void addCustomSetting(std::string_view const key, V const& value) {
this->registerCustomSetting(key, std::make_unique<T>(std::string(key), this->getID(), value));
}
std::shared_ptr<Setting> getSetting(std::string_view key) const;
/**
* Register a custom setting type. See
@ -248,7 +179,7 @@ namespace geode {
* Returns a prefixed launch argument name. See `Mod::getLaunchArgument`
* for details about mod-specific launch arguments.
*/
std::string getLaunchArgumentName(std::string_view const name) const;
std::string getLaunchArgumentName(std::string_view name) const;
/**
* Returns the names of the available mod-specific launch arguments.
*/
@ -258,7 +189,7 @@ namespace geode {
* for details about mod-specific launch arguments.
* @param name The argument name
*/
bool hasLaunchArgument(std::string_view const name) const;
bool hasLaunchArgument(std::string_view name) const;
/**
* Get a mod-specific launch argument. This is equivalent to `Loader::getLaunchArgument`
* with the argument name prefixed by the mod ID. For example, a launch argument named
@ -266,20 +197,20 @@ namespace geode {
* @param name The argument name
* @return The value, if present
*/
std::optional<std::string> getLaunchArgument(std::string_view const name) const;
std::optional<std::string> getLaunchArgument(std::string_view name) const;
/**
* Equivalent to a prefixed `Loader::getLaunchFlag` call. See `Mod::getLaunchArgument`
* for details about mod-specific launch arguments.
* @param name The argument name
*/
bool getLaunchFlag(std::string_view const name) const;
bool getLaunchFlag(std::string_view name) const;
/**
* Equivalent to a prefixed `Loader::parseLaunchArgument` call. See `Mod::getLaunchArgument`
* for details about mod-specific launch arguments.
* @param name The argument name
*/
template <class T>
std::optional<T> parseLaunchArgument(std::string_view const name) const {
std::optional<T> parseLaunchArgument(std::string_view name) const {
return Loader::get()->parseLaunchArgument<T>(this->getLaunchArgumentName(name));
}
@ -293,18 +224,18 @@ namespace geode {
* setting type has a `getValue` function which returns the value
*/
template <class T>
T getSettingValue(std::string_view const key) const {
T getSettingValue(std::string_view key) const {
using S = typename SettingTypeForValueType<T>::SettingType;
if (auto sett = cast::typeinfo_pointer_cast<S>(this->getSettingV3(key))) {
if (auto sett = cast::typeinfo_pointer_cast<S>(this->getSetting(key))) {
return sett->getValue();
}
return T();
}
template <class T>
T setSettingValue(std::string_view const key, T const& value) {
T setSettingValue(std::string_view key, T const& value) {
using S = typename SettingTypeForValueType<T>::SettingType;
if (auto sett = cast::typeinfo_pointer_cast<S>(this->getSettingV3(key))) {
if (auto sett = cast::typeinfo_pointer_cast<S>(this->getSetting(key))) {
auto old = sett->getValue();
sett->setValue(value);
return old;
@ -312,30 +243,28 @@ namespace geode {
return T();
}
bool hasSavedValue(std::string_view const key);
bool hasSavedValue(std::string_view key);
template <class T>
T getSavedValue(std::string_view const key) {
static_assert(geode::typeImplementsIsJSON<T>(), "T must implement is_json in matjson::Serialize<T>, otherwise this always returns default value.");
T getSavedValue(std::string_view key) {
auto& saved = this->getSaveContainer();
if (saved.contains(key)) {
if (auto value = saved.try_get<T>(key)) {
return *value;
}
if (auto res = saved.get(key).andThen([](auto&& v) {
return v.template as<T>();
}); res.isOk()) {
return res.unwrap();
}
return T();
}
template <class T>
T getSavedValue(std::string_view const key, T const& defaultValue) {
static_assert(geode::typeImplementsIsJSON<T>(), "T must implement is_json in matjson::Serialize<T>, otherwise this always returns default value.");
T getSavedValue(std::string_view key, T const& defaultValue) {
auto& saved = this->getSaveContainer();
if (saved.contains(key)) {
if (auto value = saved.try_get<T>(key)) {
return *value;
}
if (auto res = saved.get(key).andThen([](auto&& v) {
return v.template as<T>();
}); res.isOk()) {
return res.unwrap();
}
saved[key] = defaultValue;
saved[key] = matjson::Value(defaultValue);
return defaultValue;
}
@ -347,7 +276,7 @@ namespace geode {
* @returns The old value
*/
template <class T>
T setSavedValue(std::string_view const key, T const& value) {
T setSavedValue(std::string_view key, T const& value) {
auto& saved = this->getSaveContainer();
auto old = this->getSavedValue<T>(key);
saved[key] = value;
@ -488,7 +417,7 @@ namespace geode {
* Check whether or not this Mod
* depends on another mod
*/
bool depends(std::string_view const id) const;
bool depends(std::string_view id) const;
/**
* Check whether all the required
@ -517,7 +446,17 @@ namespace geode {
bool isLoggingEnabled() const;
void setLoggingEnabled(bool enabled);
bool hasProblems() 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;
std::vector<LoadProblem> getRecommendations() const;

View file

@ -10,9 +10,6 @@ namespace geode {
enum class ModEventType {
Loaded,
Unloaded,
Enabled,
Disabled,
DataLoaded,
DataSaved,
};
@ -43,7 +40,7 @@ namespace geode {
Mod* m_mod;
public:
ListenerResult handle(utils::MiniFunction<Callback> fn, ModStateEvent* event);
ListenerResult handle(std::function<Callback> fn, ModStateEvent* event);
/**
* Create a mod state listener

View file

@ -1,8 +1,7 @@
#pragma once
#include "../utils/Result.hpp"
#include <Geode/Result.hpp>
#include "../utils/VersionInfo.hpp"
#include "Setting.hpp"
#include "Types.hpp"
#include <matjson.hpp>
@ -123,13 +122,6 @@ namespace geode {
* character set.
*/
[[nodiscard]] std::string getName() const;
/**
* The name of the head developer.
* If the mod has multiple * developers, this will return the first
* developer in the list.
*/
[[nodiscard, deprecated("Use ModMetadata::getDevelopers() instead")]]
std::string getDeveloper() const;
/**
* The developers of this mod
*/
@ -155,11 +147,6 @@ namespace geode {
* (see MDTextArea for more info)
*/
[[nodiscard]] std::optional<std::string> getSupportInfo() const;
/**
* Git Repository of the mod
*/
[[nodiscard, deprecated("Use ModMetadata::getLinks instead")]]
std::optional<std::string> getRepository() const;
/**
* Get the links (related websites / servers / etc.) for this mod
*/
@ -184,12 +171,7 @@ namespace geode {
* Mod settings
* @note Not a map because insertion order must be preserved
*/
[[nodiscard, deprecated("Use getSettingsV3")]] std::vector<std::pair<std::string, Setting>> getSettings() const;
/**
* Mod settings
* @note Not a map because insertion order must be preserved
*/
[[nodiscard]] std::vector<std::pair<std::string, matjson::Value>> getSettingsV3() const;
[[nodiscard]] std::vector<std::pair<std::string, matjson::Value>> getSettings() const;
/**
* Get the tags for this mod
*/
@ -237,8 +219,6 @@ namespace geode {
void setDependencies(std::vector<Dependency> const& value);
void setIncompatibilities(std::vector<Incompatibility> const& value);
void setSpritesheets(std::vector<std::string> const& value);
[[deprecated("This function does NOTHING")]]
void setSettings(std::vector<std::pair<std::string, Setting>> const& value);
void setSettings(std::vector<std::pair<std::string, matjson::Value>> const& value);
void setTags(std::unordered_set<std::string> const& value);
void setNeedsEarlyLoad(bool const& value);
@ -310,7 +290,8 @@ namespace geode {
template <>
struct matjson::Serialize<geode::ModMetadata> {
static matjson::Value to_json(geode::ModMetadata const& info) {
return info.toJSON();
static Value toJson(geode::ModMetadata const& value)
{
return Value(value.toJSON());
}
};

View file

@ -1,7 +1,7 @@
#pragma once
#include <Geode/DefaultInclude.hpp>
#include "SettingV3.hpp"
#include "Setting.hpp"
namespace geode {
class Mod;
@ -57,12 +57,8 @@ namespace geode {
matjson::Value& getSaveData();
Result<> registerCustomSettingType(std::string_view type, SettingGenerator generator);
// todo in v4: remove this
Result<> registerLegacyCustomSetting(std::string_view key, std::unique_ptr<SettingValue>&& ptr);
std::shared_ptr<SettingV3> get(std::string_view key);
std::shared_ptr<SettingValue> getLegacy(std::string_view key);
std::optional<Setting> getLegacyDefinition(std::string_view key);
std::shared_ptr<Setting> get(std::string_view key);
/**
* Returns true if any setting with the `"restart-required"` attribute

View file

@ -1,324 +1,46 @@
#pragma once
#include "Types.hpp"
#include "../DefaultInclude.hpp"
#include "../utils/Result.hpp"
#include "../utils/file.hpp"
#include <matjson.hpp>
#include <optional>
#include <unordered_set>
#include <cocos2d.h>
#pragma warning(push)
#pragma warning(disable : 4275)
namespace geode {
class SettingNode;
class SettingValue;
struct JsonMaybeObject;
struct JsonMaybeValue;
/**
* A Setting for a boolean value. Represented in-game as a simple toggle
*/
struct GEODE_DLL BoolSetting final {
using ValueType = bool;
std::optional<std::string> name;
std::optional<std::string> description;
bool defaultValue;
static Result<BoolSetting> parse(JsonMaybeObject& obj);
};
/**
* A Setting for an integer value. The value can be limited using the min
* and max options
*/
struct GEODE_DLL IntSetting final {
using ValueType = int64_t;
std::optional<std::string> name;
std::optional<std::string> description;
ValueType defaultValue;
std::optional<ValueType> min;
std::optional<ValueType> max;
struct {
bool arrows = true;
bool bigArrows = false;
size_t arrowStep = 1;
size_t bigArrowStep = 5;
bool slider = true;
std::optional<ValueType> sliderStep = std::nullopt;
bool input = true;
} controls;
static Result<IntSetting> parse(JsonMaybeObject& obj);
};
/**
* A Setting for a float value. The value can be limited using the min
* and max options
*/
struct GEODE_DLL FloatSetting final {
using ValueType = double;
std::optional<std::string> name;
std::optional<std::string> description;
ValueType defaultValue;
std::optional<ValueType> min;
std::optional<ValueType> max;
struct {
bool arrows = true;
bool bigArrows = false;
size_t arrowStep = 1;
size_t bigArrowStep = 5;
bool slider = true;
std::optional<ValueType> sliderStep = std::nullopt;
bool input = true;
} controls;
static Result<FloatSetting> parse(JsonMaybeObject& obj);
};
/**
* A Setting for a string value
*/
struct GEODE_DLL StringSetting final {
using ValueType = std::string;
std::optional<std::string> name;
std::optional<std::string> description;
ValueType defaultValue;
struct Data {
/**
* A regex the string must successfully match against
*/
std::optional<std::string> match;
/**
* The CCTextInputNode's allowed character filter
*/
std::optional<std::string> filter;
/**
* A list of options the user can choose from
*/
std::optional<std::vector<std::string>> options;
};
std::unique_ptr<Data> controls;
std::array<uint8_t, sizeof(Data::match) + sizeof(Data::filter) - sizeof(controls)> m_padding;
StringSetting();
StringSetting(StringSetting const& other);
StringSetting(StringSetting&& other) noexcept;
StringSetting& operator=(StringSetting const& other);
StringSetting& operator=(StringSetting&& other) noexcept;
~StringSetting();
static Result<StringSetting> parse(JsonMaybeObject& obj);
};
/**
* A Setting for a file input. Lets the user select a file from their
* local device
*/
struct GEODE_DLL FileSetting final {
using ValueType = std::filesystem::path;
using Filter = utils::file::FilePickOptions::Filter;
std::optional<std::string> name;
std::optional<std::string> description;
ValueType defaultValue;
struct {
std::vector<Filter> filters;
} controls;
static Result<FileSetting> parse(JsonMaybeObject& obj);
};
/**
* A Setting for an RGB color. See ColorAlphaSetting for a setting that
* also allows customizing alpha
*/
struct GEODE_DLL ColorSetting final {
using ValueType = cocos2d::ccColor3B;
std::optional<std::string> name;
std::optional<std::string> description;
ValueType defaultValue;
static Result<ColorSetting> parse(JsonMaybeObject& obj);
};
/**
* A Setting for an RGBA color. See ColorSetting for a setting that doesn't
* have alpha
*/
struct GEODE_DLL ColorAlphaSetting final {
using ValueType = cocos2d::ccColor4B;
std::optional<std::string> name;
std::optional<std::string> description;
ValueType defaultValue;
static Result<ColorAlphaSetting> parse(JsonMaybeObject& obj);
};
/**
* A custom setting, defined by the mod. See
* [the tutorial page](https://docs.geode-sdk.org/mods/settings) for more
* information about how to create custom settings
*/
struct GEODE_DLL CustomSetting final {
std::shared_ptr<ModJson> json;
};
using SettingKind = std::variant<
BoolSetting,
IntSetting,
FloatSetting,
StringSetting,
FileSetting,
ColorSetting,
ColorAlphaSetting,
CustomSetting
>;
/**
* Represents a saved value for a mod that can be customized by the user
* through an in-game UI. This class is for modeling the setting's
* definition - what values are accepted, its name etc.
* See [the tutorial page](https://docs.geode-sdk.org/mods/settings)
* for more information about how settings work
* @see SettingValue
* @see SettingNode
*/
struct GEODE_DLL Setting final {
private:
std::string m_key;
std::string m_modID;
SettingKind m_kind;
Setting() = default;
public:
static Result<Setting> parse(
std::string const& key,
std::string const& mod,
JsonMaybeValue& obj
);
Setting(
std::string const& key,
std::string const& mod,
SettingKind const& kind
);
template<class T>
std::optional<T> get() {
if (std::holds_alternative<T>(m_kind)) {
return std::get<T>(m_kind);
}
return std::nullopt;
}
std::unique_ptr<SettingValue> createDefaultValue() const;
bool isCustom() const;
std::string getDisplayName() const;
std::optional<std::string> getDescription() const;
std::string getModID() const;
};
/**
* Stores the actual, current value of a Setting. See
* [the tutorial page](https://docs.geode-sdk.org/mods/settings) for more
* information, and how to create custom settings
*/
class GEODE_DLL SettingValue {
protected:
std::string m_key;
std::string m_modID;
SettingValue(std::string const& key, std::string const& mod);
void valueChanged();
public:
virtual ~SettingValue() = default;
virtual bool load(matjson::Value const& json) = 0;
virtual bool save(matjson::Value& json) const = 0;
virtual SettingNode* createNode(float width) = 0;
std::string getKey() const;
std::string getModID() const;
};
template<class T>
class GeodeSettingValue final : public SettingValue {
public:
using ValueType = typename T::ValueType;
protected:
ValueType m_value;
T m_definition;
using Valid = std::pair<ValueType, std::optional<std::string>>;
GEODE_DLL Valid toValid(ValueType const& value) const;
public:
GeodeSettingValue(std::string const& key, std::string const& modID, T const& definition)
: SettingValue(key, modID),
m_definition(definition),
m_value(definition.defaultValue) {}
bool load(matjson::Value const& json) override;
bool save(matjson::Value& json) const;
GEODE_DLL SettingNode* createNode(float width) override;
T castDefinition() const {
return m_definition;
}
Setting getDefinition() const {
return Setting(m_key, m_modID, m_definition);
}
GEODE_DLL ValueType getValue() const;
GEODE_DLL void setValue(ValueType const& value);
GEODE_DLL Result<> validate(ValueType const& value) const;
};
using BoolSettingValue = GeodeSettingValue<BoolSetting>;
using IntSettingValue = GeodeSettingValue<IntSetting>;
using FloatSettingValue = GeodeSettingValue<FloatSetting>;
using StringSettingValue = GeodeSettingValue<StringSetting>;
using FileSettingValue = GeodeSettingValue<FileSetting>;
using ColorSettingValue = GeodeSettingValue<ColorSetting>;
using ColorAlphaSettingValue = GeodeSettingValue<ColorAlphaSetting>;
// todo: remove in v3
template<class T>
struct [[deprecated("Use SettingTypeForValueType from SettingV3 instead")]] GEODE_DLL SettingValueSetter {
static T get(SettingValue* setting);
static void set(SettingValue* setting, T const& value);
};
template<class T>
bool GeodeSettingValue<T>::load(matjson::Value const& json) {
if (!json.is<ValueType>()) return false;
m_value = json.as<ValueType>();
return true;
}
template<class T>
bool GeodeSettingValue<T>::save(matjson::Value& json) const {
json = m_value;
return true;
}
}
#pragma warning(pop)
#pragma once
#include "SettingV3.hpp"
namespace geode {
using Setting = SettingV3;
using SettingGenerator = SettingGeneratorV3;
template <class T, class V = T>
using SettingBaseValue = SettingBaseValueV3<T, V>;
using TitleSetting = TitleSettingV3;
using BoolSetting = BoolSettingV3;
using IntSetting = IntSettingV3;
using FloatSetting = FloatSettingV3;
using StringSetting = StringSettingV3;
using FileSetting = FileSettingV3;
using Color3BSetting = Color3BSettingV3;
using Color4BSetting = Color4BSettingV3;
using SettingNode = SettingNodeV3;
template <class S>
using SettingValueNode = SettingValueNodeV3<S>;
using SettingChangedEvent = SettingChangedEventV3;
using SettingChangedFilter = SettingChangedFilterV3;
using SettingNodeSizeChangeEvent = SettingNodeSizeChangeEventV3;
using SettingNodeValueChangeEvent = SettingNodeValueChangeEventV3;
template <class T, class Lambda>
EventListener<SettingChangedFilter>* listenForSettingChanges(std::string_view settingKey, Lambda&& callback, Mod* mod = getMod()) {
return listenForSettingChangesV3<T>(settingKey, std::forward<Lambda>(callback), mod);
}
template <class Lambda>
EventListener<SettingChangedFilter>* listenForSettingChanges(std::string_view settingKey, Lambda&& callback, Mod* mod = getMod()) {
return listenForSettingChangesV3(settingKey, std::forward<Lambda>(callback), mod);
}
inline EventListener<SettingChangedFilter>* listenForAllSettingChanges(
std::function<void(std::shared_ptr<SettingV3>)> const& callback,
Mod* mod = getMod()
) {
return listenForAllSettingChangesV3(callback, mod);
}
}

View file

@ -1,63 +0,0 @@
#pragma once
#include "Event.hpp"
#include "Loader.hpp"
#include "Setting.hpp"
#include "Mod.hpp"
#include "SettingV3.hpp"
#include <optional>
namespace geode {
struct GEODE_DLL [[deprecated("Use SettingChangedEventV3 from SettingV3.hpp instead")]] SettingChangedEvent : public Event {
Mod* mod;
SettingValue* value;
SettingChangedEvent(Mod* mod, SettingValue* value);
};
class GEODE_DLL [[deprecated("Use SettingChangedEventV3 from SettingV3.hpp instead")]] SettingChangedFilter : public EventFilter<SettingChangedEvent> {
protected:
std::string m_modID;
std::optional<std::string> m_targetKey;
public:
using Callback = void(SettingValue*);
ListenerResult handle(utils::MiniFunction<Callback> fn, SettingChangedEvent* event);
/**
* Listen to changes on a setting, or all settings
* @param modID Mod whose settings to listen to
* @param settingID Setting to listen to, or all settings if nullopt
*/
SettingChangedFilter(
std::string const& modID,
std::optional<std::string> const& settingKey
);
SettingChangedFilter(SettingChangedFilter const&) = default;
};
/**
* Listen for built-in setting changes
*/
template<class T>
class [[deprecated("Use SettingChangedEventV3 from SettingV3.hpp instead")]] GeodeSettingChangedFilter : public SettingChangedFilter {
public:
using Callback = void(T);
ListenerResult handle(utils::MiniFunction<Callback> fn, SettingChangedEvent* event) {
if (
m_modID == event->mod->getID() &&
(!m_targetKey || m_targetKey.value() == event->value->getKey())
) {
fn(SettingValueSetter<T>::get(event->value));
}
return ListenerResult::Propagate;
}
GeodeSettingChangedFilter(
std::string const& modID,
std::string const& settingID
) : SettingChangedFilter(modID, settingID) {}
GeodeSettingChangedFilter(GeodeSettingChangedFilter const&) = default;
};
}

View file

@ -1,32 +0,0 @@
#pragma once
#include "Setting.hpp"
#include <cocos2d.h>
namespace geode {
class SettingNode;
struct SettingNodeDelegate {
virtual void settingValueChanged(SettingNode* node) {}
virtual void settingValueCommitted(SettingNode* node) {}
};
class GEODE_DLL SettingNode : public cocos2d::CCNode {
protected:
SettingValue* m_value;
SettingNodeDelegate* m_delegate = nullptr;
bool init(SettingValue* value);
void dispatchChanged();
void dispatchCommitted();
public:
void setDelegate(SettingNodeDelegate* delegate);
virtual void commit() = 0;
virtual bool hasUncommittedChanges() = 0;
virtual bool hasNonDefaultValue() = 0;
virtual void resetToDefault() = 0;
};
}

View file

@ -3,22 +3,17 @@
#include "../DefaultInclude.hpp"
#include <optional>
#include <cocos2d.h>
// todo: remove this header in 4.0.0
#include "Setting.hpp"
#include "../utils/cocos.hpp"
#include "../utils/file.hpp"
// this unfortunately has to be included because of C++ templates
#include "../utils/JsonValidation.hpp"
#include "../utils/function.hpp"
// todo in v4: this can be removed as well as the friend decl in LegacyCustomSettingV3
class LegacyCustomSettingToV3Node;
class ModSettingsPopup;
namespace geode {
class ModSettingsManager;
class SettingNodeV3;
// todo in v4: remove this
class SettingValue;
class GEODE_DLL SettingV3 : public std::enable_shared_from_this<SettingV3> {
private:
@ -125,8 +120,6 @@ namespace geode {
*/
void markChanged();
friend class ::geode::SettingValue;
public:
SettingV3();
virtual ~SettingV3();
@ -183,20 +176,9 @@ namespace geode {
* Reset this setting's value back to its original value
*/
virtual void reset() = 0;
[[deprecated(
"This function will be removed alongside legacy settings in 4.0.0! "
"You should NOT be implementing it for your own custom setting classes"
)]]
virtual std::optional<Setting> convertToLegacy() const;
[[deprecated(
"This function will be removed alongside legacy settings in 4.0.0! "
"You should NOT be implementing it for your own custom setting classes"
)]]
virtual std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const;
};
using SettingGenerator = std::function<Result<std::shared_ptr<SettingV3>>(
using SettingGeneratorV3 = std::function<Result<std::shared_ptr<SettingV3>>(
std::string const& key,
std::string const& modID,
matjson::Value const& json
@ -324,14 +306,15 @@ namespace geode {
}
bool load(matjson::Value const& json) override {
if (json.is<T>()) {
m_impl->value = json.as<T>();
return true;
auto res = json.as<T>();
if (res.isErr()) {
return false;
}
return false;
m_impl->value = res.unwrap();
return true;
}
bool save(matjson::Value& json) const override {
json = m_impl->value;
json = matjson::Value(m_impl->value);
return true;
}
};
@ -357,37 +340,6 @@ namespace geode {
void reset() override;
};
// todo in v4: remove this class completely
class GEODE_DLL LegacyCustomSettingV3 final : public SettingV3 {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
friend class ::geode::ModSettingsManager;
friend class ::LegacyCustomSettingToV3Node;
private:
class PrivateMarker {};
friend class SettingV3;
public:
LegacyCustomSettingV3(PrivateMarker);
static Result<std::shared_ptr<LegacyCustomSettingV3>> parse(std::string const& key, std::string const& modID, matjson::Value const& json);
std::shared_ptr<SettingValue> getValue() const;
void setValue(std::shared_ptr<SettingValue> value);
bool load(matjson::Value const& json) override;
bool save(matjson::Value& json) const override;
SettingNodeV3* createNode(float width) override;
bool isDefaultValue() const override;
void reset() override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL BoolSettingV3 final : public SettingBaseValueV3<bool> {
private:
class Impl;
@ -404,9 +356,6 @@ namespace geode {
Result<> isValid(bool value) const override;
SettingNodeV3* createNode(float width) override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL IntSettingV3 final : public SettingBaseValueV3<int64_t> {
@ -436,9 +385,6 @@ namespace geode {
bool isInputEnabled() const;
SettingNodeV3* createNode(float width) override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL FloatSettingV3 final : public SettingBaseValueV3<double> {
@ -468,9 +414,6 @@ namespace geode {
bool isInputEnabled() const;
SettingNodeV3* createNode(float width) override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL StringSettingV3 final : public SettingBaseValueV3<std::string, std::string_view> {
@ -493,9 +436,6 @@ namespace geode {
std::optional<std::vector<std::string>> getEnumOptions() const;
SettingNodeV3* createNode(float width) override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL FileSettingV3 final : public SettingBaseValueV3<std::filesystem::path, std::filesystem::path const&> {
@ -519,9 +459,6 @@ namespace geode {
std::optional<std::vector<utils::file::FilePickOptions::Filter>> getFilters() const;
SettingNodeV3* createNode(float width) override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL Color3BSettingV3 final : public SettingBaseValueV3<cocos2d::ccColor3B> {
@ -540,9 +477,6 @@ namespace geode {
Result<> isValid(cocos2d::ccColor3B value) const override;
SettingNodeV3* createNode(float width) override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL Color4BSettingV3 final : public SettingBaseValueV3<cocos2d::ccColor4B> {
@ -561,9 +495,6 @@ namespace geode {
Result<> isValid(cocos2d::ccColor4B value) const override;
SettingNodeV3* createNode(float width) override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL SettingNodeV3 : public cocos2d::CCNode {
@ -724,7 +655,7 @@ namespace geode {
public:
using Callback = void(std::shared_ptr<SettingV3>);
ListenerResult handle(utils::MiniFunction<Callback> fn, SettingChangedEventV3* event);
ListenerResult handle(std::function<Callback> fn, SettingChangedEventV3* event);
/**
* Listen to changes on a setting, or all settings
* @param modID Mod whose settings to listen to
@ -800,7 +731,7 @@ namespace geode {
};
template <class T>
EventListener<SettingChangedFilterV3>* listenForSettingChanges(std::string_view settingKey, auto&& callback, Mod* mod = getMod()) {
EventListener<SettingChangedFilterV3>* listenForSettingChangesV3(std::string_view settingKey, auto&& callback, Mod* mod = getMod()) {
using Ty = typename SettingTypeForValueType<T>::SettingType;
return new EventListener(
[callback = std::move(callback)](std::shared_ptr<SettingV3> setting) {
@ -811,11 +742,11 @@ namespace geode {
SettingChangedFilterV3(mod, std::string(settingKey))
);
}
EventListener<SettingChangedFilterV3>* listenForSettingChanges(std::string_view settingKey, auto&& callback, Mod* mod = getMod()) {
EventListener<SettingChangedFilterV3>* listenForSettingChangesV3(std::string_view settingKey, auto&& callback, Mod* mod = getMod()) {
using T = std::remove_cvref_t<utils::function::Arg<0, decltype(callback)>>;
return listenForSettingChanges<T>(settingKey, std::move(callback), mod);
return listenForSettingChangesV3<T>(settingKey, std::move(callback), mod);
}
GEODE_DLL EventListener<SettingChangedFilterV3>* listenForAllSettingChanges(
GEODE_DLL EventListener<SettingChangedFilterV3>* listenForAllSettingChangesV3(
std::function<void(std::shared_ptr<SettingV3>)> const& callback,
Mod* mod = getMod()
);

View file

@ -2,12 +2,13 @@
#include <Geode/platform/platform.hpp>
#include <tulip/TulipHook.hpp>
#include "../Prelude.hpp"
namespace geode::hook {
/**
* Create a calling convention wrapper for a function.
*/
GEODE_DLL tulip::hook::Result<void*> createWrapper(
GEODE_DLL Result<void*> createWrapper(
void* address,
tulip::hook::WrapperMetadata const& metadata
) noexcept;

View file

@ -89,14 +89,11 @@ namespace geode {
constexpr std::string_view GEODE_MOD_EXTENSION = ".geode";
class Mod;
class Setting;
class Loader;
class Hook;
class VersionInfo;
class Unknown;
using unknownmemfn_t = void (Unknown::*)();
using unknownfn_t = void (*)();
namespace modifier {
template <class, class>

View file

@ -1,6 +1,5 @@
#pragma once
#include "../utils/MiniFunction.hpp"
#include "Traits.hpp"
#include <Geode/loader/Loader.hpp>
@ -20,7 +19,7 @@ namespace geode::modifier {
class FieldContainer {
private:
std::vector<void*> m_containedFields;
std::vector<utils::MiniFunction<void(void*)>> m_destructorFunctions;
std::vector<std::function<void(void*)>> m_destructorFunctions;
public:
~FieldContainer() {
@ -40,9 +39,9 @@ namespace geode::modifier {
return m_containedFields.at(index);
}
void* setField(size_t index, size_t size, utils::MiniFunction<void(void*)> destructor) {
void* setField(size_t index, size_t size, std::function<void(void*)> destructor) {
m_containedFields.at(index) = operator new(size);
m_destructorFunctions.at(index) = destructor;
m_destructorFunctions.at(index) = std::move(destructor);
return m_containedFields.at(index);
}

View file

@ -104,8 +104,36 @@
} \
} 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>
class ModifyDerive;
@ -114,6 +142,9 @@ namespace geode::modifier {
public:
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) {
if (m_hooks.find(name) == m_hooks.end()) {
return Err("Hook not in this modify");
@ -121,15 +152,128 @@ namespace geode::modifier {
return Ok(m_hooks[name].get());
}
Result<> setHookPriority(std::string const& name, int32_t priority) {
auto res = this->getHook(name);
if (!res) {
return Err(res.unwrapErr());
}
res.unwrap()->setPriority(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<> setHookPriority(std::string const& name, int32_t priority = Priority::Normal) {
GEODE_UNWRAP_INTO(auto hook, this->getHook(name));
hook->setPriority(priority);
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
ModifyBase() {
struct EboCheck : ModifyDerived::Base {
@ -158,7 +302,7 @@ namespace geode::modifier {
for (auto& [uuid, hook] : m_hooks) {
auto res = Mod::get()->claimHook(hook);
if (!res) {
log::error("Failed to claim hook {}: {}", hook->getDisplayName(), res.error());
log::error("Failed to claim hook {}: {}", hook->getDisplayName(), res.unwrapErr());
}
else {
added.push_back(uuid);

View file

@ -116,14 +116,22 @@ namespace geode {
public:
// todo in v4: make these flags and add archless Mac and Android as well as Desktop and Mobile and remove Linux
enum {
Unknown = -1,
Windows = 0,
MacIntel = 1,
MacArm = 2,
iOS = 3,
Android32 = 4,
Android64 = 5,
Linux = 6,
Unknown = 0b000000,
Windows = 0b000001,
Android32 = 0b000010,
Android64 = 0b000100,
MacIntel = 0b001000,
MacArm = 0b010000,
iOS = 0b100000,
Android = Android32 | Android64,
Mac = MacIntel | MacArm,
Apple = Mac | iOS,
X64 = MacIntel | Windows,
X86 = Unknown,
ArmV7 = Android32,
ArmV8 = Android64 | MacArm | iOS,
Desktop = Windows | Mac,
Mobile = Android | iOS,
};
using Type = decltype(Unknown);
@ -190,7 +198,6 @@ namespace geode {
case iOS: return "iOS";
case Android32: return "Android32";
case Android64: return "Android64";
case Linux: return "Linux";
default: break;
}
return "Undefined";
@ -206,7 +213,6 @@ namespace geode {
case iOS: return "ios";
case Android32: return ignoreArch ? "android" : "android32";
case Android64: return ignoreArch ? "android" : "android64";
case Linux: return "linux";
default: break;
}
return "undefined";

View file

@ -30,7 +30,7 @@ namespace geode {
std::optional<std::string> m_targetID;
public:
ListenerResult handle(utils::MiniFunction<Callback> fn, AEnterLayerEvent* event);
ListenerResult handle(std::function<Callback> fn, AEnterLayerEvent* event);
AEnterLayerFilter(
std::optional<std::string> const& id
@ -63,7 +63,7 @@ namespace geode {
std::optional<std::string> m_targetID;
public:
ListenerResult handle(utils::MiniFunction<Callback> fn, EnterLayerEvent<N>* event) {
ListenerResult handle(std::function<Callback> fn, EnterLayerEvent<N>* event) {
if (m_targetID == event->getID()) {
fn(static_cast<T*>(event));
}

View file

@ -133,11 +133,6 @@ namespace geode {
*/
GEODE_DLL void openSupportPopup(Mod* mod);
GEODE_DLL void openSupportPopup(ModMetadata const& metadata);
/**
* Open the store page for a mod (if it exists)
*/
[[deprecated("Will be removed, use openInfoPopup instead")]]
GEODE_DLL void openIndexPopup(Mod* mod);
/**
* Open the settings popup for a mod (if it has any settings)
*/

View file

@ -1,41 +0,0 @@
#pragma once
#include <Geode/DefaultInclude.hpp>
#include <Geode/binding/CCTextInputNode.hpp>
#include <cocos2d.h>
namespace geode {
class GEODE_DLL InputNode : public cocos2d::CCMenuItem {
protected:
cocos2d::extension::CCScale9Sprite* m_bgSprite;
CCTextInputNode* m_input;
bool init(float, float, char const*, char const*, std::string const&, int);
bool init(float, char const*, char const*, std::string const&, int);
public:
[[deprecated("Use geode::TextInput from the ui/TextInput.hpp header instead")]]
static InputNode* create(
float width, char const* placeholder, char const* fontFile, std::string const& filter,
int limit
);
[[deprecated("Use geode::TextInput from the ui/TextInput.hpp header instead")]]
static InputNode* create(
float width, char const* placeholder, std::string const& filter, int limit
);
[[deprecated("Use geode::TextInput from the ui/TextInput.hpp header instead")]]
static InputNode* create(float width, char const* placeholder, std::string const& filter);
[[deprecated("Use geode::TextInput from the ui/TextInput.hpp header instead")]]
static InputNode* create(float width, char const* placeholder, char const* fontFile);
[[deprecated("Use geode::TextInput from the ui/TextInput.hpp header instead")]]
static InputNode* create(float width, char const* placeholder);
CCTextInputNode* getInput() const;
cocos2d::extension::CCScale9Sprite* getBG() const;
void setEnabled(bool enabled) override;
void setString(std::string const&);
std::string getString();
};
}

View file

@ -1,448 +1,430 @@
#pragma once
#include "../include/ccMacros.h"
#include "../cocoa/CCAffineTransform.h"
#include "../cocoa/CCArray.h"
#include <Geode/platform/platform.hpp>
#include <optional>
#include <memory>
NS_CC_BEGIN
class CCNode;
#pragma warning(push)
#pragma warning(disable: 4275)
/**
* Layouts automatically handle the positioning of nodes. Use CCNode::setLayout
* to apply a layout to a node, and then use CCNode::updateLayout to apply
* the layout's positioning. Geode comes with a few default layouts like
* RowLayout, ColumnLayout, and GridLayout, but if you need a different kind
* of layout you can inherit from the Layout class.
*/
class GEODE_DLL Layout : public CCObject {
protected:
CCArray* getNodesToPosition(CCNode* forNode) const;
bool m_ignoreInvisibleChildren = false;
public:
/**
* Automatically apply the layout's positioning on a set of nodes
* @param on Node to apply the layout on. Position's the node's children
* according to the layout. The content size of the node should be
* respected as a boundary the layout shouldn't overflow. The node may be
* rescaled to better fit its contents
*/
virtual void apply(CCNode* on) = 0;
/**
* Get how much space this layout would like to take up for a given target
*/
virtual CCSize getSizeHint(CCNode* on) const = 0;
void ignoreInvisibleChildren(bool ignore);
bool isIgnoreInvisibleChildren() const;
virtual ~Layout() = default;
};
class GEODE_DLL LayoutOptions : public CCObject {
public:
virtual ~LayoutOptions() = default;
};
/**
* The direction of an AxisLayout
*/
enum class Axis {
Row,
Column,
};
/**
* Specifies the alignment of something in an AxisLayout
*/
enum class AxisAlignment {
// Align items to the start
// |ooo......|
Start,
// All items are centered
// |...ooo...|
Center,
// Align items to the end
// |......ooo|
End,
// Each item gets the same portion from the layout (disregards gap)
// |.o..o..o.|
Even,
// Space between each item is the same (disregards gap)
// |o...o...o|
Between,
};
constexpr float AXISLAYOUT_DEFAULT_MIN_SCALE = 0.65f;
constexpr int AXISLAYOUT_DEFAULT_PRIORITY = 0;
/**
* Options for controlling the behaviour of individual nodes in an AxisLayout
* @example
* auto node = CCNode::create();
* // this node will have 10 units of spacing between it and the next one
* node->setLayoutOptions(
* AxisLayoutOptions::create()
* ->setNextGap(10.f)
* );
* someNodeWithALayout->addChild(node);
*/
class GEODE_DLL AxisLayoutOptions : public LayoutOptions {
protected:
class Impl;
std::unique_ptr<Impl> m_impl;
AxisLayoutOptions();
public:
static AxisLayoutOptions* create();
virtual ~AxisLayoutOptions();
std::optional<bool> getAutoScale() const;
// @note Use hasExplicitMaxScale to know if the default scale has been overwritten
float getMaxScale() const;
// @note Use hasExplicitMinScale to know if the default scale has been overwritten
float getMinScale() const;
bool hasExplicitMaxScale() const;
bool hasExplicitMinScale() const;
float getRelativeScale() const;
std::optional<float> getLength() const;
std::optional<float> getPrevGap() const;
std::optional<float> getNextGap() const;
bool getBreakLine() const;
bool getSameLine() const;
int getScalePriority() const;
std::optional<AxisAlignment> getCrossAxisAlignment() const;
/**
* Set the maximum scale this node can be if it's contained in an
* auto-scaled layout. Default is 1
*/
[[deprecated("Use AxisLayoutOptions::setScaleLimits")]]
AxisLayoutOptions* setMaxScale(float scale);
/**
* Set the minimum scale this node can be if it's contained in an
* auto-scaled layout. Default is AXISLAYOUT_DEFAULT_MIN_SCALE
*/
[[deprecated("Use AxisLayoutOptions::setScaleLimits")]]
AxisLayoutOptions* setMinScale(float scale);
/**
* Set the limits to what the node can be scaled to. Passing `std::nullopt`
* uses the parent layout's default min / max scales
*/
AxisLayoutOptions* setScaleLimits(std::optional<float> min, std::optional<float> max);
/**
* Set the relative scale of this node compared to other nodes if it's
* contained in an auto-scaled layout. Default is 1
*/
AxisLayoutOptions* setRelativeScale(float scale);
/**
* Set auto-scaling for this node, overriding the layout's auto-scale
* setting. If nullopt, the layout's auto-scale options will be used
*/
AxisLayoutOptions* setAutoScale(std::optional<bool> enabled);
/**
* Set an absolute length for this node. If nullopt, the length will be
* dynamically calculated based on content size
*/
AxisLayoutOptions* setLength(std::optional<float> length);
/**
* Override the default gap in the layout between this node and the
* previous one. If nullopt, the default gap of the layout will be used
*/
AxisLayoutOptions* setPrevGap(std::optional<float> gap);
/**
* Override the default gap in the layout between this node and the next
* one. If nullopt, the default gap of the layout will be used
*/
AxisLayoutOptions* setNextGap(std::optional<float> gap);
/**
* If enabled, the node will always cause a growable axis layout to break
* into a new line even if the current line could've fit the next node
*/
AxisLayoutOptions* setBreakLine(bool enable);
/**
* If enabled, the node will be forced to be on the same line as the
* previous node even if doing this would overflow
*/
AxisLayoutOptions* setSameLine(bool enable);
/**
* Set the scale priority of this node. Nodes with higher priority will be
* scaled down first before nodes with lower priority when an auto-scaled
* layout attempts to fit its contents. Default is
* AXISLAYOUT_DEFAULT_PRIORITY
* @note For optimal performance, the priorities should all be close to
* each other with no gaps
*/
AxisLayoutOptions* setScalePriority(int priority);
/**
* Override the cross axis alignment for this node in the layout
*/
AxisLayoutOptions* setCrossAxisAlignment(std::optional<AxisAlignment> alignment);
};
/**
* A multi-purpose dynamic layout for arranging nodes along an axis. Can be
* used to arrange nodes in a single line, a grid, or a flex layout. The
* RowLayout and ColumnLayout classes function as simple thin wrappers over
* AxisLayout. The positioning of individual nodes in the layout can be
* further controlled using AxisLayoutOptions
* @warning Calculating layouts can get increasingly expensive for large
* amounts of child nodes being fit into a small space - while this should
* never prove a real performance concern as most layouts only have a few
* hundred children at the very most, be aware that you probably shouldn't
* call CCNode::updateLayout every frame for a menu with thousands of children
* @example
* auto menu = CCMenu::create();
* // The menu's children will be arranged horizontally, unless they overflow
* // the content size width in which case a new line will be inserted and
* // aligned to the left. The menu automatically will automatically grow in
* // height to fit all the rows
* menu->setLayout(
* RowLayout::create()
* ->setGap(10.f)
* ->setGrowCrossAxis(true)
* ->setAxisAlignment(AxisAlignment::Start)
* );
* menu->setContentSize({ 200.f, 0.f });
* menu->addChild(...);
* menu->updateLayout();
*/
class GEODE_DLL AxisLayout : public Layout {
protected:
class Impl;
std::unique_ptr<Impl> m_impl;
AxisLayout(Axis);
public:
/**
* Create a new AxisLayout. Note that this class is not automatically
* managed by default, so you must assign it to a CCNode or manually
* manage the memory yourself. See the chainable setters on AxisLayout for
* what options you can customize for the layout
* @param axis The direction of the layout
* @note For convenience, you can use the RowLayout and ColumnLayout
* classes, which are just thin wrappers over AxisLayout
* @returns Created AxisLayout
*/
static AxisLayout* create(Axis axis = Axis::Row);
virtual ~AxisLayout();
void apply(CCNode* on) override;
CCSize getSizeHint(CCNode* on) const override;
Axis getAxis() const;
AxisAlignment getAxisAlignment() const;
AxisAlignment getCrossAxisAlignment() const;
AxisAlignment getCrossAxisLineAlignment() const;
float getGap() const;
bool getAxisReverse() const;
bool getCrossAxisReverse() const;
bool getAutoScale() const;
bool getGrowCrossAxis() const;
bool getCrossAxisOverflow() const;
std::optional<float> getAutoGrowAxis() const;
float getDefaultMinScale() const;
float getDefaultMaxScale() const;
AxisLayout* setAxis(Axis axis);
/**
* Sets where to align the target node's children on the main axis (X-axis
* for Row, Y-axis for Column)
*/
AxisLayout* setAxisAlignment(AxisAlignment align);
/**
* Sets where to align the target node's children on the cross-axis (Y-axis
* for Row, X-axis for Column)
*/
AxisLayout* setCrossAxisAlignment(AxisAlignment align);
/**
* Sets where to align the target node's children on the cross-axis for
* each row (Y-axis for Row, X-axis for Column)
*/
AxisLayout* setCrossAxisLineAlignment(AxisAlignment align);
/**
* The spacing between the children of the node this layout applies to.
* Measured as the space between their edges, not centres. Does not apply
* on the main / cross axis if their alignment is AxisAlignment::Even
*/
AxisLayout* setGap(float gap);
/**
* Whether to reverse the direction of the children in this layout or not
*/
AxisLayout* setAxisReverse(bool reverse);
/**
* Whether to reverse the direction of the rows on the cross-axis or not
*/
AxisLayout* setCrossAxisReverse(bool reverse);
/**
* If enabled, then the layout may scale the target's children if they are
* about to overflow. Assumes that all the childrens' intended scale is 1
*/
AxisLayout* setAutoScale(bool enable);
/**
* If true, if the main axis overflows extra nodes will be placed on new
* rows/columns on the cross-axis
*/
AxisLayout* setGrowCrossAxis(bool expand);
/**
* If true, the cross-axis content size of the target node will be
* automatically adjusted to fit the children
*/
AxisLayout* setCrossAxisOverflow(bool allow);
/**
* If not `std::nullopt`, then the axis will be automatically extended to
* fit all items in a single row whose minimum length is the specified.
* Useful for scrollable list layer contents
*/
AxisLayout* setAutoGrowAxis(std::optional<float> allowAndMinLength);
/**
* Set the default minimum/maximum scales for nodes in the layout
*/
AxisLayout* setDefaultScaleLimits(float min, float max);
};
/**
* Simple layout for arranging nodes in a row (horizontal line)
*/
class GEODE_DLL RowLayout : public AxisLayout {
protected:
RowLayout();
public:
/**
* Create a new RowLayout. See the chainable setters on RowLayout for
* what options you can customize for the layout
* @returns Created RowLayout
*/
static RowLayout* create();
};
/**
* Simple layout for arranging nodes in a column (vertical line)
*/
class GEODE_DLL ColumnLayout : public AxisLayout {
protected:
ColumnLayout();
public:
/**
* Create a new ColumnLayout. See the chainable setters on RowLayout for
* what options you can customize for the layout
* @returns Created ColumnLayout
*/
static ColumnLayout* create();
};
/**
* The relative position of a node to its parent in an AnchorLayout
*/
enum class Anchor {
Center,
TopLeft,
Top,
TopRight,
Right,
BottomRight,
Bottom,
BottomLeft,
Left,
};
/**
* Options for customizing a node's position in an AnchorLayout
*/
class GEODE_DLL AnchorLayoutOptions : public LayoutOptions {
protected:
Anchor m_anchor = Anchor::Center;
CCPoint m_offset = CCPointZero;
public:
static AnchorLayoutOptions* create();
Anchor getAnchor() const;
CCPoint getOffset() const;
AnchorLayoutOptions* setAnchor(Anchor anchor);
AnchorLayoutOptions* setOffset(CCPoint const& offset);
};
/**
* A layout for positioning nodes at specific positions relative to their
* parent's content size. See `Anchor` for available anchoring options. Useful
* for example for popups, where a popup using `AnchorLayout` can be
* automatically resized without needing to manually shuffle nodes around
*/
class GEODE_DLL AnchorLayout : public Layout {
public:
static AnchorLayout* create();
void apply(CCNode* on) override;
CCSize getSizeHint(CCNode* on) const override;
/**
* Get a position according to anchoring rules, with the same algorithm as
* `AnchorLayout` uses to position its nodes
* @param in The node whose content size to use as a reference
* @param anchor The anchor position
* @param offset Offset from the anchor
* @returns A position in `in` for the anchored and offsetted location
*/
static CCPoint getAnchoredPosition(CCNode* in, Anchor anchor, CCPoint const& offset);
};
/**
* A layout for automatically copying the content size of a node to other nodes.
* Basically main use case is for FLAlertLayers (setting the size of the
* background and `m_buttonMenu` based on `m_mainLayer`)
*/
class GEODE_DLL CopySizeLayout : public cocos2d::AnchorLayout {
protected:
cocos2d::CCArray* m_targets;
public:
static CopySizeLayout* create();
virtual ~CopySizeLayout();
/**
* Add a target to be automatically resized. Any targets' layouts will
* also be updated when this layout is updated
*/
CopySizeLayout* add(cocos2d::CCNode* target);
/**
* Remove a target from being automatically resized
*/
CopySizeLayout* remove(cocos2d::CCNode* target);
void apply(cocos2d::CCNode* in) override;
cocos2d::CCSize getSizeHint(cocos2d::CCNode* in) const override;
};
#pragma warning(pop)
NS_CC_END
#pragma once
#include <cocos2d.h>
#include <Geode/platform/platform.hpp>
#include <optional>
#include <memory>
namespace geode {
#pragma warning(push)
#pragma warning(disable: 4275)
/**
* Layouts automatically handle the positioning of nodes. Use CCNode::setLayout
* to apply a layout to a node, and then use CCNode::updateLayout to apply
* the layout's positioning. Geode comes with a few default layouts like
* RowLayout, ColumnLayout, and GridLayout, but if you need a different kind
* of layout you can inherit from the Layout class.
*/
class GEODE_DLL Layout : public cocos2d::CCObject {
protected:
cocos2d::CCArray* getNodesToPosition(cocos2d::CCNode* forNode) const;
bool m_ignoreInvisibleChildren = false;
public:
/**
* Automatically apply the layout's positioning on a set of nodes
* @param on Node to apply the layout on. Position's the node's children
* according to the layout. The content size of the node should be
* respected as a boundary the layout shouldn't overflow. The node may be
* rescaled to better fit its contents
*/
virtual void apply(cocos2d::CCNode* on) = 0;
/**
* Get how much space this layout would like to take up for a given target
*/
virtual cocos2d::CCSize getSizeHint(cocos2d::CCNode* on) const = 0;
void ignoreInvisibleChildren(bool ignore);
bool isIgnoreInvisibleChildren() const;
virtual ~Layout() = default;
};
class GEODE_DLL LayoutOptions : public cocos2d::CCObject {
public:
virtual ~LayoutOptions() = default;
};
/**
* The direction of an AxisLayout
*/
enum class Axis {
Row,
Column,
};
/**
* Specifies the alignment of something in an AxisLayout
*/
enum class AxisAlignment {
// Align items to the start
// |ooo......|
Start,
// All items are centered
// |...ooo...|
Center,
// Align items to the end
// |......ooo|
End,
// Each item gets the same portion from the layout (disregards gap)
// |.o..o..o.|
Even,
// Space between each item is the same (disregards gap)
// |o...o...o|
Between,
};
constexpr float AXISLAYOUT_DEFAULT_MIN_SCALE = 0.65f;
constexpr int AXISLAYOUT_DEFAULT_PRIORITY = 0;
/**
* Options for controlling the behaviour of individual nodes in an AxisLayout
* @example
* auto node = CCNode::create();
* // this node will have 10 units of spacing between it and the next one
* node->setLayoutOptions(
* AxisLayoutOptions::create()
* ->setNextGap(10.f)
* );
* someNodeWithALayout->addChild(node);
*/
class GEODE_DLL AxisLayoutOptions : public LayoutOptions {
protected:
class Impl;
std::unique_ptr<Impl> m_impl;
AxisLayoutOptions();
public:
static AxisLayoutOptions* create();
virtual ~AxisLayoutOptions();
std::optional<bool> getAutoScale() const;
// @note Use hasExplicitMaxScale to know if the default scale has been overwritten
float getMaxScale() const;
// @note Use hasExplicitMinScale to know if the default scale has been overwritten
float getMinScale() const;
bool hasExplicitMaxScale() const;
bool hasExplicitMinScale() const;
float getRelativeScale() const;
std::optional<float> getLength() const;
std::optional<float> getPrevGap() const;
std::optional<float> getNextGap() const;
bool getBreakLine() const;
bool getSameLine() const;
int getScalePriority() const;
std::optional<AxisAlignment> getCrossAxisAlignment() const;
/**
* Set the limits to what the node can be scaled to. Passing `std::nullopt`
* uses the parent layout's default min / max scales
*/
AxisLayoutOptions* setScaleLimits(std::optional<float> min, std::optional<float> max);
/**
* Set the relative scale of this node compared to other nodes if it's
* contained in an auto-scaled layout. Default is 1
*/
AxisLayoutOptions* setRelativeScale(float scale);
/**
* Set auto-scaling for this node, overriding the layout's auto-scale
* setting. If nullopt, the layout's auto-scale options will be used
*/
AxisLayoutOptions* setAutoScale(std::optional<bool> enabled);
/**
* Set an absolute length for this node. If nullopt, the length will be
* dynamically calculated based on content size
*/
AxisLayoutOptions* setLength(std::optional<float> length);
/**
* Override the default gap in the layout between this node and the
* previous one. If nullopt, the default gap of the layout will be used
*/
AxisLayoutOptions* setPrevGap(std::optional<float> gap);
/**
* Override the default gap in the layout between this node and the next
* one. If nullopt, the default gap of the layout will be used
*/
AxisLayoutOptions* setNextGap(std::optional<float> gap);
/**
* If enabled, the node will always cause a growable axis layout to break
* into a new line even if the current line could've fit the next node
*/
AxisLayoutOptions* setBreakLine(bool enable);
/**
* If enabled, the node will be forced to be on the same line as the
* previous node even if doing this would overflow
*/
AxisLayoutOptions* setSameLine(bool enable);
/**
* Set the scale priority of this node. Nodes with higher priority will be
* scaled down first before nodes with lower priority when an auto-scaled
* layout attempts to fit its contents. Default is
* AXISLAYOUT_DEFAULT_PRIORITY
* @note For optimal performance, the priorities should all be close to
* each other with no gaps
*/
AxisLayoutOptions* setScalePriority(int priority);
/**
* Override the cross axis alignment for this node in the layout
*/
AxisLayoutOptions* setCrossAxisAlignment(std::optional<AxisAlignment> alignment);
};
/**
* A multi-purpose dynamic layout for arranging nodes along an axis. Can be
* used to arrange nodes in a single line, a grid, or a flex layout. The
* RowLayout and ColumnLayout classes function as simple thin wrappers over
* AxisLayout. The positioning of individual nodes in the layout can be
* further controlled using AxisLayoutOptions
* @warning Calculating layouts can get increasingly expensive for large
* amounts of child nodes being fit into a small space - while this should
* never prove a real performance concern as most layouts only have a few
* hundred children at the very most, be aware that you probably shouldn't
* call CCNode::updateLayout every frame for a menu with thousands of children
* @example
* auto menu = CCMenu::create();
* // The menu's children will be arranged horizontally, unless they overflow
* // the content size width in which case a new line will be inserted and
* // aligned to the left. The menu automatically will automatically grow in
* // height to fit all the rows
* menu->setLayout(
* RowLayout::create()
* ->setGap(10.f)
* ->setGrowCrossAxis(true)
* ->setAxisAlignment(AxisAlignment::Start)
* );
* menu->setContentSize({ 200.f, 0.f });
* menu->addChild(...);
* menu->updateLayout();
*/
class GEODE_DLL AxisLayout : public Layout {
protected:
class Impl;
std::unique_ptr<Impl> m_impl;
AxisLayout(Axis);
public:
/**
* Create a new AxisLayout. Note that this class is not automatically
* managed by default, so you must assign it to a CCNode or manually
* manage the memory yourself. See the chainable setters on AxisLayout for
* what options you can customize for the layout
* @param axis The direction of the layout
* @note For convenience, you can use the RowLayout and ColumnLayout
* classes, which are just thin wrappers over AxisLayout
* @returns Created AxisLayout
*/
static AxisLayout* create(Axis axis = Axis::Row);
virtual ~AxisLayout();
void apply(cocos2d::CCNode* on) override;
cocos2d::CCSize getSizeHint(cocos2d::CCNode* on) const override;
Axis getAxis() const;
AxisAlignment getAxisAlignment() const;
AxisAlignment getCrossAxisAlignment() const;
AxisAlignment getCrossAxisLineAlignment() const;
float getGap() const;
bool getAxisReverse() const;
bool getCrossAxisReverse() const;
bool getAutoScale() const;
bool getGrowCrossAxis() const;
bool getCrossAxisOverflow() const;
std::optional<float> getAutoGrowAxis() const;
float getDefaultMinScale() const;
float getDefaultMaxScale() const;
AxisLayout* setAxis(Axis axis);
/**
* Sets where to align the target node's children on the main axis (X-axis
* for Row, Y-axis for Column)
*/
AxisLayout* setAxisAlignment(AxisAlignment align);
/**
* Sets where to align the target node's children on the cross-axis (Y-axis
* for Row, X-axis for Column)
*/
AxisLayout* setCrossAxisAlignment(AxisAlignment align);
/**
* Sets where to align the target node's children on the cross-axis for
* each row (Y-axis for Row, X-axis for Column)
*/
AxisLayout* setCrossAxisLineAlignment(AxisAlignment align);
/**
* The spacing between the children of the node this layout applies to.
* Measured as the space between their edges, not centres. Does not apply
* on the main / cross axis if their alignment is AxisAlignment::Even
*/
AxisLayout* setGap(float gap);
/**
* Whether to reverse the direction of the children in this layout or not
*/
AxisLayout* setAxisReverse(bool reverse);
/**
* Whether to reverse the direction of the rows on the cross-axis or not
*/
AxisLayout* setCrossAxisReverse(bool reverse);
/**
* If enabled, then the layout may scale the target's children if they are
* about to overflow. Assumes that all the childrens' intended scale is 1
*/
AxisLayout* setAutoScale(bool enable);
/**
* If true, if the main axis overflows extra nodes will be placed on new
* rows/columns on the cross-axis
*/
AxisLayout* setGrowCrossAxis(bool expand);
/**
* If true, the cross-axis content size of the target node will be
* automatically adjusted to fit the children
*/
AxisLayout* setCrossAxisOverflow(bool allow);
/**
* If not `std::nullopt`, then the axis will be automatically extended to
* fit all items in a single row whose minimum length is the specified.
* Useful for scrollable list layer contents
*/
AxisLayout* setAutoGrowAxis(std::optional<float> allowAndMinLength);
/**
* Set the default minimum/maximum scales for nodes in the layout
*/
AxisLayout* setDefaultScaleLimits(float min, float max);
};
/**
* Simple layout for arranging nodes in a row (horizontal line)
*/
class GEODE_DLL RowLayout : public AxisLayout {
protected:
RowLayout();
public:
/**
* Create a new RowLayout. See the chainable setters on RowLayout for
* what options you can customize for the layout
* @returns Created RowLayout
*/
static RowLayout* create();
};
/**
* Simple layout for arranging nodes in a column (vertical line)
*/
class GEODE_DLL ColumnLayout : public AxisLayout {
protected:
ColumnLayout();
public:
/**
* Create a new ColumnLayout. See the chainable setters on RowLayout for
* what options you can customize for the layout
* @returns Created ColumnLayout
*/
static ColumnLayout* create();
};
/**
* The relative position of a node to its parent in an AnchorLayout
*/
enum class Anchor {
Center,
TopLeft,
Top,
TopRight,
Right,
BottomRight,
Bottom,
BottomLeft,
Left,
};
/**
* Options for customizing a node's position in an AnchorLayout
*/
class GEODE_DLL AnchorLayoutOptions : public LayoutOptions {
protected:
Anchor m_anchor = Anchor::Center;
cocos2d::CCPoint m_offset = cocos2d::CCPointZero;
public:
static AnchorLayoutOptions* create();
Anchor getAnchor() const;
cocos2d::CCPoint getOffset() const;
AnchorLayoutOptions* setAnchor(Anchor anchor);
AnchorLayoutOptions* setOffset(cocos2d::CCPoint const& offset);
};
/**
* A layout for positioning nodes at specific positions relative to their
* parent's content size. See `Anchor` for available anchoring options. Useful
* for example for popups, where a popup using `AnchorLayout` can be
* automatically resized without needing to manually shuffle nodes around
*/
class GEODE_DLL AnchorLayout : public Layout {
public:
static AnchorLayout* create();
void apply(cocos2d::CCNode* on) override;
cocos2d::CCSize getSizeHint(cocos2d::CCNode* on) const override;
/**
* Get a position according to anchoring rules, with the same algorithm as
* `AnchorLayout` uses to position its nodes
* @param in The node whose content size to use as a reference
* @param anchor The anchor position
* @param offset Offset from the anchor
* @returns A position in `in` for the anchored and offsetted location
*/
static cocos2d::CCPoint getAnchoredPosition(cocos2d::CCNode* in, Anchor anchor, cocos2d::CCPoint const& offset);
};
/**
* A layout for automatically copying the content size of a node to other nodes.
* Basically main use case is for FLAlertLayers (setting the size of the
* background and `m_buttonMenu` based on `m_mainLayer`)
*/
class GEODE_DLL CopySizeLayout : public AnchorLayout {
protected:
cocos2d::CCArray* m_targets;
public:
static CopySizeLayout* create();
virtual ~CopySizeLayout();
/**
* Add a target to be automatically resized. Any targets' layouts will
* also be updated when this layout is updated
*/
CopySizeLayout* add(cocos2d::CCNode* target);
/**
* Remove a target from being automatically resized
*/
CopySizeLayout* remove(cocos2d::CCNode* target);
void apply(cocos2d::CCNode* in) override;
cocos2d::CCSize getSizeHint(cocos2d::CCNode* in) const override;
};
#pragma warning(pop)
}

View file

@ -11,13 +11,13 @@ namespace geode {
class GEODE_DLL MDPopup :
public Popup<
std::string const&, std::string const&, char const*, char const*,
utils::MiniFunction<void(bool)>> {
std::function<void(bool)>> {
protected:
utils::MiniFunction<void(bool)> m_onClick = nullptr;
std::function<void(bool)> m_onClick = nullptr;
bool setup(
std::string const& title, std::string const& info, char const* btn1, char const* btn2,
utils::MiniFunction<void(bool)> onClick
std::function<void(bool)> onClick
) override;
void onBtn(CCObject*);
@ -27,7 +27,7 @@ namespace geode {
public:
static MDPopup* create(
std::string const& title, std::string const& content, char const* btn1,
char const* btn2 = nullptr, utils::MiniFunction<void(bool)> onClick = nullptr
char const* btn2 = nullptr, std::function<void(bool)> onClick = nullptr
);
};
}

View file

@ -2,8 +2,8 @@
#include <Geode/binding/CCMenuItemSpriteExtra.hpp>
#include <Geode/binding/FLAlertLayer.hpp>
#include <Geode/utils/MiniFunction.hpp>
#include <Geode/utils/cocos.hpp>
#include <Geode/ui/Layout.hpp>
namespace geode {
template <class... InitArgs>
@ -51,7 +51,7 @@ namespace geode {
}
public:
ListenerResult handle(utils::MiniFunction<Callback> fn, CloseEvent* event) {
ListenerResult handle(std::function<Callback> fn, CloseEvent* event) {
if (event->getPopup() == m_impl->popup) {
fn(event);
}
@ -104,7 +104,7 @@ namespace geode {
m_mainLayer->setPosition(winSize / 2);
m_mainLayer->setContentSize(m_size);
m_mainLayer->setLayout(
cocos2d::CopySizeLayout::create()
geode::CopySizeLayout::create()
->add(m_buttonMenu)
->add(m_bgSprite)
);
@ -119,7 +119,7 @@ namespace geode {
closeSpr, this, (cocos2d::SEL_MenuHandler)(&Popup::onClose)
);
if (dynamic) {
m_buttonMenu->addChildAtPosition(m_closeBtn, cocos2d::Anchor::TopLeft, { 3.f, -3.f });
m_buttonMenu->addChildAtPosition(m_closeBtn, geode::Anchor::TopLeft, { 3.f, -3.f });
}
else {
m_closeBtn->setPosition(-m_size.width / 2 + 3.f, m_size.height / 2 - 3.f);
@ -137,14 +137,6 @@ namespace geode {
}
protected:
[[deprecated("Use Popup::initAnchored instead, as it has more reasonable menu and layer content sizes")]]
bool init(
float width, float height, InitArgs... args, char const* bg = "GJ_square01.png",
cocos2d::CCRect bgRect = { 0, 0, 80, 80 }
) {
return this->initBase(width, height, std::forward<InitArgs>(args)..., bg, bgRect, false);
}
/**
* Init with AnchorLayout and the content size of `m_buttonMenu` and
* `m_bgSprite` being tied to the size of `m_mainLayer` (rather than
@ -185,7 +177,7 @@ namespace geode {
m_title = cocos2d::CCLabelBMFont::create(title.c_str(), font);
m_title->setZOrder(2);
if (m_dynamic) {
m_mainLayer->addChildAtPosition(m_title, cocos2d::Anchor::Top, ccp(0, -offset));
m_mainLayer->addChildAtPosition(m_title, geode::Anchor::Top, ccp(0, -offset));
}
else {
auto winSize = cocos2d::CCDirector::get()->getWinSize();
@ -221,21 +213,21 @@ namespace geode {
GEODE_DLL FLAlertLayer* createQuickPopup(
char const* title, std::string const& content, char const* btn1, char const* btn2,
utils::MiniFunction<void(FLAlertLayer*, bool)> selected, bool doShow = true
std::function<void(FLAlertLayer*, bool)> selected, bool doShow = true
);
GEODE_DLL FLAlertLayer* createQuickPopup(
char const* title, std::string const& content, char const* btn1, char const* btn2,
float width, utils::MiniFunction<void(FLAlertLayer*, bool)> selected, bool doShow = true
float width, std::function<void(FLAlertLayer*, bool)> selected, bool doShow = true
);
GEODE_DLL FLAlertLayer* createQuickPopup(
char const* title, std::string const& content, char const* btn1, char const* btn2,
utils::MiniFunction<void(FLAlertLayer*, bool)> selected, bool doShow, bool cancelledByEscape
std::function<void(FLAlertLayer*, bool)> selected, bool doShow, bool cancelledByEscape
);
GEODE_DLL FLAlertLayer* createQuickPopup(
char const* title, std::string const& content, char const* btn1, char const* btn2,
float width, utils::MiniFunction<void(FLAlertLayer*, bool)> selected, bool doShow, bool cancelledByEscape
float width, std::function<void(FLAlertLayer*, bool)> selected, bool doShow, bool cancelledByEscape
);
}

View file

@ -8,6 +8,8 @@
#include <Geode/utils/cocos.hpp>
namespace geode {
struct SceneSwitch;
class GEODE_DLL SceneManager final {
protected:
std::vector<Ref<cocos2d::CCNode>> m_persistedNodes;
@ -15,6 +17,10 @@ namespace geode {
virtual ~SceneManager();
void willSwitchToScene(cocos2d::CCScene* scene);
friend struct SceneSwitch;
public:
static SceneManager* get();
@ -34,9 +40,5 @@ namespace geode {
* Gets a span of the persisted nodes. To add new nodes to the list, use keepAcrossScenes.
*/
std::span<Ref<cocos2d::CCNode> const> getPersistedNodes();
// This method should only be called by geode itself
// TODO(v4): hide this
void willSwitchToScene(cocos2d::CCScene* scene);
};
}

View file

@ -14,13 +14,13 @@ namespace geode {
protected:
std::vector<T> m_list;
size_t m_index = 0;
utils::MiniFunction<void(T const&, size_t)> m_onChange;
std::function<void(T const&, size_t)> m_onChange;
cocos2d::CCLabelBMFont* m_label;
CCMenuItemSpriteExtra* m_prevBtn;
CCMenuItemSpriteExtra* m_nextBtn;
bool init(
float width, std::vector<T> const& list, utils::MiniFunction<void(T const&, size_t)> onChange
float width, std::vector<T> const& list, std::function<void(T const&, size_t)> onChange
) {
if (!cocos2d::CCMenu::init()) return false;
@ -94,7 +94,7 @@ namespace geode {
public:
static SelectList* create(
float width, std::vector<T> const& list, utils::MiniFunction<void(T const&, size_t)> onChange
float width, std::vector<T> const& list, std::function<void(T const&, size_t)> onChange
) {
auto ret = new SelectList();
if (ret->init(width, list, onChange)) {

View file

@ -1,8 +1,8 @@
#pragma once
#include "CCNode.h"
#include <cocos2d.h>
NS_CC_BEGIN
namespace geode {
#pragma warning(push)
#pragma warning(disable: 4275)
@ -22,7 +22,7 @@ NS_CC_BEGIN
* @note If you want to specify a minimum width for a SpacerNode, add
* AxisLayoutOptions for it and use setLength
*/
class GEODE_DLL SpacerNode : public CCNode {
class GEODE_DLL SpacerNode : public cocos2d::CCNode {
protected:
size_t m_grow;
@ -61,9 +61,9 @@ public:
*/
class GEODE_DLL SpacerNodeChild : public SpacerNode {
protected:
CCNode* m_child = nullptr;
cocos2d::CCNode* m_child = nullptr;
bool init(CCNode* child, size_t grow);
bool init(cocos2d::CCNode* child, size_t grow);
public:
/**
@ -73,11 +73,11 @@ public:
* factors (akin to CSS flew grow)
* @param grow The grow factor for this node. Default is 1
*/
static SpacerNodeChild* create(CCNode* child, size_t grow = 1);
static SpacerNodeChild* create(cocos2d::CCNode* child, size_t grow = 1);
void setContentSize(CCSize const& size) override;
void setContentSize(cocos2d::CCSize const& size) override;
};
#pragma warning(pop)
NS_CC_END
}

View file

@ -24,34 +24,34 @@ namespace geode {
*/
class GEODE_DLL SimpleTextArea : public cocos2d::CCNode {
public:
static SimpleTextArea* create(const std::string& text, const std::string& font = "chatFont.fnt", const float scale = 1);
static SimpleTextArea* create(const std::string& text, const std::string& font, const float scale, const float width);
static SimpleTextArea* create(const std::string& text, const std::string& font = "chatFont.fnt", float scale = 1.0f);
static SimpleTextArea* create(const std::string& text, const std::string& font, float scale, float width);
void setFont(const std::string& font);
std::string getFont();
void setColor(const cocos2d::ccColor4B& color);
cocos2d::ccColor4B getColor();
void setAlignment(const cocos2d::CCTextAlignment alignment);
void setAlignment(cocos2d::CCTextAlignment alignment);
cocos2d::CCTextAlignment getAlignment();
void setWrappingMode(const WrappingMode mode);
void setWrappingMode(WrappingMode mode);
WrappingMode getWrappingMode();
void setText(const std::string& text);
std::string getText();
void setMaxLines(const size_t maxLines);
void setMaxLines(size_t maxLines);
size_t getMaxLines();
void setWidth(const float width);
void setWidth(float width);
float getWidth();
void setScale(const float scale) override;
void setScale(float scale) override;
float getScale() override;
void setLinePadding(const float padding);
void setLinePadding(float padding);
float getLinePadding();
std::vector<cocos2d::CCLabelBMFont*> getLines();
float getHeight();
float getLineHeight();
private:
static SimpleTextArea* create(const std::string& font, const std::string& text, const float scale, const float width, const bool artificialWidth);
static SimpleTextArea* create(const std::string& font, const std::string& text, float scale, float width, const bool artificialWidth);
bool init(const std::string& font, const std::string& text, const float scale, const float width, const bool artificialWidth);
bool init(const std::string& font, const std::string& text, float scale, float width, const bool artificialWidth);
bool m_shouldUpdate = false;
bool m_artificialWidth = false;
@ -67,9 +67,9 @@ namespace geode {
float m_lineHeight = 0.f;
float m_linePadding = 0.f;
cocos2d::CCLabelBMFont* createLabel(const std::string& text, const float top);
cocos2d::CCLabelBMFont* createLabel(const std::string& text, float top);
float calculateOffset(cocos2d::CCLabelBMFont* label);
void charIteration(const std::function<cocos2d::CCLabelBMFont*(cocos2d::CCLabelBMFont* line, const char c, const float top)>& overflowHandling);
void charIteration(const std::function<cocos2d::CCLabelBMFont*(cocos2d::CCLabelBMFont* line, char c, float top)>& overflowHandling);
void updateLinesNoWrap();
void updateLinesWordWrap(bool spaceWrap);
void updateLinesCutoffWrap();

View file

@ -122,7 +122,7 @@ namespace geode {
* to distinguish between bold, italic and
* regular text.
*/
using Font = utils::MiniFunction<Label(int)>;
using Font = std::function<Label(int)>;
protected:
cocos2d::CCPoint m_origin = cocos2d::CCPointZero;

View file

@ -21,7 +21,7 @@ namespace geode {
std::string m_id;
public:
ListenerResult handle(utils::MiniFunction<Callback> fn, ColorProvidedEvent* event);
ListenerResult handle(std::function<Callback> fn, ColorProvidedEvent* event);
ColorProvidedFilter(std::string const& id);
};

View file

@ -4,8 +4,7 @@
#include "../loader/Log.hpp"
#include <set>
#include <variant>
#include <Geode/utils/MiniFunction.hpp>
#include <Geode/utils/Result.hpp>
#include <Geode/Result.hpp>
namespace geode {
struct JsonChecker;
@ -73,231 +72,11 @@ namespace geode {
}
template <class T>
using JsonValueValidator = utils::MiniFunction<bool(T const&)>;
using JsonValueValidator = std::function<bool(T const&)>;
struct JsonMaybeObject;
struct JsonMaybeValue;
struct GEODE_DLL
[[deprecated("Use JsonExpectedValue via the checkJson function instead")]]
JsonMaybeSomething {
protected:
JsonChecker& m_checker;
matjson::Value& m_json;
std::string m_hierarchy;
bool m_hasValue;
friend struct JsonMaybeObject;
friend struct JsonMaybeValue;
void setError(std::string const& error);
public:
matjson::Value& json();
JsonMaybeSomething(
JsonChecker& checker, matjson::Value& json, std::string const& hierarchy, bool hasValue
);
bool isError() const;
std::string getError() const;
operator bool() const;
};
struct GEODE_DLL
[[deprecated("Use JsonExpectedValue via the checkJson function instead")]]
JsonMaybeValue : public JsonMaybeSomething {
bool m_inferType = true;
JsonMaybeValue(
JsonChecker& checker, matjson::Value& json, std::string const& hierarchy, bool hasValue
);
JsonMaybeSomething& self();
template <matjson::Type T>
JsonMaybeValue& as() {
if (this->isError()) return *this;
if (!jsonConvertibleTo(self().m_json.type(), T)) {
this->setError(
self().m_hierarchy + ": Invalid type \"" + jsonValueTypeToString(self().m_json.type()) +
"\", expected \"" + jsonValueTypeToString(T) + "\""
);
}
m_inferType = false;
return *this;
}
JsonMaybeValue& array();
template <matjson::Type... T>
JsonMaybeValue& asOneOf() {
if (this->isError()) return *this;
bool isOneOf = (... || jsonConvertibleTo(self().m_json.type(), T));
if (!isOneOf) {
this->setError(
self().m_hierarchy + ": Invalid type \"" + jsonValueTypeToString(self().m_json.type()) +
"\", expected one of \"" + (jsonValueTypeToString(T), ...) + "\""
);
}
m_inferType = false;
return *this;
}
template <class T>
bool is() {
if (this->isError()) return false;
return self().m_json.is<T>();
}
template <class T>
JsonMaybeValue& validate(JsonValueValidator<T> validator) {
if (this->isError()) return *this;
if (self().m_json.is<T>()) {
if (!validator(self().m_json.as<T>())) {
this->setError(self().m_hierarchy + ": Invalid value format");
}
}
else {
this->setError(
self().m_hierarchy + ": Invalid type \"" +
std::string(jsonValueTypeToString(self().m_json.type())) + "\""
);
}
return *this;
}
template <class T>
JsonMaybeValue& inferType() {
if (this->isError() || !m_inferType) return *this;
return this->as<getJsonType<T>()>();
}
template <class T>
JsonMaybeValue& intoRaw(T& target) {
if (this->isError()) return *this;
target = self().m_json;
return *this;
}
template <class T>
JsonMaybeValue& into(T& target) {
return this->intoAs<T, T>(target);
}
template <class T>
JsonMaybeValue& into(std::optional<T>& target) {
return this->intoAs<T, std::optional<T>>(target);
}
template <class A, class T>
JsonMaybeValue& intoAs(T& target) {
this->inferType<A>();
if (this->isError()) return *this;
if (self().m_json.is<A>()) {
try {
target = self().m_json.as<A>();
}
catch(matjson::JsonException const& e) {
this->setError(
self().m_hierarchy + ": Error parsing JSON: " + std::string(e.what())
);
}
}
else {
this->setError(
self().m_hierarchy + ": Invalid type \"" +
std::string(jsonValueTypeToString(self().m_json.type())) + "\""
);
}
return *this;
}
template <class T>
T get() {
this->inferType<T>();
if (this->isError()) return T();
if (self().m_json.is<T>()) {
return self().m_json.as<T>();
}
return T();
}
JsonMaybeObject obj();
template <class T>
struct Iterator {
std::vector<T> m_values;
using iterator = typename std::vector<T>::iterator;
using const_iterator = typename std::vector<T>::const_iterator;
iterator begin() {
return m_values.begin();
}
iterator end() {
return m_values.end();
}
const_iterator begin() const {
return m_values.begin();
}
const_iterator end() const {
return m_values.end();
}
};
JsonMaybeValue at(size_t i);
Iterator<JsonMaybeValue> iterate();
Iterator<std::pair<std::string, JsonMaybeValue>> items();
};
struct
[[deprecated("Use JsonExpectedValue via the checkJson function instead")]]
GEODE_DLL JsonMaybeObject : JsonMaybeSomething {
std::set<std::string> m_knownKeys;
JsonMaybeObject(
JsonChecker& checker, matjson::Value& json, std::string const& hierarchy, bool hasValue
);
JsonMaybeSomething& self();
void addKnownKey(std::string const& key);
matjson::Value& json();
JsonMaybeValue emptyValue();
JsonMaybeValue has(std::string const& key);
JsonMaybeValue needs(std::string const& key);
void checkUnknownKeys();
};
struct
[[deprecated("Use JsonExpectedValue via the checkJson function instead")]]
GEODE_DLL JsonChecker {
std::variant<std::monostate, std::string> m_result;
matjson::Value& m_json;
JsonChecker(matjson::Value& json);
bool isError() const;
std::string getError() const;
JsonMaybeValue root(std::string const& hierarchy);
};
class GEODE_DLL JsonExpectedValue final {
protected:
class Impl;
@ -325,21 +104,14 @@ namespace geode {
return this->getJSONRef();
}
else {
try {
if (this->getJSONRef().is<T>()) {
return this->getJSONRef().as<T>();
}
else {
this->setError(
"unexpected type {}",
this->matJsonTypeToString(this->getJSONRef().type())
);
}
}
// matjson can throw variant exceptions too so you need to do this
catch(std::exception const& e) {
this->setError("unable to parse json: {}", e);
auto res = this->getJSONRef().as<T>();
if (res) {
return res.unwrap();
}
this->setError(
"unexpected type {}",
this->matJsonTypeToString(this->getJSONRef().type())
);
}
return std::nullopt;
}
@ -451,6 +223,13 @@ namespace geode {
* @returns The key, which is a no-op value if it didn't exist
*/
JsonExpectedValue has(std::string_view key);
/**
* Check if this object has an optional key. Asserts that this JSON
* value is an object. If the key doesn't exist, or the value is null, returns a
* `JsonExpectValue` that does nothing
* @returns The key, which is a no-op value if it didn't exist, or was null
*/
JsonExpectedValue hasNullable(std::string_view key);
/**
* Check if this object has an optional key. Asserts that this JSON
* value is an object. If the key doesn't exist, sets an error and

View file

@ -1,141 +0,0 @@
#pragma once
#include <Geode/DefaultInclude.hpp>
#include <memory>
#include <concepts>
#include "terminate.hpp"
namespace geode::utils {
template <class FunctionType>
class MiniFunction;
template <class Ret, class... Args>
class MiniFunctionStateBase {
public:
virtual ~MiniFunctionStateBase() = default;
virtual Ret call(Args... args) const = 0;
virtual MiniFunctionStateBase* clone() const = 0;
};
template <class Type, class Ret, class... Args>
class MiniFunctionState final : public MiniFunctionStateBase<Ret, Args...> {
public:
Type m_func;
explicit MiniFunctionState(Type func) : m_func(func) {}
Ret call(Args... args) const override {
return const_cast<Type&>(m_func)(std::forward<Args>(args)...);
}
MiniFunctionStateBase<Ret, Args...>* clone() const override {
return new MiniFunctionState(*this);
}
};
template <class Type, class Ret, class... Args>
class MiniFunctionStatePointer final : public MiniFunctionStateBase<Ret, Args...> {
public:
Type m_func;
explicit MiniFunctionStatePointer(Type func) : m_func(func) {}
Ret call(Args... args) const override {
return const_cast<Type&>(*m_func)(std::forward<Args>(args)...);
}
MiniFunctionStateBase<Ret, Args...>* clone() const override {
return new MiniFunctionStatePointer(*this);
}
};
template <class Type, class Ret, class Class, class... Args>
class MiniFunctionStateMemberPointer final : public MiniFunctionStateBase<Ret, Class, Args...> {
public:
Type m_func;
explicit MiniFunctionStateMemberPointer(Type func) : m_func(func) {}
Ret call(Class self, Args... args) const override {
return const_cast<Type&>(self->*m_func)(std::forward<Args>(args)...);
}
MiniFunctionStateBase<Ret, Class, Args...>* clone() const override {
return new MiniFunctionStateMemberPointer(*this);
}
};
template <class Callable, class Ret, class... Args>
concept MiniFunctionCallable = requires(Callable&& func, Args... args) {
{ func(std::forward<Args>(args)...) } -> std::same_as<Ret>;
};
template <class Ret, class... Args>
class MiniFunction<Ret(Args...)> {
public:
using FunctionType = Ret(Args...);
using StateType = MiniFunctionStateBase<Ret, Args...>;
private:
StateType* m_state;
public:
MiniFunction() : m_state(nullptr) {}
MiniFunction(std::nullptr_t) : MiniFunction() {}
MiniFunction(MiniFunction const& other) :
m_state(other.m_state ? other.m_state->clone() : nullptr) {}
MiniFunction(MiniFunction&& other) : m_state(other.m_state) {
other.m_state = nullptr;
}
~MiniFunction() {
if (m_state) delete m_state;
}
template <class Callable>
requires(MiniFunctionCallable<Callable, Ret, Args...> && !std::is_same_v<std::decay_t<Callable>, MiniFunction<FunctionType>>)
MiniFunction(Callable&& func) :
m_state(new MiniFunctionState<std::decay_t<Callable>, Ret, Args...>(std::forward<Callable>(func))) {}
template <class FunctionPointer>
requires(!MiniFunctionCallable<FunctionPointer, Ret, Args...> && std::is_pointer_v<FunctionPointer> && std::is_function_v<std::remove_pointer_t<FunctionPointer>>)
MiniFunction(FunctionPointer func) :
m_state(new MiniFunctionStatePointer<FunctionPointer, Ret, Args...>(func)) {}
template <class MemberFunctionPointer>
requires(std::is_member_function_pointer_v<MemberFunctionPointer>)
MiniFunction(MemberFunctionPointer func) :
m_state(new MiniFunctionStateMemberPointer<MemberFunctionPointer, Ret, Args...>(func)) {}
MiniFunction& operator=(MiniFunction const& other) {
if (m_state) delete m_state;
m_state = other.m_state ? other.m_state->clone() : nullptr;
return *this;
}
MiniFunction& operator=(MiniFunction&& other) {
if (m_state) delete m_state;
m_state = other.m_state;
other.m_state = nullptr;
return *this;
}
Ret operator()(Args... args) const {
if (!m_state) {
utils::terminate(
"Attempted to call a MiniFunction that was never assigned "
"any function, or one that has been moved"
);
}
return m_state->call(std::forward<Args>(args)...);
}
explicit operator bool() const {
return m_state;
}
};
}

View file

@ -1,9 +1,11 @@
#pragma once
#include <Geode/modify/Modify.hpp>
#include "cocos.hpp"
namespace geode::node_ids {
using namespace cocos2d;
static constexpr int32_t GEODE_ID_PRIORITY = 0x100000;
static constexpr int32_t GEODE_ID_PRIORITY = Priority::VeryEarlyPost;
template <class T = CCNode>
requires std::is_base_of_v<CCNode, T>

View file

@ -1,7 +1,7 @@
#pragma once
#include "../loader/Hook.hpp"
#include "Result.hpp"
#include <Geode/Result.hpp>
namespace geode {
namespace hook {

View file

@ -1,305 +0,0 @@
#pragma once
#include "../DefaultInclude.hpp"
#include "../external/result/result.hpp"
#include <fmt/format.h>
#include <string>
#include <string_view>
#include <type_traits>
#include <variant>
#include <optional>
namespace geode {
namespace impl {
using DefaultValue = std::monostate;
using DefaultError = std::string;
template <class T>
using WrappedResult = std::conditional_t<
std::is_lvalue_reference_v<T>, std::reference_wrapper<std::remove_reference_t<T>>,
std::remove_const_t<T>>;
template <class E = impl::DefaultError>
class [[nodiscard]] Failure {
public:
WrappedResult<E> m_error;
Failure() = default;
template <class E2>
requires(std::is_constructible_v<E, E2 const&>)
explicit constexpr Failure(E2 const& e) : m_error(e) {}
template <class E2>
requires(std::is_constructible_v<E, E2 &&>)
explicit constexpr Failure(E2&& e) : m_error(std::move(e)) {}
E& error() & noexcept {
return m_error;
}
E const& error() const& noexcept {
return m_error;
}
E&& error() && noexcept {
return static_cast<E&&>(m_error);
}
E const&& error() const&& noexcept {
return static_cast<E&&>(m_error);
}
};
template <class T = impl::DefaultValue>
class [[nodiscard]] Success {
public:
WrappedResult<T> m_value;
Success() = default;
template <class T2>
requires(std::is_constructible_v<T, T2 const&>)
explicit constexpr Success(T2 const& v) : m_value(v) {}
template <class T2>
requires(std::is_constructible_v<T, T2 &&>)
explicit constexpr Success(T2&& v) : m_value(std::forward<T2>(v)) {}
T& value() & noexcept {
return m_value;
}
T const& value() const& noexcept {
return m_value;
}
T&& value() && noexcept {
return static_cast<T&&>(m_value);
}
T const&& value() const&& noexcept {
return static_cast<T&&>(m_value);
}
};
}
template <class T = impl::DefaultValue, class E = impl::DefaultError>
class [[nodiscard]] Result : public cpp::result<T, E> {
public:
using Base = cpp::result<T, E>;
using ValueType = typename Base::value_type;
using ErrorType = typename Base::error_type;
using Base::result;
template <class U>
requires(cpp::detail::result_is_implicit_value_convertible<T, U>::value)
constexpr Result(U&& value) = delete;
template <class E2>
requires(std::is_constructible_v<E, E2 const&>)
constexpr Result(impl::Failure<E2> const& e) : Base(cpp::failure<E>(e.error())) {}
template <class E2>
requires(std::is_constructible_v<E, E2 &&>)
constexpr Result(impl::Failure<E2>&& e) : Base(cpp::failure<E>(std::move(e.error()))) {}
template <class T2>
requires(std::is_constructible_v<T, T2 const&>)
constexpr Result(impl::Success<T2> const& s) : Base(s.value()) {}
template <class T2>
requires(std::is_constructible_v<T, T2 &&>)
constexpr Result(impl::Success<T2>&& s) : Base(std::move(s.value())) {}
[[nodiscard]] constexpr explicit operator bool() const noexcept {
return this->Base::operator bool();
}
[[nodiscard]] constexpr bool isOk() const noexcept {
return this->Base::has_value();
}
[[nodiscard]] constexpr bool isErr() const noexcept {
return this->Base::has_error();
}
[[nodiscard]] constexpr decltype(auto) unwrap() & {
return this->Base::value();
}
[[nodiscard]] constexpr decltype(auto) unwrap() const& {
return this->Base::value();
}
[[nodiscard]] constexpr decltype(auto) unwrap() && {
return this->Base::value();
}
[[nodiscard]] constexpr decltype(auto) unwrap() const&& {
return this->Base::value();
}
[[nodiscard]] constexpr decltype(auto) unwrapErr() & {
return this->Base::error();
}
[[nodiscard]] constexpr decltype(auto) unwrapErr() const& {
return this->Base::error();
}
[[nodiscard]] constexpr decltype(auto) unwrapErr() && {
return this->Base::error();
}
[[nodiscard]] constexpr decltype(auto) unwrapErr() const&& {
return this->Base::error();
}
template <class... Args>
requires(std::is_constructible_v<T, T &&>)
[[nodiscard]] Result<T, std::string> expect(std::string const& format, Args&&... args) {
if (this->isErr()) {
return impl::Failure<std::string>(fmt::format(
fmt::runtime(format), std::forward<Args>(args)...,
fmt::arg("error", this->unwrapErr())
));
}
else {
return std::move(*this);
}
}
template <class... Args>
requires(std::is_constructible_v<T, T const&>)
[[nodiscard]] Result<T, std::string> expect(std::string const& format, Args&&... args)
const {
if (this->isErr()) {
return impl::Failure<std::string>(fmt::format(
fmt::runtime(format), std::forward<Args>(args)...,
fmt::arg("error", this->unwrapErr())
));
}
else {
return *this;
}
}
template <class U>
[[nodiscard]] constexpr decltype(auto) unwrapOr(U&& val) && {
return this->Base::value_or(std::forward<U>(val));
}
template <class U>
[[nodiscard]] constexpr decltype(auto) unwrapOr(U&& val) const& {
return this->Base::value_or(std::forward<U>(val));
}
[[nodiscard]] constexpr decltype(auto) unwrapOrDefault() && requires std::is_default_constructible_v<T> {
return this->Base::value_or(T());
}
[[nodiscard]] constexpr decltype(auto) unwrapOrDefault() const& requires std::is_default_constructible_v<T> {
return this->Base::value_or(T());
}
template <class U>
[[nodiscard]] constexpr decltype(auto) errorOr(U&& val) && {
return this->Base::error_or(std::forward<U>(val));
}
template <class U>
[[nodiscard]] constexpr decltype(auto) errorOr(U&& val) const& {
return this->Base::error_or(std::forward<U>(val));
}
/**
* Convert the result into an optional containing the value if Ok, and
* nullopt if Err
*/
[[nodiscard]] constexpr std::optional<T> ok() const& {
if (this->isOk()) {
return this->unwrap();
}
return std::nullopt;
}
/**
* Convert the result into an optional containing the value if Ok, and
* nullopt if Err
*/
[[nodiscard]] constexpr std::optional<T> ok() && {
if (this->isOk()) {
return this->unwrap();
}
return std::nullopt;
}
/**
* Convert the result into an optional containing the error if Err, and
* nullopt if Ok
*/
[[nodiscard]] constexpr std::optional<E> err() const& {
if (this->isErr()) {
return this->unwrapErr();
}
return std::nullopt;
}
/**
* Convert the result into an optional containing the error if Err, and
* nullopt if Ok
*/
[[nodiscard]] constexpr std::optional<E> err() && {
if (this->isErr()) {
return this->unwrapErr();
}
return std::nullopt;
}
/**
* Completely disregard the result. Only recommended if the result is
* inconsequential
*/
constexpr void disregard() && {}
};
template <class T = impl::DefaultValue>
constexpr impl::Success<T> Ok() {
return impl::Success<T>();
}
template <class T>
constexpr impl::Success<T> Ok(T value) {
// DO NOT MAKE THE PARAMETER T&&!!!! THAT WILL CAUSE C++ TO DO UNEXPECTED
// IMPLICIT MOVES FOR EXAMPLE WHEN DOING `Ok(unordered_map.at(value))`
return impl::Success<T>(std::forward<T>(value));
}
template <class E>
constexpr impl::Failure<E> Err(E error) {
return impl::Failure<E>(std::forward<E>(error));
}
template <class... Args>
inline impl::Failure<std::string> Err(std::string const& format, Args&&... args) {
return impl::Failure<std::string>(
fmt::format(fmt::runtime(format), std::forward<Args>(args)...)
);
}
#define GEODE_UNWRAP_INTO(into, ...) \
auto GEODE_CONCAT(unwrap_res_, __LINE__) = (__VA_ARGS__); \
if (GEODE_CONCAT(unwrap_res_, __LINE__).isErr()) { \
return geode::Err(std::move(GEODE_CONCAT(unwrap_res_, __LINE__).unwrapErr())); \
} \
into = std::move(GEODE_CONCAT(unwrap_res_, __LINE__).unwrap())
#define GEODE_UNWRAP(...) \
do { \
auto GEODE_CONCAT(unwrap_res_, __LINE__) = (__VA_ARGS__); \
if (GEODE_CONCAT(unwrap_res_, __LINE__).isErr()) { \
return geode::Err(std::move(GEODE_CONCAT(unwrap_res_, __LINE__).unwrapErr())); \
} \
} while(false)
}

View file

@ -1,13 +1,21 @@
#pragma once
#include "general.hpp"
#include "MiniFunction.hpp"
#include "../loader/Event.hpp"
#include "../loader/Loader.hpp"
#include <mutex>
#include <string_view>
#include <coroutine>
namespace geode {
namespace geode_internal {
template <class T, class P>
struct TaskPromise;
template <class T, class P>
struct TaskAwaiter;
}
/**
* Tasks represent an asynchronous operation that will be finished at some
* unknown point in the future. Tasks can report their progress, and will
@ -141,7 +149,7 @@ namespace geode {
class PrivateMarker final {};
static std::shared_ptr<Handle> create(std::string_view const name) {
static std::shared_ptr<Handle> create(std::string_view name) {
return std::make_shared<Handle>(PrivateMarker(), name);
}
@ -153,8 +161,14 @@ namespace geode {
template <std::move_constructible T2, std::move_constructible P2>
friend class Task;
template <class, class>
friend struct geode_internal::TaskPromise;
template <class, class>
friend struct geode_internal::TaskAwaiter;
public:
Handle(PrivateMarker, std::string_view const name) : m_name(name) {}
Handle(PrivateMarker, std::string_view name) : m_name(name) {}
~Handle() {
// If this Task was still pending when the Handle was destroyed,
// it can no longer be listened to so just cancel and cleanup
@ -249,11 +263,11 @@ namespace geode {
using Value = T;
using Progress = P;
using PostResult = utils::MiniFunction<void(Result)>;
using PostProgress = utils::MiniFunction<void(P)>;
using HasBeenCancelled = utils::MiniFunction<bool()>;
using Run = utils::MiniFunction<Result(PostProgress, HasBeenCancelled)>;
using RunWithCallback = utils::MiniFunction<void(PostResult, PostProgress, HasBeenCancelled)>;
using PostResult = std::function<void(Result&&)>;
using PostProgress = std::function<void(P)>;
using HasBeenCancelled = std::function<bool()>;
using Run = std::function<Result(PostProgress, HasBeenCancelled)>;
using RunWithCallback = std::function<void(PostResult, PostProgress, HasBeenCancelled)>;
using Callback = void(Event*);
@ -308,6 +322,12 @@ namespace geode {
template <std::move_constructible T2, std::move_constructible P2>
friend class Task;
template <class, class>
friend struct geode_internal::TaskPromise;
template <class, class>
friend struct geode_internal::TaskAwaiter;
public:
// Allow default-construction
Task() : m_handle(nullptr) {}
@ -394,7 +414,7 @@ namespace geode {
* Create a new Task that is immediately cancelled
* @param name The name of the Task; used for debugging
*/
static Task cancelled(std::string_view const name = "<Cancelled Task>") {
static Task cancelled(std::string_view name = "<Cancelled Task>") {
auto task = Task(Handle::create(name));
Task::cancel(task.m_handle);
return task;
@ -405,7 +425,7 @@ namespace geode {
* @param value The value the Task shall be finished with
* @param name The name of the Task; used for debugging
*/
static Task immediate(T value, std::string_view const name = "<Immediate Task>") {
static Task immediate(T value, std::string_view name = "<Immediate Task>") {
auto task = Task(Handle::create(name));
Task::finish(task.m_handle, std::move(value));
return task;
@ -417,7 +437,7 @@ namespace geode {
* function MUST be synchronous - Task creates the thread for you!
* @param name The name of the Task; used for debugging
*/
static Task run(Run&& body, std::string_view const name = "<Task>") {
static Task run(Run&& body, std::string_view name = "<Task>") {
auto task = Task(Handle::create(name));
std::thread([handle = std::weak_ptr(task.m_handle), name = std::string(name), body = std::move(body)] {
utils::thread::setName(fmt::format("Task '{}'", name));
@ -450,7 +470,7 @@ namespace geode {
* calls will always be ignored
* @param name The name of the Task; used for debugging
*/
static Task runWithCallback(RunWithCallback&& body, std::string_view const name = "<Callback Task>") {
static Task runWithCallback(RunWithCallback&& body, std::string_view name = "<Callback Task>") {
auto task = Task(Handle::create(name));
std::thread([handle = std::weak_ptr(task.m_handle), name = std::string(name), body = std::move(body)] {
utils::thread::setName(fmt::format("Task '{}'", name));
@ -485,7 +505,7 @@ namespace geode {
* were cancelled!
*/
template <std::move_constructible NP>
static Task<std::vector<T*>, std::monostate> all(std::vector<Task<T, NP>>&& tasks, std::string_view const name = "<Multiple Tasks>") {
static Task<std::vector<T*>, std::monostate> all(std::vector<Task<T, NP>>&& tasks, std::string_view name = "<Multiple Tasks>") {
using AllTask = Task<std::vector<T*>, std::monostate>;
// If there are no tasks, return an immediate task that does nothing
@ -582,7 +602,7 @@ namespace geode {
* the mapped task is appended to the end
*/
template <class ResultMapper, class ProgressMapper, class OnCancelled>
auto map(ResultMapper&& resultMapper, ProgressMapper&& progressMapper, OnCancelled&& onCancelled, std::string_view const name = "<Mapping Task>") const {
auto map(ResultMapper&& resultMapper, ProgressMapper&& progressMapper, OnCancelled&& onCancelled, std::string_view name = "<Mapping Task>") const {
using T2 = decltype(resultMapper(std::declval<T*>()));
using P2 = decltype(progressMapper(std::declval<P*>()));
@ -652,7 +672,7 @@ namespace geode {
* @param name The name of the Task; used for debugging. The name of
* the mapped task is appended to the end
*/ template <class ResultMapper, class ProgressMapper>
auto map(ResultMapper&& resultMapper, ProgressMapper&& progressMapper, std::string_view const name = "<Mapping Task>") const {
auto map(ResultMapper&& resultMapper, ProgressMapper&& progressMapper, std::string_view name = "<Mapping Task>") const {
return this->map(std::move(resultMapper), std::move(progressMapper), +[]() {}, name);
}
@ -670,7 +690,7 @@ namespace geode {
* the mapped task is appended to the end
*/ template <class ResultMapper>
requires std::copy_constructible<P>
auto map(ResultMapper&& resultMapper, std::string_view const name = "<Mapping Task>") const {
auto map(ResultMapper&& resultMapper, std::string_view name = "<Mapping Task>") const {
return this->map(std::move(resultMapper), +[](P* p) -> P { return *p; }, name);
}
@ -752,7 +772,94 @@ namespace geode {
this->listen(std::move(onResult), [](auto const&) {}, [] {});
}
ListenerResult handle(utils::MiniFunction<Callback> fn, Event* e) {
/**
* 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) {
if (e->m_handle == m_handle && (!e->m_for || e->m_for == m_listener)) {
fn(e);
}
@ -797,3 +904,132 @@ namespace geode {
static_assert(is_filter<Task<int>>, "The Task class must be a valid event filter!");
}
// - C++20 coroutine support for Task - //
// Example usage (function must return a Task):
// ```
// Task<int> someTask() {
// auto response = co_await web::WebRequest().get("https://example.com");
// co_return response.code();
// }
// ```
// This will create a Task that will finish with the response code of the
// web request.
//
// Note: If the Task the coroutine is waiting on is cancelled, the coroutine
// will be destroyed and the Task will be cancelled as well. If the Task returned
// by the coroutine is cancelled, the coroutine will be destroyed as well and execution
// stops as soon as possible.
//
// The body of the coroutine is ran in whatever thread it got called in.
// TODO: maybe guarantee main thread?
//
// The coroutine can also yield progress values using `co_yield`:
// ```
// Task<std::string, int> someTask() {
// for (int i = 0; i < 10; i++) {
// co_yield i;
// }
// co_return "done!";
// }
// ```
namespace geode {
namespace geode_internal {
template <class T, class P>
struct TaskPromise {
using MyTask = Task<T, P>;
std::weak_ptr<typename MyTask::Handle> m_handle;
~TaskPromise() {
// does nothing if its not pending
MyTask::cancel(m_handle.lock());
}
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
// TODO: do something here?
void unhandled_exception() {}
MyTask get_return_object() {
auto handle = MyTask::Handle::create("<Coroutine Task>");
m_handle = handle;
return handle;
}
void return_value(T x) {
MyTask::finish(m_handle.lock(), std::move(x));
}
std::suspend_never yield_value(P value) {
MyTask::progress(m_handle.lock(), std::move(value));
return {};
}
bool isCancelled() {
if (auto p = m_handle.lock()) {
return p->is(MyTask::Status::Cancelled);
}
return true;
}
};
template <class T, class P>
struct TaskAwaiter {
Task<T, P> task;
bool await_ready() {
return task.isFinished();
}
template <class U, class V>
void await_suspend(std::coroutine_handle<TaskPromise<U, V>> handle) {
if (handle.promise().isCancelled()) {
handle.destroy();
return;
}
// this should be fine because the parent task can only have
// one pending task at a time
auto parentHandle = handle.promise().m_handle.lock();
if (!parentHandle) {
handle.destroy();
return;
}
parentHandle->m_extraData = std::make_unique<typename Task<U, V>::Handle::ExtraData>(
static_cast<void*>(new EventListener<Task<T, P>>(
[handle](auto* event) {
if (event->getValue()) {
handle.resume();
}
if (event->isCancelled()) {
handle.destroy();
}
},
task
)),
+[](void* ptr) {
delete static_cast<EventListener<Task<T, P>>*>(ptr);
},
+[](void* ptr) {
static_cast<EventListener<Task<T, P>>*>(ptr)->getFilter().cancel();
}
);
}
T await_resume() {
return std::move(*task.getFinishedValue());
}
};
}
}
template <class T, class P>
auto operator co_await(geode::Task<T, P> task) {
return geode::geode_internal::TaskAwaiter<T, P>{task};
}
template <class T, class P, class... Args>
struct std::coroutine_traits<geode::Task<T, P>, Args...> {
using promise_type = geode::geode_internal::TaskPromise<T, P>;
};

View file

@ -4,7 +4,8 @@
#include <string_view>
#include <matjson.hpp>
#include <tuple>
#include "../utils/Result.hpp"
#include <Geode/Result.hpp>
#include <fmt/format.h>
namespace geode {
enum class VersionCompare {
@ -186,8 +187,6 @@ namespace geode {
std::tie(other.m_major, other.m_minor, other.m_patch, other.m_tag);
}
[[deprecated("Use toNonVString or toVString instead")]]
std::string toString(bool includeTag = true) const;
std::string toVString(bool includeTag = true) const;
std::string toNonVString(bool includeTag = true) const;
@ -258,25 +257,15 @@ namespace geode {
template <class V>
requires std::is_same_v<V, geode::VersionInfo> || std::is_same_v<V, geode::ComparableVersionInfo>
struct matjson::Serialize<V> {
static matjson::Value to_json(V const& info) {
return info.toString();
static geode::Result<V, std::string> fromJson(Value const& value) {
GEODE_UNWRAP_INTO(auto str, value.asString());
GEODE_UNWRAP_INTO(auto version, V::parse(str).mapErr([](auto&& err) {
return fmt::format("Invalid version format: {}", err);
}));
return geode::Ok(version);
}
static bool is_json(matjson::Value const& json) {
if (json.is_string()) {
auto ver = V::parse(json.as_string());
return !ver.isErr();
}
return false;
}
static V from_json(matjson::Value const& json) {
auto ver = V::parse(json.as_string());
if (!ver) {
throw matjson::JsonException(
"Invalid version format: " + ver.unwrapErr()
);
}
return ver.unwrap();
static Value toJson(V const& value) {
return Value(value.toString());
}
};

View file

@ -10,22 +10,21 @@
#include "../loader/Event.hpp"
#include <Geode/binding/CCMenuItemSpriteExtra.hpp>
#include <Geode/binding/CCMenuItemToggler.hpp>
#include "MiniFunction.hpp"
#include "../ui/Layout.hpp"
#include "../ui/SpacerNode.hpp"
// support converting ccColor3B / ccColor4B to / from json
template <>
struct matjson::Serialize<cocos2d::ccColor3B> {
static matjson::Value GEODE_DLL to_json(cocos2d::ccColor3B const& color);
static cocos2d::ccColor3B GEODE_DLL from_json(matjson::Value const& color);
static bool GEODE_DLL is_json(matjson::Value const& json);
static geode::Result<cocos2d::ccColor3B> GEODE_DLL fromJson(Value const& value);
static Value GEODE_DLL toJson(cocos2d::ccColor3B const& value);
};
template <>
struct matjson::Serialize<cocos2d::ccColor4B> {
static matjson::Value GEODE_DLL to_json(cocos2d::ccColor4B const& color);
static cocos2d::ccColor4B GEODE_DLL from_json(matjson::Value const& color);
static bool GEODE_DLL is_json(matjson::Value const& json);
static geode::Result<cocos2d::ccColor4B> GEODE_DLL fromJson(Value const& value);
static Value GEODE_DLL toJson(cocos2d::ccColor4B const& value);
};
// operators for CC geometry
@ -615,18 +614,6 @@ namespace geode::cocos {
return static_cast<T*>(x->getChildren()->objectAtIndex(i));
}
/**
* Get nth child that is a given type. Checks bounds.
* @returns Child at index cast to the given type,
* or nullptr if index exceeds bounds
*/
template <class Type = cocos2d::CCNode>
[[deprecated("Use CCNode::getChildByType instead")]]
static Type* getChildOfType(cocos2d::CCNode* node, int index) {
return node->getChildByType<Type>(index);
}
/**
* Return a node, or create a default one if it's
* nullptr. Syntactic sugar function
@ -670,7 +657,7 @@ namespace geode::cocos {
*/
GEODE_DLL cocos2d::CCScene* switchToScene(cocos2d::CCLayer* layer);
using CreateLayerFunc = utils::MiniFunction<cocos2d::CCLayer*()>;
using CreateLayerFunc = std::function<cocos2d::CCLayer*()>;
/**
* Reload textures, overwriting the scene to return to after the loading
@ -738,7 +725,7 @@ namespace geode::cocos {
* there is none
*/
template <class Type = cocos2d::CCNode>
Type* findFirstChildRecursive(cocos2d::CCNode* node, utils::MiniFunction<bool(Type*)> predicate) {
Type* findFirstChildRecursive(cocos2d::CCNode* node, std::function<bool(Type*)> predicate) {
if (cast::typeinfo_cast<Type*>(node) && predicate(static_cast<Type*>(node)))
return static_cast<Type*>(node);
@ -856,30 +843,6 @@ namespace geode::cocos {
return {color.r / 255.f, color.g / 255.f, color.b / 255.f, color.a / 255.f};
}
[[deprecated("This function may have unintended behavior, use cc3bFromHexString or manually expand the color instead")]]
constexpr cocos2d::ccColor3B cc3x(int hexValue) {
if (hexValue <= 0xf)
return cocos2d::ccColor3B{
static_cast<GLubyte>(hexValue * 17),
static_cast<GLubyte>(hexValue * 17),
static_cast<GLubyte>(hexValue * 17)};
if (hexValue <= 0xff)
return cocos2d::ccColor3B{
static_cast<GLubyte>(hexValue),
static_cast<GLubyte>(hexValue),
static_cast<GLubyte>(hexValue)};
if (hexValue <= 0xfff)
return cocos2d::ccColor3B{
static_cast<GLubyte>((hexValue >> 8 & 0xf) * 17),
static_cast<GLubyte>((hexValue >> 4 & 0xf) * 17),
static_cast<GLubyte>((hexValue >> 0 & 0xf) * 17)};
else
return cocos2d::ccColor3B{
static_cast<GLubyte>(hexValue >> 16 & 0xff),
static_cast<GLubyte>(hexValue >> 8 & 0xff),
static_cast<GLubyte>(hexValue >> 0 & 0xff)};
}
/**
* Parse a ccColor3B from a hexadecimal string. The string may contain
* a leading '#'
@ -916,7 +879,7 @@ namespace geode::cocos {
}
template <typename T, typename C, typename = std::enable_if_t<std::is_pointer_v<C>>>
static cocos2d::CCArray* vectorToCCArray(std::vector<T> const& vec, utils::MiniFunction<C(T)> convFunc) {
static cocos2d::CCArray* vectorToCCArray(std::vector<T> const& vec, std::function<C(T)> convFunc) {
auto res = cocos2d::CCArray::createWithCapacity(vec.size());
for (auto const& item : vec)
res->addObject(convFunc(item));
@ -943,7 +906,7 @@ namespace geode::cocos {
template <
typename K, typename V, typename C,
typename = std::enable_if_t<std::is_same_v<C, std::string> || std::is_same_v<C, intptr_t>>>
static cocos2d::CCDictionary* mapToCCDict(std::map<K, V> const& map, utils::MiniFunction<C(K)> convFunc) {
static cocos2d::CCDictionary* mapToCCDict(std::map<K, V> const& map, std::function<C(K)> convFunc) {
auto res = cocos2d::CCDictionary::create();
for (auto const& [key, value] : map)
res->setObject(value, convFunc(key));
@ -969,10 +932,10 @@ namespace std {
};
template <typename T>
struct std::hash<geode::WeakRef<T>> {
struct hash<geode::WeakRef<T>> {
size_t operator()(geode::WeakRef<T> const& ref) const {
// the explicit template argument is needed here because it would otherwise cast to WeakRef and recurse
return hash<std::shared_ptr<geode::WeakRefController>>{}(ref.m_controller);
return std::hash<std::shared_ptr<geode::WeakRefController>>{}(ref.m_controller);
}
};
}
@ -1216,11 +1179,11 @@ namespace geode::cocos {
template <class Node>
class LambdaCallback : public cocos2d::CCObject {
public:
utils::MiniFunction<void(Node*)> m_callback;
std::function<void(Node*)> m_callback;
static LambdaCallback* create(utils::MiniFunction<void(Node*)>&& callback) {
static LambdaCallback* create(std::function<void(Node*)> callback) {
auto ret = new (std::nothrow) LambdaCallback();
if (ret->init(std::forward<std::remove_reference_t<decltype(callback)>>(callback))) {
if (ret->init(std::move(callback))) {
ret->autorelease();
return ret;
}
@ -1228,8 +1191,8 @@ namespace geode::cocos {
return nullptr;
}
bool init(utils::MiniFunction<void(Node*)>&& callback) {
m_callback = std::forward<std::remove_reference_t<decltype(callback)>>(callback);
bool init(std::function<void(Node*)> callback) {
m_callback = std::move(callback);
return true;
}
@ -1240,20 +1203,20 @@ namespace geode::cocos {
public:
static cocos2d::CCMenuItem* create(
utils::MiniFunction<void(cocos2d::CCMenuItem*)>&& callback
std::function<void(cocos2d::CCMenuItem*)> callback
) {
auto item = cocos2d::CCMenuItem::create();
assignCallback(item, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
assignCallback(item, std::move(callback));
return item;
}
static cocos2d::CCMenuItemSprite* createSprite(
cocos2d::CCNode* normalSprite,
cocos2d::CCNode* selectedSprite,
utils::MiniFunction<void(cocos2d::CCMenuItemSprite*)>&& callback
std::function<void(cocos2d::CCMenuItemSprite*)> callback
) {
auto item = cocos2d::CCMenuItemSprite::create(normalSprite, selectedSprite);
assignCallback(item, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
assignCallback(item, std::move(callback));
return item;
}
@ -1261,57 +1224,57 @@ namespace geode::cocos {
cocos2d::CCNode* normalSprite,
cocos2d::CCNode* selectedSprite,
cocos2d::CCNode* disabledSprite,
utils::MiniFunction<void(cocos2d::CCMenuItemSprite*)>&& callback
std::function<void(cocos2d::CCMenuItemSprite*)> callback
) {
auto item = cocos2d::CCMenuItemSprite::create(normalSprite, selectedSprite, disabledSprite);
assignCallback(item, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
assignCallback(item, std::move(callback));
return item;
}
static CCMenuItemSpriteExtra* createSpriteExtra(
cocos2d::CCNode* normalSprite,
utils::MiniFunction<void(CCMenuItemSpriteExtra*)>&& callback
std::function<void(CCMenuItemSpriteExtra*)> callback
) {
auto item = CCMenuItemSpriteExtra::create(normalSprite, nullptr, nullptr);
assignCallback(item, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
assignCallback(item, std::move(callback));
return item;
}
static CCMenuItemSpriteExtra* createSpriteExtraWithFilename(
std::string_view normalSpriteName,
float scale,
utils::MiniFunction<void(CCMenuItemSpriteExtra*)>&& callback
std::function<void(CCMenuItemSpriteExtra*)> callback
) {
auto sprite = cocos2d::CCSprite::create(normalSpriteName.data());
sprite->setScale(scale);
return createSpriteExtra(sprite, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
return createSpriteExtra(sprite, std::move(callback));
}
static CCMenuItemSpriteExtra* createSpriteExtraWithFrameName(
std::string_view normalSpriteName,
float scale,
utils::MiniFunction<void(CCMenuItemSpriteExtra*)>&& callback
std::function<void(CCMenuItemSpriteExtra*)> callback
) {
auto sprite = cocos2d::CCSprite::createWithSpriteFrameName(normalSpriteName.data());
sprite->setScale(scale);
return createSpriteExtra(sprite, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
return createSpriteExtra(sprite, std::move(callback));
}
static CCMenuItemToggler* createToggler(
cocos2d::CCNode* onSprite,
cocos2d::CCNode* offSprite,
utils::MiniFunction<void(CCMenuItemToggler*)>&& callback
std::function<void(CCMenuItemToggler*)> callback
) {
auto item = CCMenuItemToggler::create(offSprite, onSprite, nullptr, nullptr);
assignCallback(item, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
assignCallback(item, std::move(callback));
return item;
}
static CCMenuItemToggler* createTogglerWithStandardSprites(
float scale,
utils::MiniFunction<void(CCMenuItemToggler*)>&& callback
std::function<void(CCMenuItemToggler*)> callback
) {
auto offSprite = cocos2d::CCSprite::createWithSpriteFrameName("GJ_checkOff_001.png");
auto onSprite = cocos2d::CCSprite::createWithSpriteFrameName("GJ_checkOn_001.png");
@ -1319,14 +1282,14 @@ namespace geode::cocos {
offSprite->setScale(scale);
onSprite->setScale(scale);
return createToggler(onSprite, offSprite, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
return createToggler(onSprite, offSprite, std::move(callback));
}
static CCMenuItemToggler* createTogglerWithFilename(
std::string_view onSpriteName,
std::string_view offSpriteName,
float scale,
utils::MiniFunction<void(CCMenuItemToggler*)>&& callback
std::function<void(CCMenuItemToggler*)> callback
) {
auto offSprite = cocos2d::CCSprite::create(offSpriteName.data());
auto onSprite = cocos2d::CCSprite::create(onSpriteName.data());
@ -1334,14 +1297,14 @@ namespace geode::cocos {
offSprite->setScale(scale);
onSprite->setScale(scale);
return createToggler(onSprite, offSprite, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
return createToggler(onSprite, offSprite, std::move(callback));
}
static CCMenuItemToggler* createTogglerWithFrameName(
std::string_view onSpriteName,
std::string_view offSpriteName,
float scale,
utils::MiniFunction<void(CCMenuItemToggler*)>&& callback
std::function<void(CCMenuItemToggler*)> callback
) {
auto offSprite = cocos2d::CCSprite::createWithSpriteFrameName(offSpriteName.data());
auto onSprite = cocos2d::CCSprite::createWithSpriteFrameName(onSpriteName.data());
@ -1349,15 +1312,15 @@ namespace geode::cocos {
offSprite->setScale(scale);
onSprite->setScale(scale);
return createToggler(onSprite, offSprite, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
return createToggler(onSprite, offSprite, std::move(callback));
}
template <class Node>
static void assignCallback(
cocos2d::CCMenuItem* item,
utils::MiniFunction<void(Node*)>&& callback
std::function<void(Node*)> callback
) {
auto lambda = LambdaCallback<Node>::create(std::forward<std::remove_reference_t<decltype(callback)>>(callback));
auto lambda = LambdaCallback<Node>::create(std::move(callback));
item->setTarget(lambda, menu_selector(LambdaCallback<Node>::execute));
item->setUserObject("lambda-callback", lambda);
}

View file

@ -1,6 +1,6 @@
#pragma once
#include "Result.hpp"
#include <Geode/Result.hpp>
#include "general.hpp"
#include "../loader/Event.hpp"
#include "Task.hpp"
@ -13,14 +13,15 @@
template <>
struct matjson::Serialize<std::filesystem::path> {
static matjson::Value to_json(std::filesystem::path const& path) {
return path.string();
static geode::Result<std::filesystem::path, std::string> fromJson(Value const& value)
{
GEODE_UNWRAP_INTO(auto str, value.asString());
return geode::Ok(std::filesystem::path(str).make_preferred());
}
static std::filesystem::path from_json(matjson::Value const& value) {
return std::filesystem::path(value.as_string()).make_preferred();
}
static bool is_json(matjson::Value const& value) {
return value.is_string();
static Value toJson(std::filesystem::path const& value)
{
return Value(value.string());
}
};
@ -32,10 +33,7 @@ namespace geode::utils::file {
template <class T>
Result<T> readFromJson(std::filesystem::path const& file) {
GEODE_UNWRAP_INTO(auto json, readJson(file));
if (!json.is<T>()) {
return Err("JSON is not of type {}", typeid(T).name());
}
return Ok(json.as<T>());
return json.as<T>();
}
GEODE_DLL Result<> writeString(std::filesystem::path const& path, std::string const& data);
@ -162,7 +160,7 @@ namespace geode::utils::file {
* @param callback Callback to call with the progress of the unzip operation
*/
void setProgressCallback(
utils::MiniFunction<void(uint32_t, uint32_t)> callback
std::function<void(uint32_t, uint32_t)> callback
);
/**
@ -213,7 +211,7 @@ namespace geode::utils::file {
);
static Result<> intoDir(
utils::MiniFunction<void(uint32_t, uint32_t)> progressCallback,
std::function<void(uint32_t, uint32_t)> progressCallback,
Path const& from,
Path const& to,
bool deleteZipAfter = false
@ -281,7 +279,7 @@ namespace geode::utils::file {
public:
using Callback = void(FileWatchEvent*);
ListenerResult handle(utils::MiniFunction<Callback> callback, FileWatchEvent* event);
ListenerResult handle(std::function<Callback> callback, FileWatchEvent* event);
FileWatchFilter(std::filesystem::path const& path);
};

View file

@ -1,6 +1,6 @@
#pragma once
#include "Result.hpp"
#include <Geode/Result.hpp>
#include "../DefaultInclude.hpp"
#include <chrono>
@ -13,19 +13,11 @@
#include <charconv>
#include <clocale>
#include <type_traits>
#include <fmt/format.h>
namespace geode {
using ByteVector = std::vector<uint8_t>;
// todo in v4: remove this
template <typename T>
[[deprecated("Use geode::toBytes instead")]]
ByteVector toByteArray(T const& a) {
ByteVector out;
out.resize(sizeof(T));
std::memcpy(out.data(), &a, sizeof(T));
return out;
}
template <typename T>
ByteVector toBytes(T const& a) {
ByteVector out;
@ -125,7 +117,7 @@ namespace geode {
* @returns String as number, or Err if the string couldn't be converted
*/
template <class Num>
Result<Num> numFromString(std::string_view const str, int base = 10) {
Result<Num> numFromString(std::string_view str, int base = 10) {
if constexpr (std::is_floating_point_v<Num>
#if defined(__cpp_lib_to_chars)
&& false
@ -168,12 +160,18 @@ namespace geode {
*/
GEODE_DLL float getDisplayFactor();
}
template <class... Args>
requires (sizeof...(Args) > 0)
constexpr auto Err(fmt::format_string<Args...> fmt, Args&&... args) {
return Err(fmt::format(fmt, std::forward<Args>(args)...));
}
}
template<>
struct matjson::Serialize<geode::ByteVector> {
static matjson::Value to_json(geode::ByteVector const& bytes) {
return matjson::Array(bytes.begin(), bytes.end());
static Value toJson(geode::ByteVector const& bytes) {
return std::vector<matjson::Value>(bytes.begin(), bytes.end());
}
};

View file

@ -1,6 +1,6 @@
#pragma once
#include "Result.hpp"
#include <Geode/Result.hpp>
#include <Geode/DefaultInclude.hpp>
#include <functional>
@ -20,7 +20,7 @@ namespace geode::utils::map {
* false if not.
*/
template <typename T, typename R, typename H>
bool contains(std::unordered_map<T, R, H> const& map, utils::MiniFunction<bool(R)> containFunc) {
bool contains(std::unordered_map<T, R, H> const& map, std::function<bool(R)> containFunc) {
for (auto const& [_, r] : map) {
if (containFunc(r)) return true;
}
@ -39,7 +39,7 @@ namespace geode::utils::map {
* a pointer.
*/
template <class T, class R, class H>
R select(std::unordered_map<T, R, H> const& map, utils::MiniFunction<bool(R)> selectFunc) {
R select(std::unordered_map<T, R, H> const& map, std::function<bool(R)> selectFunc) {
for (auto const& [_, r] : map) {
if (selectFunc(r)) return r;
}
@ -59,7 +59,7 @@ namespace geode::utils::map {
*/
template <class T, class R, class H>
std::vector<R> selectAll(
std::unordered_map<T, R, H> const& map, utils::MiniFunction<bool(R)> selectFunc
std::unordered_map<T, R, H> const& map, std::function<bool(R)> selectFunc
) {
std::vector<R> res;
for (auto const& [_, r] : map) {
@ -111,7 +111,7 @@ namespace geode::utils::map {
template <class T1, class V1, class H1, class T2, class V2, class H2>
std::unordered_map<T2, V2, H2> remap(
std::unordered_map<T1, V1, H1> const& map,
utils::MiniFunction<std::pair<T2, V2>(std::pair<T1, V1>)> remapFunc
std::function<std::pair<T2, V2>(std::pair<T1, V1>)> remapFunc
) {
std::unordered_map<T2, V2, H2> res;
for (auto const& [t, v] : map) {

View file

@ -1,7 +1,6 @@
#pragma once
#include <Geode/DefaultInclude.hpp>
#include "MiniFunction.hpp"
#include <string_view>
namespace geode::utils::permission {
@ -21,5 +20,5 @@ namespace geode::utils::permission {
* @param permission The permission
* @param callback The callback, passed value is 'true' if permission was granted and 'false' otherwise.
*/
void GEODE_DLL requestPermission(Permission permission, utils::MiniFunction<void(bool)> callback);
void GEODE_DLL requestPermission(Permission permission, std::function<void(bool)> callback);
}

View file

@ -4,6 +4,7 @@
#include <string>
#include <vector>
#include <compare>
#include "../DefaultInclude.hpp"
namespace geode::utils::string {
/**

View file

@ -2,7 +2,7 @@
#include <Geode/loader/Loader.hpp> // another great circular dependency fix
#include <matjson.hpp>
#include "Result.hpp"
#include <Geode/Result.hpp>
#include "Task.hpp"
#include <chrono>
#include <optional>

View file

@ -85,6 +85,12 @@
"name": "Enable Geode-Themed Colors",
"description": "When enabled, the Geode menu has a <ca>Geode-themed color scheme</c>. <cy>This does not affect any other menus!</c>"
},
"infinite-local-mods-list": {
"type": "bool",
"default": false,
"name": "Expand Installed Mods List",
"description": "Make the installed mods list a single infinite scrollable list instead of having pages"
},
"developer-title": {
"type": "title",
"name": "Developer Settings"

View file

@ -13,7 +13,7 @@ namespace geode::stl {
void free();
char* getStorage();
void setStorage(const std::string_view);
void setStorage(std::string_view);
size_t getSize();
void setSize(size_t);

View file

@ -100,11 +100,11 @@ namespace gd {
bool string::operator==(string const& other) const {
return std::string_view(*this) == std::string_view(other);
}
bool string::operator==(std::string_view const other) const {
bool string::operator==(std::string_view other) const {
return std::string_view(*this) == other;
}
std::strong_ordering string::operator<=>(std::string_view const other) const {
std::strong_ordering string::operator<=>(std::string_view other) const {
return static_cast<std::strong_ordering>(std::string_view(*this).compare(other) <=> 0);
}

View file

@ -1026,14 +1026,6 @@ std::optional<AxisAlignment> AxisLayoutOptions::getCrossAxisAlignment() const {
return m_impl->m_crossAxisAlignment;
}
AxisLayoutOptions* AxisLayoutOptions::setMaxScale(float scale) {
m_impl->m_scaleLimits.second = scale;
return this;
}
AxisLayoutOptions* AxisLayoutOptions::setMinScale(float scale) {
m_impl->m_scaleLimits.first = scale;
return this;
}
AxisLayoutOptions* AxisLayoutOptions::setScaleLimits(std::optional<float> min, std::optional<float> max) {
m_impl->m_scaleLimits = { min, max };
return this;

View file

@ -62,10 +62,6 @@ public:
return meta;
}
FieldContainer* getFieldContainer() {
return nullptr;
}
FieldContainer* getFieldContainer(char const* forClass) {
if (!m_classFieldContainers.count(forClass)) {
m_classFieldContainers[forClass] = new FieldContainer();
@ -104,16 +100,11 @@ size_t modifier::getFieldIndexForClass(char const* name) {
return s_nextIndex[name]++;
}
// not const because might modify contents
FieldContainer* CCNode::getFieldContainer() {
return GeodeNodeMetadata::set(this)->getFieldContainer();
}
FieldContainer* CCNode::getFieldContainer(char const* forClass) {
return GeodeNodeMetadata::set(this)->getFieldContainer(forClass);
}
std::string CCNode::getID() {
const std::string& CCNode::getID() {
return GeodeNodeMetadata::set(this)->m_id;
}
@ -121,7 +112,11 @@ void CCNode::setID(std::string const& id) {
GeodeNodeMetadata::set(this)->m_id = id;
}
CCNode* CCNode::getChildByID(std::string const& id) {
void CCNode::setID(std::string&& id) {
GeodeNodeMetadata::set(this)->m_id = std::move(id);
}
CCNode* CCNode::getChildByID(std::string_view id) {
for (auto child : CCArrayExt<CCNode*>(this->getChildren())) {
if (child->getID() == id) {
return child;
@ -130,7 +125,7 @@ CCNode* CCNode::getChildByID(std::string const& id) {
return nullptr;
}
CCNode* CCNode::getChildByIDRecursive(std::string const& id) {
CCNode* CCNode::getChildByIDRecursive(std::string_view id) {
if (auto child = this->getChildByID(id)) {
return child;
}
@ -189,7 +184,7 @@ private:
std::unique_ptr<NodeQuery> m_next = nullptr;
public:
static Result<std::unique_ptr<NodeQuery>> parse(std::string const& query) {
static Result<std::unique_ptr<NodeQuery>> parse(std::string_view query) {
if (query.empty()) {
return Err("Query may not be empty");
}
@ -287,7 +282,7 @@ public:
}
};
CCNode* CCNode::querySelector(std::string const& queryStr) {
CCNode* CCNode::querySelector(std::string_view queryStr) {
auto res = NodeQuery::parse(queryStr);
if (!res) {
log::error("Invalid CCNode::querySelector query '{}': {}", queryStr, res.unwrapErr());
@ -298,7 +293,7 @@ CCNode* CCNode::querySelector(std::string const& queryStr) {
return query->match(this);
}
void CCNode::removeChildByID(std::string const& id) {
void CCNode::removeChildByID(std::string_view id) {
if (auto child = this->getChildByID(id)) {
this->removeChild(child);
}
@ -344,7 +339,7 @@ void CCNode::updateLayout(bool updateChildOrder) {
UserObjectSetEvent::UserObjectSetEvent(CCNode* node, std::string const& id, CCObject* value)
: node(node), id(id), value(value) {}
ListenerResult AttributeSetFilter::handle(MiniFunction<Callback> fn, UserObjectSetEvent* event) {
ListenerResult AttributeSetFilter::handle(std::function<Callback> fn, UserObjectSetEvent* event) {
if (event->id == m_targetID) {
fn(event);
}
@ -455,13 +450,4 @@ void CCNode::updateAnchoredPosition(Anchor anchor, CCPoint const& offset, CCPoin
}
}
#ifdef GEODE_EXPORTING
void CCNode::setAttribute(std::string const& attr, matjson::Value const& value) {}
std::optional<matjson::Value> CCNode::getAttributeInternal(std::string const& attr) {
return std::nullopt;
}
#endif
#pragma warning(pop)

View file

@ -75,6 +75,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
this->fixSocialMenu();
//this code doesnt run have fun figuring out why idc enough
if (auto node = this->getChildByID("settings-gamepad-icon")) {
node->setPositionX(
bottomMenu->getChildByID("settings-button")->getPositionX() + winSize.width / 2
@ -83,13 +84,12 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
}
// show if some mods failed to load
if (Loader::get()->getProblems().size()) {
if (Loader::get()->getLoadProblems().size()) {
static bool shownProblemPopup = false;
if (!shownProblemPopup) {
shownProblemPopup = true;
Notification::create("There were errors - see Geode page!", NotificationIcon::Error)->show();
}
if (m_fields->m_geodeButton) {
m_fields->m_exclamation = CCSprite::createWithSpriteFrameName("exMark_001.png");
m_fields->m_exclamation->setPosition(m_fields->m_geodeButton->getContentSize() - ccp(10, 10));

View file

@ -41,9 +41,9 @@ $execute {
// hook them to call our own handler
if (LoaderImpl::get()->isForwardCompatMode()) return;
#if GEODE_COMP_GD_VERSION == 22060
const uintptr_t offset1 = 0x75d00; // member function in CCEGLView
const uintptr_t offset2 = 0x75d60; // static function
#if GEODE_COMP_GD_VERSION == 22074
const uintptr_t offset1 = 0x75D90; // member function in CCEGLView
const uintptr_t offset2 = 0x75DF0; // static function
(void) Mod::get()->hook(
reinterpret_cast<void*>(geode::base::getCocos() + offset1),

View file

@ -2,10 +2,15 @@
using namespace geode::prelude;
#ifdef GEODE_IS_WINDOWS
#include <Geode/modify/AppDelegate.hpp>
#else
#include <Geode/modify/AchievementNotifier.hpp>
#endif
namespace geode {
#ifdef GEODE_IS_WINDOWS
#include <Geode/modify/AppDelegate.hpp>
struct SceneSwitch : Modify<SceneSwitch, AppDelegate> {
GEODE_FORWARD_COMPAT_DISABLE_HOOKS("persist disabled")
void willSwitchToScene(CCScene* scene) {
@ -15,8 +20,6 @@ struct SceneSwitch : Modify<SceneSwitch, AppDelegate> {
};
#else
#include <Geode/modify/AchievementNotifier.hpp>
struct SceneSwitch : Modify<SceneSwitch, AchievementNotifier> {
GEODE_FORWARD_COMPAT_DISABLE_HOOKS("persist disabled")
void willSwitchToScene(CCScene* scene) {
@ -26,3 +29,5 @@ struct SceneSwitch : Modify<SceneSwitch, AchievementNotifier> {
};
#endif
}

View file

@ -46,6 +46,11 @@ $register_ids(MenuLayer) {
}
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
if (auto menu = this->getChildByType<CCMenu>(0)) {

View file

@ -2,15 +2,14 @@
#include <Geode/DefaultInclude.hpp>
//#include <Geode/utils/general.hpp>
#include <Geode/utils/MiniFunction.hpp>
#include <filesystem>
#include <functional>
#include <string>
class FileWatcher {
public:
using FileWatchCallback = geode::utils::MiniFunction<void(std::filesystem::path)>;
using ErrorCallback = geode::utils::MiniFunction<void(std::string)>;
using FileWatchCallback = std::function<void(std::filesystem::path)>;
using ErrorCallback = std::function<void(std::string)>;
protected:
std::filesystem::path m_file;

View file

@ -20,7 +20,7 @@ protected:
std::string content;
std::string optionA;
std::string optionB;
MiniFunction<void(bool)> after;
std::function<void(bool)> after;
};
EventListener<server::ModDownloadFilter> m_download;
@ -79,7 +79,7 @@ protected:
public:
void start() {
for (auto problem : Loader::get()->getProblems()) {
for (auto problem : Loader::get()->getAllProblems()) {
switch (problem.type) {
// Errors where the correct solution is to just delete the invalid .geode package
case LoadProblem::Type::InvalidFile:

View file

@ -24,7 +24,8 @@ void crashlog::printGeodeInfo(std::stringstream& stream) {
<< "Loader Commit: " << about::getLoaderCommitHash() << "\n"
<< "Bindings Commit: " << about::getBindingsCommitHash() << "\n"
<< "Installed mods: " << Loader::get()->getAllMods().size() << "\n"
<< "Problems: " << Loader::get()->getProblems().size() << "\n";
<< "Outdated mods: " << Loader::get()->getOutdated().size() << "\n"
<< "Problems: " << Loader::get()->getLoadProblems().size() << "\n";
}
void crashlog::printMods(std::stringstream& stream) {
@ -44,7 +45,8 @@ void crashlog::printMods(std::stringstream& stream) {
stream << fmt::format("{} | [{}] {}\n",
mod->isCurrentlyLoading() ? "o"sv :
mod->isEnabled() ? "x"sv :
mod->hasProblems() ? "!"sv : // thank you for this bug report
mod->hasLoadProblems() ? "!"sv : // thank you 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

@ -7,7 +7,6 @@
#include <Geode/loader/Loader.hpp>
#include <Geode/loader/Log.hpp>
#include <Geode/loader/Mod.hpp>
#include <Geode/loader/SettingEvent.hpp>
#include <Geode/utils/JsonValidation.hpp>
#include <loader/LogImpl.hpp>
@ -30,8 +29,7 @@ $on_mod(Loaded) {
std::vector<matjson::Value> res;
auto args = *event->messageData;
JsonChecker checker(args);
auto root = checker.root("[ipc/list-mods]").obj();
auto root = checkJson(args, "[ipc/list-mods]");
auto includeRunTimeInfo = root.has("include-runtime-info").get<bool>();
auto dontIncludeLoader = root.has("dont-include-loader").get<bool>();
@ -86,7 +84,7 @@ void tryShowForwardCompat() {
#ifdef GEODE_IS_WINDOWS
bool safeModeCheck() {
// yes this is quite funny
if (GetAsyncKeyState(VK_SHIFT) == 0) {
if (!(GetAsyncKeyState(VK_SHIFT) & (1 << 15))) {
return false;
}

View file

@ -98,7 +98,7 @@ std::string_view Hook::Impl::getDisplayName() const {
}
matjson::Value Hook::Impl::getRuntimeInfo() const {
auto json = matjson::Object();
matjson::Value json;
json["address"] = std::to_string(reinterpret_cast<uintptr_t>(m_address));
json["detour"] = std::to_string(reinterpret_cast<uintptr_t>(m_detour));
json["name"] = m_displayName;

View file

@ -19,7 +19,7 @@ ipc::IPCEvent::IPCEvent(
ipc::IPCEvent::~IPCEvent() {}
ListenerResult ipc::IPCFilter::handle(utils::MiniFunction<Callback> fn, IPCEvent* event) {
ListenerResult ipc::IPCFilter::handle(std::function<Callback> fn, IPCEvent* event) {
if (event->targetModID == m_modID && event->messageID == m_messageID) {
event->replyData = fn(event);
return ListenerResult::Stop;
@ -33,19 +33,18 @@ ipc::IPCFilter::IPCFilter(std::string const& modID, std::string const& messageID
matjson::Value ipc::processRaw(void* rawHandle, std::string const& buffer) {
matjson::Value reply;
std::string error;
auto res = matjson::parse(buffer, error);
if (error.size() > 0) {
log::warn("Received IPC message that isn't valid JSON: {}", error);
auto res = matjson::Value::parse(buffer);
if (!res) {
log::warn("Received IPC message that isn't valid JSON: {}", res.unwrapErr());
return reply;
}
matjson::Value json = res.value();
matjson::Value json = res.unwrap();
if (!json.contains("mod") || !json["mod"].is_string()) {
if (!json.contains("mod") || !json["mod"].isString()) {
log::warn("Received IPC message without 'mod' field");
return reply;
}
if (!json.contains("message") || !json["message"].is_string()) {
if (!json.contains("message") || !json["message"].isString()) {
log::warn("Received IPC message without 'message' field");
return reply;
}
@ -55,6 +54,6 @@ matjson::Value ipc::processRaw(void* rawHandle, std::string const& buffer) {
}
// log::debug("Posting IPC event");
// ! warning: if the event system is ever made asynchronous this will break!
IPCEvent(rawHandle, json["mod"].as_string(), json["message"].as_string(), data, reply).post();
IPCEvent(rawHandle, json["mod"].asString().unwrap(), json["message"].asString().unwrap(), data, reply).post();
return reply;
}

View file

@ -68,13 +68,19 @@ std::vector<Mod*> Loader::getAllMods() {
std::vector<LoadProblem> Loader::getAllProblems() const {
return m_impl->getProblems();
}
std::vector<LoadProblem> Loader::getProblems() 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
) {
if (problem.isProblem()) {
result.push_back(problem);
}
}
return result;
}
std::vector<LoadProblem> Loader::getOutdated() const {
std::vector<LoadProblem> result;
for (auto problem : this->getAllProblems()) {
if (problem.isOutdated()) {
result.push_back(problem);
}
}
@ -83,10 +89,7 @@ std::vector<LoadProblem> Loader::getProblems() 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);
}
}
@ -109,14 +112,14 @@ std::vector<std::string> Loader::getLaunchArgumentNames() const {
return m_impl->getLaunchArgumentNames();
}
bool Loader::hasLaunchArgument(std::string_view const name) const {
bool Loader::hasLaunchArgument(std::string_view name) const {
return m_impl->hasLaunchArgument(name);
}
std::optional<std::string> Loader::getLaunchArgument(std::string_view const name) const {
std::optional<std::string> Loader::getLaunchArgument(std::string_view name) const {
return m_impl->getLaunchArgument(name);
}
bool Loader::getLaunchFlag(std::string_view const name) const {
bool Loader::getLaunchFlag(std::string_view name) const {
return m_impl->getLaunchFlag(name);
}

View file

@ -42,6 +42,11 @@ Loader::Impl::~Impl() = default;
// Initialization
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()) {
m_forwardCompatMode = !this->getGameVersion().empty() &&
this->getGameVersion() != GEODE_STR(GEODE_GD_VERSION);
@ -384,6 +389,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 +480,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) {
@ -496,20 +503,20 @@ void Loader::Impl::loadModGraph(Mod* node, bool early) {
thread::setName("Mod Unzip");
log::loadNest(nest);
auto res = unzipFunction();
auto prevNest = log::saveNest();
log::loadNest(nest);
if (!res) {
this->addProblem({
LoadProblem::Type::UnzipFailed,
node,
res.unwrapErr()
});
log::error("Failed to unzip: {}", res.unwrapErr());
m_refreshingModCount -= 1;
log::loadNest(prevNest);
return;
}
this->queueInMainThread([=, this]() {
auto prevNest = log::saveNest();
log::loadNest(nest);
if (!res) {
this->addProblem({
LoadProblem::Type::UnzipFailed,
node,
res.unwrapErr()
});
log::error("Failed to unzip: {}", res.unwrapErr());
m_refreshingModCount -= 1;
log::loadNest(prevNest);
return;
}
loadFunction();
log::loadNest(prevNest);
});
@ -524,6 +531,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();
@ -927,11 +938,11 @@ std::vector<std::string> Loader::Impl::getLaunchArgumentNames() const {
return map::keys(m_launchArgs);
}
bool Loader::Impl::hasLaunchArgument(std::string_view const name) const {
bool Loader::Impl::hasLaunchArgument(std::string_view name) const {
return m_launchArgs.find(std::string(name)) != m_launchArgs.end();
}
std::optional<std::string> Loader::Impl::getLaunchArgument(std::string_view const name) const {
std::optional<std::string> Loader::Impl::getLaunchArgument(std::string_view name) const {
auto value = m_launchArgs.find(std::string(name));
if (value == m_launchArgs.end()) {
return std::nullopt;
@ -939,7 +950,7 @@ std::optional<std::string> Loader::Impl::getLaunchArgument(std::string_view cons
return std::optional(value->second);
}
bool Loader::Impl::getLaunchFlag(std::string_view const name) const {
bool Loader::Impl::getLaunchFlag(std::string_view name) const {
auto arg = this->getLaunchArgument(name);
return arg.has_value() && arg.value() == "true";
}

View file

@ -7,10 +7,9 @@
#include <Geode/loader/Loader.hpp>
#include <Geode/loader/Log.hpp>
#include <Geode/loader/Mod.hpp>
#include <Geode/utils/Result.hpp>
#include <Geode/Result.hpp>
#include <Geode/utils/map.hpp>
#include <Geode/utils/ranges.hpp>
#include <Geode/utils/MiniFunction.hpp>
#include "ModImpl.hpp"
#include <crashlog.hpp>
#include <mutex>
@ -41,7 +40,7 @@ namespace geode {
LoadingState m_loadingState = LoadingState::None;
std::vector<utils::MiniFunction<void(void)>> m_mainThreadQueue;
std::vector<std::function<void(void)>> m_mainThreadQueue;
mutable std::mutex m_mainThreadMutex;
std::vector<std::pair<Hook*, Mod*>> m_uninitializedHooks;
bool m_readyToHook = false;
@ -116,9 +115,9 @@ namespace geode {
std::string getLaunchCommand() const;
void initLaunchArguments();
std::vector<std::string> getLaunchArgumentNames() const;
bool hasLaunchArgument(std::string_view const name) const;
std::optional<std::string> getLaunchArgument(std::string_view const name) const;
bool getLaunchFlag(std::string_view const name) const;
bool hasLaunchArgument(std::string_view name) const;
std::optional<std::string> getLaunchArgument(std::string_view name) const;
bool getLaunchFlag(std::string_view name) const;
void updateResources(bool forceReload);

View file

@ -20,10 +20,6 @@ std::string Mod::getName() const {
return m_impl->getName();
}
std::string Mod::getDeveloper() const {
return m_impl->getDevelopers().empty() ? "" : m_impl->getDevelopers().front();
}
std::vector<std::string> Mod::getDevelopers() const {
return m_impl->getDevelopers();
}
@ -101,9 +97,6 @@ std::vector<Mod*> Mod::getDependants() const {
}
#endif
std::optional<VersionInfo> Mod::hasAvailableUpdate() const {
return std::nullopt;
}
Mod::CheckUpdatesTask Mod::checkUpdates() const {
return server::checkUpdates(this).map(
[](auto* result) -> Mod::CheckUpdatesTask::Value {
@ -155,28 +148,14 @@ std::vector<std::string> Mod::getSettingKeys() const {
return m_impl->getSettingKeys();
}
bool Mod::hasSetting(std::string_view const key) const {
bool Mod::hasSetting(std::string_view key) const {
return m_impl->hasSetting(key);
}
std::optional<Setting> Mod::getSettingDefinition(std::string_view const key) const {
return m_impl->m_settings->getLegacyDefinition(std::string(key));
}
SettingValue* Mod::getSetting(std::string_view const key) const {
return m_impl->m_settings->getLegacy(std::string(key)).get();
}
std::shared_ptr<SettingV3> Mod::getSettingV3(std::string_view const key) const {
std::shared_ptr<Setting> Mod::getSetting(std::string_view key) const {
return m_impl->m_settings->get(std::string(key));
}
void Mod::registerCustomSetting(std::string_view const key, std::unique_ptr<SettingValue> value) {
auto reg = m_impl->m_settings->registerLegacyCustomSetting(key, std::move(value));
if (!reg) {
log::error("Unable to register custom setting: {}", reg.unwrapErr());
}
}
Result<> Mod::registerCustomSettingType(std::string_view type, SettingGenerator generator) {
return m_impl->m_settings->registerCustomSettingType(type, generator);
}
@ -185,15 +164,15 @@ std::vector<std::string> Mod::getLaunchArgumentNames() const {
return m_impl->getLaunchArgumentNames();
}
bool Mod::hasLaunchArgument(std::string_view const name) const {
bool Mod::hasLaunchArgument(std::string_view name) const {
return m_impl->hasLaunchArgument(name);
}
std::optional<std::string> Mod::getLaunchArgument(std::string_view const name) const {
std::optional<std::string> Mod::getLaunchArgument(std::string_view name) const {
return m_impl->getLaunchArgument(name);
}
bool Mod::getLaunchFlag(std::string_view const name) const {
bool Mod::getLaunchFlag(std::string_view name) const {
return m_impl->getLaunchFlag(name);
}
@ -241,7 +220,7 @@ ModRequestedAction Mod::getRequestedAction() const {
return m_impl->getRequestedAction();
}
bool Mod::depends(std::string_view const id) const {
bool Mod::depends(std::string_view id) const {
return m_impl->depends(id);
}
@ -269,12 +248,20 @@ void Mod::setLoggingEnabled(bool enabled) {
m_impl->setLoggingEnabled(enabled);
}
bool Mod::hasSavedValue(std::string_view const key) {
bool Mod::hasSavedValue(std::string_view key) {
return this->getSaveContainer().contains(key);
}
bool Mod::hasProblems() const {
return m_impl->hasProblems();
bool Mod::hasLoadProblems() const {
return m_impl->hasLoadProblems();
}
std::optional<LoadProblem> Mod::targetsOutdatedVersion() const {
for (auto problem : this->getAllProblems()) {
if (problem.isOutdated()) {
return problem;
}
}
return std::nullopt;
}
std::vector<LoadProblem> Mod::getAllProblems() const {
return m_impl->getProblems();
@ -282,10 +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
) {
if (problem.isProblem()) {
result.push_back(problem);
}
}
@ -294,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

@ -12,7 +12,7 @@ Mod* ModStateEvent::getMod() const {
return m_mod;
}
ListenerResult ModStateFilter::handle(utils::MiniFunction<Callback> fn, ModStateEvent* event) {
ListenerResult ModStateFilter::handle(std::function<Callback> fn, ModStateEvent* event) {
// log::debug("Event mod filter: {}, {}, {}, {}", m_mod, static_cast<int>(m_type), event->getMod(), static_cast<int>(event->getType()));
if ((!m_mod || event->getMod() == m_mod) && event->getType() == m_type) {
fn(event);

View file

@ -51,7 +51,9 @@ Result<> Mod::Impl::setup() {
(void) utils::file::createDirectoryAll(m_saveDirPath);
// always create temp dir for all mods, even if disabled, so resources can be loaded
GEODE_UNWRAP(this->createTempDir().expect("Unable to create temp dir: {error}"));
GEODE_UNWRAP(this->createTempDir().mapErr([](auto const& err) {
return fmt::format("Unable to create temp dir: {}", err);
}));
m_settings = std::make_unique<ModSettingsManager>(m_metadata);
auto loadRes = this->loadData();
@ -188,15 +190,12 @@ Result<> Mod::Impl::loadData() {
auto savedPath = m_saveDirPath / "saved.json";
if (std::filesystem::exists(savedPath)) {
GEODE_UNWRAP_INTO(auto data, utils::file::readString(savedPath));
std::string error;
auto res = matjson::parse(data, error);
if (error.size() > 0) {
return Err("Unable to parse saved values: " + error);
}
m_saved = res.value();
if (!m_saved.is_object()) {
m_saved = GEODE_UNWRAP(matjson::parse(data).mapErr([](auto&& err) {
return fmt::format("Unable to parse saved values: {}", err);
}));
if (!m_saved.isObject()) {
log::warn("saved.json was somehow not an object, forcing it to one");
m_saved = matjson::Object();
m_saved = matjson::Value::object();
}
}
@ -229,19 +228,19 @@ Result<> Mod::Impl::saveData() {
}
bool Mod::Impl::hasSettings() const {
return m_metadata.getSettingsV3().size();
return m_metadata.getSettings().size();
}
std::vector<std::string> Mod::Impl::getSettingKeys() const {
std::vector<std::string> keys;
for (auto& [key, _] : m_metadata.getSettingsV3()) {
for (auto& [key, _] : m_metadata.getSettings()) {
keys.push_back(key);
}
return keys;
}
bool Mod::Impl::hasSetting(std::string_view const key) const {
for (auto& setting : m_metadata.getSettingsV3()) {
bool Mod::Impl::hasSetting(std::string_view key) const {
for (auto& setting : m_metadata.getSettings()) {
if (setting.first == key) {
return true;
}
@ -249,7 +248,7 @@ bool Mod::Impl::hasSetting(std::string_view const key) const {
return false;
}
std::string Mod::Impl::getLaunchArgumentName(std::string_view const name) const {
std::string Mod::Impl::getLaunchArgumentName(std::string_view name) const {
return this->getID() + "." + std::string(name);
}
@ -264,15 +263,15 @@ std::vector<std::string> Mod::Impl::getLaunchArgumentNames() const {
return names;
}
bool Mod::Impl::hasLaunchArgument(std::string_view const name) const {
bool Mod::Impl::hasLaunchArgument(std::string_view name) const {
return Loader::get()->hasLaunchArgument(this->getLaunchArgumentName(name));
}
std::optional<std::string> Mod::Impl::getLaunchArgument(std::string_view const name) const {
std::optional<std::string> Mod::Impl::getLaunchArgument(std::string_view name) const {
return Loader::get()->getLaunchArgument(this->getLaunchArgumentName(name));
}
bool Mod::Impl::getLaunchFlag(std::string_view const name) const {
bool Mod::Impl::getLaunchFlag(std::string_view name) const {
return Loader::get()->getLaunchFlag(this->getLaunchArgumentName(name));
}
@ -318,7 +317,6 @@ Result<> Mod::Impl::loadBinary() {
ModStateEvent(m_self, ModEventType::Loaded).post();
ModStateEvent(m_self, ModEventType::Enabled).post();
ModStateEvent(m_self, ModEventType::DataLoaded).post();
m_isCurrentlyLoading = false;
@ -385,7 +383,7 @@ Result<> Mod::Impl::uninstall(bool deleteSaveData) {
ModRequestedAction::Uninstall;
// Make loader forget the mod should be disabled
Mod::get()->getSaveContainer().try_erase("should-load-" + m_metadata.getID());
Mod::get()->getSaveContainer().erase("should-load-" + m_metadata.getID());
std::error_code ec;
std::filesystem::remove(m_metadata.getPath(), ec);
@ -440,7 +438,7 @@ bool Mod::Impl::hasUnresolvedIncompatibilities() const {
return false;
}
bool Mod::Impl::depends(std::string_view const id) const {
bool Mod::Impl::depends(std::string_view id) const {
return utils::ranges::contains(m_metadata.getDependencies(), [id](ModMetadata::Dependency const& t) {
return t.id == id;
});
@ -672,17 +670,15 @@ std::string_view Mod::Impl::expandSpriteName(std::string_view name) {
ModJson Mod::Impl::getRuntimeInfo() const {
auto json = m_metadata.toJSON();
auto obj = matjson::Object();
obj["hooks"] = matjson::Array();
auto obj = matjson::Value::object();
obj["hooks"] = matjson::Value::array();
for (auto hook : m_hooks) {
obj["hooks"].as_array().push_back(ModJson(hook->getRuntimeInfo()));
obj["hooks"].push(ModJson(hook->getRuntimeInfo()));
}
obj["patches"] = matjson::Array();
obj["patches"] = matjson::Value::array();
for (auto patch : m_patches) {
obj["patches"].as_array().push_back(ModJson(patch->getRuntimeInfo()));
obj["patches"].push(ModJson(patch->getRuntimeInfo()));
}
// TODO: so which one is it
// obj["enabled"] = m_enabled;
obj["loaded"] = m_enabled;
obj["temp-dir"] = this->getTempDir();
obj["save-dir"] = this->getSaveDir();
@ -708,11 +704,11 @@ bool Mod::Impl::isCurrentlyLoading() const {
return m_isCurrentlyLoading;
}
bool Mod::Impl::hasProblems() const {
for (auto const& item : m_problems) {
if (item.type <= LoadProblem::Type::Recommendation)
continue;
return true;
bool Mod::Impl::hasLoadProblems() const {
for (auto const& problem : m_problems) {
if (problem.isProblem()) {
return true;
}
}
return false;
}
@ -722,12 +718,9 @@ std::vector<LoadProblem> Mod::Impl::getProblems() const {
}
static Result<ModMetadata> getModImplInfo() {
std::string error;
auto res = matjson::parse(about::getLoaderModJson(), error);
if (error.size() > 0) {
return Err("Unable to parse mod.json: " + error);
}
matjson::Value json = res.value();
auto json = GEODE_UNWRAP(matjson::parse(about::getLoaderModJson()).mapErr([](auto&& err) {
return fmt::format("Unable to parse mod.json: {}", err);
}));
GEODE_UNWRAP_INTO(auto info, ModMetadata::create(json));
return Ok(info);

View file

@ -47,7 +47,7 @@ namespace geode {
/**
* Saved values
*/
matjson::Value m_saved = matjson::Object();
matjson::Value m_saved = matjson::Value();
/**
* Setting values. This is behind unique_ptr for interior mutability
*/
@ -112,13 +112,13 @@ namespace geode {
bool hasSettings() const;
std::vector<std::string> getSettingKeys() const;
bool hasSetting(std::string_view const key) const;
bool hasSetting(std::string_view key) const;
std::string getLaunchArgumentName(std::string_view const name) const;
std::string getLaunchArgumentName(std::string_view name) const;
std::vector<std::string> getLaunchArgumentNames() const;
bool hasLaunchArgument(std::string_view const name) const;
std::optional<std::string> getLaunchArgument(std::string_view const name) const;
bool getLaunchFlag(std::string_view const name) const;
bool hasLaunchArgument(std::string_view name) const;
std::optional<std::string> getLaunchArgument(std::string_view name) const;
bool getLaunchFlag(std::string_view name) const;
Result<Hook*> claimHook(std::shared_ptr<Hook> hook);
Result<> disownHook(Hook* hook);
@ -136,7 +136,7 @@ namespace geode {
// 1.3.0 additions
ModRequestedAction getRequestedAction() const;
bool depends(std::string_view const id) const;
bool depends(std::string_view id) const;
Result<> updateDependencies();
bool hasUnresolvedDependencies() const;
bool hasUnresolvedIncompatibilities() const;
@ -151,7 +151,7 @@ namespace geode {
std::vector<LoadProblem> getProblems() const;
bool hasProblems() const;
bool hasLoadProblems() const;
bool shouldLoad() const;
bool isCurrentlyLoading() const;
};

View file

@ -113,14 +113,14 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
auto checkerRoot = fmt::format(
"[{}/v0.0.0/mod.json]",
rawJson.contains("id") ? rawJson["id"].as_string() : "unknown.mod"
rawJson.contains("id") ? GEODE_UNWRAP(rawJson["id"].asString()) : "unknown.mod"
);
// JsonChecker did it this way too
try {
checkerRoot = fmt::format(
"[{}/{}/mod.json]",
rawJson.contains("id") ? rawJson["id"].as_string() : "unknown.mod",
rawJson.contains("version") ? rawJson["version"].as<VersionInfo>().toVString() : "v0.0.0"
rawJson.contains("id") ? GEODE_UNWRAP(rawJson["id"].asString()) : "unknown.mod",
rawJson.contains("version") ? GEODE_UNWRAP(rawJson["version"].as<VersionInfo>()).toVString() : "v0.0.0"
);
}
catch (...) { }
@ -304,11 +304,14 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
Result<ModMetadata> ModMetadata::Impl::create(ModJson const& json) {
// Check mod.json target version
auto schema = about::getLoaderVersion();
if (json.contains("geode") && json["geode"].is_string()) {
if (json.contains("geode") && json["geode"].isString()) {
GEODE_UNWRAP_INTO(
schema,
VersionInfo::parse(json["geode"].as_string())
.expect("[mod.json] has invalid target loader version: {error}")
VersionInfo::parse(GEODE_UNWRAP(json["geode"].asString())).mapErr(
[](auto const& err) {
return fmt::format("[mod.json] has invalid target loader version: {}", err);
}
)
);
}
else {
@ -352,13 +355,9 @@ Result<ModMetadata> ModMetadata::Impl::create(ModJson const& json) {
Result<ModMetadata> ModMetadata::Impl::createFromFile(std::filesystem::path const& path) {
GEODE_UNWRAP_INTO(auto read, utils::file::readString(path));
std::string error;
auto res = matjson::parse(read, error);
if (error.size() > 0) {
return Err(std::string("Unable to parse mod.json: ") + error);
}
GEODE_UNWRAP_INTO(auto info, ModMetadata::create(res.value()));
GEODE_UNWRAP_INTO(auto info, ModMetadata::create(GEODE_UNWRAP(matjson::parse(read).mapErr([&](auto const& err) {
return fmt::format("Unable to parse mod.json: {}", err);
}))));
auto impl = info.m_impl.get();
@ -382,25 +381,24 @@ Result<ModMetadata> ModMetadata::Impl::createFromGeodeZip(file::Unzip& unzip) {
// Read mod.json & parse if possible
GEODE_UNWRAP_INTO(
auto jsonData, unzip.extract("mod.json").expect("Unable to read mod.json: {error}")
auto jsonData, unzip.extract("mod.json").mapErr([](auto const& err) {
return fmt::format("Unable to extract mod.json: {}", err);
})
);
std::string error;
auto res = matjson::parse(std::string(jsonData.begin(), jsonData.end()), error);
if (error.size() > 0) {
return Err(std::string("Unable to parse mod.json: ") + error);
}
ModJson json = res.value();
ModJson json = GEODE_UNWRAP(matjson::parse(std::string(jsonData.begin(), jsonData.end())).mapErr([](auto const& err) {
return fmt::format("Unable to parse mod.json: {}", err);
}));
auto res2 = ModMetadata::create(json);
if (!res2) {
return Err("\"" + unzip.getPath().string() + "\" - " + res2.unwrapErr());
}
auto info = res2.unwrap();
auto info = GEODE_UNWRAP(ModMetadata::create(json).mapErr([&](auto const& err) {
return fmt::format("\"{}\" - {}", unzip.getPath().string(), err);
}));
auto impl = info.m_impl.get();
impl->m_path = unzip.getPath();
GEODE_UNWRAP(info.addSpecialFiles(unzip).expect("Unable to add extra files: {error}"));
GEODE_UNWRAP(info.addSpecialFiles(unzip).mapErr([](auto const& err) {
return fmt::format("Unable to add extra files: {}", err);
}));
return Ok(info);
}
@ -409,7 +407,11 @@ Result<> ModMetadata::Impl::addSpecialFiles(file::Unzip& unzip) {
// unzip known MD files
for (auto& [file, target] : this->getSpecialFiles()) {
if (unzip.hasEntry(file)) {
GEODE_UNWRAP_INTO(auto data, unzip.extract(file).expect("Unable to extract \"{}\"", file));
// reference to local binding 'file' declared in enclosing function
std::string_view fileStr(file);
GEODE_UNWRAP_INTO(auto data, unzip.extract(fileStr).mapErr([&](auto const& err) {
return fmt::format("Unable to extract \"{}\": {}", fileStr, err);
}));
*target = sanitizeDetailsData(std::string(data.begin(), data.end()));
}
}
@ -477,12 +479,6 @@ std::string ModMetadata::getName() const {
return m_impl->m_name;
}
std::string ModMetadata::getDeveloper() const {
// m_developers should be guaranteed to never be empty, but this is
// just in case it is anyway somehow
return m_impl->m_developers.empty() ? "" : m_impl->m_developers.front();
}
std::string ModMetadata::formatDeveloperDisplayString(std::vector<std::string> const& developers) {
switch (developers.size()) {
case 0: return "Unknown"; break;
@ -510,9 +506,6 @@ std::optional<std::string> ModMetadata::getChangelog() const {
std::optional<std::string> ModMetadata::getSupportInfo() const {
return m_impl->m_supportInfo;
}
std::optional<std::string> ModMetadata::getRepository() const {
return m_impl->m_links.getSourceURL();
}
ModMetadataLinks ModMetadata::getLinks() const {
return m_impl->m_links;
}
@ -528,19 +521,7 @@ std::vector<ModMetadata::Incompatibility> ModMetadata::getIncompatibilities() co
std::vector<std::string> ModMetadata::getSpritesheets() const {
return m_impl->m_spritesheets;
}
std::vector<std::pair<std::string, Setting>> ModMetadata::getSettings() const {
std::vector<std::pair<std::string, Setting>> res;
for (auto [key, sett] : m_impl->m_settings) {
auto checker = JsonChecker(sett);
auto value = checker.root("");
auto legacy = Setting::parse(key, m_impl->m_id, value);
if (!checker.isError() && legacy.isOk()) {
res.push_back(std::make_pair(key, *legacy));
}
}
return res;
}
std::vector<std::pair<std::string, matjson::Value>> ModMetadata::getSettingsV3() const {
std::vector<std::pair<std::string, matjson::Value>> ModMetadata::getSettings() const {
return m_impl->m_settings;
}
std::unordered_set<std::string> ModMetadata::getTags() const {
@ -641,10 +622,6 @@ void ModMetadata::setIncompatibilities(std::vector<Incompatibility> const& value
void ModMetadata::setSpritesheets(std::vector<std::string> const& value) {
m_impl->m_spritesheets = value;
}
void ModMetadata::setSettings(std::vector<std::pair<std::string, Setting>> const& value) {
// intentionally no-op because no one is supposed to be using this
// without subscribing to "internals are not stable" mentality
}
void ModMetadata::setSettings(std::vector<std::pair<std::string, matjson::Value>> const& value) {
m_impl->m_settings = value;
}

View file

@ -4,7 +4,7 @@
#include <Geode/loader/Mod.hpp>
#include <Geode/utils/JsonValidation.hpp>
#include <Geode/utils/VersionInfo.hpp>
#include <Geode/loader/SettingV3.hpp>
#include <Geode/loader/Setting.hpp>
using namespace geode::prelude;
@ -74,52 +74,44 @@ namespace geode {
template <>
struct matjson::Serialize<geode::ModMetadata::Dependency::Importance> {
static matjson::Value GEODE_DLL to_json(geode::ModMetadata::Dependency::Importance const& importance) {
switch (importance) {
case geode::ModMetadata::Dependency::Importance::Required: return {"required"};
case geode::ModMetadata::Dependency::Importance::Recommended: return {"recommended"};
case geode::ModMetadata::Dependency::Importance::Suggested: return {"suggested"};
default: return {"unknown"};
}
}
static geode::ModMetadata::Dependency::Importance GEODE_DLL from_json(matjson::Value const& importance) {
auto impStr = importance.as_string();
if (impStr == "required")
return geode::ModMetadata::Dependency::Importance::Required;
if (impStr == "recommended")
return geode::ModMetadata::Dependency::Importance::Recommended;
if (impStr == "suggested")
return geode::ModMetadata::Dependency::Importance::Suggested;
throw matjson::JsonException(R"(Expected importance to be "required", "recommended" or "suggested")");
static geode::Result<geode::ModMetadata::Dependency::Importance, std::string> fromJson(Value const& value)
{
auto str = GEODE_UNWRAP(value.asString());
if (str == "required") return geode::Ok(geode::ModMetadata::Dependency::Importance::Required);
if (str == "recommended") return geode::Ok(geode::ModMetadata::Dependency::Importance::Recommended);
if (str == "suggested") return geode::Ok(geode::ModMetadata::Dependency::Importance::Suggested);
return geode::Err("Invalid importance");
}
static bool is_json(matjson::Value const& value) {
return value.is_string();
static Value toJson(geode::ModMetadata::Dependency::Importance const& value)
{
switch (value) {
case geode::ModMetadata::Dependency::Importance::Required: return "required";
case geode::ModMetadata::Dependency::Importance::Recommended: return "recommended";
case geode::ModMetadata::Dependency::Importance::Suggested: return "suggested";
}
return "unknown";
}
};
template <>
struct matjson::Serialize<geode::ModMetadata::Incompatibility::Importance> {
static matjson::Value GEODE_DLL to_json(geode::ModMetadata::Incompatibility::Importance const& importance) {
switch (importance) {
case geode::ModMetadata::Incompatibility::Importance::Breaking: return {"breaking"};
case geode::ModMetadata::Incompatibility::Importance::Conflicting: return {"conflicting"};
case geode::ModMetadata::Incompatibility::Importance::Superseded: return {"superseded"};
default: return {"unknown"};
}
}
static geode::ModMetadata::Incompatibility::Importance GEODE_DLL from_json(matjson::Value const& importance) {
auto impStr = importance.as_string();
if (impStr == "breaking")
return geode::ModMetadata::Incompatibility::Importance::Breaking;
if (impStr == "conflicting")
return geode::ModMetadata::Incompatibility::Importance::Conflicting;
if (impStr == "superseded")
return geode::ModMetadata::Incompatibility::Importance::Superseded;
throw matjson::JsonException(R"(Expected importance to be "breaking", "conflicting", or "superseded")");
static geode::Result<geode::ModMetadata::Incompatibility::Importance, std::string> fromJson(Value const& value)
{
auto str = GEODE_UNWRAP(value.asString());
if (str == "breaking") return geode::Ok(geode::ModMetadata::Incompatibility::Importance::Breaking);
if (str == "conflicting") return geode::Ok(geode::ModMetadata::Incompatibility::Importance::Conflicting);
if (str == "superseded") return geode::Ok(geode::ModMetadata::Incompatibility::Importance::Superseded);
return geode::Err("Invalid importance");
}
static bool is_json(matjson::Value const& value) {
return value.is_string();
static Value toJson(geode::ModMetadata::Incompatibility::Importance const& value)
{
switch (value) {
case geode::ModMetadata::Incompatibility::Importance::Breaking: return "breaking";
case geode::ModMetadata::Incompatibility::Importance::Conflicting: return "conflicting";
case geode::ModMetadata::Incompatibility::Importance::Superseded: return "superseded";
}
return "unknown";
}
};

View file

@ -8,24 +8,36 @@ using namespace geode::prelude;
// #1 no need to duplicate the built-in settings between all mods
// #2 easier lookup of custom settings if a mod uses another mod's custom setting type
namespace {
auto changeToGenerator(auto&& function) {
return [function](
std::string const& key,
std::string const& modID,
matjson::Value const& json
) -> Result<std::shared_ptr<SettingV3>> {
return function(key, modID, json).map([](auto&& ptr) {
return std::shared_ptr<SettingV3>(ptr);
});
};
}
}
class SharedSettingTypesPool final {
private:
std::unordered_map<std::string, SettingGenerator> m_types;
SharedSettingTypesPool() : m_types({
// todo in v4: remove this
{ "custom", &LegacyCustomSettingV3::parse },
{ "title", &TitleSettingV3::parse },
{ "bool", &BoolSettingV3::parse },
{ "int", &IntSettingV3::parse },
{ "float", &FloatSettingV3::parse },
{ "string", &StringSettingV3::parse },
{ "file", &FileSettingV3::parse },
{ "folder", &FileSettingV3::parse },
{ "path", &FileSettingV3::parse },
{ "rgb", &Color3BSettingV3::parse },
{ "color", &Color3BSettingV3::parse },
{ "rgba", &Color4BSettingV3::parse },
{ "title", changeToGenerator(TitleSettingV3::parse) },
{ "bool", changeToGenerator(BoolSettingV3::parse) },
{ "int", changeToGenerator(IntSettingV3::parse) },
{ "float", changeToGenerator(FloatSettingV3::parse) },
{ "string", changeToGenerator(StringSettingV3::parse) },
{ "file", changeToGenerator(FileSettingV3::parse) },
{ "folder", changeToGenerator(FileSettingV3::parse) },
{ "path", changeToGenerator(FileSettingV3::parse) },
{ "rgb", changeToGenerator(Color3BSettingV3::parse) },
{ "color", changeToGenerator(Color3BSettingV3::parse) },
{ "rgba", changeToGenerator(Color4BSettingV3::parse) },
}) {}
public:
@ -80,9 +92,7 @@ public:
struct SettingInfo final {
std::string type;
matjson::Value json;
std::shared_ptr<SettingV3> v3 = nullptr;
// todo: remove in v4
std::shared_ptr<SettingValue> legacy = nullptr;
std::shared_ptr<Setting> v3 = nullptr;
};
std::string modID;
std::unordered_map<std::string, SettingInfo> settings;
@ -114,16 +124,11 @@ public:
// Store the value in an intermediary so if `save` fails the existing
// value loaded from disk isn't overwritten
matjson::Value value;
try {
if (sett.v3->save(value)) {
this->savedata[key] = value;
}
else {
log::error("Unable to save setting '{}' for mod {}", key, this->modID);
}
if (sett.v3->save(value)) {
this->savedata[key] = value;
}
catch(matjson::JsonException const& e) {
log::error("Unable to save setting '{}' for mod {} (JSON exception): {}", key, this->modID, e.what());
else {
log::error("Unable to save setting '{}' for mod {}", key, this->modID);
}
}
}
@ -139,7 +144,7 @@ public:
continue;
}
if (auto v3 = (*gen)(key, modID, setting.json)) {
setting.v3 = *v3;
setting.v3 = v3.unwrap();
this->loadSettingValueFromSave(key);
}
else {
@ -161,7 +166,7 @@ ModSettingsManager::ModSettingsManager(ModMetadata const& metadata)
: m_impl(std::make_unique<Impl>())
{
m_impl->modID = metadata.getID();
for (auto const& [key, json] : metadata.getSettingsV3()) {
for (auto const& [key, json] : metadata.getSettings()) {
auto setting = Impl::SettingInfo();
setting.json = json;
auto root = checkJson(json, "setting");
@ -197,33 +202,13 @@ Result<> ModSettingsManager::registerCustomSettingType(std::string_view type, Se
m_impl->createSettings();
return Ok();
}
Result<> ModSettingsManager::registerLegacyCustomSetting(std::string_view key, std::unique_ptr<SettingValue>&& ptr) {
auto id = std::string(key);
if (!m_impl->settings.count(id)) {
return Err("No such setting '{}' in mod {}", id, m_impl->modID);
}
auto& sett = m_impl->settings.at(id);
if (auto custom = typeinfo_pointer_cast<LegacyCustomSettingV3>(sett.v3)) {
if (!custom->getValue()) {
custom->setValue(std::move(ptr));
m_impl->loadSettingValueFromSave(id);
}
else {
return Err("Setting '{}' in mod {} has already been registed", id, m_impl->modID);
}
}
else {
return Err("Setting '{}' in mod {} is not a legacy custom setting", id, m_impl->modID);
}
return Ok();
}
Result<> ModSettingsManager::load(matjson::Value const& json) {
if (json.is_object()) {
if (json.isObject()) {
// Save this so when custom settings are registered they can load their
// values properly
m_impl->savedata = json.as_object();
for (auto const& [key, _] : json.as_object()) {
m_impl->savedata = json;
for (auto const& [key, _] : json) {
m_impl->loadSettingValueFromSave(key);
}
}
@ -240,37 +225,10 @@ matjson::Value& ModSettingsManager::getSaveData() {
return m_impl->savedata;
}
std::shared_ptr<SettingV3> ModSettingsManager::get(std::string_view key) {
std::shared_ptr<Setting> ModSettingsManager::get(std::string_view key) {
auto id = std::string(key);
return m_impl->settings.count(id) ? m_impl->settings.at(id).v3 : nullptr;
}
std::shared_ptr<SettingValue> ModSettingsManager::getLegacy(std::string_view key) {
auto id = std::string(key);
if (!m_impl->settings.count(id)) {
return nullptr;
}
auto& info = m_impl->settings.at(id);
// If this setting has alreay been given a legacy interface, give that
if (info.legacy) {
return info.legacy;
}
// Uninitialized settings are null
if (!info.v3) {
return nullptr;
}
// Generate new legacy interface
if (auto legacy = info.v3->convertToLegacyValue()) {
info.legacy.swap(*legacy);
return info.legacy;
}
return nullptr;
}
std::optional<Setting> ModSettingsManager::getLegacyDefinition(std::string_view key) {
if (auto s = this->get(key)) {
return s->convertToLegacy();
}
return std::nullopt;
}
bool ModSettingsManager::restartRequired() const {
return m_impl->restartRequired;

View file

@ -92,8 +92,8 @@ Result<> Patch::Impl::updateBytes(const ByteVector& bytes) {
if (m_enabled) {
auto res = this->disable();
if (!res) return Err("Failed to update patch: {}", res.unwrapErr());
res = this->enable();
if (!res) return Err("Failed to update patch: {}", res.unwrapErr());
auto res2 = this->enable();
if (!res2) return Err("Failed to update patch: {}", res2.unwrapErr());
}
return Ok();
@ -104,7 +104,7 @@ uintptr_t Patch::Impl::getAddress() const {
}
matjson::Value Patch::Impl::getRuntimeInfo() const {
auto json = matjson::Object();
auto json = matjson::Value::object();
json["address"] = std::to_string(reinterpret_cast<uintptr_t>(m_address));
json["original"] = m_original;
json["patch"] = m_patch;

View file

@ -1,498 +0,0 @@
#include <ui/mods/settings/GeodeSettingNode.hpp>
#include <Geode/loader/Mod.hpp>
#include <Geode/loader/Setting.hpp>
#include <Geode/loader/SettingV3.hpp>
#include <Geode/loader/SettingEvent.hpp>
#include <Geode/loader/SettingNode.hpp>
#include <Geode/utils/general.hpp>
#include <Geode/utils/JsonValidation.hpp>
#include <regex>
using namespace geode::prelude;
template<class T>
static void parseCommon(T& sett, JsonMaybeObject& obj) {
obj.has("name").into(sett.name);
obj.has("description").into(sett.description);
if (auto defValue = obj.needs("default")) {
// Platform-specific default value
if (defValue.is<matjson::Object>()) {
auto def = defValue.obj();
if (auto plat = def.has(PlatformID::toShortString(GEODE_PLATFORM_TARGET, true))) {
plat.into(sett.defaultValue);
}
else {
defValue.into(sett.defaultValue);
}
}
else {
defValue.into(sett.defaultValue);
}
}
}
Result<BoolSetting> BoolSetting::parse(JsonMaybeObject& obj) {
BoolSetting sett;
parseCommon(sett, obj);
return Ok(sett);
}
Result<IntSetting> IntSetting::parse(JsonMaybeObject& obj) {
IntSetting sett;
parseCommon(sett, obj);
obj.has("min").into(sett.min);
obj.has("max").into(sett.max);
if (auto controls = obj.has("control").obj()) {
controls.has("arrows").into(sett.controls.arrows);
controls.has("big-arrows").into(sett.controls.bigArrows);
controls.has("arrow-step").into(sett.controls.arrowStep);
controls.has("big-arrow-step").into(sett.controls.bigArrowStep);
controls.has("slider").into(sett.controls.slider);
controls.has("slider-step").into(sett.controls.sliderStep);
controls.has("input").into(sett.controls.input);
}
return Ok(sett);
}
Result<FloatSetting> FloatSetting::parse(JsonMaybeObject& obj) {
FloatSetting sett;
parseCommon(sett, obj);
obj.has("min").into(sett.min);
obj.has("max").into(sett.max);
if (auto controls = obj.has("control").obj()) {
controls.has("arrows").into(sett.controls.arrows);
controls.has("big-arrows").into(sett.controls.bigArrows);
controls.has("arrow-step").into(sett.controls.arrowStep);
controls.has("big-arrow-step").into(sett.controls.bigArrowStep);
controls.has("slider").into(sett.controls.slider);
controls.has("slider-step").into(sett.controls.sliderStep);
controls.has("input").into(sett.controls.input);
}
return Ok(sett);
}
Result<StringSetting> StringSetting::parse(JsonMaybeObject& obj) {
StringSetting sett;
parseCommon(sett, obj);
obj.has("match").into(sett.controls->match);
obj.has("filter").into(sett.controls->filter);
obj.has("one-of").into(sett.controls->options);
return Ok(sett);
}
Result<FileSetting> FileSetting::parse(JsonMaybeObject& obj) {
FileSetting sett;
parseCommon(sett, obj);
if (auto controls = obj.has("control").obj()) {
for (auto& item : controls.has("filters").iterate()) {
if (auto iobj = item.obj()) {
Filter filter;
iobj.has("description").into(filter.description);
std::vector<matjson::Value> files;
iobj.has("files").into(files);
for (auto& i : files) {
filter.files.insert(i.as<std::string>());
}
sett.controls.filters.push_back(filter);
}
}
}
return Ok(sett);
}
Result<ColorSetting> ColorSetting::parse(JsonMaybeObject& obj) {
ColorSetting sett;
parseCommon(sett, obj);
return Ok(sett);
}
Result<ColorAlphaSetting> ColorAlphaSetting::parse(JsonMaybeObject& obj) {
ColorAlphaSetting sett;
parseCommon(sett, obj);
return Ok(sett);
}
Result<Setting> Setting::parse(
std::string const& key,
std::string const& mod,
JsonMaybeValue& value
) {
auto sett = Setting();
sett.m_key = key;
sett.m_modID = mod;
if (auto obj = value.obj()) {
std::string type;
obj.needs("type").into(type);
if (type.size()) {
switch (hash(type.c_str())) {
case hash("bool"): {
GEODE_UNWRAP_INTO(sett.m_kind, BoolSetting::parse(obj));
} break;
case hash("int"): {
GEODE_UNWRAP_INTO(sett.m_kind, IntSetting::parse(obj));
} break;
case hash("float"): {
GEODE_UNWRAP_INTO(sett.m_kind, FloatSetting::parse(obj));
} break;
case hash("string"): {
GEODE_UNWRAP_INTO(sett.m_kind, StringSetting::parse(obj));
} break;
case hash("rgb"): case hash("color"): {
GEODE_UNWRAP_INTO(sett.m_kind, ColorSetting::parse(obj));
} break;
case hash("rgba"): {
GEODE_UNWRAP_INTO(sett.m_kind, ColorAlphaSetting::parse(obj));
} break;
case hash("path"): case hash("file"): {
GEODE_UNWRAP_INTO(sett.m_kind, FileSetting::parse(obj));
} break;
case hash("custom"): {
sett.m_kind = CustomSetting {
.json = std::make_shared<ModJson>(obj.json())
};
// skip checking unknown keys
return Ok(sett);
} break;
default: return Err("Unknown setting type \"" + type + "\"");
}
}
// this is handled before the setting is parsed
obj.addKnownKey("platforms");
obj.checkUnknownKeys();
}
// if the type wasn't an object or a string, the JsonChecker that gave the
// JsonMaybeValue will fail eventually so we can continue on
return Ok(sett);
}
Setting::Setting(
std::string const& key,
std::string const& mod,
SettingKind const& kind
) : m_key(key), m_modID(mod), m_kind(kind) {}
std::unique_ptr<SettingValue> Setting::createDefaultValue() const {
return std::visit(makeVisitor {
[&](BoolSetting const& sett) -> std::unique_ptr<SettingValue> {
return std::make_unique<BoolSettingValue>(m_key, m_modID, sett);
},
[&](IntSetting const& sett) -> std::unique_ptr<SettingValue> {
return std::make_unique<IntSettingValue>(m_key, m_modID, sett);
},
[&](FloatSetting const& sett) -> std::unique_ptr<SettingValue> {
return std::make_unique<FloatSettingValue>(m_key, m_modID, sett);
},
[&](StringSetting const& sett) -> std::unique_ptr<SettingValue> {
return std::make_unique<StringSettingValue>(m_key, m_modID, sett);
},
[&](FileSetting const& sett) -> std::unique_ptr<SettingValue> {
return std::make_unique<FileSettingValue>(m_key, m_modID, sett);
},
[&](ColorSetting const& sett) -> std::unique_ptr<SettingValue> {
return std::make_unique<ColorSettingValue>(m_key, m_modID, sett);
},
[&](ColorAlphaSetting const& sett) -> std::unique_ptr<SettingValue> {
return std::make_unique<ColorAlphaSettingValue>(m_key, m_modID, sett);
},
[&](auto const& sett) -> std::unique_ptr<SettingValue> {
return nullptr;
},
}, m_kind);
}
bool Setting::isCustom() const {
return std::holds_alternative<CustomSetting>(m_kind);
}
std::string Setting::getDisplayName() const {
return std::visit(makeVisitor {
[&](CustomSetting const& sett) {
return std::string();
},
[&](auto const& sett) {
return sett.name.value_or(m_key);
},
}, m_kind);
}
std::optional<std::string> Setting::getDescription() const {
return std::visit(makeVisitor {
[&](CustomSetting const& sett) -> std::optional<std::string> {
return std::nullopt;
},
[&](auto const& sett) {
return sett.description;
},
}, m_kind);
}
std::string Setting::getModID() const {
return m_modID;
}
// StringSetting
StringSetting::StringSetting() : name(), description(), defaultValue(), controls(new Data()) {}
StringSetting::StringSetting(StringSetting const& other) : name(other.name), description(other.description),
defaultValue(other.defaultValue), controls(new Data(*other.controls)) {}
StringSetting::StringSetting(StringSetting&& other) noexcept : name(std::move(other.name)), description(std::move(other.description)),
defaultValue(std::move(other.defaultValue)), controls(std::move(other.controls)) {}
StringSetting& StringSetting::operator=(StringSetting const& other) {
name = other.name;
description = other.description;
defaultValue = other.defaultValue;
*controls = *other.controls;
return *this;
}
StringSetting& StringSetting::operator=(StringSetting&& other) noexcept {
name = std::move(other.name);
description = std::move(other.description);
defaultValue = std::move(other.defaultValue);
controls = std::move(other.controls);
return *this;
}
StringSetting::~StringSetting() = default;
// SettingValue
SettingValue::SettingValue(std::string const& key, std::string const& mod)
: m_key(key), m_modID(mod) {}
std::string SettingValue::getKey() const {
return m_key;
}
std::string SettingValue::getModID() const {
return m_modID;
}
void SettingValue::valueChanged() {
if (auto mod = Loader::get()->getLoadedMod(m_modID)) {
if (auto sett = mod->getSettingV3(m_key)) {
sett->markChanged();
}
}
}
// GeodeSettingValue & SettingValueSetter specializations
#define IMPL_NODE_AND_SETTERS(type_) \
template<> \
SettingNode* GeodeSettingValue< \
type_##Setting \
>::createNode(float width) { \
return type_##SettingNode::create(this, width); \
} \
template<> \
typename GeodeSettingValue<type_##Setting>::ValueType \
GeodeSettingValue<type_##Setting>::getValue() const { \
using S = typename SettingTypeForValueType<ValueType>::SettingType; \
if (auto mod = Loader::get()->getInstalledMod(m_modID)) { \
if (auto setting = typeinfo_pointer_cast<S>(mod->getSettingV3(m_key))) {\
return setting->getValue(); \
} \
} \
return m_value; \
} \
template<> \
void GeodeSettingValue< \
type_##Setting \
>::setValue(ValueType const& value) { \
using S = typename SettingTypeForValueType<ValueType>::SettingType; \
if (auto mod = Loader::get()->getInstalledMod(m_modID)) { \
if (auto setting = typeinfo_pointer_cast<S>(mod->getSettingV3(m_key))) {\
return setting->setValue(value); \
} \
} \
} \
template<> \
Result<> GeodeSettingValue< \
type_##Setting \
>::validate(ValueType const& value) const { \
using S = typename SettingTypeForValueType<ValueType>::SettingType; \
if (auto mod = Loader::get()->getInstalledMod(m_modID)) { \
if (auto setting = typeinfo_pointer_cast<S>(mod->getSettingV3(m_key))) {\
return setting->isValid(value); \
} \
} \
return Ok(); \
} \
template<> \
typename type_##Setting::ValueType SettingValueSetter< \
typename type_##Setting::ValueType \
>::get(SettingValue* setting) { \
using S = typename SettingTypeForValueType<typename type_##Setting::ValueType>::SettingType; \
if (auto mod = Loader::get()->getInstalledMod(setting->getModID())) { \
if (auto sett = typeinfo_pointer_cast<S>(mod->getSettingV3(setting->getKey()))) { \
return sett->getValue(); \
} \
} \
return typename type_##Setting::ValueType(); \
} \
template<> \
void SettingValueSetter< \
typename type_##Setting::ValueType \
>::set( \
SettingValue* setting, \
typename type_##Setting::ValueType const& value \
) { \
using S = typename SettingTypeForValueType<typename type_##Setting::ValueType>::SettingType; \
if (auto mod = Loader::get()->getInstalledMod(setting->getModID())) { \
if (auto sett = typeinfo_pointer_cast<S>(mod->getSettingV3(setting->getKey()))) { \
return sett->setValue(value); \
} \
} \
}
#define IMPL_TO_VALID(type_) \
template<> \
typename GeodeSettingValue<type_##Setting>::Valid \
GeodeSettingValue<type_##Setting>::toValid( \
typename type_##Setting::ValueType const& value \
) const
// instantiate values
namespace geode {
template class GeodeSettingValue<BoolSetting>;
template class GeodeSettingValue<IntSetting>;
template class GeodeSettingValue<FloatSetting>;
template class GeodeSettingValue<StringSetting>;
template class GeodeSettingValue<FileSetting>;
template class GeodeSettingValue<ColorSetting>;
template class GeodeSettingValue<ColorAlphaSetting>;
}
IMPL_TO_VALID(Bool) {
return { value, std::nullopt };
}
IMPL_TO_VALID(Int) {
if (m_definition.min && value < m_definition.min) {
return { m_definition.min.value(), fmt::format(
"Value must be more than or equal to {}",
m_definition.min.value()
) };
}
if (m_definition.max && value > m_definition.max) {
return { m_definition.max.value(), fmt::format(
"Value must be less than or equal to {}",
m_definition.max.value()
) };
}
return { value, std::nullopt };
}
IMPL_TO_VALID(Float) {
if (m_definition.min && value < m_definition.min) {
return { m_definition.min.value(), fmt::format(
"Value must be more than or equal to {}",
m_definition.min.value()
) };
}
if (m_definition.max && value > m_definition.max) {
return { m_definition.max.value(), fmt::format(
"Value must be less than or equal to {}",
m_definition.max.value()
) };
}
return { value, std::nullopt };
}
IMPL_TO_VALID(String) {
if (m_definition.controls->match) {
if (!std::regex_match(value, std::regex{m_definition.controls->match.value()})) {
return {
m_definition.defaultValue,
fmt::format(
"Value must match regex {}",
m_definition.controls->match.value()
)
};
}
}
else if (m_definition.controls->options) {
if (std::find(
m_definition.controls->options.value().begin(),
m_definition.controls->options.value().end(),
value
) == m_definition.controls->options.value().end()) {
return {
m_definition.defaultValue,
fmt::format(
"Value must be one of {}",
fmt::join(m_definition.controls->options.value(), ", ")
)
};
}
}
return { value, std::nullopt };
}
IMPL_TO_VALID(File) {
return { value, std::nullopt };
}
IMPL_TO_VALID(Color) {
return { value, std::nullopt };
}
IMPL_TO_VALID(ColorAlpha) {
return { value, std::nullopt };
}
IMPL_NODE_AND_SETTERS(Bool);
IMPL_NODE_AND_SETTERS(Int);
IMPL_NODE_AND_SETTERS(Float);
IMPL_NODE_AND_SETTERS(String);
IMPL_NODE_AND_SETTERS(File);
IMPL_NODE_AND_SETTERS(Color);
IMPL_NODE_AND_SETTERS(ColorAlpha);
// instantiate value setters
namespace geode {
template struct SettingValueSetter<typename BoolSetting::ValueType>;
template struct SettingValueSetter<typename IntSetting::ValueType>;
template struct SettingValueSetter<typename FloatSetting::ValueType>;
template struct SettingValueSetter<typename StringSetting::ValueType>;
template struct SettingValueSetter<typename FileSetting::ValueType>;
template struct SettingValueSetter<typename ColorSetting::ValueType>;
template struct SettingValueSetter<typename ColorAlphaSetting::ValueType>;
}
// SettingChangedEvent
SettingChangedEvent::SettingChangedEvent(Mod* mod, SettingValue* value)
: mod(mod), value(value) {}
// SettingChangedFilter
ListenerResult SettingChangedFilter::handle(
utils::MiniFunction<Callback> fn, SettingChangedEvent* event
) {
if (m_modID == event->mod->getID() &&
(!m_targetKey || m_targetKey.value() == event->value->getKey())
) {
fn(event->value);
}
return ListenerResult::Propagate;
}
SettingChangedFilter::SettingChangedFilter(
std::string const& modID,
std::optional<std::string> const& settingKey
) : m_modID(modID), m_targetKey(settingKey) {}

View file

@ -1,25 +0,0 @@
#include <Geode/loader/SettingNode.hpp>
#include <Geode/utils/cocos.hpp>
using namespace geode::prelude;
void SettingNode::dispatchChanged() {
if (m_delegate) {
m_delegate->settingValueChanged(this);
}
}
void SettingNode::dispatchCommitted() {
if (m_delegate) {
m_delegate->settingValueCommitted(this);
}
}
bool SettingNode::init(SettingValue* value) {
m_value = value;
return true;
}
void SettingNode::setDelegate(SettingNodeDelegate* delegate) {
m_delegate = delegate;
}

View file

@ -0,0 +1,16 @@
#pragma once
#include "SettingNodeV3.hpp"
using namespace geode::prelude;
using TitleSettingNode = TitleSettingNodeV3;
using BoolSettingNode = BoolSettingNodeV3;
template <class S>
using NumberSettingNode = NumberSettingNodeV3<S>;
using IntSettingNode = IntSettingNodeV3;
using FloatSettingNode = FloatSettingNodeV3;
using StringSettingNode = StringSettingNodeV3;
using FileSettingNode = FileSettingNodeV3;
using Color3BSettingNode = Color3BSettingNodeV3;
using Color4BSettingNode = Color4BSettingNodeV3;
using UnresolvedCustomSettingNode = UnresolvedCustomSettingNodeV3;

View file

@ -1,5 +1,4 @@
#include "SettingNodeV3.hpp"
#include <Geode/loader/SettingNode.hpp>
#include <Geode/utils/ColorProvider.hpp>
#include <Geode/utils/ranges.hpp>
#include <Geode/loader/Dirs.hpp>
@ -137,7 +136,7 @@ void SettingNodeV3::updateState(CCNode* invoker) {
m_impl->bg->setOpacity(75);
}
m_impl->nameMenu->setContentWidth(this->getContentWidth() - m_impl->buttonMenu->getContentWidth() - 20);
m_impl->nameMenu->setContentWidth(this->getContentWidth() - m_impl->buttonMenu->getContentWidth() - 25);
m_impl->nameMenu->updateLayout();
}
@ -463,6 +462,7 @@ void FileSettingNodeV3::updateState(CCNode* invoker) {
// which is clever and good UX but also a hack so I also need to hack to support that
const auto isTextualDefaultValue = [this, setting = this->getSetting()]() {
if (this->hasNonDefaultValue()) return false;
if (setting->getDefaultValue().string().size() > 20) return false;
std::error_code ec;
return setting->isFolder() ?
!std::filesystem::is_directory(setting->getDefaultValue(), ec) :
@ -683,51 +683,3 @@ UnresolvedCustomSettingNodeV3* UnresolvedCustomSettingNodeV3::create(std::string
CC_SAFE_DELETE(ret);
return nullptr;
}
// LegacyCustomSettingToV3Node
bool LegacyCustomSettingToV3Node::init(std::shared_ptr<LegacyCustomSettingV3> original, float width) {
if (!SettingNodeV3::init(original, width))
return false;
this->getNameMenu()->setVisible(false);
this->getButtonMenu()->setVisible(false);
m_original = original->getValue()->createNode(width);
m_original->setDelegate(this);
this->setContentSize({ width, m_original->getContentHeight() });
this->addChildAtPosition(m_original, Anchor::BottomLeft, ccp(0, 0), ccp(0, 0));
return true;
}
void LegacyCustomSettingToV3Node::settingValueChanged(SettingNode*) {
SettingNodeValueChangeEventV3(this, false).post();
}
void LegacyCustomSettingToV3Node::settingValueCommitted(SettingNode*) {
SettingNodeValueChangeEventV3(this, true).post();
}
void LegacyCustomSettingToV3Node::onCommit() {
m_original->commit();
}
bool LegacyCustomSettingToV3Node::hasUncommittedChanges() const {
return m_original->hasUncommittedChanges();
}
bool LegacyCustomSettingToV3Node::hasNonDefaultValue() const {
return m_original->hasNonDefaultValue();
}
void LegacyCustomSettingToV3Node::onResetToDefault() {
m_original->resetToDefault();
}
LegacyCustomSettingToV3Node* LegacyCustomSettingToV3Node::create(std::shared_ptr<LegacyCustomSettingV3> original, float width) {
auto ret = new LegacyCustomSettingToV3Node();
if (ret && ret->init(original, width)) {
ret->autorelease();
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}

View file

@ -1,7 +1,6 @@
#pragma once
#include <Geode/loader/SettingV3.hpp>
#include <Geode/loader/SettingNode.hpp>
#include <Geode/loader/Setting.hpp>
#include <Geode/binding/CCMenuItemToggler.hpp>
#include <Geode/binding/ColorChannelSprite.hpp>
#include <Geode/binding/Slider.hpp>
@ -314,25 +313,3 @@ public:
bool hasNonDefaultValue() const override;
void onResetToDefault() override;
};
// If these classes do get exposed in headers,
// LegacyCustomSettingToV3Node SHOULD NOT BE EXPOSED!!!!!! DO NOT DO THAT!!!!
class LegacyCustomSettingToV3Node : public SettingNodeV3, public SettingNodeDelegate {
protected:
SettingNode* m_original;
bool init(std::shared_ptr<LegacyCustomSettingV3> original, float width);
void onCommit() override;
void settingValueChanged(SettingNode*) override;
void settingValueCommitted(SettingNode*) override;
public:
static LegacyCustomSettingToV3Node* create(std::shared_ptr<LegacyCustomSettingV3> original, float width);
bool hasUncommittedChanges() const override;
bool hasNonDefaultValue() const override;
void onResetToDefault() override;
};

View file

@ -1,5 +1,5 @@
#include <Geode/loader/SettingV3.hpp>
#include <Geode/loader/SettingEvent.hpp>
#include <Geode/loader/Mod.hpp>
#include <Geode/loader/Setting.hpp>
#include <Geode/loader/ModSettingsManager.hpp>
#include <Geode/utils/ranges.hpp>
#include <Geode/utils/string.hpp>
@ -7,6 +7,7 @@
#include <Geode/utils/JsonValidation.hpp>
#include <regex>
#include "SettingNodeV3.hpp"
#include <matjson/std.hpp>
using namespace geode::prelude;
@ -46,7 +47,7 @@ namespace enable_if_parsing {
if (!mod->hasSetting(settingID)) {
return Err("Mod '{}' does not have setting '{}'", mod->getName(), settingID);
}
if (!typeinfo_pointer_cast<BoolSettingV3>(mod->getSettingV3(settingID))) {
if (!typeinfo_pointer_cast<BoolSettingV3>(mod->getSetting(settingID))) {
return Err("Setting '{}' in mod '{}' is not a boolean setting", settingID, mod->getName());
}
}
@ -60,7 +61,7 @@ namespace enable_if_parsing {
// This is an if-check just in case, even though check() should already
// make sure that getSettingV3 is guaranteed to return true
auto name = settingID;
if (auto sett = mod->getSettingV3(settingID)) {
if (auto sett = mod->getSetting(settingID)) {
name = sett->getDisplayName();
}
if (modID == defaultModID) {
@ -153,18 +154,21 @@ namespace enable_if_parsing {
return Ok();
}
Result<> eval(std::string const& defaultModID) const override {
Result<> err = Ok();
std::optional<std::string> err;
for (auto& comp : components) {
auto res = comp->eval(defaultModID);
if (res) {
return Ok();
}
// Only show first condition that isn't met
if (err.isOk()) {
err = Err(res.unwrapErr());
if (!err.has_value()) {
err = res.unwrapErr();
}
}
return err;
if (err.has_value()) {
return Err(*err);
}
return Ok();
}
};
@ -236,7 +240,10 @@ namespace enable_if_parsing {
auto original = m_index;
auto ret = this->nextWord();
m_index = original;
return ret ? *ret : std::nullopt;
if (!ret) {
return std::nullopt;
}
return ret.unwrap();
}
Result<std::unique_ptr<Component>> nextComponent() {
GEODE_UNWRAP_INTO(auto maybeWord, this->nextWord());
@ -423,7 +430,7 @@ public:
std::optional<std::string> settingKey;
};
ListenerResult SettingChangedFilterV3::handle(utils::MiniFunction<Callback> fn, SettingChangedEventV3* event) {
ListenerResult SettingChangedFilterV3::handle(std::function<Callback> fn, SettingChangedEventV3* event) {
if (
event->getSetting()->getModID() == m_impl->modID &&
!m_impl->settingKey || event->getSetting()->getKey() == m_impl->settingKey
@ -447,7 +454,7 @@ SettingChangedFilterV3::SettingChangedFilterV3(Mod* mod, std::optional<std::stri
SettingChangedFilterV3::SettingChangedFilterV3(SettingChangedFilterV3 const&) = default;
EventListener<SettingChangedFilterV3>* geode::listenForAllSettingChanges(
EventListener<SettingChangedFilterV3>* geode::listenForAllSettingChangesV3(
std::function<void(std::shared_ptr<SettingV3>)> const& callback,
Mod* mod
) {
@ -573,20 +580,7 @@ void SettingV3::markChanged() {
manager->markRestartRequired();
}
SettingChangedEventV3(shared_from_this()).post();
if (manager) {
// Use ModSettingsManager rather than convertToLegacyValue since it
// caches the result and we want to have that for performance
SettingChangedEvent(this->getMod(), manager->getLegacy(this->getKey()).get()).post();
}
}
std::optional<Setting> SettingV3::convertToLegacy() const {
return std::nullopt;
}
std::optional<std::shared_ptr<SettingValue>> SettingV3::convertToLegacyValue() const {
return std::nullopt;
}
class TitleSettingV3::Impl final {
public:
};
@ -618,63 +612,6 @@ bool TitleSettingV3::isDefaultValue() const {
}
void TitleSettingV3::reset() {}
class LegacyCustomSettingV3::Impl final {
public:
matjson::Value json;
std::shared_ptr<SettingValue> legacyValue = nullptr;
};
LegacyCustomSettingV3::LegacyCustomSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
Result<std::shared_ptr<LegacyCustomSettingV3>> LegacyCustomSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto ret = std::make_shared<LegacyCustomSettingV3>(PrivateMarker());
ret->init(key, modID);
ret->m_impl->json = json;
return Ok(ret);
}
std::shared_ptr<SettingValue> LegacyCustomSettingV3::getValue() const {
return m_impl->legacyValue;
}
void LegacyCustomSettingV3::setValue(std::shared_ptr<SettingValue> value) {
m_impl->legacyValue = value;
}
bool LegacyCustomSettingV3::load(matjson::Value const& json) {
if (m_impl->legacyValue) {
return m_impl->legacyValue->load(json);
}
return true;
}
bool LegacyCustomSettingV3::save(matjson::Value& json) const {
if (m_impl->legacyValue) {
return m_impl->legacyValue->save(json);
}
return true;
}
SettingNodeV3* LegacyCustomSettingV3::createNode(float width) {
if (m_impl->legacyValue) {
return LegacyCustomSettingToV3Node::create(
std::static_pointer_cast<LegacyCustomSettingV3>(shared_from_this()), width
);
}
return UnresolvedCustomSettingNodeV3::create(this->getKey(), this->getMod(), width);
}
bool LegacyCustomSettingV3::isDefaultValue() const {
return true;
}
void LegacyCustomSettingV3::reset() {}
std::optional<Setting> LegacyCustomSettingV3::convertToLegacy() const {
return Setting(this->getKey(), this->getModID(), SettingKind(CustomSetting {
.json = std::make_shared<ModJson>(m_impl->json)
}));
}
std::optional<std::shared_ptr<SettingValue>> LegacyCustomSettingV3::convertToLegacyValue() const {
return m_impl->legacyValue ? std::optional(m_impl->legacyValue) : std::nullopt;
}
class BoolSettingV3::Impl final {
public:
};
@ -699,17 +636,6 @@ SettingNodeV3* BoolSettingV3::createNode(float width) {
);
}
std::optional<Setting> BoolSettingV3::convertToLegacy() const {
return Setting(this->getKey(), this->getModID(), SettingKind(BoolSetting {
.name = this->getName(),
.description = this->getDescription(),
.defaultValue = this->getDefaultValue(),
}));
}
std::optional<std::shared_ptr<SettingValue>> BoolSettingV3::convertToLegacyValue() const {
return this->convertToLegacy()->createDefaultValue();
}
class IntSettingV3::Impl final {
public:
std::optional<int64_t> minValue;
@ -819,28 +745,6 @@ SettingNodeV3* IntSettingV3::createNode(float width) {
);
}
std::optional<Setting> IntSettingV3::convertToLegacy() const {
return Setting(this->getKey(), this->getModID(), SettingKind(IntSetting {
.name = this->getName(),
.description = this->getDescription(),
.defaultValue = this->getDefaultValue(),
.min = this->getMinValue(),
.max = this->getMaxValue(),
.controls = {
.arrows = this->isArrowsEnabled(),
.bigArrows = this->isBigArrowsEnabled(),
.arrowStep = this->getArrowStepSize(),
.bigArrowStep = this->getBigArrowStepSize(),
.slider = this->isSliderEnabled(),
.sliderStep = this->getSliderSnap(),
.input = this->isInputEnabled(),
},
}));
}
std::optional<std::shared_ptr<SettingValue>> IntSettingV3::convertToLegacyValue() const {
return this->convertToLegacy()->createDefaultValue();
}
class FloatSettingV3::Impl final {
public:
std::optional<double> minValue;
@ -948,27 +852,6 @@ SettingNodeV3* FloatSettingV3::createNode(float width) {
);
}
std::optional<Setting> FloatSettingV3::convertToLegacy() const {
return Setting(this->getKey(), this->getModID(), SettingKind(FloatSetting {
.name = this->getName(),
.description = this->getDescription(),
.defaultValue = this->getDefaultValue(),
.min = this->getMinValue(),
.max = this->getMaxValue(),
.controls = {
.arrows = this->isArrowsEnabled(),
.bigArrows = this->isBigArrowsEnabled(),
.arrowStep = static_cast<size_t>(this->getArrowStepSize()),
.bigArrowStep = static_cast<size_t>(this->getBigArrowStepSize()),
.slider = this->isSliderEnabled(),
.sliderStep = this->getSliderSnap(),
.input = this->isInputEnabled(),
},
}));
}
std::optional<std::shared_ptr<SettingValue>> FloatSettingV3::convertToLegacyValue() const {
return this->convertToLegacy()->createDefaultValue();
}
class StringSettingV3::Impl final {
public:
@ -1026,20 +909,6 @@ SettingNodeV3* StringSettingV3::createNode(float width) {
);
}
std::optional<Setting> StringSettingV3::convertToLegacy() const {
auto setting = StringSetting();
setting.name = this->getName();
setting.description = this->getDescription();
setting.defaultValue = this->getDefaultValue();
setting.controls->filter = this->getAllowedCharacters();
setting.controls->match = this->getRegexValidator();
setting.controls->options = this->getEnumOptions();
return Setting(this->getKey(), this->getModID(), SettingKind(setting));
}
std::optional<std::shared_ptr<SettingValue>> StringSettingV3::convertToLegacyValue() const {
return this->convertToLegacy()->createDefaultValue();
}
class FileSettingV3::Impl final {
public:
bool folder = false;
@ -1153,18 +1022,6 @@ SettingNodeV3* FileSettingV3::createNode(float width) {
);
}
std::optional<Setting> FileSettingV3::convertToLegacy() const {
auto setting = FileSetting();
setting.name = this->getName();
setting.description = this->getDescription();
setting.defaultValue = this->getDefaultValue();
setting.controls.filters = this->getFilters().value_or(std::vector<utils::file::FilePickOptions::Filter>());
return Setting(this->getKey(), this->getModID(), SettingKind(setting));
}
std::optional<std::shared_ptr<SettingValue>> FileSettingV3::convertToLegacyValue() const {
return this->convertToLegacy()->createDefaultValue();
}
class Color3BSettingV3::Impl final {
public:
};
@ -1189,17 +1046,6 @@ SettingNodeV3* Color3BSettingV3::createNode(float width) {
);
}
std::optional<Setting> Color3BSettingV3::convertToLegacy() const {
auto setting = ColorSetting();
setting.name = this->getName();
setting.description = this->getDescription();
setting.defaultValue = this->getDefaultValue();
return Setting(this->getKey(), this->getModID(), SettingKind(setting));
}
std::optional<std::shared_ptr<SettingValue>> Color3BSettingV3::convertToLegacyValue() const {
return this->convertToLegacy()->createDefaultValue();
}
class Color4BSettingV3::Impl final {
public:
};
@ -1223,14 +1069,3 @@ SettingNodeV3* Color4BSettingV3::createNode(float width) {
std::static_pointer_cast<Color4BSettingV3>(shared_from_this()), width
);
}
std::optional<Setting> Color4BSettingV3::convertToLegacy() const {
auto setting = ColorAlphaSetting();
setting.name = this->getName();
setting.description = this->getDescription();
setting.defaultValue = this->getDefaultValue();
return Setting(this->getKey(), this->getModID(), SettingKind(setting));
}
std::optional<std::shared_ptr<SettingValue>> Color4BSettingV3::convertToLegacyValue() const {
return this->convertToLegacy()->createDefaultValue();
}

View file

@ -1,7 +1,9 @@
#include <Geode/loader/Tulip.hpp>
tulip::hook::Result<void*> geode::hook::createWrapper(
using namespace geode::prelude;
Result<void*> geode::hook::createWrapper(
void* address,
tulip::hook::WrapperMetadata const& metadata
) noexcept {

View file

@ -16,7 +16,7 @@ updater::ResourceDownloadEvent::ResourceDownloadEvent(
) : status(std::move(status)) {}
ListenerResult updater::ResourceDownloadFilter::handle(
const utils::MiniFunction<Callback>& fn,
const std::function<Callback>& fn,
ResourceDownloadEvent* event
) {
fn(event);
@ -30,7 +30,7 @@ updater::LoaderUpdateEvent::LoaderUpdateEvent(
) : status(std::move(status)) {}
ListenerResult updater::LoaderUpdateFilter::handle(
const utils::MiniFunction<Callback>& fn,
const std::function<Callback>& fn,
LoaderUpdateEvent* event
) {
fn(event);
@ -45,8 +45,8 @@ std::optional<matjson::Value> s_latestGithubRelease;
bool s_isNewUpdateDownloaded = false;
void updater::fetchLatestGithubRelease(
const utils::MiniFunction<void(matjson::Value const&)>& then,
utils::MiniFunction<void(std::string const&)> expect, bool force
const std::function<void(matjson::Value const&)>& then,
std::function<void(std::string const&)> expect, bool force
) {
if (s_latestGithubRelease) {
return then(s_latestGithubRelease.value());
@ -83,7 +83,7 @@ void updater::fetchLatestGithubRelease(
}
else {
Mod::get()->setSavedValue("last-modified-auto-update-check", response->header("Last-Modified").value_or(""));
s_latestGithubRelease = *json;
s_latestGithubRelease = json.unwrap();
then(*s_latestGithubRelease);
}
}
@ -102,13 +102,10 @@ void updater::downloadLatestLoaderResources() {
log::debug("Downloading latest resources", Loader::get()->getVersion().toVString());
fetchLatestGithubRelease(
[](matjson::Value const& raw) {
auto json = raw;
JsonChecker checker(json);
auto root = checker.root("[]").obj();
auto root = checkJson(raw, "[]");
// find release asset
for (auto asset : root.needs("assets").iterate()) {
auto obj = asset.obj();
for (auto& obj : root.needs("assets").items()) {
if (obj.needs("name").get<std::string>() == "resources.zip") {
updater::tryDownloadLoaderResources(
obj.needs("browser_download_url").get<std::string>(),
@ -144,7 +141,7 @@ void updater::tryDownloadLoaderResources(std::string const& url, bool tryLatestO
// unzip resources zip
auto unzip = file::Unzip::create(response->data());
if (unzip) {
auto ok = unzip->extractAllTo(resourcesDir);
auto ok = unzip.unwrap().extractAllTo(resourcesDir);
if (ok) {
updater::updateSpecialFiles();
ResourceDownloadEvent(UpdateFinished()).post();
@ -206,13 +203,10 @@ void updater::downloadLoaderResources(bool useLatestRelease) {
RUNNING_REQUESTS.erase("@downloadLoaderResources");
if (response->ok()) {
if (auto ok = response->json()) {
auto json = ok.unwrap();
JsonChecker checker(json);
auto root = checker.root("[]").obj();
auto root = checkJson(ok.unwrap(), "[]");
// find release asset
for (auto asset : root.needs("assets").iterate()) {
auto obj = asset.obj();
for (auto& obj : root.needs("assets").items()) {
if (obj.needs("name").get<std::string>() == "resources.zip") {
updater::tryDownloadLoaderResources(
obj.needs("browser_download_url").get<std::string>(),
@ -314,7 +308,7 @@ void updater::downloadLoaderUpdate(std::string const& url) {
// unzip resources zip
auto unzip = file::Unzip::create(response->data());
if (unzip) {
auto ok = unzip->extractAllTo(targetDir);
auto ok = unzip.unwrap().extractAllTo(targetDir);
if (ok) {
s_isNewUpdateDownloaded = true;
LoaderUpdateEvent(UpdateFinished()).post();
@ -362,9 +356,7 @@ void updater::checkForLoaderUpdates() {
// Check for updates in the background
fetchLatestGithubRelease(
[](matjson::Value const& raw) {
auto json = raw;
JsonChecker checker(json);
auto root = checker.root("[]").obj();
auto root = checkJson(raw, "[]");
VersionInfo ver { 0, 0, 0 };
root.needs("tag_name").into(ver);
@ -388,8 +380,7 @@ void updater::checkForLoaderUpdates() {
}
// find release asset
for (auto asset : root.needs("assets").iterate()) {
auto obj = asset.obj();
for (auto& obj : root.needs("assets").items()) {
if (string::endsWith(
obj.needs("name").get<std::string>(),
fmt::format("{}.zip", PlatformID::toShortString(GEODE_PLATFORM_TARGET, true))

View file

@ -2,7 +2,6 @@
#include <string>
#include <matjson.hpp>
#include <Geode/utils/MiniFunction.hpp>
#include <Geode/loader/Event.hpp>
namespace geode::updater {
@ -20,7 +19,7 @@ namespace geode::updater {
public:
using Callback = void(ResourceDownloadEvent*);
static ListenerResult handle(const utils::MiniFunction<Callback>& fn, ResourceDownloadEvent* event);
static ListenerResult handle(const std::function<Callback>& fn, ResourceDownloadEvent* event);
ResourceDownloadFilter();
};
@ -33,7 +32,7 @@ namespace geode::updater {
public:
using Callback = void(LoaderUpdateEvent*);
static ListenerResult handle(const utils::MiniFunction<Callback>& fn, LoaderUpdateEvent* event);
static ListenerResult handle(const std::function<Callback>& fn, LoaderUpdateEvent* event);
LoaderUpdateFilter();
};
@ -43,8 +42,8 @@ namespace geode::updater {
void downloadLatestLoaderResources();
void downloadLoaderUpdate(std::string const& url);
void fetchLatestGithubRelease(
const utils::MiniFunction<void(matjson::Value const&)>& then,
utils::MiniFunction<void(std::string const&)> expect,
const std::function<void(matjson::Value const&)>& then,
std::function<void(std::string const&)> expect,
bool force = false
);

View file

@ -21,6 +21,7 @@ std::string Loader::Impl::getGameVersion() {
case 37: m_gdVersion = "2.200"; break;
case 38: m_gdVersion = "2.205"; break;
case 39: m_gdVersion = "2.206"; break;
case 40: m_gdVersion = "2.2074"; break;
default: m_gdVersion = std::to_string(version_code);
}
}

View file

@ -261,12 +261,12 @@ int writeAndGetPid() {
auto res = file::readString(pidFile);
if (!res) {
log::warn("Failed to read last-pid file: {}", res.error());
log::warn("Failed to read last-pid file: {}", res.unwrapErr());
}
else {
auto res = numFromString<int>(res.unwrap());
if (!res) {
log::warn("Failed to parse last-pid file: {}", res.error());
log::warn("Failed to parse last-pid file: {}", res.unwrapErr());
}
else lastPid = res.unwrap();
}
@ -281,7 +281,7 @@ int writeAndGetPid() {
auto res = file::writeString(pidFile, std::to_string(getpid()));
if (!res) {
log::warn("Failed to write last-pid file: {}", res.error());
log::warn("Failed to write last-pid file: {}", res.unwrapErr());
}
return lastPid;

View file

@ -101,7 +101,7 @@ namespace geode::stl {
}
// TODO: add a copyFrom(string const&) to take advantage
// of gnustl refcounted strings
void StringImpl::setStorage(const std::string_view str) {
void StringImpl::setStorage(std::string_view str) {
this->free();
if (str.size() == 0) {

View file

@ -6,14 +6,13 @@ using namespace geode::prelude;
#include <Geode/utils/web.hpp>
#include <filesystem>
#include <Geode/utils/general.hpp>
#include <Geode/utils/MiniFunction.hpp>
#include <Geode/utils/permission.hpp>
#include <Geode/utils/Task.hpp>
#include <Geode/loader/Loader.hpp>
#include <Geode/binding/AppDelegate.hpp>
#include <Geode/loader/Log.hpp>
#include <Geode/binding/MenuLayer.hpp>
#include <Geode/utils/Result.hpp>
#include <Geode/Result.hpp>
#include <Geode/DefaultInclude.hpp>
#include <optional>
#include <mutex>
@ -139,9 +138,9 @@ bool utils::file::openFolder(std::filesystem::path const& path) {
}
std::mutex s_callbackMutex;
static utils::MiniFunction<void(Result<std::filesystem::path>)> s_fileCallback {};
static utils::MiniFunction<void(Result<std::vector<std::filesystem::path>>)> s_filesCallback {};
static utils::MiniFunction<bool()> s_taskCancelled {};
static std::function<void(Result<std::filesystem::path>)> s_fileCallback {};
static std::function<void(Result<std::vector<std::filesystem::path>>)> s_filesCallback {};
static std::function<bool()> s_taskCancelled {};
extern "C"
JNIEXPORT void JNICALL Java_com_geode_launcher_utils_GeodeUtils_selectFileCallback(
@ -356,7 +355,7 @@ bool geode::utils::permission::getPermissionStatus(Permission permission) {
return false;
}
static MiniFunction<void(bool)> s_permissionCallback;
static std::function<void(bool)> s_permissionCallback;
extern "C"
JNIEXPORT void JNICALL Java_com_geode_launcher_utils_GeodeUtils_permissionCallback(
@ -371,7 +370,7 @@ JNIEXPORT void JNICALL Java_com_geode_launcher_utils_GeodeUtils_permissionCallba
}
}
void geode::utils::permission::requestPermission(Permission permission, utils::MiniFunction<void(bool)> callback) {
void geode::utils::permission::requestPermission(Permission permission, std::function<void(bool)> callback) {
s_permissionCallback = callback;
JniMethodInfo info;
if (JniHelper::getStaticMethodInfo(info, "com/geode/launcher/utils/GeodeUtils", "requestPermission", "(Ljava/lang/String;)V")) {

View file

@ -39,6 +39,6 @@ bool geode::utils::permission::getPermissionStatus(Permission permission) {
return true; // unimplemented
}
void geode::utils::permission::requestPermission(Permission permission, utils::MiniFunction<void(bool)> callback) {
void geode::utils::permission::requestPermission(Permission permission, std::function<void(bool)> callback) {
callback(true); // unimplemented
}

Some files were not shown because too many files have changed in this diff Show more