Skip to content

Commit

Permalink
v1.5.0
Browse files Browse the repository at this point in the history
  • Loading branch information
hiimjustin000 committed Jun 19, 2024
1 parent e0fbf43 commit ed954f5
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 83 deletions.
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 2 additions & 1 deletion about.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
6 changes: 3 additions & 3 deletions mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down Expand Up @@ -41,4 +41,4 @@
"content",
"online"
]
}
}
102 changes: 49 additions & 53 deletions src/IDListLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,40 +18,6 @@ CCScene* IDListLayer::scene() {
return ret;
}

template<typename T>
std::vector<T> IDListLayer::pluck(matjson::Array const& arr, std::string const& key) {
std::vector<T> 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<T>());
}
return ret;
}

void IDListLayer::loadAREDL(EventListener<web::WebTask>&& listenerRef, bool fromMenuLayer, utils::MiniFunction<void()> 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<int>(json, "level_id");
AREDL_NAMES = pluck<std::string>(json, "name");
AREDL_POSITIONS = pluck<int>(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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 <cg>All Rated Extreme Demons List</c> (AREDL) is an unofficial ranking of all rated <cr>Extreme Demons</c> in Geometry Dash.\n";
std::string line2 = "It is managed by <cy>iiLogan</c>, <cy>SEDTHEPRODIGY</c>, <cy>Megu</c>, and <cy>Minebox260</c>.";
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 <cg>Pemonlist</c> is an unofficial ranking of all rated <cj>platformer mode</c> <cr>Demons</c> in Geometry Dash.\n"
"It is managed by <cy>camila314</c>, <cy>Extatica</c>, and <cy>mariokirby1703</c>." :
"The <cg>All Rated Extreme Demons List</c> (AREDL) is an unofficial ranking of all rated <cj>classic mode</c> <cr>Extreme Demons</c> in Geometry Dash.\n"
"It is managed by <cy>iiLogan</c>, <cy>SEDTHEPRODIGY</c>, <cy>Megu</c>, and <cy>Minebox260</c>.",
"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);
Expand All @@ -157,7 +139,7 @@ bool IDListLayer::init() {
std::uniform_int_distribution<int> 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
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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));
}
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
});
Expand Down
20 changes: 4 additions & 16 deletions src/IDListLayer.hpp
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
#include <Geode/Geode.hpp>
#include <Geode/utils/web.hpp>

using namespace geode::prelude;
#include "IntegratedDemonlist.hpp"

class IDListLayer : public CCLayer, SetIDPopupDelegate, LevelManagerDelegate {
private:
inline static bool PEMONLIST = false;
public:
inline static std::vector<int> AREDL = {};
inline static std::vector<std::string> AREDL_NAMES = {};
inline static std::vector<int> AREDL_POSITIONS = {};
inline static bool AREDL_TRIED_LOADING = false;

static IDListLayer* create();
static CCScene* scene();
template<class T>
static std::vector<T> pluck(matjson::Array const&, std::string const&);
static void loadAREDL(EventListener<web::WebTask>&&, bool, MiniFunction<void()> callback = []() {});
static float createGap(CCNode*, CCNode*, float);

void search();
void page(int);
Expand All @@ -25,6 +15,7 @@ class IDListLayer : public CCLayer, SetIDPopupDelegate, LevelManagerDelegate {

~IDListLayer();
protected:
EventListener<web::WebTask> m_listener;
GJListLayer* m_list;
CCLabelBMFont* m_listLabel;
LoadingCircle* m_loadingCircle;
Expand All @@ -35,16 +26,13 @@ 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;
CCMenuItemSpriteExtra* m_lastButton;
int m_page = 0;
std::string m_query = "";
std::vector<std::string> m_fullSearchResults;
EventListener<web::WebTask> m_listener;

bool init() override;
void addSearchBar();
Expand Down
65 changes: 65 additions & 0 deletions src/IntegratedDemonlist.cpp
Original file line number Diff line number Diff line change
@@ -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<web::WebTask> 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<web::WebTask>&& listenerRef, utils::MiniFunction<void()> 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<web::WebTask> 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<web::WebTask>&& listenerRef, utils::MiniFunction<void()> 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"));
}
23 changes: 23 additions & 0 deletions src/IntegratedDemonlist.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include <Geode/Geode.hpp>
#include <Geode/utils/web.hpp>

using namespace geode::prelude;

struct IDListDemon {
int id;
std::string name;
int position;
};

class IntegratedDemonlist {
public:
inline static std::vector<IDListDemon> AREDL = {};
inline static std::vector<IDListDemon> PEMONLIST = {};
inline static bool TRIED_LOADING = false;

static void initializeDemons(web::WebResponse*, bool);
static void loadAREDL();
static void loadAREDL(EventListener<web::WebTask>&&, MiniFunction<void()> callback);
static void loadPemonlist();
static void loadPemonlist(EventListener<web::WebTask>&&, MiniFunction<void()> callback);
};
Loading

0 comments on commit ed954f5

Please sign in to comment.