diff --git a/.github/ISSUE_TEMPLATE/crash-report.yml b/.github/ISSUE_TEMPLATE/crash-report.yml index f4c372a44..9e882af88 100644 --- a/.github/ISSUE_TEMPLATE/crash-report.yml +++ b/.github/ISSUE_TEMPLATE/crash-report.yml @@ -2,17 +2,16 @@ name: Crash Report description: Report a Geode bug (not mods themselves) that crashes the game or prevents startup caused by Geode Loader (not mods created by others). labels: [ "unverified", "crash" ] body: - - type: input - id: geode-confirmation + - type: checkboxes attributes: label: Geode Issue description: | The Geode repository is for issues of *Geode Loader*, not individual mods created by other developers. - When submitting a crash report, please make sure that the crash is *actually* related to ***Geode Loader itself*** and not to a mod or mod combination, after you do that type in "confirm" in the input field above. + When submitting a crash report, please make sure that the crash is *actually* related to ***Geode Loader itself*** and not to a mod or mod combination. Failing to do this will get your issue *closed without explanation*. - placeholder: "Please, read the text below." - validations: - required: true + options: + - label: I confirm that this crash is NOT related to a mod but directly to Geode Loader itself. + required: true - type: dropdown id: platform attributes: diff --git a/bindings/Cocos2d.bro b/bindings/Cocos2d.bro index c658ce97b..9e85414ba 100644 --- a/bindings/Cocos2d.bro +++ b/bindings/Cocos2d.bro @@ -89,6 +89,16 @@ class cocos2d::CCCallFuncO { static auto create(cocos2d::CCObject*, cocos2d::SEL_CallFuncO, cocos2d::CCObject*) = mac 0x455940; } +[[link(win, android)]] +class cocos2d::CCCallFuncND { + static auto create(cocos2d::CCObject*, cocos2d::SEL_CallFuncND, void*) = mac 0x455470; +} + +[[link(win, android)]] +class cocos2d::CCCallFuncND { + static auto create(cocos2d::CCObject*, cocos2d::SEL_CallFuncND, void*) = mac 0x455470; +} + [[link(win, android)]] class cocos2d::CCClippingNode { CCClippingNode() { diff --git a/bindings/GeometryDash.bro b/bindings/GeometryDash.bro index 04a6ea0c8..76b62e67f 100644 --- a/bindings/GeometryDash.bro +++ b/bindings/GeometryDash.bro @@ -1069,16 +1069,44 @@ class CustomSongLayer : FLAlertLayer, FLAlertLayerProtocol, TextInputDelegate, G [[link(android)]] class CustomSongWidget : cocos2d::CCNode, MusicDownloadDelegate, FLAlertLayerProtocol { - bool init(SongInfoObject*, LevelSettingsObject*, bool, bool, bool, bool, bool hideBackground) = mac 0x37be20, win 0x685b0; + bool init(SongInfoObject*, LevelSettingsObject*, bool, bool, bool, bool, bool) = mac 0x37be20, win 0x685b0; void FLAlert_Clicked(FLAlertLayer*, bool) {} void loadSongInfoFinished(SongInfoObject*) {} + void startDownload() = win 0x69610; + void startMonitorDownload() = win 0x696b0; + void updatePlaybackBtn() = win 0x69970; + void updateSongInfo() = win 0x69bf0; void updateSongObject(SongInfoObject* song) = win 0x69280, mac 0x37d690; + void onCancelDownload(CCObject*) = win 0x693b0; + void onDownload(CCObject*) = win 0x69540; + void onGetSongInfo(CCObject*) = win 0x69490; + void onMore(CCObject*) = win 0x68e20; + void onPlayback(CCObject*) = win 0x697b0; + void onSelect(CCObject*) = win 0x69760; SongInfoObject* m_songInfo; - PAD = win 0x1C; + cocos2d::CCMenu* m_buttonMenu; + cocos2d::CCLabelBMFont* m_songLabel; + cocos2d::CCLabelBMFont* m_artistLabel; + cocos2d::CCLabelBMFont* m_songIDLabel; + cocos2d::CCLabelBMFont* m_errorLabel; CCMenuItemSpriteExtra* m_downloadBtn; - PAD = win 0x30; + CCMenuItemSpriteExtra* m_cancelDownloadBtn; + CCMenuItemSpriteExtra* m_selectSongBtn; + CCMenuItemSpriteExtra* m_getSongInfoBtn; + CCMenuItemSpriteExtra* m_playMusicBtn; + CCMenuItemSpriteExtra* m_moreBtn; + cocos2d::CCSprite* m_sliderGroove; + cocos2d::CCSprite* m_sliderBar; + LevelSettingsObject* m_levelSettings; + bool m_showSelectSongBtn; + bool m_showPlayMusicBtn; + bool m_showDownloadButtons; + bool m_isNotDownloading; + bool m_hasDefaultSong; + int m_customSongID; + bool m_unkBool; } [[link(android)]] @@ -1205,9 +1233,9 @@ class DrawGridLayer : cocos2d::CCLayer { return editorLayer->m_drawGridLayer; } - bool init(cocos2d::CCNode* grid, LevelEditorLayer* editor) = win 0x16c4d0; + bool init(cocos2d::CCNode* grid, LevelEditorLayer* editor) = win 0x16c4d0, mac 0x2729a0; virtual void draw() = win 0x16ce90, mac 0xa3c40; - virtual void update(float) = win 0x16cd80; + virtual void update(float) = win 0x16cd80, mac 0xa3b30; void clearPlayerPoints() { m_playerNodePoints->removeAllObjects(); m_player2NodePoints->removeAllObjects(); @@ -1479,6 +1507,7 @@ class EditorUI : cocos2d::CCLayer, FLAlertLayerProtocol, ColorSelectDelegate, GJ void onSettings(cocos2d::CCObject* sender) = win 0x77fe0; void activateRotationControl(cocos2d::CCObject* sender) = win 0x8fe70, mac 0x24480; void activateScaleControl(cocos2d::CCObject* sender) = mac 0x24c80, win 0x889b0; + void deactivateScaleControl() = win 0x88bf0, mac 0xb290; void dynamicGroupUpdate(bool idk) = win 0x8ad10; void createRockOutline() = win 0x89c10; void createRockEdges() = win 0x88ec0; @@ -1499,6 +1528,7 @@ class EditorUI : cocos2d::CCLayer, FLAlertLayerProtocol, ColorSelectDelegate, GJ void repositionObjectsToCenter(cocos2d::CCArray* objs, cocos2d::CCPoint center, bool ignoreGroupParent) = mac 0x1fcd0, win 0x88410; virtual void draw() = win 0x8fbe0; float valueFromXPos(float val) = win 0x78e30, mac 0x1c810; + void processSelectObjects(cocos2d::CCArray*) = win 0x86f70, mac 0x24110; bool m_isPlayingMusic; EditButtonBar* m_buttonBar; @@ -1804,15 +1834,17 @@ class FLAlertLayerProtocol { [[link(android)]] class FMODAudioEngine : cocos2d::CCNode { static FMODAudioEngine* sharedEngine() = mac 0x20ef80, win 0x239f0; + void setupAudioEngine() = win 0x23a70; void preloadEffect(gd::string filename) = win 0x24240; bool isBackgroundMusicPlaying() = win 0x24050; bool isBackgroundMusicPlaying(gd::string path) = win 0x24080; void playBackgroundMusic(gd::string path, bool fade, bool paused) = win 0x23d80; - + void setBackgroundMusicTime(float time) = win 0x23fb0; + virtual void update(float) = win 0x23b20; cocos2d::CCDictionary* m_dictionary; - std::string m_filePath; + gd::string m_filePath; float m_backgroundMusicVolume; float m_effectsVolume; float m_pulse1; @@ -2533,10 +2565,14 @@ class GJGarageLayer : cocos2d::CCLayer, TextInputDelegate, FLAlertLayerProtocol, void onDartIcon(cocos2d::CCObject* sender) = win 0x128420; void onRobotIcon(cocos2d::CCObject* sender) = win 0x1286d0; void onSpiderIcon(cocos2d::CCObject* sender) = win 0x128890; + void onPlayerDeathEffect(cocos2d::CCObject* sender) = win 0x128a50; + void onPlayerTrail(cocos2d::CCObject* sender) = win 0x128af0; void onShards(cocos2d::CCObject* sender) = win 0x12ad70; void onBack(cocos2d::CCObject* sender) = win 0x12adf0; void onShop(cocos2d::CCObject* sender) = win 0x12ad90; void setupColorSelect() = mac 0x1b7500; + void showCircleWave() = win 0x12aad0; + void showBlackCircleWave() = win 0x12a9d0; PAD = mac 0x10, win 0x8; CCTextInputNode* m_nameInput; SimplePlayer* m_playerPreview; @@ -2660,6 +2696,7 @@ class GJRobotSprite : CCAnimatedSprite { virtual void hideSecondary() = mac 0x34c3b0, win 0x146c90; static GJRobotSprite* create() = mac 0x34ac00, win 0x1457a0; void updateColor02(cocos2d::_ccColor3B) = mac 0x34bbd0, win 0x1461c0; + void updateGlowColor(cocos2d::_ccColor3B, bool) = win 0x1460C0; void updateFrame(int) = mac 0x34bdd0, win 0x146700; void hideGlow() = mac 0x34b860; @@ -2793,7 +2830,7 @@ class GJSpiderSprite : GJRobotSprite { class GJSpriteColor : cocos2d::CCNode { int m_colorID; int m_defaultColorID; - float m_unk_0F4; + float m_opacity; cocos2d::ccHSVValue m_hsv; bool m_usesHSV; float unk_10C; @@ -3401,15 +3438,16 @@ class GameObject : CCSpritePlus { void addToTempOffset(float, float) = mac 0x335700; void calculateOrientedBox() = mac 0x342b20, win 0xef1a0; void canChangeCustomColor() = mac 0x342db0; - cocos2d::_ccColor3B& colorForMode(int, bool) = mac 0x343460, win 0xef8d0; float groupOpacityMod() = win 0xebda0; + cocos2d::_ccColor3B& colorForMode(int colorMode, bool isMain) = mac 0x343460, win 0xef8d0; + cocos2d::_ccColor3B& groupColor(cocos2d::_ccColor3B const&, bool) = win 0xef9e0; + cocos2d::_ccColor3B& getActiveColorForMode(int, bool) = mac 0x343860, win 0xefb10; void commonSetup() = mac 0x2f5570, win 0xcfac0; void copyGroups(GameObject*) = mac 0x33ae30, win 0xeb9d0; static GameObject* createWithFrame(const char*) = mac 0x2f5490, win 0xcf8f0; static GameObject* createWithKey(int) = mac 0x2f4ce0, win 0xcf4f0; void destroyObject() = mac 0x336a00; void determineSlopeDirection() = mac 0x33a9e0, win 0xeb670; - cocos2d::_ccColor3B& getActiveColorForMode(int, bool) = mac 0x343860, win 0xefb10; void getBallFrame(int) = mac 0x341bf0; cocos2d::CCPoint getBoxOffset() = mac 0x3353d0, win 0xef350; const cocos2d::_ccColor3B& getColorIndex() = mac 0x343b90; @@ -3429,7 +3467,7 @@ class GameObject : CCSpritePlus { void groupWasDisabled() = mac 0x33b110; void groupWasEnabled() = mac 0x33b0f0; void hasBeenActivated() = mac 0x342a80; - void hasBeenActivatedByPlayer(GameObject*) = mac 0x342a50; + bool hasBeenActivatedByPlayer(GameObject*) = mac 0x342a50, win 0xEF110; void hasSecondaryColor() = mac 0x342f80; void ignoreEnter() = mac 0x3352a0; void ignoreFade() = mac 0x335290; @@ -3440,7 +3478,15 @@ class GameObject : CCSpritePlus { void loadGroupsFromString(gd::string str) = mac 0x33b380, win 0xebcb0; static GameObject* objectFromString(gd::string, bool) = mac 0x33b720, win 0xebe50; void playShineEffect() = mac 0x2fa9d0, win 0xeab20; - void quickUpdatePosition() = mac 0x335790; + //void quickUpdatePosition() = mac 0x335790; + // inlined on windows + void quickUpdatePosition() { + cocos2d::CCPoint newPos = getRealPosition(); + this->setPosition(newPos); + if (m_detailSprite && !m_hasDetailColor) { + m_detailSprite->setPosition(newPos); + } + } void removeGlow() = mac 0x2f7f70; void resetGroupDisabled() = mac 0x2fa7e0; void saveActiveColors() = mac 0x33d250, win 0xee3e0; @@ -3469,8 +3515,7 @@ class GameObject : CCSpritePlus { bool canAllowMultiActivate() = mac 0x343ca0, win 0xf06b0; void createGroupContainer(int size) = mac 0x33aca0, win 0xeb870; - bool m_unk3; - bool m_isBlueMaybe; + cocos2d::_ccColor3B m_color; float m_unk2; float m_unk; float m_unk3f; @@ -3573,7 +3618,7 @@ class GameObject : CCSpritePlus { int m_objectID; bool m_unk364; bool m_unk365; - bool m_unk366; + bool m_ignoreEnter; bool m_ignoreFade; bool m_unk368; bool m_unk369; @@ -3627,8 +3672,8 @@ class GameObject : CCSpritePlus { int m_unk414; PAD = mac 0xc, win 0xc, android 0xc; cocos2d::CCPoint m_firstPosition; - bool m_unk42C; - bool m_unk42D; + bool m_queuedForPositionUpdate; + bool m_shouldUpdateFirstPosition; PAD = mac 0x6, win 0x6, android 0x6; bool m_isAnimated; PAD = mac 0x7, win 0x7, android 0x7; @@ -3645,7 +3690,7 @@ class GameObject : CCSpritePlus { GJEffectManager* m_effectManager; bool m_unk458; bool m_unk459; - bool m_unk45A; + bool m_inOptimizedGroup; bool m_wasForcedRotatedPositionUpdateIdk; PAD = mac 0x8, win 0x8, android 0x8; bool m_orbMultiActivate; @@ -3997,7 +4042,7 @@ class LevelEditorLayer : GJBaseGameLayer, LevelSettingsDelegate { virtual void calculateColorValues(EffectGameObject*, EffectGameObject*, int, float, ColorActionSprite*, GJEffectManager*) = mac 0x9c590, win 0x166f90; virtual void addToGroup(GameObject*, int, bool) = mac 0x9dab0, win 0x167310; virtual void removeFromGroup(GameObject*, int) = mac 0x9db60, win 0x1673a0; - virtual void timeForXPos(float) = mac 0x9c7d0, win 0x167210; + virtual float timeForXPos(float) = mac 0x9c7d0, win 0x167210; virtual void xPosForTime(float) = mac 0x9c800, win 0x167250; virtual void levelSettingsUpdated() = mac 0x93f30, win 0x1606c0; static LevelEditorLayer* create(GJGameLevel* level) = mac 0x90fb0, win 0x15ed60, ios 0x261628; @@ -4016,7 +4061,7 @@ class LevelEditorLayer : GJBaseGameLayer, LevelSettingsDelegate { cocos2d::CCArray* createObjectsFromString(gd::string, bool) = mac 0x94730, win 0x160980; void getLastObjectX() = mac 0x9c860, win 0x167290; gd::string getLevelString() = mac 0x97790, win 0x162480; - void getNextColorChannel() = mac 0x9a610; + int getNextColorChannel() = mac 0x9a610, win 0x164e10; void getNextFreeBlockID(cocos2d::CCArray*) = mac 0x9a4e0; int getNextFreeGroupID(cocos2d::CCArray*) = mac 0x9a1b0, win 0x164ae0; void getNextFreeItemID(cocos2d::CCArray*) = mac 0x9a390; @@ -4190,8 +4235,13 @@ class LevelInfoLayer : cocos2d::CCLayer, LevelDownloadDelegate, LevelUpdateDeleg void setupLevelInfo() = mac 0x161C80, win 0x178680; void downloadLevel() = win 0x177d90, mac 0x161b90; void onPlay(cocos2d::CCObject* sender) = mac 0x161840, win 0x179730; + void onBack(cocos2d::CCObject* sender) = mac 0x163810, win 0x17C110; + void onDelete(cocos2d::CCObject* sender) = mac 0x162f30, win 0x17A2B0; + virtual void levelDownloadFinished(GJGameLevel*) = mac 0x164C00, win 0x1790C0; virtual void levelUpdateFinished(GJGameLevel*, UpdateResponse) = mac 0x164E60, win 0x1792B0; + virtual void keyBackClicked() = win 0x17C1D0; + void showUpdateAlert(UpdateResponse) = mac 0x164ED0, win 0x179300; void updateLabelValues() = mac 0x164090, win 0x17b170; @@ -4257,6 +4307,7 @@ class LevelSearchLayer : cocos2d::CCLayer { [[link(android)]] class LevelSelectLayer : cocos2d::CCLayer { static LevelSelectLayer* create(int lvl) = win 0x185500; + bool init(int lvl) = win 0x1855a0, mac 0x2384e0; PAD = win 0x10; BoomScrollLayer* m_scrollLayer; @@ -4394,6 +4445,7 @@ class LikeItemLayer : FLAlertLayer { [[link(android)]] class ListButtonBar : cocos2d::CCNode { BoomScrollLayer* m_scrollLayer; + void switchedPage(int page) = win 0x29c50; } [[link(android)]] @@ -4518,7 +4570,7 @@ class MessageListDelegate {} [[link(android)]] class MoreSearchLayer : FLAlertLayer { - static MoreSearchLayer* create() = mac 0x38ab40, win 0x182520; + static MoreSearchLayer* create() = mac 0x388180, win 0x182520; virtual bool init() = mac 0x3896b0, win 0x1825c0; void onClose(cocos2d::CCObject*) = mac 0x38aa40, win 0x1848f0; } @@ -4863,8 +4915,9 @@ class PlayLayer : GJBaseGameLayer, CCCircleWaveDelegate, CurrencyRewardDelegate, void switchToRobotMode(PlayerObject*, GameObject*, bool) = mac 0x7bc80; void switchToRollMode(PlayerObject*, GameObject*, bool) = mac 0x7bbe0; void switchToSpiderMode(PlayerObject*, GameObject*, bool) = mac 0x7bd20; - void timeForXPos(float) = mac 0x7d120, win 0x2087d0; - void timeForXPos2(float, bool) = mac 0x293eb0, win 0x1fd3d0; + callback float timeForXPos(float) = mac 0x7d120, win 0x2087d0; + float timeForXPos2(float, bool) = mac 0x293eb0, win 0x208800; + callback float xPosForTime(float) = mac 0x7d140, win 0x208840; void toggleBGEffectVisibility(bool) = mac 0x7fe80; void toggleDualMode(GameObject*, bool, PlayerObject*, bool) = mac 0x7bf90, win 0x208880; void toggleFlipped(bool, bool) = mac 0x7bdc0, win 0x20ab20; @@ -4893,7 +4946,6 @@ class PlayLayer : GJBaseGameLayer, CCCircleWaveDelegate, CurrencyRewardDelegate, virtual void visit() = mac 0x75ef0, win 0x200020; void visitWithColorFlash() = mac 0x761f0, win 0x200190; void willSwitchToMode(int, PlayerObject*) = mac 0x7b9e0; - void xPosForTime(float) = mac 0x7d140, win 0x208840; ~PlayLayer() = mac 0x6b090, win 0x1fafc0; // there is 0x10 more for android between this and ccdrawnode @@ -4929,7 +4981,7 @@ class PlayLayer : GJBaseGameLayer, CCCircleWaveDelegate, CurrencyRewardDelegate, EndPortalObject* m_endPortal; cocos2d::CCArray* m_checkpoints; cocos2d::CCArray* m_speedObjects; - cocos2d::CCArray* unk340; + cocos2d::CCArray* m_allSpeedObjects; cocos2d::CCArray* unk344; cocos2d::CCSprite* unk348; float m_backgroundRepeat; @@ -5105,7 +5157,7 @@ class PlayerObject : GameObject, AnimatedSpriteDelegate { void boostPlayer(float) = mac 0x21d6b0, win 0x1f8f30; void bumpPlayer(float, int) = mac 0x22d890; void buttonDown(PlayerButton) = mac 0x22b7e0; - void checkSnapJumpToObject(GameObject*) = mac 0x2217f0; + void checkSnapJumpToObject(GameObject*) = mac 0x2217f0, win 0x1ece70; bool collidedWithObject(float fl, GameObject* obj) { auto rect = obj->getObjectRect(); return collidedWithObject(fl, obj, rect); @@ -5250,7 +5302,7 @@ class PlayerObject : GameObject, AnimatedSpriteDelegate { PAD = mac 0x14, win 0x14, android 0x14; bool m_unk4D4; cocos2d::CCArray* m_particleSystems; - bool m_unk4DC; + bool m_hasGlow; bool m_isHidden; int m_hasGhostTrail; GhostTrailEffect* m_ghostTrail; @@ -5643,6 +5695,12 @@ class SetupPulsePopup : FLAlertLayer, cocos2d::extension::ColorPickerDelegate, T int m_pulseMode; // 0x38c on mac } +[[link(android)]] +class SetupRotatePopup : FLAlertLayer { + void onClose(cocos2d::CCObject*) = win 0x244150; + virtual void keyBackClicked() = win 0x2441a0; +} + [[link(android)]] class SetupShakePopup : FLAlertLayer { static SetupShakePopup* create(EffectGameObject*, cocos2d::CCArray*) = mac 0x3adc00; @@ -5816,12 +5874,11 @@ class SpawnTriggerAction : cocos2d::CCNode { [[link(android)]] class SpeedObject : cocos2d::CCNode { - float m_unknown; - float m_somethingToCompare; - float m_idk3; - float m_idk4; + Speed m_speed; + float m_xPos; + GameObject* m_object; - static SpeedObject* create(GameObject*, int, float) = win 0x20DE70; + static SpeedObject* create(GameObject* object, Speed speed, float x) = win 0x20de70; } [[link(android)]] diff --git a/cmake/GeodeFile.cmake b/cmake/GeodeFile.cmake index 4c3fe779d..a77f175a5 100644 --- a/cmake/GeodeFile.cmake +++ b/cmake/GeodeFile.cmake @@ -1,7 +1,7 @@ set(GEODE_CLI_MINIMUM_VERSION 1.0.5) # Find Geode CLI -if (NOT DEFINED GEODE_CLI) +if (NOT DEFINED GEODE_CLI OR GEODE_CLI STREQUAL "GEODE_CLI-NOTFOUND") find_program(GEODE_CLI NAMES geode.exe geode-cli.exe geode geode-cli PATHS ${CLI_PATH}) endif() @@ -59,7 +59,7 @@ function(setup_geode_mod proname) if(GEODE_CLI STREQUAL "GEODE_CLI-NOTFOUND") message(FATAL_ERROR "setup_geode_mod called, but Geode CLI was not found - " - "Please install CLI: https://docs.geode-sdk.org/info/installcli/" + "Please install CLI: https://docs.geode-sdk.org/" ) return() endif() diff --git a/loader/include/Geode/c++stl/gnustl.hpp b/loader/include/Geode/c++stl/gnustl.hpp index b47afad8b..a7d87cb6e 100644 --- a/loader/include/Geode/c++stl/gnustl.hpp +++ b/loader/include/Geode/c++stl/gnustl.hpp @@ -405,8 +405,11 @@ namespace gd { } ~vector() { - for (auto i = m_start; i != m_finish; ++i) { - delete i; + if (m_start) { + for (auto& x : *this) { + x.~T(); + } + delete m_start; } } diff --git a/loader/include/Geode/ui/SceneManager.hpp b/loader/include/Geode/ui/SceneManager.hpp index 2539f1822..2077f49bc 100644 --- a/loader/include/Geode/ui/SceneManager.hpp +++ b/loader/include/Geode/ui/SceneManager.hpp @@ -13,6 +13,7 @@ namespace geode { class GEODE_DLL SceneManager { protected: cocos2d::CCArray* m_persistedNodes; + cocos2d::CCScene* m_lastScene = nullptr; bool setup(); diff --git a/loader/include/Geode/ui/TextArea.hpp b/loader/include/Geode/ui/TextArea.hpp index 0ac0cf313..c923d4e4e 100644 --- a/loader/include/Geode/ui/TextArea.hpp +++ b/loader/include/Geode/ui/TextArea.hpp @@ -4,36 +4,47 @@ #include namespace geode { + enum WrappingMode { + NO_WRAP, + WORD_WRAP, + CUTOFF_WRAP + }; + /** * 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 + * - Configurable and automatic word wrapping * - Line padding * * Contact me on Discord (\@smjs) if you have any questions, suggestions or bugs. */ class GEODE_DLL SimpleTextArea : public cocos2d::CCNode { + 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); + cocos2d::CCMenu* m_container; std::string m_font; std::string m_text; std::vector m_lines; + cocos2d::ccColor4B m_color; cocos2d::CCTextAlignment m_alignment; + WrappingMode m_wrappingMode; 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 setColor(const cocos2d::ccColor4B& color); + cocos2d::ccColor4B getColor(); void setAlignment(const cocos2d::CCTextAlignment alignment); cocos2d::CCTextAlignment getAlignment(); + void setWrappingMode(const WrappingMode mode); + WrappingMode getWrappingMode(); void setText(const std::string& text); std::string getText(); void setMaxLines(const size_t maxLines); @@ -50,11 +61,17 @@ namespace geode { private: static SimpleTextArea* create(const std::string& font, const std::string& text, const float scale, const float width, const bool artificialWidth); + bool m_shouldUpdate; + bool m_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(); + void charIteration(const std::function& overflowHandling); + void updateLinesNoWrap(); + void updateLinesWordWrap(); + void updateLinesCutoffWrap(); + void updateContainer(); + virtual void draw() override; }; } \ No newline at end of file diff --git a/loader/src/loader/Index.cpp b/loader/src/loader/Index.cpp index acb950642..ff1d4b1d1 100644 --- a/loader/src/loader/Index.cpp +++ b/loader/src/loader/Index.cpp @@ -252,7 +252,7 @@ class Index::Impl final { friend class Index; void cleanupItems(); - void downloadIndex(); + void downloadIndex(std::string commitHash = ""); void checkForUpdates(); void updateFromLocalTree(); void installNext(size_t index, IndexInstallList const& list); @@ -296,7 +296,7 @@ bool Index::hasTriedToUpdate() const { return m_impl->m_triedToUpdate; } -void Index::Impl::downloadIndex() { +void Index::Impl::downloadIndex(std::string commitHash) { log::debug("Downloading index"); IndexUpdateEvent(UpdateProgress(0, "Beginning download")).post(); @@ -307,7 +307,7 @@ void Index::Impl::downloadIndex() { .join("index-download") .fetch("https://github.com/geode-sdk/mods/zipball/main") .into(targetFile) - .then([this, targetFile](auto) { + .then([this, targetFile, commitHash](auto) { auto targetDir = dirs::getIndexDir() / "v0"; // delete old unzipped index try { @@ -333,6 +333,10 @@ void Index::Impl::downloadIndex() { // remove the directory github adds to the root of the zip (void)flattenGithubRepo(targetDir); + if (!commitHash.empty()) { + auto const checksumPath = dirs::getIndexDir() / ".checksum"; + (void)file::writeString(checksumPath, commitHash); + } Loader::get()->queueInMainThread([this] { // update index @@ -387,8 +391,7 @@ void Index::Impl::checkForUpdates() { } // otherwise save hash and download source else { - (void)file::writeString(checksum, newSHA); - this->downloadIndex(); + this->downloadIndex(newSHA); } }) .expect([](std::string const& err) { @@ -591,7 +594,7 @@ bool Index::isUpdateAvailable(IndexItemHandle item) const { bool Index::areUpdatesAvailable() const { for (auto& mod : Loader::get()->getAllMods()) { auto item = this->getMajorItem(mod->getID()); - if (item && item->getMetadata().getVersion() > mod->getVersion()) { + if (item && item->getMetadata().getVersion() > mod->getVersion() && mod->isEnabled()) { return true; } } diff --git a/loader/src/ui/nodes/Notification.cpp b/loader/src/ui/nodes/Notification.cpp index f9bad08ba..449ddde2c 100644 --- a/loader/src/ui/nodes/Notification.cpp +++ b/loader/src/ui/nodes/Notification.cpp @@ -173,7 +173,6 @@ void Notification::show() { auto winSize = CCDirector::get()->getWinSize(); this->setPosition(winSize.width / 2, winSize.height / 4); this->setZOrder(CCScene::get()->getHighestChildZ() + 100); - CCScene::get()->addChild(this); } SceneManager::get()->keepAcrossScenes(this); m_showing = true; diff --git a/loader/src/ui/nodes/SceneManager.cpp b/loader/src/ui/nodes/SceneManager.cpp index 0ec112d7e..ee7a4247a 100644 --- a/loader/src/ui/nodes/SceneManager.cpp +++ b/loader/src/ui/nodes/SceneManager.cpp @@ -23,6 +23,10 @@ SceneManager::~SceneManager() { } void SceneManager::keepAcrossScenes(CCNode* node) { + if (m_lastScene) { + node->removeFromParentAndCleanup(false); + m_lastScene->addChild(node); + } m_persistedNodes->addObject(node); } @@ -36,4 +40,5 @@ void SceneManager::willSwitchToScene(CCScene* scene) { node->removeFromParentAndCleanup(false); scene->addChild(node); } + m_lastScene = scene; } diff --git a/loader/src/ui/nodes/TextArea.cpp b/loader/src/ui/nodes/TextArea.cpp index 9dce696fa..a2b747175 100644 --- a/loader/src/ui/nodes/TextArea.cpp +++ b/loader/src/ui/nodes/TextArea.cpp @@ -2,11 +2,11 @@ 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& text, const std::string& font, const float scale) { + return SimpleTextArea::create(font, text, scale, CCDirector::sharedDirector()->getWinSize().width / 2, false); } -SimpleTextArea* SimpleTextArea::create(const std::string& font, const std::string& text, const float scale, const float width) { +SimpleTextArea* SimpleTextArea::create(const std::string& text, const std::string& font, const float scale, const float width) { return SimpleTextArea::create(font, text, scale, width, true); } @@ -30,9 +30,12 @@ SimpleTextArea::SimpleTextArea(const std::string& font, const std::string& text, m_maxLines = 0; m_scale = scale; m_linePadding = 0; + m_color = { 0xFF, 0xFF, 0xFF, 0xFF }; m_alignment = kCCTextAlignmentLeft; + m_wrappingMode = WORD_WRAP; m_artificialWidth = artificialWidth; m_container = CCMenu::create(); + m_shouldUpdate = true; this->setAnchorPoint({ 0.5f, 0.5f }); m_container->setPosition({ 0, 0 }); @@ -40,33 +43,47 @@ SimpleTextArea::SimpleTextArea(const std::string& font, const std::string& text, m_container->setContentSize({ width, 0 }); this->addChild(m_container); - this->updateContents(); } void SimpleTextArea::setFont(const std::string& font) { m_font = font; - - this->updateContents(); + m_shouldUpdate = true; } std::string SimpleTextArea::getFont() { return m_font; } +void SimpleTextArea::setColor(const ccColor4B& color) { + m_color = color; + m_shouldUpdate = true; +} + +ccColor4B SimpleTextArea::getColor() { + return m_color; +} + void SimpleTextArea::setAlignment(const CCTextAlignment alignment) { m_alignment = alignment; - - this->updateContents(); + m_shouldUpdate = true; } CCTextAlignment SimpleTextArea::getAlignment() { return m_alignment; } +void SimpleTextArea::setWrappingMode(const WrappingMode mode) { + m_wrappingMode = mode; + m_shouldUpdate = true; +} + +WrappingMode SimpleTextArea::getWrappingMode() { + return m_wrappingMode; +} + void SimpleTextArea::setText(const std::string& text) { m_text = text; - - this->updateContents(); + m_shouldUpdate = true; } std::string SimpleTextArea::getText() { @@ -75,8 +92,7 @@ std::string SimpleTextArea::getText() { void SimpleTextArea::setMaxLines(const size_t maxLines) { m_maxLines = maxLines; - - this->updateContents(); + m_shouldUpdate = true; } size_t SimpleTextArea::getMaxLines() { @@ -85,6 +101,7 @@ size_t SimpleTextArea::getMaxLines() { void SimpleTextArea::setWidth(const float width) { m_artificialWidth = true; + m_shouldUpdate = true; this->setContentSize({ width, this->getContentSize().height }); m_container->setContentSize(this->getContentSize()); @@ -96,8 +113,7 @@ float SimpleTextArea::getWidth() { void SimpleTextArea::setScale(const float scale) { m_scale = scale; - - this->updateContents(); + m_shouldUpdate = true; } float SimpleTextArea::getScale() { @@ -106,8 +122,7 @@ float SimpleTextArea::getScale() { void SimpleTextArea::setLinePadding(const float padding) { m_linePadding = padding; - - this->updateContents(); + m_shouldUpdate = true; } float SimpleTextArea::getLinePadding() { @@ -131,34 +146,17 @@ CCLabelBMFont* SimpleTextArea::createLabel(const std::string& text, const float label->setScale(m_scale); label->setPosition({ 0, top }); + label->setColor({ m_color.r, m_color.g, m_color.b }); + label->setOpacity(m_color.a); 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() { +void SimpleTextArea::charIteration(const std::function& overflowHandling) { float top = 0; CCLabelBMFont* line = this->createLabel("", top); m_lines = { line }; @@ -173,21 +171,87 @@ void SimpleTextArea::updateLines() { break; } else if (c == '\n') { - line = this->createLabel("", top -= this->calculateOffset(line)); + m_lines.push_back(line = this->createLabel("", top -= this->calculateOffset(line))); + } else if (m_artificialWidth && line->getContentSize().width * m_scale >= this->getWidth()) { + m_lines.push_back(line = overflowHandling(line, c, top -= this->calculateOffset(line))); + } else { + line->setString((std::string(line->getString()) + c).c_str()); + } + } +} - m_lines.push_back(line); - } else if (m_artificialWidth && line->getContentSize().width >= this->getWidth()) { - line = this->moveOverflow(line, c, top -= this->calculateOffset(line)); +void SimpleTextArea::updateLinesNoWrap() { + std::stringstream stream(m_text); + std::string part; + float top = 0; + + while (std::getline(stream, part)) { + if (m_maxLines && m_lines.size() >= m_maxLines) { + CCLabelBMFont* last = m_lines.at(m_maxLines - 1); + const std::string text = last->getString(); + + last->setString(text.substr(0, text.size() - 3).append("...").c_str()); + + break; } else { - const std::string text = line->getString(); + CCLabelBMFont* line = this->createLabel(part, 0); + + top -= this->calculateOffset(line); - line->setString((text + c).c_str()); + m_lines.push_back(line); } } } -void SimpleTextArea::updateContents() { - this->updateLines(); +void SimpleTextArea::updateLinesWordWrap() { + this->charIteration([this](CCLabelBMFont* line, const char c, const float top) { + static std::string delimiters(" `~!@#$%^&*()-_=+[{}];:'\",<.>/?\\|"); + + if (delimiters.find(c) == std::string_view::npos) { + const std::string text = line->getString(); + const size_t position = text.find_last_of(delimiters) + 1; + + line->setString(text.substr(0, position).c_str()); + + return this->createLabel(text.substr(position) + c, top); + } else { + return this->createLabel(std::string(c, c != ' '), top); + } + }); +} + +void SimpleTextArea::updateLinesCutoffWrap() { + this->charIteration([this](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()); + } + } + + return newLine; + }); +} + +void SimpleTextArea::updateContainer() { + switch (m_wrappingMode) { + case NO_WRAP: { + this->updateLinesNoWrap(); + } break; + case WORD_WRAP: { + this->updateLinesWordWrap(); + } break; + case CUTOFF_WRAP: { + this->updateLinesCutoffWrap(); + } break; + } + const size_t lineCount = m_lines.size(); const float width = this->getWidth(); @@ -212,7 +276,7 @@ void SimpleTextArea::updateContents() { line->setPosition({ 0, y }); } break; case kCCTextAlignmentCenter: { - line->setAnchorPoint({ 0.5f, 1 }); + line->setAnchorPoint({ 0, 1 }); line->setPosition({ width / 2, y }); } break; case kCCTextAlignmentRight: { @@ -224,3 +288,13 @@ void SimpleTextArea::updateContents() { m_container->addChild(line); } } + +void SimpleTextArea::draw() { + CCNode::draw(); + + if (m_shouldUpdate) { + this->updateContainer(); + + m_shouldUpdate = false; + } +} \ No newline at end of file