From acd48d90189e5b11bab3334c64ed601fe504322e Mon Sep 17 00:00:00 2001 From: Justin Pridgen Date: Sat, 12 Oct 2024 12:42:36 -0400 Subject: [PATCH] It's Goin' Down (feat. Nitti) --- CMakeLists.txt | 4 +- README.md | 7 ++- about.md | 7 ++- changelog.md | 5 ++ mod.json | 7 ++- resources/MI_moreIcons_001.png | Bin 0 -> 24287 bytes src/MoreIcons.cpp | 49 +++++++++++++++--- src/MoreIcons.hpp | 18 ++++++- src/classes/LogCell.cpp | 77 ++++++++++++++++++++++++++++ src/classes/LogCell.hpp | 12 +++++ src/classes/LogLayer.cpp | 91 +++++++++++++++++++++++++++++++++ src/classes/LogLayer.hpp | 8 +++ src/hooks/GJGarageLayer.cpp | 25 ++++++++- 13 files changed, 293 insertions(+), 17 deletions(-) create mode 100644 resources/MI_moreIcons_001.png create mode 100644 src/classes/LogCell.cpp create mode 100644 src/classes/LogCell.hpp create mode 100644 src/classes/LogLayer.cpp create mode 100644 src/classes/LogLayer.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d6cbfa7..762aabb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,10 +4,12 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64") set(CMAKE_CXX_VISIBILITY_PRESET hidden) -project(MoreIcons VERSION 1.2.7) +project(MoreIcons VERSION 1.3.0) add_library(${PROJECT_NAME} SHARED src/classes/DummyNode.cpp + src/classes/LogCell.cpp + src/classes/LogLayer.cpp src/hooks/AppDelegate.cpp src/hooks/CharacterColorPage.cpp src/hooks/GameManager.cpp diff --git a/README.md b/README.md index c66f267..1d3385b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # More Icons A mod that loads custom icons. -[YouTube Tutorial](https://youtu.be/s2AM98Yj59k) +[Windows Tutorial](https://youtu.be/Dn0S3DPuq08)\ +[Android Tutorial](https://youtu.be/GJKoLUnkyBk)\ +[macOS Tutorial](https://youtu.be/1sI4WJE0yqE) ## Adding Icons To add a custom icon, you need an icon spritesheet (.plist) and an icon atlas (.png). Tools like [GDBrowser's Icon Kit](https://gdbrowser.com/iconkit) can be used to create these spritesheets and atlases, with the "Developer Mode" option enabled in the settings. @@ -31,10 +33,11 @@ The spritesheets and atlases should be placed in `(Geometry Dash folder)/geode/c - spider - swing - jetpack +- trail This can also be done with individual images per icon piece, with the same naming conventions as above. The sprites should be placed in `(Geometry Dash folder)/geode/config/hiimjustin000.more_icons/(gamemode)/(icon name)`, where `(icon name)` is the name of the icon. -If anything goes wrong, the mod will log warnings and errors to the console, which can be checked in `(Geometry Dash folder)/geode/logs`. +If anything goes wrong, the mod will log warnings and errors to the console, which can be checked in the icon kit with a button on the left side of the screen. ## Texture Packs For adding icons to texture packs, the process is similar to adding icons to the mod. However, you will need the mod "Texture Loader" by Geode Team installed to use these texture packs. diff --git a/about.md b/about.md index ed4220e..742f5b5 100644 --- a/about.md +++ b/about.md @@ -1,7 +1,9 @@ # More Icons A mod that loads custom icons. -[YouTube Tutorial](https://youtu.be/s2AM98Yj59k) +[Windows Tutorial](https://youtu.be/Dn0S3DPuq08)\ +[Android Tutorial](https://youtu.be/GJKoLUnkyBk)\ +[macOS Tutorial](https://youtu.be/1sI4WJE0yqE) ## Adding Icons To add a custom icon, you need an icon spritesheet (.plist) and an icon atlas (.png). Tools like [GDBrowser's Icon Kit](https://gdbrowser.com/iconkit) can be used to create these spritesheets and atlases, with the "Developer Mode" option enabled in the settings. @@ -31,10 +33,11 @@ The spritesheets and atlases should be placed in `(Geometry Dash folder)/geode/c - spider - swing - jetpack +- trail This can also be done with individual images per icon piece, with the same naming conventions as above. The sprites should be placed in `(Geometry Dash folder)/geode/config/hiimjustin000.more_icons/(gamemode)/(icon name)`, where `(icon name)` is the name of the icon. -If anything goes wrong, the mod will log warnings and errors to the console, which can be checked in `(Geometry Dash folder)/geode/logs`. +If anything goes wrong, the mod will log warnings and errors to the console, which can be checked in the icon kit with a button on the left side of the screen. ## Texture Packs For adding icons to texture packs, the process is similar to adding icons to the mod. However, you will need the mod "Texture Loader" by Geode Team installed to use these texture packs. diff --git a/changelog.md b/changelog.md index 76c7f9e..4783064 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,9 @@ # More Icons Changelog +## v1.3.0 (2024-10-12) +- Added ability to view logs and amount of loaded icons in the icon kit +- Changed logs to be more specific +- Updated tutorial links in the mod's description + ## v1.2.7 (2024-10-10) - Fixed plist icons changing names diff --git a/mod.json b/mod.json index b9683c3..19f36a3 100644 --- a/mod.json +++ b/mod.json @@ -5,7 +5,7 @@ "win": "2.206", "mac": "2.206" }, - "version": "v1.2.7", + "version": "v1.3.0", "id": "hiimjustin000.more_icons", "name": "More Icons", "developer": "hiimjustin000", @@ -17,6 +17,11 @@ "importance": "required" } ], + "resources": { + "sprites": [ + "resources/*.png" + ] + }, "links": { "community": "https://discord.gg/QVKmbvBXA7", "source": "https://github.com/hiimjustin000/MoreIcons", diff --git a/resources/MI_moreIcons_001.png b/resources/MI_moreIcons_001.png new file mode 100644 index 0000000000000000000000000000000000000000..79508da7e84b96bd1179703519a84bceba52e67e GIT binary patch literal 24287 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

t4Y?OZ7FuG#`auuCphTd7)@F2 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 literal 0 HcmV?d00001 diff --git a/src/MoreIcons.cpp b/src/MoreIcons.cpp index 824521b..5ca270b 100644 --- a/src/MoreIcons.cpp +++ b/src/MoreIcons.cpp @@ -206,7 +206,13 @@ void MoreIcons::loadIcon(const std::filesystem::path& path, IconType type) { auto fileQuality = kTextureQualityLow; if (pathFilename.ends_with("-uhd.png")) { if (textureQuality != kTextureQualityHigh) { - log::warn("Ignoring too high quality PNG file: {}", subEntryPath.parent_path().filename() / pathFilename); + auto logMessage = fmt::format("{}: Ignoring high-quality PNG file for {} texture quality", subEntryPath.string(), + textureQuality == kTextureQualityMedium ? "medium" : "low"); + log::warn("{}", logMessage); + { + std::lock_guard lock(LOG_MUTEX); + LOGS.push_back({ .message = logMessage, .type = LogType::Info }); + } continue; } @@ -214,7 +220,12 @@ void MoreIcons::loadIcon(const std::filesystem::path& path, IconType type) { } else if (pathFilename.ends_with("-hd.png")) { if (textureQuality != kTextureQualityHigh && textureQuality != kTextureQualityMedium) { - log::warn("Ignoring too high quality PNG file: {}", subEntryPath.parent_path().filename() / pathFilename); + auto logMessage = fmt::format("{}: Ignoring medium-quality PNG file for low texture quality", subEntryPath.string()); + log::warn("{}", logMessage); + { + std::lock_guard lock(LOG_MUTEX); + LOGS.push_back({ .message = logMessage, .type = LogType::Info }); + } continue; } @@ -267,7 +278,13 @@ void MoreIcons::loadIcon(const std::filesystem::path& path, IconType type) { auto fileQuality = kTextureQualityLow; if (pathFilename.ends_with("-uhd.plist")) { if (textureQuality != kTextureQualityHigh) { - log::warn("Ignoring too high quality plist file: {}", pathFilename); + auto logMessage = fmt::format("{}: Ignoring high-quality PLIST file for {} texture quality", path.string(), + textureQuality == kTextureQualityMedium ? "medium" : "low"); + log::warn("{}", logMessage); + { + std::lock_guard lock(LOG_MUTEX); + LOGS.push_back({ .message = logMessage, .type = LogType::Info }); + } return; } @@ -275,7 +292,12 @@ void MoreIcons::loadIcon(const std::filesystem::path& path, IconType type) { } else if (pathFilename.ends_with("-hd.plist")) { if (textureQuality != kTextureQualityHigh && textureQuality != kTextureQualityMedium) { - log::warn("Ignoring too high quality plist file: {}", pathFilename); + auto logMessage = fmt::format("{}: Ignoring medium-quality PLIST file for low texture quality", path.string()); + log::warn("{}", logMessage); + { + std::lock_guard lock(LOG_MUTEX); + LOGS.push_back({ .message = logMessage, .type = LogType::Info }); + } return; } @@ -317,9 +339,16 @@ void MoreIcons::loadIcon(const std::filesystem::path& path, IconType type) { } dict->setObject(frames, "frames"); auto metadata = static_cast(dict->objectForKey("metadata")); - auto fullTexturePath = (path.parent_path() / std::filesystem::path(metadata->valueForKey("textureFileName")->getCString()).filename()).string(); + auto texturePath = std::filesystem::path(metadata->valueForKey("textureFileName")->getCString()).filename().string(); + auto fullTexturePath = (path.parent_path() / texturePath).string(); if (!std::filesystem::exists(fullTexturePath)) { - log::warn("Texture file not found: {}", fullTexturePath); + auto logMessage = fmt::format("{}: Texture file {} not found", path.string(), texturePath); + log::warn("{}", logMessage); + { + std::lock_guard lock(LOG_MUTEX); + LOGS.push_back({ .message = logMessage, .type = LogType::Error }); + if (HIGHEST_SEVERITY < LogType::Error) HIGHEST_SEVERITY = LogType::Error; + } return; } @@ -388,7 +417,13 @@ void MoreIcons::loadTrail(const std::filesystem::path& path) { std::string error; auto tryJson = matjson::parse(bufferStream.str(), error); if (!error.empty()) { - log::warn("Failed to parse JSON file {}: {}", jsonPath.filename().string(), error); + auto logMessage = fmt::format("{}: Failed to parse JSON file ({})", path.string(), error); + log::warn("{}", logMessage); + { + std::lock_guard lock(LOG_MUTEX); + LOGS.push_back({ .message = logMessage, .type = LogType::Warn }); + if (HIGHEST_SEVERITY < LogType::Warn) HIGHEST_SEVERITY = LogType::Warn; + } json = matjson::Object { { "blend", false }, { "tint", false } }; } else json = tryJson.value_or(matjson::Object { { "blend", false }, { "tint", false } }); diff --git a/src/MoreIcons.hpp b/src/MoreIcons.hpp index 4e71814..4edb14c 100644 --- a/src/MoreIcons.hpp +++ b/src/MoreIcons.hpp @@ -18,6 +18,17 @@ struct ImageData { bool tint; }; +enum class LogType { + Info, + Warn, + Error +}; + +struct LogData { + std::string message; + LogType type; +}; + // https://github.com/GlobedGD/globed2/blob/v1.6.2/src/util/cocos.cpp#L44 namespace { template @@ -35,7 +46,6 @@ namespace { void _addSpriteFramesWithDictionary(cocos2d::CCDictionary*, cocos2d::CCTexture2D*); } - class MoreIcons { public: static inline std::vector ICONS; @@ -54,7 +64,9 @@ class MoreIcons { static inline std::vector TRAIL_DUPLICATES; static inline std::vector IMAGES; static inline std::mutex IMAGE_MUTEX; - static inline IconType LOAD_TYPE = IconType::Cube; + static inline std::vector LOGS; + static inline std::mutex LOG_MUTEX; + static inline LogType HIGHEST_SEVERITY = LogType::Info; static bool hasIcon(const std::string& name) { return !ICONS.empty() && std::find(ICONS.begin(), ICONS.end(), name) != ICONS.end(); @@ -115,6 +127,8 @@ class MoreIcons { saveTrails(); TRAIL_INFO.clear(); removeSaved(); + LOGS.clear(); + HIGHEST_SEVERITY = LogType::Info; } static void removeSaved() { diff --git a/src/classes/LogCell.cpp b/src/classes/LogCell.cpp new file mode 100644 index 0000000..5b16570 --- /dev/null +++ b/src/classes/LogCell.cpp @@ -0,0 +1,77 @@ +#include "LogCell.hpp" + +using namespace geode::prelude; + +LogCell* LogCell::create(LogData const& data, int index, int total, bool dark) { + auto ret = new LogCell(); + if (ret->init(data, index, total, dark)) { + ret->autorelease(); + return ret; + } + delete ret; + return nullptr; +} + +bool LogCell::init(LogData const& data, int index, int total, bool dark) { + if (!CCLayer::init()) return false; + + ignoreAnchorPointForPosition(false); + + setContentSize({ 400.0f, 70.0f }); + + m_index = index; + m_total = total; + + auto bg = CCLayerColor::create({ 0, 0, 0, 255 }, 400.0f, 70.0f); + if (dark) bg->setColor(index % 2 == 0 ? ccColor3B { 48, 48, 48 } : ccColor3B { 80, 80, 80 }); + else bg->setColor(index % 2 == 0 ? ccColor3B { 161, 88, 44 } : ccColor3B { 194, 114, 62 }); + bg->ignoreAnchorPointForPosition(false); + if (index == 0) { + bg->setContentSize({ 400.0f, 35.0f }); + bg->setPosition(200.0f, 17.5f); + } + else if (index == total - 1) { + bg->setContentSize({ 400.0f, 35.0f }); + bg->setPosition(200.0f, 52.5f); + } + else { + bg->setContentSize({ 400.0f, 70.0f }); + bg->setPosition(200.0f, 35.0f); + } + addChild(bg, -1); + + if (index == 0 || index == total - 1) { + auto bgBg = CCScale9Sprite::create("square02b_001.png", { 0, 0, 80, 80 }); + bgBg->setContentSize({ 400.0f, 70.0f }); + bgBg->setPosition(200.0f, 35.0f); + bgBg->setColor(bg->getColor()); + addChild(bgBg, -2); + } + + auto infoFrame = ""; + switch (data.type) { + case LogType::Info: infoFrame = "GJ_infoIcon_001.png"; break; + case LogType::Warn: infoFrame = "geode.loader/info-warning.png"; break; + case LogType::Error: infoFrame = "geode.loader/info-alert.png"; break; + } + + auto infoIcon = CCSprite::createWithSpriteFrameName(infoFrame); + infoIcon->setPosition({ 20.0f, 35.0f }); + addChild(infoIcon); + + auto textArea = TextArea::create(data.message, "bigFont.fnt", 0.25f, 350.0f, { 0.0f, 1.0f }, 10.0f, true); + textArea->setContentSize({ textArea->m_width, textArea->m_height * textArea->m_label->m_lines->count() }); + textArea->m_label->setPosition({ 0.0f, textArea->getContentHeight() }); + textArea->setPosition({ 40.0f, 35.0f }); + textArea->setAnchorPoint({ 0.0f, 0.5f }); + addChild(textArea); + + return true; +} + +void LogCell::draw() { + ccDrawColor4B(0, 0, 0, 75); + glLineWidth(2.0f); + if (m_index < m_total - 1) ccDrawLine({ 0.0f, 0.0f }, { 400.0f, 0.0f }); + if (m_index > 0) ccDrawLine({ 0.0f, 70.0f }, { 400.0f, 70.0f }); +} diff --git a/src/classes/LogCell.hpp b/src/classes/LogCell.hpp new file mode 100644 index 0000000..13cef37 --- /dev/null +++ b/src/classes/LogCell.hpp @@ -0,0 +1,12 @@ +#include "../MoreIcons.hpp" + +class LogCell : public cocos2d::CCLayer { +protected: + int m_index = 0; + int m_total = 0; + + bool init(LogData const& data, int index, int total, bool dark); +public: + static LogCell* create(LogData const& data, int index, int total, bool dark); + void draw() override; +}; diff --git a/src/classes/LogLayer.cpp b/src/classes/LogLayer.cpp new file mode 100644 index 0000000..66276db --- /dev/null +++ b/src/classes/LogLayer.cpp @@ -0,0 +1,91 @@ +#include "LogCell.hpp" +#include "LogLayer.hpp" + +using namespace geode::prelude; + +LogLayer* LogLayer::create() { + auto ret = new LogLayer(); + if (ret->initAnchored(440.0f, 290.0f, "GJ_square04.png")) { + ret->autorelease(); + return ret; + } + delete ret; + return nullptr; +} + +bool LogLayer::setup() { + setTitle("More Icons"); + + auto background = CCScale9Sprite::create("square02_001.png", { 0, 0, 80, 80 }); + background->setContentSize({ 400.0f, 230.0f }); + background->setPosition(220.0f, 135.0f); + background->setOpacity(127); + m_mainLayer->addChild(background); + + auto scrollLayer = ScrollLayer::create({ 400.0f, 230.0f }); + scrollLayer->setPosition(20.0f, 20.0f); + scrollLayer->m_contentLayer->setLayout( + ColumnLayout::create() + ->setAxisReverse(true) + ->setAxisAlignment(AxisAlignment::End) + ->setAutoGrowAxis(230.0f) + ->setGap(0.0f) + ); + m_mainLayer->addChild(scrollLayer); + + auto& vec = MoreIcons::LOGS; + auto size = std::min((int)vec.size(), 100); + auto dark = Loader::get()->isModLoaded("bitz.darkmode_v4"); + for (int i = 0; i < size; i++) { + scrollLayer->m_contentLayer->addChild(LogCell::create(vec[i], i, size, dark)); + } + + scrollLayer->m_contentLayer->updateLayout(); + scrollLayer->scrollToTop(); + + auto topButtons = CCMenu::create(); + topButtons->setPosition(420.0f, 270.0f); + topButtons->setContentSize({ 100.0f, 30.0f }); + topButtons->setAnchorPoint({ 1.0f, 0.5f }); + topButtons->setLayout( + RowLayout::create() + ->setAxisAlignment(AxisAlignment::End) + ->setAxisReverse(true) + ); + m_mainLayer->addChild(topButtons); + + topButtons->addChild(CCMenuItemExt::createSpriteExtraWithFrameName("GJ_infoIcon_001.png", 1.0f, [this](auto) { + FLAlertLayer::create( + "More Icons", + fmt::format( + "Icons: {}\n" + "Ships: {}\n" + "Balls: {}\n" + "UFOs: {}\n" + "Waves: {}\n" + "Robots: {}\n" + "Spiders: {}\n" + "Swings: {}\n" + "Jetpacks: {}\n" + "Trails: {}", + MoreIcons::ICONS.size(), + MoreIcons::SHIPS.size(), + MoreIcons::BALLS.size(), + MoreIcons::UFOS.size(), + MoreIcons::WAVES.size(), + MoreIcons::ROBOTS.size(), + MoreIcons::SPIDERS.size(), + MoreIcons::SWINGS.size(), + MoreIcons::JETPACKS.size(), + MoreIcons::TRAILS.size() + ), + "OK" + )->show(); + })); + topButtons->addChild(CCMenuItemExt::createSpriteExtraWithFrameName("folderIcon_001.png", 0.7f, [this](auto) { + file::openFolder(Mod::get()->getConfigDir()); + })); + topButtons->updateLayout(); + + return true; +} diff --git a/src/classes/LogLayer.hpp b/src/classes/LogLayer.hpp new file mode 100644 index 0000000..5be84f9 --- /dev/null +++ b/src/classes/LogLayer.hpp @@ -0,0 +1,8 @@ + + +class LogLayer : public geode::Popup<> { +protected: + bool setup() override; +public: + static LogLayer* create(); +}; diff --git a/src/hooks/GJGarageLayer.cpp b/src/hooks/GJGarageLayer.cpp index a92dd26..d2cc6d2 100644 --- a/src/hooks/GJGarageLayer.cpp +++ b/src/hooks/GJGarageLayer.cpp @@ -1,4 +1,5 @@ #include "../MoreIcons.hpp" +#include "../classes/LogLayer.hpp" using namespace geode::prelude; @@ -52,6 +53,26 @@ class $modify(MIGarageLayer, GJGarageLayer) { swap2PButton->setTarget(this, menu_selector(MIGarageLayer::newSwap2PKit)); } + auto moreIconsSprite = CircleButtonSprite::createWithSprite("MI_moreIcons_001.png"_spr, 1.0f, CircleBaseColor::Gray, CircleBaseSize::Small); + if (!MoreIcons::LOGS.empty()) { + auto severityFrame = ""; + switch (MoreIcons::HIGHEST_SEVERITY) { + case LogType::Info: severityFrame = "GJ_infoIcon_001.png"; break; + case LogType::Warn: severityFrame = "geode.loader/info-warning.png"; break; + case LogType::Error: severityFrame = "geode.loader/info-alert.png"; break; + } + auto severitySprite = CCSprite::createWithSpriteFrameName(severityFrame); + severitySprite->setPosition(moreIconsSprite->getContentSize() - CCPoint { 6.0f, 6.0f }); + severitySprite->setScale(0.6f); + moreIconsSprite->addChild(severitySprite, 1); + } + auto moreIconsButton = CCMenuItemExt::createSpriteExtra(moreIconsSprite, [this](auto) { LogLayer::create()->show(); }); + moreIconsButton->setID("more-icons-button"_spr); + if (auto shardsMenu = getChildByID("shards-menu")) { + shardsMenu->addChild(moreIconsButton); + shardsMenu->updateLayout(); + } + return true; } @@ -323,7 +344,7 @@ class $modify(MIGarageLayer, GJGarageLayer) { square->setColor({ 150, 150, 150 }); auto texture = CCTextureCache::get()->textureForKey(MoreIcons::TRAIL_INFO[name].texture.c_str()); auto streak = CCSprite::createWithTexture(texture); - limitNodeWidth(streak, 27.0f, 99.0f, 0.01f); + limitNodeWidth(streak, 27.0f, 999.0f, 0.001f); streak->setRotation(-90.0f); square->addChild(streak); streak->setPosition(square->getContentSize() / 2); @@ -341,7 +362,7 @@ class $modify(MIGarageLayer, GJGarageLayer) { auto square = CCSprite::createWithSpriteFrameName("playerSquare_001.png"); square->setColor({ 150, 150, 150 }); auto streak = CCSprite::createWithTexture(texture); - limitNodeWidth(streak, 27.0f, 99.0f, 0.01f); + limitNodeWidth(streak, 27.0f, 999.0f, 0.001f); streak->setRotation(-90.0f); square->addChild(streak); streak->setPosition(square->getContentSize() / 2);