From ed954f582f1c5153c18817759d4feee540670a45 Mon Sep 17 00:00:00 2001 From: Justin Pridgen Date: Wed, 19 Jun 2024 11:17:09 -0400 Subject: [PATCH] v1.5.0 --- CMakeLists.txt | 3 +- README.md | 3 +- about.md | 3 +- changelog.md | 4 ++ mod.json | 6 +-- src/IDListLayer.cpp | 102 +++++++++++++++++------------------- src/IDListLayer.hpp | 20 ++----- src/IntegratedDemonlist.cpp | 65 +++++++++++++++++++++++ src/IntegratedDemonlist.hpp | 23 ++++++++ src/main.cpp | 25 ++++++--- 10 files changed, 171 insertions(+), 83 deletions(-) create mode 100644 src/IntegratedDemonlist.cpp create mode 100644 src/IntegratedDemonlist.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bfe190a..ed466d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,11 +4,12 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64") set(CMAKE_CXX_VISIBILITY_PRESET hidden) -project(IntegratedDemonlist VERSION 1.4.9) +project(IntegratedDemonlist VERSION 1.5.0) add_library(${PROJECT_NAME} SHARED src/IDListLayer.cpp src/main.cpp + src/IntegratedDemonlist.cpp ) if (NOT DEFINED ENV{GEODE_SDK}) diff --git a/README.md b/README.md index 8b59f5b..74fea18 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ # Integrated Demonlist -A mod that integrates a list of the top extreme demons, according to [aredl.net](https://aredl.net), into Geometry Dash. +A mod that integrates [aredl.net](https://aredl.net) and [pemonlist.com](https://pemonlist.com) into Geometry Dash. # Features - A new button in the level/list search screen that opens the demon list. - A search box that allows you to search for a demon in the list by name. - Page navigation buttons that allow you to navigate through the list. +- A button that allows you to switch between the AREDL (For classic demons) and the Pemonlist (For platformer demons). - If on the list, and if enabled, there will be text on the demon's search box that states its position on the list. # License diff --git a/about.md b/about.md index ffcf8c3..97ee540 100644 --- a/about.md +++ b/about.md @@ -1,8 +1,9 @@ # Integrated Demonlist -A mod that integrates a list of the top extreme demons, according to [aredl.net](https://aredl.net), into Geometry Dash. +A mod that integrates [aredl.net](https://aredl.net) and [pemonlist.com](https://pemonlist.com) into Geometry Dash. # Features - A new button in the level/list search screen that opens the demon list. - A search box that allows you to search for a demon in the list by name. - Page navigation buttons that allow you to navigate through the list. +- A button that allows you to switch between the AREDL (For classic demons) and the Pemonlist (For platformer demons). - If on the list, and if enabled, there will be text on the demon's search box that states its position on the list. \ No newline at end of file diff --git a/changelog.md b/changelog.md index 1a38c5b..90af26c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,8 @@ # Integrated Demonlist Changelog +## v1.5.0 (2024-06-19) +- Added Pemonlist support +- Fixed a bug where the list would not load if the main menu was exited too quickly + ## v1.4.9 (2024-06-14) - Finalized the release of v1.4.9 diff --git a/mod.json b/mod.json index 2f95026..5ba126e 100644 --- a/mod.json +++ b/mod.json @@ -5,11 +5,11 @@ "win": "2.206", "mac": "2.206" }, - "version": "v1.4.9", + "version": "v1.5.0", "id": "hiimjustin000.integrated_demonlist", "name": "Integrated Demonlist", "developer": "hiimjustin000", - "description": "A mod that integrates a list of the top extreme demons, according to aredl.net, into Geometry Dash.", + "description": "A mod that integrates aredl.net and pemonlist.com into Geometry Dash.", "repository": "https://github.com/hiimjustin000/IntegratedDemonlist", "resources": { "sprites": [ @@ -41,4 +41,4 @@ "content", "online" ] -} \ No newline at end of file +} diff --git a/src/IDListLayer.cpp b/src/IDListLayer.cpp index aec8ba5..5b612e5 100644 --- a/src/IDListLayer.cpp +++ b/src/IDListLayer.cpp @@ -18,40 +18,6 @@ CCScene* IDListLayer::scene() { return ret; } -template -std::vector IDListLayer::pluck(matjson::Array const& arr, std::string const& key) { - std::vector ret = {}; - for (auto const& val : arr) { - if ((!val.contains("legacy") || !val["legacy"].as_bool()) && !val["two_player"].as_bool()) ret.push_back(val[key].as()); - } - return ret; -} - -void IDListLayer::loadAREDL(EventListener&& listenerRef, bool fromMenuLayer, utils::MiniFunction callback) { - auto&& listener = std::move(listenerRef); - listener.bind([fromMenuLayer, callback](auto e) { - if (auto res = e->getValue()) { - if (res->ok()) { - auto json = res->json().value().as_array(); - AREDL = pluck(json, "level_id"); - AREDL_NAMES = pluck(json, "name"); - AREDL_POSITIONS = pluck(json, "position"); - callback(); - } - else { - if (fromMenuLayer) Notification::create("Failed to load AREDL", NotificationIcon::Error)->show(); - else FLAlertLayer::create("Load Failed", "Failed to load AREDL. Please try again later.", "OK")->show(); - } - } - }); - - listener.setFilter(web::WebRequest().get("https://api.aredl.net/api/aredl/levels")); -} - -float IDListLayer::createGap(CCNode* node1, CCNode* node2, float gap) { - return node1->getPositionY() - node1->getContentSize().height / 2 - node2->getContentSize().height / 2 - gap; -} - bool IDListLayer::init() { if (!CCLayer::init()) return false; @@ -84,7 +50,8 @@ bool IDListLayer::init() { m_countLabel->setPosition(winSize.width - 7.0f, winSize.height - 3.0f); addChild(m_countLabel); - m_list = GJListLayer::create(CustomListView::create(CCArray::create(), BoomListType::Level, 190.0f, 358.0f), "All Rated Extreme Demons List", { 0, 0, 0, 180 }, 358.0f, 220.0f, 0); + m_list = GJListLayer::create(CustomListView::create(CCArray::create(), BoomListType::Level, 190.0f, 358.0f), + PEMONLIST ? "Pemonlist" : "All Rated Extreme Demons List", { 0, 0, 0, 180 }, 358.0f, 220.0f, 0); m_list->setZOrder(2); m_list->setPosition(winSize / 2 - m_list->getContentSize() / 2); addChild(m_list); @@ -120,19 +87,34 @@ bool IDListLayer::init() { m_rightButton->setPosition(winSize.width - 24.0f, winSize.height / 2); menu->addChild(m_rightButton); - m_infoButton = CCMenuItemExt::createSpriteExtraWithFrameName("GJ_infoIcon_001.png", 1.0f, [](auto) { - std::string line1 = "The All Rated Extreme Demons List (AREDL) is an unofficial ranking of all rated Extreme Demons in Geometry Dash.\n"; - std::string line2 = "It is managed by iiLogan, SEDTHEPRODIGY, Megu, and Minebox260."; - FLAlertLayer::create("AREDL", line1 + line2, "OK")->show(); + auto infoButton = CCMenuItemExt::createSpriteExtraWithFrameName("GJ_infoIcon_001.png", 1.0f, [](auto) { + FLAlertLayer::create(PEMONLIST ? "Pemonlist" : "AREDL", PEMONLIST ? + "The Pemonlist is an unofficial ranking of all rated platformer mode Demons in Geometry Dash.\n" + "It is managed by camila314, Extatica, and mariokirby1703." : + "The All Rated Extreme Demons List (AREDL) is an unofficial ranking of all rated classic mode Extreme Demons in Geometry Dash.\n" + "It is managed by iiLogan, SEDTHEPRODIGY, Megu, and Minebox260.", + "OK")->show(); }); - m_infoButton->setPosition(30.0f, 30.0f); - menu->addChild(m_infoButton, 2); + infoButton->setPosition(30.0f, 30.0f); + menu->addChild(infoButton, 2); auto refreshBtnSpr = CCSprite::createWithSpriteFrameName("GJ_updateBtn_001.png"); auto& refreshBtnSize = refreshBtnSpr->getContentSize(); - m_refreshButton = CCMenuItemExt::createSpriteExtra(refreshBtnSpr, [this](auto) { loadAREDL(std::move(m_listener), false, [this]() { populateList(m_query); }); }); - m_refreshButton->setPosition(winSize.width - refreshBtnSize.width / 2 - 4.0f, refreshBtnSize.height / 2 + 4.0f); - menu->addChild(m_refreshButton, 2); + auto refreshButton = CCMenuItemExt::createSpriteExtra(refreshBtnSpr, [this](auto) { + if (PEMONLIST) IntegratedDemonlist::loadPemonlist(std::move(m_listener), [this]() { populateList(m_query); }); + else IntegratedDemonlist::loadAREDL(std::move(m_listener), [this]() { populateList(m_query); }); + }); + refreshButton->setPosition(winSize.width - refreshBtnSize.width / 2 - 4.0f, refreshBtnSize.height / 2 + 4.0f); + menu->addChild(refreshButton, 2); + + auto listToggler = CCMenuItemExt::createTogglerWithFrameName("GJ_moonsIcon_001.png", "GJ_starsIcon_001.png", 1.1f, [this](auto) { + PEMONLIST = !PEMONLIST; + if (PEMONLIST) IntegratedDemonlist::loadPemonlist(std::move(m_listener), [this]() { page(0); }); + else IntegratedDemonlist::loadAREDL(std::move(m_listener), [this]() { page(0); }); + }); + listToggler->toggle(PEMONLIST); + listToggler->setPosition(30.0f, 60.0f); + menu->addChild(listToggler, 2); auto pageBtnSpr = CCSprite::create("GJ_button_02.png"); pageBtnSpr->setScale(0.7f); @@ -157,7 +139,7 @@ bool IDListLayer::init() { std::uniform_int_distribution distribute(0, getMaxPage()); page(distribute(generator)); }); - m_randomButton->setPositionY(createGap(m_pageButton, m_randomButton, 5.0f)); + m_randomButton->setPositionY(m_pageButton->getPositionY() - m_pageButton->getContentSize().height / 2 - m_randomButton->getContentSize().height / 2 - 5.0f); menu->addChild(m_randomButton); // oh boy // https://github.com/Cvolton/betterinfo-geode/blob/v4.0.0/src/hooks/LevelBrowserLayer.cpp#L118 @@ -173,7 +155,7 @@ bool IDListLayer::init() { arrowParent->addChild(secondArrow); arrowParent->setScale(0.4f); m_lastButton = CCMenuItemExt::createSpriteExtra(arrowParent, [this](auto) { page(getMaxPage()); }); - m_lastButton->setPositionY(createGap(m_randomButton, m_lastButton, 2.0f)); + m_lastButton->setPositionY(m_randomButton->getPositionY() - m_randomButton->getContentSize().height / 2 - m_lastButton->getContentSize().height / 2 - 2.0f); menu->addChild(m_lastButton); auto x = winSize.width - 3.0f - m_randomButton->getContentSize().width / 2; m_pageButton->setPositionX(x); @@ -209,7 +191,14 @@ bool IDListLayer::init() { m_randomButton->setVisible(false); setKeyboardEnabled(true); - if (!AREDL.empty()) populateList(""); + if (PEMONLIST) { + if (!IntegratedDemonlist::PEMONLIST.empty()) populateList(""); + else IntegratedDemonlist::loadPemonlist(std::move(m_listener), [this]() { populateList(""); }); + } + else { + if (!IntegratedDemonlist::AREDL.empty()) populateList(""); + else IntegratedDemonlist::loadAREDL(std::move(m_listener), [this]() { populateList(""); }); + } return true; } @@ -250,16 +239,18 @@ void IDListLayer::populateList(std::string query) { m_pageButton->setVisible(false); m_randomButton->setVisible(false); m_fullSearchResults.clear(); + + auto& list = PEMONLIST ? IntegratedDemonlist::PEMONLIST : IntegratedDemonlist::AREDL; if (query != m_query && !query.empty()) { auto queryLowercase = string::toLower(query); - for (int i = 0; i < AREDL.size(); i++) { - if (string::startsWith(string::toLower(AREDL_NAMES[i]), queryLowercase)) m_fullSearchResults.push_back(std::to_string(AREDL[i])); + for (auto const& level : list) { + if (string::startsWith(string::toLower(level.name), queryLowercase)) m_fullSearchResults.push_back(std::to_string(level.id)); } } m_query = query; if (query.empty()) { - for (int i = 0; i < AREDL.size(); i++) { - m_fullSearchResults.push_back(std::to_string(AREDL[i])); + for (auto const& level : list) { + m_fullSearchResults.push_back(std::to_string(level.id)); } } @@ -290,7 +281,8 @@ int IDListLayer::getMaxPage() { void IDListLayer::loadLevelsFinished(CCArray* levels, const char*) { auto winSize = CCDirector::sharedDirector()->getWinSize(); if (m_list->getParent() == this) removeChild(m_list); - m_list = GJListLayer::create(CustomListView::create(levels, BoomListType::Level, 190.0f, 358.0f), "All Rated Extreme Demons List", { 0, 0, 0, 180 }, 358.0f, 220.0f, 0); + m_list = GJListLayer::create(CustomListView::create(levels, BoomListType::Level, 190.0f, 358.0f), + PEMONLIST ? "Pemonlist" : "All Rated Extreme Demons List", { 0, 0, 0, 180 }, 358.0f, 220.0f, 0); m_list->setZOrder(2); m_list->setPosition(winSize / 2 - m_list->getContentSize() / 2); addChild(m_list); @@ -326,7 +318,11 @@ void IDListLayer::setupPageInfo(gd::string, const char*) { void IDListLayer::search() { auto searchString = m_searchBar->getString(); if (m_query != searchString) { - loadAREDL(std::move(m_listener), false, [this, searchString]() { + if (PEMONLIST) IntegratedDemonlist::loadPemonlist(std::move(m_listener), [this, searchString]() { + m_page = 0; + populateList(searchString); + }); + else IntegratedDemonlist::loadAREDL(std::move(m_listener), [this, searchString]() { m_page = 0; populateList(searchString); }); diff --git a/src/IDListLayer.hpp b/src/IDListLayer.hpp index 0e10a04..5ebaac5 100644 --- a/src/IDListLayer.hpp +++ b/src/IDListLayer.hpp @@ -1,21 +1,11 @@ -#include -#include - -using namespace geode::prelude; +#include "IntegratedDemonlist.hpp" class IDListLayer : public CCLayer, SetIDPopupDelegate, LevelManagerDelegate { +private: + inline static bool PEMONLIST = false; public: - inline static std::vector AREDL = {}; - inline static std::vector AREDL_NAMES = {}; - inline static std::vector AREDL_POSITIONS = {}; - inline static bool AREDL_TRIED_LOADING = false; - static IDListLayer* create(); static CCScene* scene(); - template - static std::vector pluck(matjson::Array const&, std::string const&); - static void loadAREDL(EventListener&&, bool, MiniFunction callback = []() {}); - static float createGap(CCNode*, CCNode*, float); void search(); void page(int); @@ -25,6 +15,7 @@ class IDListLayer : public CCLayer, SetIDPopupDelegate, LevelManagerDelegate { ~IDListLayer(); protected: + EventListener m_listener; GJListLayer* m_list; CCLabelBMFont* m_listLabel; LoadingCircle* m_loadingCircle; @@ -35,8 +26,6 @@ class IDListLayer : public CCLayer, SetIDPopupDelegate, LevelManagerDelegate { CCMenuItemSpriteExtra* m_backButton; CCMenuItemSpriteExtra* m_leftButton; CCMenuItemSpriteExtra* m_rightButton; - CCMenuItemSpriteExtra* m_infoButton; - CCMenuItemSpriteExtra* m_refreshButton; CCMenuItemSpriteExtra* m_pageButton; CCMenuItemSpriteExtra* m_randomButton; CCMenuItemSpriteExtra* m_firstButton; @@ -44,7 +33,6 @@ class IDListLayer : public CCLayer, SetIDPopupDelegate, LevelManagerDelegate { int m_page = 0; std::string m_query = ""; std::vector m_fullSearchResults; - EventListener m_listener; bool init() override; void addSearchBar(); diff --git a/src/IntegratedDemonlist.cpp b/src/IntegratedDemonlist.cpp new file mode 100644 index 0000000..d54a703 --- /dev/null +++ b/src/IntegratedDemonlist.cpp @@ -0,0 +1,65 @@ +#include "IntegratedDemonlist.hpp" + +void IntegratedDemonlist::initializeDemons(web::WebResponse* res, bool pemonlist) { + auto& list = pemonlist ? PEMONLIST : AREDL; + list.clear(); + for (auto const& level : res->json().value().as_array()) { + list.push_back({ + level["level_id"].as_int(), + level["name"].as_string(), + level[pemonlist ? "placement" : "position"].as_int() + }); + } +} + +void IntegratedDemonlist::loadAREDL() { + static std::optional task = std::nullopt; + task = web::WebRequest().get("https://api.aredl.net/api/aredl/levels").map([](web::WebResponse* res) { + if (res->ok()) initializeDemons(res, false); + else Notification::create("Failed to load AREDL", NotificationIcon::Error)->show(); + + task = std::nullopt; + return *res; + }); +} + +void IntegratedDemonlist::loadAREDL(EventListener&& listenerRef, utils::MiniFunction callback) { + auto&& listener = std::move(listenerRef); + listener.bind([callback](web::WebTask::Event* e) { + if (auto res = e->getValue()) { + if (res->ok()) { + initializeDemons(res, false); + callback(); + } + else FLAlertLayer::create("Load Failed", "Failed to load AREDL. Please try again later.", "OK")->show(); + } + }); + + listener.setFilter(web::WebRequest().get("https://api.aredl.net/api/aredl/levels")); +} + +void IntegratedDemonlist::loadPemonlist() { + static std::optional task = std::nullopt; + task = web::WebRequest().get("https://pemonlist.com/api/list").map([](web::WebResponse* res) { + if (res->ok()) initializeDemons(res, true); + else Notification::create("Failed to load Pemonlist", NotificationIcon::Error)->show(); + + task = std::nullopt; + return *res; + }); +} + +void IntegratedDemonlist::loadPemonlist(EventListener&& listenerRef, utils::MiniFunction callback) { + auto&& listener = std::move(listenerRef); + listener.bind([callback](web::WebTask::Event* e) { + if (auto res = e->getValue()) { + if (res->ok()) { + initializeDemons(res, true); + callback(); + } + else FLAlertLayer::create("Load Failed", "Failed to load Pemonlist. Please try again later.", "OK")->show(); + } + }); + + listener.setFilter(web::WebRequest().get("https://pemonlist.com/api/list")); +} diff --git a/src/IntegratedDemonlist.hpp b/src/IntegratedDemonlist.hpp new file mode 100644 index 0000000..add0de2 --- /dev/null +++ b/src/IntegratedDemonlist.hpp @@ -0,0 +1,23 @@ +#include +#include + +using namespace geode::prelude; + +struct IDListDemon { + int id; + std::string name; + int position; +}; + +class IntegratedDemonlist { +public: + inline static std::vector AREDL = {}; + inline static std::vector PEMONLIST = {}; + inline static bool TRIED_LOADING = false; + + static void initializeDemons(web::WebResponse*, bool); + static void loadAREDL(); + static void loadAREDL(EventListener&&, MiniFunction callback); + static void loadPemonlist(); + static void loadPemonlist(EventListener&&, MiniFunction callback); +}; diff --git a/src/main.cpp b/src/main.cpp index 0282030..ce7f404 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,9 +9,10 @@ class $modify(IDMenuLayer, MenuLayer) { bool init() { if (!MenuLayer::init()) return false; - if (IDListLayer::AREDL_TRIED_LOADING) return true; - IDListLayer::AREDL_TRIED_LOADING = true; - IDListLayer::loadAREDL(std::move(m_fields->m_listener), true); + if (IntegratedDemonlist::TRIED_LOADING) return true; + IntegratedDemonlist::TRIED_LOADING = true; + IntegratedDemonlist::loadAREDL(); + IntegratedDemonlist::loadPemonlist(); return true; } @@ -53,11 +54,19 @@ class $modify(IDLevelCell, LevelCell) { LevelCell::loadCustomLevelCell(); if (Mod::get()->getSettingValue("enable-rank")) { - auto begin = IDListLayer::AREDL.begin(); - auto end = IDListLayer::AREDL.end(); - auto found = std::find(begin, end, m_level->m_levelID.value()); - if (found != end) { - auto rankTextNode = CCLabelBMFont::create(fmt::format("#{} on AREDL", IDListLayer::AREDL_POSITIONS[found - begin]).c_str(), "chatFont.fnt"); + auto rankText = std::string(); + auto found = std::find_if(IntegratedDemonlist::AREDL.begin(), IntegratedDemonlist::AREDL.end(), [this](auto const& demon) { + return demon.id == m_level->m_levelID; + }); + if (found != IntegratedDemonlist::AREDL.end()) rankText = fmt::format("#{} AREDL", found->position); + else { + found = std::find_if(IntegratedDemonlist::PEMONLIST.begin(), IntegratedDemonlist::PEMONLIST.end(), [this](auto const& demon) { + return demon.id == m_level->m_levelID; + }); + if (found != IntegratedDemonlist::PEMONLIST.end()) rankText = fmt::format("#{} Pemonlist", found->position); + } + if (!rankText.empty()) { + auto rankTextNode = CCLabelBMFont::create(rankText.c_str(), "chatFont.fnt"); rankTextNode->setPosition(346.0f, m_level->m_dailyID.value() > 0 ? 6.0f : m_compactView ? 9.0f : 12.0f); rankTextNode->setAnchorPoint({ 1.0f, 1.0f }); rankTextNode->setScale(m_compactView ? 0.45f : 0.6f);