diff --git a/.github/workflows/test-offsets.yml b/.github/workflows/test-offsets.yml index 66cfcf470..23403f67e 100644 --- a/.github/workflows/test-offsets.yml +++ b/.github/workflows/test-offsets.yml @@ -3,6 +3,9 @@ name: Test Offsets on: workflow_dispatch: push: + paths: + - 'bindings/**' # only when adjusting bindings + - 'loader/test/members/**' branches: - '**' # every branch - '!no-build-**' # unless marked as no-build diff --git a/bindings/GeometryDash.bro b/bindings/GeometryDash.bro index 58835641d..6ab08b3c6 100644 --- a/bindings/GeometryDash.bro +++ b/bindings/GeometryDash.bro @@ -1002,15 +1002,15 @@ class CreateMenuItem : CCMenuItemSpriteExtra { class CreatorLayer : cocos2d::CCLayer, cocos2d::CCSceneTransitionDelegate, DialogDelegate { void onBack(cocos2d::CCObject*) = win 0x4fae0; void onChallenge(cocos2d::CCObject*) = mac 0x142960, win 0x4f1b0; - void onLeaderboards(cocos2d::CCObject*) = win 0x4ed20; + void onLeaderboards(cocos2d::CCObject*) = mac 0x142920, win 0x4ed20; void onMyLevels(cocos2d::CCObject*) = mac 0x142b70, win 0x4eaa0; void onSavedLevels(cocos2d::CCObject*) = mac 0x142860, win 0x4ebe0; - void onDailyLevel(cocos2d::CCObject*) = win 0x4f170; - void onWeeklyLevel(cocos2d::CCObject*) = win 0x4f190; - void onFeaturedLevels(cocos2d::CCObject*) = win 0x4edf0; - void onFameLevels(cocos2d::CCObject*) = win 0x4ee70; - void onMapPacks(cocos2d::CCObject*) = win 0x4efb0; - void onOnlineLevels(cocos2d::CCObject*) = win 0x4ef60; + void onDailyLevel(cocos2d::CCObject*) = mac 0x142980, win 0x4f170; + void onWeeklyLevel(cocos2d::CCObject*) = mac 0x1429a0, win 0x4f190; + void onFeaturedLevels(cocos2d::CCObject*) = mac 0x142a20, win 0x4edf0; + void onFameLevels(cocos2d::CCObject*) = mac 0x142a80, win 0x4ee70; + void onMapPacks(cocos2d::CCObject*) = mac 0x1429c0, win 0x4efb0; + void onOnlineLevels(cocos2d::CCObject*) = mac 0x142ae0, win 0x4ef60; void onGauntlets(cocos2d::CCObject*) = mac 0x142b20, win 0x4f0a0; void onSecretVault(cocos2d::CCObject*) = win 0x4f1d0; void onTreasureRoom(cocos2d::CCObject*) = win 0x4f540; @@ -1138,7 +1138,7 @@ class CustomizeObjectLayer : FLAlertLayer, TextInputDelegate, HSVWidgetPopupDele [[link(android)]] class DailyLevelPage : FLAlertLayer, FLAlertLayerProtocol, GJDailyLevelDelegate, LevelDownloadDelegate { - static DailyLevelPage* create(bool weekly) = win 0x6a860; + static DailyLevelPage* create(bool weekly) = mac 0x108ac0, win 0x6a860; bool init(bool weekly) = mac 0x108C90, win 0x6a900; void updateTimers(float) = mac 0x109780, win 0x6bef0; virtual void show() = mac 0x10a4b0, win 0x3f360; @@ -3694,7 +3694,8 @@ class GameStatsManager : cocos2d::CCNode { int getBaseCurrencyForLevel(GJGameLevel*) = mac 0x43470, win 0xf8530; GJChallengeItem* getChallenge(int id) = mac 0x451f0, win 0xa2fb0; void getSecretCoinKey(char const*) = mac 0x429f0; - int getStat(char const*) = mac 0x3d310, win 0xf3580; + int getStat(char const* type) = mac 0x3d310, win 0xf3580; + void setStat(char const* type, int amount) = win 0xf3690; void hasPendingUserCoin(char const*) = mac 0x42730, win 0xf7c50; void hasSecretCoin(char const*) = mac 0x40730, win 0xf7dc0; void hasUserCoin(char const*) = mac 0x427e0, win 0xf7ae0; @@ -3706,6 +3707,7 @@ class GameStatsManager : cocos2d::CCNode { void storeSecretCoin(char const*) = mac 0x42a10; void storeUserCoin(char const*) = mac 0x42890; bool isItemUnlocked(UnlockType type, int id) = win 0xfbb80; + void checkAchievement(char const* type) = win 0xf37c0; PAD = mac 0x50, win 0x28, android 0x24; cocos2d::CCDictionary* m_dailyChests; diff --git a/loader/include/Geode/ui/TextArea.hpp b/loader/include/Geode/ui/TextArea.hpp new file mode 100644 index 000000000..0ac0cf313 --- /dev/null +++ b/loader/include/Geode/ui/TextArea.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include + +namespace geode { + /** + * A class which provides a textarea with proper alignment and some extra features like: + * + * - Max lines + * - Changing all aspects after creation + * - Custom text alignment + * - Automatic line wrapping and cutoff + * - Line padding + * + * Contact me on Discord (\@smjs) if you have any questions, suggestions or bugs. + */ + class GEODE_DLL SimpleTextArea : public cocos2d::CCNode { + cocos2d::CCMenu* m_container; + std::string m_font; + std::string m_text; + std::vector m_lines; + cocos2d::CCTextAlignment m_alignment; + size_t m_maxLines; + float m_scale; + float m_lineHeight; + float m_linePadding; + bool m_artificialWidth; + public: + static SimpleTextArea* create(const std::string& font, const std::string& text, const float scale); + static SimpleTextArea* create(const std::string& font, const std::string& text, const float scale, const float width); + + void setFont(const std::string& font); + std::string getFont(); + void setAlignment(const cocos2d::CCTextAlignment alignment); + cocos2d::CCTextAlignment getAlignment(); + void setText(const std::string& text); + std::string getText(); + void setMaxLines(const size_t maxLines); + size_t getMaxLines(); + void setWidth(const float width); + float getWidth(); + void setScale(const float scale); + float getScale(); + void setLinePadding(const float padding); + float getLinePadding(); + std::vector 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); + + SimpleTextArea(const std::string& font, const std::string& text, const float scale, const float width, const bool artificialWidth); + cocos2d::CCLabelBMFont* createLabel(const std::string& text, const float top); + cocos2d::CCLabelBMFont* moveOverflow(cocos2d::CCLabelBMFont* line, const char c, const float top); + float calculateOffset(cocos2d::CCLabelBMFont* label); + void updateLines(); + void updateContents(); + }; +} \ No newline at end of file diff --git a/loader/src/ui/nodes/TextArea.cpp b/loader/src/ui/nodes/TextArea.cpp new file mode 100644 index 000000000..9dce696fa --- /dev/null +++ b/loader/src/ui/nodes/TextArea.cpp @@ -0,0 +1,226 @@ +#include + +using namespace geode::prelude; + +SimpleTextArea* SimpleTextArea::create(const std::string& font, const std::string& text, const float scale = 1) { + return SimpleTextArea::create(font, text, scale, 500, false); +} + +SimpleTextArea* SimpleTextArea::create(const std::string& font, const std::string& text, const float scale, const float width) { + return SimpleTextArea::create(font, text, scale, width, true); +} + +SimpleTextArea* SimpleTextArea::create(const std::string& font, const std::string& text, const float scale, const float width, const bool artificialWidth) { + SimpleTextArea* instance = new SimpleTextArea(font, text, scale, width, artificialWidth); + + if (instance && instance->init()) { + instance->autorelease(); + + return instance; + } else { + CC_SAFE_DELETE(instance); + + return nullptr; + } +} + +SimpleTextArea::SimpleTextArea(const std::string& font, const std::string& text, const float scale, const float width, const bool artificialWidth) { + m_font = font; + m_text = text; + m_maxLines = 0; + m_scale = scale; + m_linePadding = 0; + m_alignment = kCCTextAlignmentLeft; + m_artificialWidth = artificialWidth; + m_container = CCMenu::create(); + + this->setAnchorPoint({ 0.5f, 0.5f }); + m_container->setPosition({ 0, 0 }); + m_container->setAnchorPoint({ 0, 1 }); + m_container->setContentSize({ width, 0 }); + + this->addChild(m_container); + this->updateContents(); +} + +void SimpleTextArea::setFont(const std::string& font) { + m_font = font; + + this->updateContents(); +} + +std::string SimpleTextArea::getFont() { + return m_font; +} + +void SimpleTextArea::setAlignment(const CCTextAlignment alignment) { + m_alignment = alignment; + + this->updateContents(); +} + +CCTextAlignment SimpleTextArea::getAlignment() { + return m_alignment; +} + +void SimpleTextArea::setText(const std::string& text) { + m_text = text; + + this->updateContents(); +} + +std::string SimpleTextArea::getText() { + return m_text; +} + +void SimpleTextArea::setMaxLines(const size_t maxLines) { + m_maxLines = maxLines; + + this->updateContents(); +} + +size_t SimpleTextArea::getMaxLines() { + return m_maxLines; +} + +void SimpleTextArea::setWidth(const float width) { + m_artificialWidth = true; + + this->setContentSize({ width, this->getContentSize().height }); + m_container->setContentSize(this->getContentSize()); +} + +float SimpleTextArea::getWidth() { + return m_container->getContentSize().width; +} + +void SimpleTextArea::setScale(const float scale) { + m_scale = scale; + + this->updateContents(); +} + +float SimpleTextArea::getScale() { + return m_scale; +} + +void SimpleTextArea::setLinePadding(const float padding) { + m_linePadding = padding; + + this->updateContents(); +} + +float SimpleTextArea::getLinePadding() { + return m_linePadding; +} + +std::vector SimpleTextArea::getLines() { + return m_lines; +} + +float SimpleTextArea::getHeight() { + return m_container->getContentSize().height; +} + +float SimpleTextArea::getLineHeight() { + return m_lineHeight; +} + +CCLabelBMFont* SimpleTextArea::createLabel(const std::string& text, const float top) { + CCLabelBMFont* label = CCLabelBMFont::create(text.c_str(), m_font.c_str()); + + label->setScale(m_scale); + label->setPosition({ 0, top }); + + return label; +} + +CCLabelBMFont* SimpleTextArea::moveOverflow(CCLabelBMFont* line, const char c, const float top) { + const std::string text = line->getString(); + const char back = text.back(); + const bool lastIsSpace = back == ' '; + CCLabelBMFont* newLine = this->createLabel(std::string(!lastIsSpace, back).append(std::string(c != ' ', c)), top); + + if (!lastIsSpace) { + if (text[text.size() - 2] == ' ') { + line->setString(text.substr(0, text.size() - 1).c_str()); + } else { + line->setString((text.substr(0, text.size() - 1) + '-').c_str()); + } + } + + m_lines.push_back(newLine); + + return newLine; +} + +float SimpleTextArea::calculateOffset(CCLabelBMFont* label) { + return m_linePadding + label->getContentSize().height * m_scale; +} + +void SimpleTextArea::updateLines() { + float top = 0; + CCLabelBMFont* line = this->createLabel("", top); + m_lines = { line }; + + for (const char c : m_text) { + if (m_maxLines && m_lines.size() > m_maxLines) { + CCLabelBMFont* last = m_lines.at(m_maxLines - 1); + const std::string text = last->getString(); + + m_lines.pop_back(); + last->setString(text.substr(0, text.size() - 3).append("...").c_str()); + + break; + } else if (c == '\n') { + line = this->createLabel("", top -= this->calculateOffset(line)); + + m_lines.push_back(line); + } else if (m_artificialWidth && line->getContentSize().width >= this->getWidth()) { + line = this->moveOverflow(line, c, top -= this->calculateOffset(line)); + } else { + const std::string text = line->getString(); + + line->setString((text + c).c_str()); + } + } +} + +void SimpleTextArea::updateContents() { + this->updateLines(); + const size_t lineCount = m_lines.size(); + const float width = this->getWidth(); + + if (lineCount > 0) { + m_lineHeight = m_lines.back()->getContentSize().height * m_scale; + } else { + m_lineHeight = 0; + } + + float height = m_lineHeight * lineCount + m_linePadding * (lineCount - 1); + + this->setContentSize({ width, height }); + m_container->setContentSize(this->getContentSize()); + m_container->removeAllChildren(); + + for (CCLabelBMFont* line : m_lines) { + const float y = height + line->getPositionY(); + + switch (m_alignment) { + case kCCTextAlignmentLeft: { + line->setAnchorPoint({ 0, 1 }); + line->setPosition({ 0, y }); + } break; + case kCCTextAlignmentCenter: { + line->setAnchorPoint({ 0.5f, 1 }); + line->setPosition({ width / 2, y }); + } break; + case kCCTextAlignmentRight: { + line->setAnchorPoint({ 1, 1 }); + line->setPosition({ width, y }); + } break; + } + + m_container->addChild(line); + } +}