diff --git a/CHANGELOG.md b/CHANGELOG.md index 9243fc1a..e559a5f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Geode Changelog +## v2.0.0-beta.26 + * Bring in several UI helpers from the `new-index-but-better` branch: `ListBorders`, `addSideArt`, `AxisLayout` improvements, ... (26729c3, 7ff257c) + * Make it possible to compile mods in Debug mode (517ad45) + * Add `GJDifficultyName` and `GJFeatureState` (#706) + * Add `geode::cocos::isSpriteName` and `geode::cocos::getChildBySpriteName` (#725) + * Add some Android keycodes (4fa8098) + * Update FMOD on Mac (c4a682b) + * Bump JSON version (5cc23e7) + * Fixes to `InputNode` touches (29b4732) + * Fix `file::readFromJson` (77e0f2e) + * Fix issues with TulipHook (f2ce7d0) + ## v2.0.0-beta.25 * Fix updater sometimes skipping releases (18dd0b7) * Fix resources getting downloaded every time (5f571d9) diff --git a/VERSION b/VERSION index d093a126..e555e41e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.0-beta.25 \ No newline at end of file +2.0.0-beta.26 \ No newline at end of file diff --git a/loader/include/Geode/cocos/base_nodes/CCNode.h b/loader/include/Geode/cocos/base_nodes/CCNode.h index b5c9a176..3b01f37f 100644 --- a/loader/include/Geode/cocos/base_nodes/CCNode.h +++ b/loader/include/Geode/cocos/base_nodes/CCNode.h @@ -913,6 +913,22 @@ public: */ GEODE_DLL CCNode* getChildByIDRecursive(std::string const& id); + /** + * Get a child based on a query. Searches the child tree for a matching + * child. The query currently only supports the following features: + * - `node-id`: Match a node with a specific ID + * - `node-id-1 node-id-2`: Match a descendant (possibly not immediate) + * child of a node with a specific ID + * - `node-id-1 > node-id-2`: Match the immediate child of a node with a + * specific ID + * For example, the query "my-layer button-menu > mod.id/epic-button" is + * equivalent to `getChildByIDRecursive("my-layer") + * ->getChildByIDRecursive("button-menu") + * ->getChildByID("mod.id/epic-button")` + * @returns The first matching node, or nullptr if none was found + */ + GEODE_DLL CCNode* querySelector(std::string const& query); + /** * Removes a child from the container by its ID. * @param id The ID of the node diff --git a/loader/include/Geode/cocos/base_nodes/Layout.hpp b/loader/include/Geode/cocos/base_nodes/Layout.hpp index 8a31c25e..592d3fd3 100644 --- a/loader/include/Geode/cocos/base_nodes/Layout.hpp +++ b/loader/include/Geode/cocos/base_nodes/Layout.hpp @@ -5,7 +5,7 @@ #include "../cocoa/CCArray.h" #include <Geode/platform/platform.hpp> #include <optional> -#include <unordered_map> +#include <memory> NS_CC_BEGIN diff --git a/loader/include/Geode/cocos/particle_nodes/CCParticleSystem.h b/loader/include/Geode/cocos/particle_nodes/CCParticleSystem.h index cdaac739..5b2676e6 100644 --- a/loader/include/Geode/cocos/particle_nodes/CCParticleSystem.h +++ b/loader/include/Geode/cocos/particle_nodes/CCParticleSystem.h @@ -313,7 +313,31 @@ public: virtual bool isBlendAdditive(); virtual void setBlendAdditive(bool value); ////////////////////////////////////////////////////////////////////////// - + + RT_ADD( + float m_fFadeInTime; + float m_fFadeInTimeVar; + float m_fFadeOutTime; + float m_fFadeOutTimeVar; + float m_fFrictionPos; + float m_fFrictionPosVar; + float m_fFrictionSize; + float m_fFrictionSizeVar; + float m_fFrictionRot; + float m_fFrictionRotVar; + float m_fRespawn; + float m_fRespawnVar; + bool m_bStartSpinEqualToEnd; + bool m_bStartSizeEqualToEnd; + bool m_bStartRadiusEqualToEnd; + bool m_bDynamicRotationIsDir; + bool m_bOrderSensitive; + bool m_bStartRGBVarSync; + bool m_bEndRGBVarSync; + bool m_bWasRemoved; + bool m_bUsingSchedule; + ) + /** start size in pixels of each particle */ CC_PROPERTY(float, m_fStartSize, StartSize) /** size variance in pixels of each particle */ diff --git a/loader/include/Geode/cocos/platform/win32/CCApplication.h b/loader/include/Geode/cocos/platform/win32/CCApplication.h index 60455572..36b1ba50 100644 --- a/loader/include/Geode/cocos/platform/win32/CCApplication.h +++ b/loader/include/Geode/cocos/platform/win32/CCApplication.h @@ -5,7 +5,7 @@ #include "CCStdC.h" #include "../CCCommon.h" #include "../CCApplicationProtocol.h" -#include "CCControllerHandler.h" +#include "CXBOXController.h" #include <string> NS_CC_BEGIN @@ -51,7 +51,7 @@ public: RT_ADD( void setupVerticalSync(); void updateVerticalSync(); - void updateControllerKeys(); + void updateControllerKeys(CXBOXController* controller, int userIndex); int getTimeElapsed(); void resetForceTimer(); @@ -96,8 +96,8 @@ public: LARGE_INTEGER m_nVsyncInterval; gd::string m_resourceRootPath; gd::string m_startupScriptFilename; - CCControllerHandler* m_pControllerHandler; - void* m_unk; //might be swapped with m_pControllerHandler + CXBOXController* m_pControllerHandler; + CXBOXController* m_pController2Handler; //might be swapped with m_pControllerHandler bool m_bUpdateController; CC_SYNTHESIZE_NV(bool, m_bShutdownCalled, ShutdownCalled); INPUT m_iInput; @@ -114,6 +114,7 @@ public: CC_SYNTHESIZE_NV(bool, m_bFullscreen, Fullscreen); CC_SYNTHESIZE_NV(bool, m_bBorderless, Borderless); +protected: static CCApplication * sm_pSharedApplication; }; diff --git a/loader/include/Geode/cocos/platform/win32/CCEGLView.h b/loader/include/Geode/cocos/platform/win32/CCEGLView.h index f3e73378..32a6795c 100644 --- a/loader/include/Geode/cocos/platform/win32/CCEGLView.h +++ b/loader/include/Geode/cocos/platform/win32/CCEGLView.h @@ -208,7 +208,9 @@ public: float m_fMouseX; float m_fMouseY; bool m_bIsFullscreen; + bool m_bIsBorderless; bool m_bShouldHideCursor; + bool m_bCursorLocked; bool m_bShouldCallGLFinish; ) diff --git a/loader/include/Geode/cocos/platform/win32/CCControllerHandler.h b/loader/include/Geode/cocos/platform/win32/CXBOXController.h similarity index 79% rename from loader/include/Geode/cocos/platform/win32/CCControllerHandler.h rename to loader/include/Geode/cocos/platform/win32/CXBOXController.h index 83e8108d..56cee344 100644 --- a/loader/include/Geode/cocos/platform/win32/CCControllerHandler.h +++ b/loader/include/Geode/cocos/platform/win32/CXBOXController.h @@ -1,14 +1,12 @@ -#ifndef __CC_CONTROLLER_HANDLER_WIN32_H__ -#define __CC_CONTROLLER_HANDLER_WIN32_H__ +#ifndef __CXBOXCONTROLLER_WIN32_H__ +#define __CXBOXCONTROLLER_WIN32_H__ #include "../../include/ccMacros.h" #include "CCStdC.h" #include "CCControllerState.h" #include <Xinput.h> -NS_CC_BEGIN - -class CC_DLL CCControllerHandler +class CC_DLL CXBOXController { GEODE_FRIEND_MODIFY public: @@ -35,6 +33,4 @@ public: bool m_buttonY; }; -NS_CC_END - -#endif \ No newline at end of file +#endif diff --git a/loader/include/Geode/ui/General.hpp b/loader/include/Geode/ui/General.hpp index e8b6acbc..1578521b 100644 --- a/loader/include/Geode/ui/General.hpp +++ b/loader/include/Geode/ui/General.hpp @@ -27,6 +27,13 @@ namespace geode { return static_cast<bool>(static_cast<int>(a) & static_cast<int>(b)); } + enum class SideArtStyle { + Layer, + LayerGray, + PopupBlue, + PopupGold, + }; + /** * Add side art (corner pieces) for a layer * @param to Layer to add corner pieces to @@ -34,7 +41,25 @@ namespace geode { * @param useAnchorLayout If true, `to` is given an `AnchorLayout` and the * corners' positions are dynamically updated */ - GEODE_DLL void addSideArt(cocos2d::CCNode* to, SideArt sides = SideArt::All, bool useAnchorLayout = false); + GEODE_DLL void addSideArt( + cocos2d::CCNode* to, + SideArt sides = SideArt::All, + bool useAnchorLayout = false + ); + /** + * Add side art (corner pieces) for a layer + * @param to Layer to add corner pieces to + * @param sides Which corners to populate; by default, populates all + * @param style Which side art sprites to use + * @param useAnchorLayout If true, `to` is given an `AnchorLayout` and the + * corners' positions are dynamically updated + */ + GEODE_DLL void addSideArt( + cocos2d::CCNode* to, + SideArt sides, + SideArtStyle style, + bool useAnchorLayout = false + ); /** * Add the rounded comment borders to a node diff --git a/loader/include/Geode/utils/Result.hpp b/loader/include/Geode/utils/Result.hpp index 4d58a15c..dff90491 100644 --- a/loader/include/Geode/utils/Result.hpp +++ b/loader/include/Geode/utils/Result.hpp @@ -196,6 +196,13 @@ namespace geode { 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)); diff --git a/loader/include/Geode/utils/general.hpp b/loader/include/Geode/utils/general.hpp index 9fcfa284..cfd0b55d 100644 --- a/loader/include/Geode/utils/general.hpp +++ b/loader/include/Geode/utils/general.hpp @@ -153,6 +153,13 @@ namespace geode { } GEODE_DLL std::string timePointAsString(std::chrono::system_clock::time_point const& tp); + + /** + * Gets the display pixel factor for the current screen, + * i.e. the ratio between physical pixels and logical pixels on one axis. + * On most platforms this is 1.0, but on retina displays for example this returns 2.0. + */ + GEODE_DLL float getDisplayFactor(); } } diff --git a/loader/src/hooks/GeodeNodeMetadata.cpp b/loader/src/hooks/GeodeNodeMetadata.cpp index 612e9bef..a36241ff 100644 --- a/loader/src/hooks/GeodeNodeMetadata.cpp +++ b/loader/src/hooks/GeodeNodeMetadata.cpp @@ -3,6 +3,7 @@ #include <Geode/modify/Field.hpp> #include <Geode/modify/CCNode.hpp> #include <cocos2d.h> +#include <queue> using namespace geode::prelude; using namespace geode::modifier; @@ -143,6 +144,162 @@ CCNode* CCNode::getChildByIDRecursive(std::string const& id) { return nullptr; } +class BFSNodeTreeCrawler final { +private: + std::queue<CCNode*> m_queue; + std::unordered_set<CCNode*> m_explored; + +public: + BFSNodeTreeCrawler(CCNode* target) { + if (auto first = getChild(target, 0)) { + m_explored.insert(first); + m_queue.push(first); + } + } + + CCNode* next() { + if (m_queue.empty()) { + return nullptr; + } + auto node = m_queue.front(); + m_queue.pop(); + for (auto sibling : CCArrayExt<CCNode*>(node->getParent()->getChildren())) { + if (!m_explored.contains(sibling)) { + m_explored.insert(sibling); + m_queue.push(sibling); + } + } + for (auto child : CCArrayExt<CCNode*>(node->getChildren())) { + if (!m_explored.contains(child)) { + m_explored.insert(child); + m_queue.push(child); + } + } + return node; + } +}; + +class NodeQuery final { +private: + enum class Op { + ImmediateChild, + DescendantChild, + }; + + std::string m_targetID; + Op m_nextOp; + std::unique_ptr<NodeQuery> m_next = nullptr; + +public: + static Result<std::unique_ptr<NodeQuery>> parse(std::string const& query) { + if (query.empty()) { + return Err("Query may not be empty"); + } + + auto result = std::make_unique<NodeQuery>(); + NodeQuery* current = result.get(); + + size_t i = 0; + std::string collectedID; + std::optional<Op> nextOp = Op::DescendantChild; + while (i < query.size()) { + auto c = query.at(i); + if (c == ' ') { + if (!nextOp) { + nextOp.emplace(Op::DescendantChild); + } + } + else if (c == '>') { + if (!nextOp || *nextOp == Op::DescendantChild) { + nextOp.emplace(Op::ImmediateChild); + } + // Double >> is syntax error + else { + return Err("Can't have multiple child operators at once (index {})", i); + } + } + // ID-valid characters + else if (std::isalnum(c) || c == '-' || c == '_' || c == '/' || c == '.') { + if (nextOp) { + current->m_next = std::make_unique<NodeQuery>(); + current->m_nextOp = *nextOp; + current->m_targetID = collectedID; + current = current->m_next.get(); + + collectedID = ""; + nextOp = std::nullopt; + } + collectedID.push_back(c); + } + // Any other character is syntax error due to needing to reserve + // stuff for possible future features + else { + return Err("Unexpected character '{}' at index {}", c, i); + } + i += 1; + } + if (nextOp || collectedID.empty()) { + return Err("Expected node ID but got end of query"); + } + current->m_targetID = collectedID; + + return Ok(std::move(result)); + } + + CCNode* match(CCNode* node) const { + // Make sure this matches the ID being looked for + if (!m_targetID.empty() && node->getID() != m_targetID) { + return nullptr; + } + // If this is the last thing to match, return the result + if (!m_next) { + return node; + } + switch (m_nextOp) { + case Op::ImmediateChild: { + for (auto c : CCArrayExt<CCNode*>(node->getChildren())) { + if (auto r = m_next->match(c)) { + return r; + } + } + } break; + + case Op::DescendantChild: { + auto crawler = BFSNodeTreeCrawler(node); + while (auto c = crawler.next()) { + if (auto r = m_next->match(c)) { + return r; + } + } + } break; + } + return nullptr; + } + + std::string toString() const { + auto str = m_targetID.empty() ? "&" : m_targetID; + if (m_next) { + switch (m_nextOp) { + case Op::ImmediateChild: str += " > "; break; + case Op::DescendantChild: str += " "; break; + } + str += m_next->toString(); + } + return str; + } +}; + +CCNode* CCNode::querySelector(std::string const& queryStr) { + auto res = NodeQuery::parse(queryStr); + if (!res) { + log::error("Invalid CCNode::querySelector query '{}': {}", queryStr, res.unwrapErr()); + return nullptr; + } + auto query = std::move(res.unwrap()); + log::info("parsed query: {}", query->toString()); + return query->match(this); +} + void CCNode::removeChildByID(std::string const& id) { if (auto child = this->getChildByID(id)) { this->removeChild(child); diff --git a/loader/src/platform/mac/util.mm b/loader/src/platform/mac/util.mm index d49409ae..16c66a0a 100644 --- a/loader/src/platform/mac/util.mm +++ b/loader/src/platform/mac/util.mm @@ -9,6 +9,10 @@ using namespace geode::prelude; #include <objc/runtime.h> #include <Geode/utils/web.hpp> +#define CommentType CommentTypeDummy +#import <Cocoa/Cocoa.h> +#undef CommentType + bool utils::clipboard::write(std::string const& data) { [[NSPasteboard generalPasteboard] clearContents]; [[NSPasteboard generalPasteboard] setString:[NSString stringWithUTF8String:data.c_str()] @@ -332,3 +336,16 @@ std::string geode::utils::thread::getDefaultName() { void geode::utils::thread::platformSetName(std::string const& name) { pthread_setname_np(name.c_str()); } + +float geode::utils::getDisplayFactor() { + float displayScale = 1.f; + if ([[NSScreen mainScreen] respondsToSelector:@selector(backingScaleFactor)]) { + NSArray* screens = [NSScreen screens]; + for (int i = 0; i < screens.count; i++) { + float s = [screens[i] backingScaleFactor]; + if (s > displayScale) + displayScale = s; + } + } + return displayScale; +} \ No newline at end of file diff --git a/loader/src/ui/nodes/General.cpp b/loader/src/ui/nodes/General.cpp index fc8f6722..9f927828 100644 --- a/loader/src/ui/nodes/General.cpp +++ b/loader/src/ui/nodes/General.cpp @@ -18,28 +18,40 @@ CCSprite* geode::createLayerBG() { return bg; } -void geode::addSideArt(CCNode* to, SideArt sides, bool useAnchorLayout) { +void geode::addSideArt(CCNode* to, SideArt sides, SideArtStyle style, bool useAnchorLayout) { + const char* sprite; + float offset; + switch (style) { + default: + case SideArtStyle::Layer: sprite = "GJ_sideArt_001.png"; offset = 35; break; + case SideArtStyle::LayerGray: sprite = "gauntletCorner_001.png"; offset = 35; break; + case SideArtStyle::PopupBlue: sprite = "rewardCorner_001.png"; offset = 24.75f; break; + case SideArtStyle::PopupGold: sprite = "dailyLevelCorner_001.png"; offset = 24.75f; break; + } if (sides & SideArt::BottomLeft) { - auto spr = CCSprite::createWithSpriteFrameName("GJ_sideArt_001.png"); - to->addChildAtPosition(spr, Anchor::BottomLeft, ccp(35, 35), useAnchorLayout); + auto spr = CCSprite::createWithSpriteFrameName(sprite); + to->addChildAtPosition(spr, Anchor::BottomLeft, ccp(offset, offset), useAnchorLayout); } if (sides & SideArt::BottomRight) { - auto spr = CCSprite::createWithSpriteFrameName("GJ_sideArt_001.png"); + auto spr = CCSprite::createWithSpriteFrameName(sprite); spr->setFlipX(true); - to->addChildAtPosition(spr, Anchor::BottomRight, ccp(-35, 35), useAnchorLayout); + to->addChildAtPosition(spr, Anchor::BottomRight, ccp(-offset, offset), useAnchorLayout); } if (sides & SideArt::TopLeft) { - auto spr = CCSprite::createWithSpriteFrameName("GJ_sideArt_001.png"); + auto spr = CCSprite::createWithSpriteFrameName(sprite); spr->setFlipY(true); - to->addChildAtPosition(spr, Anchor::TopLeft, ccp(35, -35), useAnchorLayout); + to->addChildAtPosition(spr, Anchor::TopLeft, ccp(offset, -offset), useAnchorLayout); } if (sides & SideArt::TopRight) { - auto spr = CCSprite::createWithSpriteFrameName("GJ_sideArt_001.png"); + auto spr = CCSprite::createWithSpriteFrameName(sprite); spr->setFlipX(true); spr->setFlipY(true); - to->addChildAtPosition(spr, Anchor::TopRight, ccp(-35, -35), useAnchorLayout); + to->addChildAtPosition(spr, Anchor::TopRight, ccp(-offset, -offset), useAnchorLayout); } } +void geode::addSideArt(CCNode* to, SideArt sides, bool useAnchorLayout) { + return addSideArt(to, sides, SideArtStyle::Layer, useAnchorLayout); +} void geode::addListBorders(CCNode* to, CCPoint const& center, CCSize const& size) { // if the size is 346.f, the top aligns perfectly by default :3 diff --git a/loader/src/utils/general.cpp b/loader/src/utils/general.cpp new file mode 100644 index 00000000..c9ba8b0d --- /dev/null +++ b/loader/src/utils/general.cpp @@ -0,0 +1,8 @@ +#include <Geode/utils/general.hpp> + +#ifndef GEODE_IS_MACOS +// feel free to properly implement this for other platforms +float geode::utils::getDisplayFactor() { + return 1.0f; +} +#endif \ No newline at end of file