diff --git a/CMakeLists.txt b/CMakeLists.txt index 507f3eb..5642f6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ else() set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64") endif() -project(AvalancheIndex VERSION 1.1.2) +project(AvalancheIndex VERSION 1.2.0) file(GLOB_RECURSE SOURCES src/*.cpp diff --git a/README.md b/README.md index c88c26c..aec909d 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ When viewing level cells or pages, you may notice the changes in the background Level cells with a glow emerging from the side represent levels in Avalanche's hall of fame. These levels stand out the most among the team's creations. By default, unrated or unlisted levels will not be highlighted. -If a level is featured on Avalanche's servers, you may see a button with the Avalanche logo appear on the left-hand side menu on the level's information page, or the right-hand side menu on the pause menu while playing the level. Pressing this will create a pop-up showing its showcase video thumbnail as the background, and gives more information about the level as a project by the team or its members, the type of project it is, its host, etc. You will also see a button to watch its full showcase video. +If a level is featured on Cubic's servers for Avalanche, you may see a button with the Avalanche logo appear on the left-hand side menu on the level's information page, or the right-hand side menu on the pause menu while playing the level. Pressing this will create a pop-up showing its level thumbnail as the background, and gives more information about the level as a project by the team or its members, the type of project it is, its host, etc. You will also see a button to watch its full showcase video. --- diff --git a/SECURITY.md b/SECURITY.md index fe15e26..474f8cb 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,8 +4,10 @@ List of versions of this mod that are currently being supported with security updates. | Version | Supported | -| ------- | --------- | -| 1.0.x | ✅ | +| ------- | :-------: | +| 1.2.x | ✅ | +| 1.1.x | ✅ | +| 1.0.x | ❌ | ## Reporting a Vulnerability If you discover a security vulnerability, please follow the steps below: diff --git a/about.md b/about.md index 419c599..8cf4d44 100644 --- a/about.md +++ b/about.md @@ -43,7 +43,7 @@ When viewing level cells or pages, you may notice the changes in the background Level cells with a glow emerging from the side represent levels in Avalanche's hall of fame. These levels stand out the most among the team's creations. By default, unrated or unlisted levels will not be highlighted. -If a level is featured on Avalanche's servers, you may see a button with the Avalanche logo appear on the left-hand side menu on the level's information page, or the right-hand side menu on the pause menu while playing the level. Pressing this will create a pop-up showing its showcase video thumbnail as the background, and gives more information about the level as a project by the team or its members, the type of project it is, its host, etc. You will also see a button to watch its full showcase video. +If a level is featured on Cubic's servers for Avalanche, you may see a button with the Avalanche logo appear on the left-hand side menu on the level's information page, or the right-hand side menu on the pause menu while playing the level. Pressing this will create a pop-up showing its level thumbnail as the background, and gives more information about the level as a project by the team or its members, the type of project it is, its host, etc. You will also see a button to watch its full showcase video. --- diff --git a/changelog.md b/changelog.md index 90baa2d..23679df 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,19 @@ +## v1.2.0 +#### Team Project pop-up + +#### Changes +- **Updated** Geode compatibility to version `4.6.3` +- **Added** level thumbnail background to Team Project information pop-up +- **Added** promotional container for parent Team Project, if any, in information pop-up for independent levels +- **Reworked** level showcase menu in Team Project information pop-up + +#### Developers +- **Updated** URLs to use Cubic Studios API endpoints for all web requests + +###### Latest +--- +###### Older + ### v1.1.2 ##### Patches @@ -7,10 +23,6 @@ - **Added** toggle setting `Collaboration Level Pages` - Minor tweaks -###### Latest ---- -###### Older - ### v1.1.1 ##### Patches diff --git a/incl/Avalanche.hpp b/incl/Avalanche.hpp index e05014e..26175f0 100644 --- a/incl/Avalanche.hpp +++ b/incl/Avalanche.hpp @@ -20,53 +20,76 @@ namespace avalanche { // Avalanche Index mod namespace extern int ACC_PUBLISHER; // Account ID of Avalanche's level publisher account - constexpr const char* URL_CUBIC = "https://www.cubicstudios.xyz/"; // URL to Cubic Studios's official website + constexpr const char* URL_CUBIC = "https://www.cubicstudios.xyz/"; // URL to Cubic Studios's official website constexpr const char* URL_AVALANCHE = "https://avalanche.cubicstudios.xyz/"; // URL to Avalanche's official website - constexpr const char* URL_API_BADGES = "https://gh.cubicstudios.xyz/WebLPS/data/avalProfiles.json"; // URL to remote JSON file containing all data on profiles - constexpr const char* URL_API_LEVELS = "https://gh.cubicstudios.xyz/WebLPS/data/avalProjects.json"; // URL to remote JSON file containing all data on projects + constexpr const char* URL_API_BADGES = "https://api.cubicstudios.xyz/avalanche/v1/profiles"; // URL to remote JSON file containing all data on profiles + constexpr const char* URL_API_LEVELS = "https://api.cubicstudios.xyz/avalanche/v1/projects"; // URL to remote JSON file containing all data on projects constexpr const char* und = "undefined"; constexpr const char* err = "404: Not Found"; + // Profile class class Profile { public: enum class Badge { - NONE, // No badge - CUBIC, // Staff of Cubic Studios - DIRECTOR, // Leads the whole team - MANAGER, // Helps keep things in check - MEMBER, // Participates in projects + NONE, // No badge + CUBIC, // Staff of Cubic Studios + DIRECTOR, // Leads the whole team + MANAGER, // Helps keep things in check + MEMBER, // Participates in projects COLLABORATOR, // Non-members of the team who also worked on a project }; static std::map profileBadgeEnum; // Convert a string to a Badge enum std::string name; // Official pseudonym of the member - Badge badge; // ID of the member's badge + Badge badge; // ID of the member's badge - Profile(std::string n = "Name", Badge b = Badge::NONE) : name(n), badge(b) {}; + Profile( + std::string n = "Name", + Badge b = Badge::NONE + ) : name(n), badge(b) {}; }; + // Avalanche project class class Project { public: enum class Type { - NONE, // Not a project - SOLO, // A project that a member worked on by themself - TEAM, // A project that members of the team worked on + NONE, // Not a project + SOLO, // A project that a member worked on by themself + TEAM, // A project that members of the team worked on COLLAB, // A project that involves the work of Collaborators - EVENT, // A project that resulted from a public or private event hosted by Avalanche + EVENT, // A project that resulted from a public or private event hosted by Avalanche }; - static std::map projectTypeEnum; // Convert a string to a Type enum + // Link to the main team project + class LinkToMain { + public: + bool enabled; // If the link is enabled + int level_id; // ID of the in-game level for the linked project + }; - std::string name; // Official name of the level - std::string host; // Team member that hosted the level - std::string showcase_url; // Tiny YouTube video URL of the full showcase of the level - Type type; // Type of project the level is featured as - bool fame; // If the level will be highlighted on lists + static std::map projectTypeEnum; // Convert a string to a Type enum - Project(std::string n = "Name", std::string h = "Host", std::string su = URL_AVALANCHE, Type t = Type::NONE, bool f = false) : name(n), host(h), showcase_url(su), type(t), fame(f) {}; + std::string name; // Official name of the level + std::string host; // Team member that hosted the level + std::string showcase; // Tiny YouTube video URL of the full showcase of the level + std::string thumbnail; // Imgur URL for a custom thumbnail for the level + Type type; // Type of project the level is featured as + bool fame; // If the level will be highlighted on lists + + LinkToMain link_to_main; // Optional link to the main team project + + Project( + std::string n = "Name", + std::string h = "Host", + std::string su = URL_AVALANCHE, + std::string ct = "", + Type t = Type::NONE, + bool f = false, + LinkToMain ltm = LinkToMain() + ) : name(n), host(h), showcase(su), thumbnail(ct), type(t), fame(f), link_to_main(ltm) {}; }; class Handler { @@ -81,10 +104,10 @@ namespace avalanche { // Avalanche Index mod namespace void scanAll(); static std::map badgeStringID; // Convert a Badge enum to a string ID - static std::map badgeSpriteName; // Get the sprite from the string badge ID - static std::map badgeColor; // Get the color from the string badge ID - static std::map badgeToAPI; // Convert badge node ID to API ID - static std::map apiToString; // Convert API-provided string to string ID + static std::map badgeSpriteName; // Get the sprite from the string badge ID + static std::map badgeColor; // Get the color from the string badge ID + static std::map badgeToAPI; // Convert badge node ID to API ID + static std::map apiToString; // Convert API-provided string to string ID // Get profile data on a player Profile GetProfile(int id); @@ -103,7 +126,7 @@ namespace avalanche { // Avalanche Index mod namespace log::debug("Creating badge for {}...", profile.name); std::string idString = avalanche::Handler::badgeStringID[profile.badge]; // gets the string equivalent - bool idFailTest = idString.empty(); // checks the map for this value to see if its invalid + bool idFailTest = idString.empty(); // checks the map for this value to see if its invalid if (idFailTest) { log::error("Badge is invalid."); diff --git a/incl/src/Avalanche.cpp b/incl/src/Avalanche.cpp index 5a8dfca..834a0d2 100644 --- a/incl/src/Avalanche.cpp +++ b/incl/src/Avalanche.cpp @@ -187,15 +187,27 @@ namespace avalanche { auto c_name = cacheStd["name"].asString().unwrapOr(und); auto c_host = cacheStd["host"].asString().unwrapOr(und); auto c_showcase = cacheStd["showcase"].asString().unwrapOr(und); + auto c_thumbnail = cacheStd["thumbnail"].asString().unwrapOr(""); auto c_type = (lType != avalanche::Project::projectTypeEnum.end()) ? lType->second : avalanche::Project::Type::NONE; auto c_fame = cacheStd["fame"].asBool().unwrapOr(false); - avalanche::Project res(c_name, c_host, c_showcase, c_type, c_fame); + auto c_linked = cacheStd["project"]; + avalanche::Project::LinkToMain ltm; + + if (c_linked.isObject()) { + ltm.enabled = c_linked["enabled"].asBool().unwrapOr(false); + ltm.level_id = c_linked["id"].asInt().unwrapOr(0); + } else { + ltm.enabled = false; + ltm.level_id = 0; + }; + + avalanche::Project res(c_name, c_host, c_showcase, c_thumbnail, c_type, c_fame, ltm); return res; } else { log::error("Project ID is invalid"); - avalanche::Project res("Name", "Host", URL_AVALANCHE, avalanche::Project::Type::NONE, false); + avalanche::Project res("Name", "Host", URL_AVALANCHE, "", avalanche::Project::Type::NONE, false, avalanche::Project::LinkToMain()); return res; }; }; diff --git a/mod.json b/mod.json index 0beb37b..8fc0977 100644 --- a/mod.json +++ b/mod.json @@ -1,5 +1,5 @@ { - "geode": "4.6.2", + "geode": "4.6.3", "gd": { "win": "2.2074", "android": "2.2074", @@ -8,7 +8,7 @@ }, "id": "cubicstudios.avalancheindex", "name": "Avalanche Index", - "version": "1.1.2", + "version": "1.2.0", "developers": [ "Cheeseworks" ], diff --git a/src/headers/ParticleHelper.hpp b/src/ParticleHelper.hpp similarity index 99% rename from src/headers/ParticleHelper.hpp rename to src/ParticleHelper.hpp index 495983a..0c4c5b9 100644 --- a/src/headers/ParticleHelper.hpp +++ b/src/ParticleHelper.hpp @@ -1,7 +1,7 @@ #ifndef PARTICLEHELPER_H #define PARTICLEHELPER_H -#include "../Debugger.hpp" +#include "./Debugger.hpp" #include diff --git a/src/headers/ProjectInfoPopup.hpp b/src/headers/ProjectInfoPopup.hpp index 5ebaf07..5e7fef5 100644 --- a/src/headers/ProjectInfoPopup.hpp +++ b/src/headers/ProjectInfoPopup.hpp @@ -15,12 +15,13 @@ class ProjectInfoPopup : public geode::Popup<> { ProjectInfoPopup* setProject(GJGameLevel* level); void show() override; - protected: std::string m_avalPublisher = "Avalanche"; + std::string m_linkedPublisher = "Avalanche"; std::string m_cornerArtType = "rewardCorner_001.png"; Project m_avalProject; + Project m_linkedProject; GJGameLevel* m_level; @@ -28,6 +29,9 @@ class ProjectInfoPopup : public geode::Popup<> { CCClippingNode* m_clippingNode; + void doInfo(Project proj, std::string publisher); + void doShowcase(Project proj, std::string publisher); + void infoPopup(CCObject*); void settingsPopup(CCObject*); @@ -35,5 +39,8 @@ class ProjectInfoPopup : public geode::Popup<> { void onPlayShowcase(CCObject*); + void infoPopupLinked(CCObject*); + void onPlayShowcaseLinked(CCObject*); + bool setup() override; }; \ No newline at end of file diff --git a/src/headers/src/AvalancheFeatured.cpp b/src/headers/src/AvalancheFeatured.cpp index 1bd1c18..80d425f 100644 --- a/src/headers/src/AvalancheFeatured.cpp +++ b/src/headers/src/AvalancheFeatured.cpp @@ -116,7 +116,7 @@ bool AvalancheFeatured::setup() { art_bottomLeft->setID("bottom-left-corner"); art_bottomLeft->setAnchorPoint({ 0, 0 }); art_bottomLeft->setPosition({ 0, 0 }); - art_bottomLeft->setScale(1.250); + art_bottomLeft->setScale(1.250f); art_bottomLeft->setFlipX(false); art_bottomLeft->setFlipY(false); art_bottomLeft->setZOrder(0); @@ -127,7 +127,7 @@ bool AvalancheFeatured::setup() { art_bottomRight->setID("bottom-right-corner"); art_bottomRight->setAnchorPoint({ 1, 0 }); art_bottomRight->setPosition({ m_overlayMenu->getScaledContentWidth(), 0 }); - art_bottomRight->setScale(1.250); + art_bottomRight->setScale(1.250f); art_bottomRight->setFlipX(true); art_bottomRight->setFlipY(false); art_bottomLeft->setZOrder(0); @@ -138,7 +138,7 @@ bool AvalancheFeatured::setup() { art_topLeft->setID("top-left-corner"); art_topLeft->setAnchorPoint({ 0, 1 }); art_topLeft->setPosition({ 0, m_overlayMenu->getScaledContentHeight() }); - art_topLeft->setScale(1.250); + art_topLeft->setScale(1.250f); art_topLeft->setFlipX(false); art_topLeft->setFlipY(true); art_topLeft->setZOrder(0); @@ -149,7 +149,7 @@ bool AvalancheFeatured::setup() { art_topRight->setID("top-right-corner"); art_topRight->setAnchorPoint({ 1, 1 }); art_topRight->setPosition({ m_overlayMenu->getScaledContentWidth(), m_overlayMenu->getScaledContentHeight() }); - art_topRight->setScale(1.250); + art_topRight->setScale(1.250f); art_topRight->setFlipX(true); art_topRight->setFlipY(true); art_topRight->setZOrder(0); @@ -209,7 +209,7 @@ bool AvalancheFeatured::setup() { projThumb->setPosition({ m_mainLayer->getContentWidth() / 2.f, m_mainLayer->getContentHeight() / 2.f }); projThumb->setLoadCallback([this, projThumb](Result<> res) { - if (res) { + if (res.isOk()) { // Success: scale and position the sprite AVAL_LOG_INFO("Sprite loaded successfully"); } else { @@ -223,7 +223,7 @@ bool AvalancheFeatured::setup() { projThumb->setScale(scale); }); - projThumb->loadFromUrl("https://gh.cubicstudios.xyz/WebLPS/aval-project/thumbnail.png", LazySprite::Format::kFmtUnKnown, false); + projThumb->loadFromUrl("https://api.cubicstudios.xyz/avalanche/v1/featured/thumbnail", LazySprite::Format::kFmtUnKnown, false); m_clippingNode->addChild(projThumb); if (AVAL_GEODE_MOD->getSettingValue("dev-mode")) { diff --git a/src/headers/src/ProjectInfoPopup.cpp b/src/headers/src/ProjectInfoPopup.cpp index d05a733..032d619 100644 --- a/src/headers/src/ProjectInfoPopup.cpp +++ b/src/headers/src/ProjectInfoPopup.cpp @@ -4,6 +4,7 @@ #include "../../incl/Avalanche.hpp" +#include #include #include @@ -21,23 +22,30 @@ using namespace geode::prelude; using namespace avalanche; -ProjectInfoPopup* ProjectInfoPopup::create() { - auto ret = new ProjectInfoPopup; - if (ret->initAnchored(440, 290)) { - ret->autorelease(); - return ret; +Handler* avalHandler = Handler::get(); + +inline std::string url_encode(const std::string& value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (char c : value) { + if (isalnum(static_cast(c)) || c == '-' || c == '_' || c == '.' || c == '~') { + escaped << c; + } else { + escaped << '%' << std::setw(2) << int((unsigned char)c); + }; }; - CC_SAFE_DELETE(ret); - return nullptr; + return escaped.str(); }; -void ProjectInfoPopup::infoPopup(CCObject*) { +void ProjectInfoPopup::doInfo(Project proj, std::string publisher) { std::ostringstream typeOfProj; - switch (m_avalProject.type) { + switch (proj.type) { case Project::Type::TEAM: - typeOfProj << "a team project hosted by " << m_avalProject.host << ""; + typeOfProj << "a team project hosted by " << proj.host << ""; break; case Project::Type::COLLAB: @@ -58,60 +66,87 @@ void ProjectInfoPopup::infoPopup(CCObject*) { }; std::ostringstream body; - body << "" << m_avalPublisher << " - '" << m_avalProject.name << "' is " << typeOfProj.str() << ". You can watch its showcase here."; + body << "" << publisher << " - '" << proj.name << "' is " << typeOfProj.str() << ". You can watch its showcase here."; std::string resultBody = body.str(); createQuickPopup( - m_avalProject.name.c_str(), + proj.name.c_str(), resultBody.c_str(), "OK", "Watch", - [this](auto, bool btn2) { + [proj](auto, bool btn2) { if (btn2) { - AVAL_LOG_INFO("Opening showcase link in browser: {}", this->m_avalProject.showcase_url); - web::openLinkInBrowser(this->m_avalProject.showcase_url); + AVAL_LOG_INFO("Opening showcase link in browser: {}", proj.showcase); + web::openLinkInBrowser(proj.showcase); } else { AVAL_LOG_DEBUG("User clicked OK"); }; }, true); }; -void ProjectInfoPopup::onFameInfo(CCObject*) { +void ProjectInfoPopup::doShowcase(Project proj, std::string publisher) { std::ostringstream body; - body << "This level, '" << m_avalProject.name << "', is featured in Avalanche's Hall of Fame. It is a special list of levels that are considered to be the best of the best from the team."; + body << "Watch the full showcase video for " << publisher << " - '" << proj.name << "'?"; std::string resultBody = body.str(); createQuickPopup( - "Hall of Fame", + proj.name.c_str(), resultBody.c_str(), - "OK", "Learn More", - [](auto, bool btn2) { + "Cancel", "Watch", + [proj](auto, bool btn2) { if (btn2) { - AVAL_LOG_INFO("Opening Hall of Fame link in browser"); - web::openLinkInBrowser(URL_AVALANCHE); + AVAL_LOG_INFO("Opening showcase link in browser: {}", proj.showcase); + web::openLinkInBrowser(proj.showcase); } else { - AVAL_LOG_DEBUG("User clicked OK"); + AVAL_LOG_DEBUG("User clicked Cancel"); }; }, true); }; +ProjectInfoPopup* ProjectInfoPopup::create() { + auto ret = new ProjectInfoPopup; + if (ret->initAnchored(440, 290)) { + ret->autorelease(); + return ret; + }; + + CC_SAFE_DELETE(ret); + return nullptr; +}; + +void ProjectInfoPopup::infoPopup(CCObject*) { + ProjectInfoPopup::doInfo(m_avalProject, m_avalPublisher); +}; + void ProjectInfoPopup::onPlayShowcase(CCObject*) { + ProjectInfoPopup::doShowcase(m_avalProject, m_avalPublisher); +}; + +void ProjectInfoPopup::infoPopupLinked(CCObject*) { + ProjectInfoPopup::doInfo(m_linkedProject, m_linkedPublisher); +}; + +void ProjectInfoPopup::onPlayShowcaseLinked(CCObject*) { + ProjectInfoPopup::doShowcase(m_linkedProject, m_linkedPublisher); +}; + +void ProjectInfoPopup::onFameInfo(CCObject*) { std::ostringstream body; - body << "Watch the full showcase video for " << m_avalPublisher << " - '" << m_avalProject.name << "'?"; + body << "The level '" << m_avalProject.name << "' by " << m_avalPublisher << " is featured in Avalanche's Hall of Fame. It is a special list of levels that are considered to be the best of the best from the team."; std::string resultBody = body.str(); createQuickPopup( - m_avalProject.name.c_str(), + "Hall of Fame", resultBody.c_str(), - "Cancel", "Watch", - [this](auto, bool btn2) { + "OK", "Learn More", + [](auto, bool btn2) { if (btn2) { - AVAL_LOG_INFO("Opening showcase link in browser: {}", this->m_avalProject.showcase_url); - web::openLinkInBrowser(this->m_avalProject.showcase_url); + AVAL_LOG_INFO("Opening Hall of Fame link in browser"); + web::openLinkInBrowser(URL_AVALANCHE); } else { - AVAL_LOG_DEBUG("User clicked Cancel"); + AVAL_LOG_DEBUG("User clicked OK"); }; }, true); }; @@ -164,7 +199,7 @@ ProjectInfoPopup* ProjectInfoPopup::setProject(GJGameLevel* level) { setZOrder(10); m_level = level; - m_avalProject = Handler::get()->GetProject(m_level->m_levelID.value()); + m_avalProject = avalHandler->GetProject(m_level->m_levelID.value()); if (m_avalProject.type == Project::Type::NONE) { AVAL_LOG_ERROR("Avalanche project type is NONE"); @@ -210,6 +245,7 @@ ProjectInfoPopup* ProjectInfoPopup::setProject(GJGameLevel* level) { fameIcon->setScale(0.25f); auto fameLabel = CCLabelBMFont::create("Avalanche Hall of Fame", "goldFont.fnt"); + fameLabel->setAlignment(CCTextAlignment::kCCTextAlignmentCenter); fameLabel->ignoreAnchorPointForPosition(false); fameLabel->setAnchorPoint({ 0.5, 0.5 }); fameLabel->setScale(0.375f); @@ -265,7 +301,7 @@ ProjectInfoPopup* ProjectInfoPopup::setProject(GJGameLevel* level) { art_bottomLeft->setID("bottom-left-corner"); art_bottomLeft->setAnchorPoint({ 0, 0 }); art_bottomLeft->setPosition({ 0, 0 }); - art_bottomLeft->setScale(1.250); + art_bottomLeft->setScale(1.250f); art_bottomLeft->setFlipX(false); art_bottomLeft->setFlipY(false); art_bottomLeft->setZOrder(0); @@ -276,7 +312,7 @@ ProjectInfoPopup* ProjectInfoPopup::setProject(GJGameLevel* level) { art_bottomRight->setID("bottom-right-corner"); art_bottomRight->setAnchorPoint({ 1, 0 }); art_bottomRight->setPosition({ m_overlayMenu->getScaledContentWidth(), 0 }); - art_bottomRight->setScale(1.250); + art_bottomRight->setScale(1.250f); art_bottomRight->setFlipX(true); art_bottomRight->setFlipY(false); art_bottomLeft->setZOrder(0); @@ -287,7 +323,7 @@ ProjectInfoPopup* ProjectInfoPopup::setProject(GJGameLevel* level) { art_topLeft->setID("top-left-corner"); art_topLeft->setAnchorPoint({ 0, 1 }); art_topLeft->setPosition({ 0, m_overlayMenu->getScaledContentHeight() }); - art_topLeft->setScale(1.250); + art_topLeft->setScale(1.250f); art_topLeft->setFlipX(false); art_topLeft->setFlipY(true); art_topLeft->setZOrder(0); @@ -298,7 +334,7 @@ ProjectInfoPopup* ProjectInfoPopup::setProject(GJGameLevel* level) { art_topRight->setID("top-right-corner"); art_topRight->setAnchorPoint({ 1, 1 }); art_topRight->setPosition({ m_overlayMenu->getScaledContentWidth(), m_overlayMenu->getScaledContentHeight() }); - art_topRight->setScale(1.250); + art_topRight->setScale(1.250f); art_topRight->setFlipX(true); art_topRight->setFlipY(true); art_topRight->setZOrder(0); @@ -331,27 +367,163 @@ ProjectInfoPopup* ProjectInfoPopup::setProject(GJGameLevel* level) { auto hostName_label = CCLabelBMFont::create(hostLabelTxt, "bigFont.fnt"); hostName_label->setID("host-name-label"); + hostName_label->setAnchorPoint({ 0, 1 }); hostName_label->ignoreAnchorPointForPosition(false); - hostName_label->setAnchorPoint({ 0, 0.5 }); - hostName_label->setPosition({ 10.f, (m_mainLayer->getScaledContentHeight() / 2.f) + 70.f }); + hostName_label->setAlignment(CCTextAlignment::kCCTextAlignmentCenter); + hostName_label->setPosition({ 10.f, m_mainLayer->getScaledContentHeight() - 55.f }); hostName_label->setScale(0.25f); auto hostName = CCLabelBMFont::create(m_avalProject.host.c_str(), "goldFont.fnt"); hostName->setID("host-name"); + hostName->setAnchorPoint({ 0, 1 }); hostName->ignoreAnchorPointForPosition(false); - hostName->setAnchorPoint({ 0, 0.5 }); - hostName->setPosition({ 10.f, (m_mainLayer->getScaledContentHeight() / 2.f) + 55.f }); + hostName->setAlignment(CCTextAlignment::kCCTextAlignmentCenter); + hostName->setPosition({ 10.f, m_mainLayer->getScaledContentHeight() - 65.f }); hostName->setScale(0.75f); m_overlayMenu->addChild(hostName_label); m_overlayMenu->addChild(hostName); - auto playShowcase_label = CCLabelBMFont::create("Watch the Showcase", "chatFont.fnt"); + AVAL_LOG_INFO("Adding showcase container to main project '{}'", m_avalProject.name); + + // create showcase project button + AVAL_LOG_DEBUG("Creating showcase project button"); + auto showcaseProjMenu = CCMenu::create(); + showcaseProjMenu->setID("showcase-project-menu"); + showcaseProjMenu->setAnchorPoint({ 0.5, 0 }); + showcaseProjMenu->ignoreAnchorPointForPosition(false); + showcaseProjMenu->setPosition({ m_mainLayer->getScaledContentWidth() / 2.f, 12.5f }); + showcaseProjMenu->setScaledContentSize({ 192.f, 108.f }); + showcaseProjMenu->setZOrder(2); + + m_clippingNode->addChild(showcaseProjMenu); + + // create clipping node for showcase project + AVAL_LOG_DEBUG("Creating clipping node for showcase project"); + auto showcaseProjClippingNode = CCClippingNode::create(); + showcaseProjClippingNode->setID("clipping-node"); + showcaseProjClippingNode->setContentSize(showcaseProjMenu->getScaledContentSize()); + showcaseProjClippingNode->ignoreAnchorPointForPosition(false); + showcaseProjClippingNode->setAnchorPoint({ 0.5f, 0.5f }); + showcaseProjClippingNode->setPosition({ showcaseProjMenu->getScaledContentWidth() / 2.f, showcaseProjMenu->getScaledContentHeight() / 2.f }); + showcaseProjClippingNode->setStencil(CCLayerColor::create({ 250, 250, 250 }, showcaseProjMenu->getScaledContentWidth(), showcaseProjMenu->getScaledContentHeight()));; + showcaseProjClippingNode->setZOrder(-1); + + showcaseProjMenu->addChild(showcaseProjClippingNode); + + auto showcaseProjClippingNodeBg = CCScale9Sprite::create("GJ_square04.png"); + showcaseProjClippingNodeBg->setID("background"); + showcaseProjClippingNodeBg->setContentSize(showcaseProjClippingNode->getContentSize()); + showcaseProjClippingNodeBg->setPosition(showcaseProjClippingNode->getPosition()); + showcaseProjClippingNodeBg->setAnchorPoint(showcaseProjClippingNode->getAnchorPoint()); + showcaseProjClippingNodeBg->setZOrder(-1); + + showcaseProjClippingNode->addChild(showcaseProjClippingNodeBg); + + // corner art deco for showcase project + auto corner = "rewardCorner_001.png"; + + auto art_bottomLeft_showcaseProj = CCSprite::createWithSpriteFrameName(corner); + art_bottomLeft_showcaseProj->setID("bottom-left-corner"); + art_bottomLeft_showcaseProj->setAnchorPoint({ 0, 0 }); + art_bottomLeft_showcaseProj->setPosition({ 0, 0 }); + art_bottomLeft_showcaseProj->setScale(0.75f); + art_bottomLeft_showcaseProj->setFlipX(false); + art_bottomLeft_showcaseProj->setFlipY(false); + art_bottomLeft_showcaseProj->setZOrder(4); + + showcaseProjClippingNode->addChild(art_bottomLeft_showcaseProj); + + auto art_bottomRight_showcaseProj = CCSprite::createWithSpriteFrameName(corner); + art_bottomRight_showcaseProj->setID("bottom-right-corner"); + art_bottomRight_showcaseProj->setAnchorPoint({ 1, 0 }); + art_bottomRight_showcaseProj->setPosition({ showcaseProjClippingNode->getScaledContentWidth(), 0 }); + art_bottomRight_showcaseProj->setScale(0.75f); + art_bottomRight_showcaseProj->setFlipX(true); + art_bottomRight_showcaseProj->setFlipY(false); + art_bottomRight_showcaseProj->setZOrder(4); + + showcaseProjClippingNode->addChild(art_bottomRight_showcaseProj); + + auto art_topLeft_showcaseProj = CCSprite::createWithSpriteFrameName(corner); + art_topLeft_showcaseProj->setID("top-left-corner"); + art_topLeft_showcaseProj->setAnchorPoint({ 0, 1 }); + art_topLeft_showcaseProj->setPosition({ 0, showcaseProjClippingNode->getScaledContentHeight() }); + art_topLeft_showcaseProj->setScale(0.75f); + art_topLeft_showcaseProj->setFlipX(false); + art_topLeft_showcaseProj->setFlipY(true); + art_topLeft_showcaseProj->setZOrder(4); + + showcaseProjClippingNode->addChild(art_topLeft_showcaseProj); + + auto art_topRight_showcaseProj = CCSprite::createWithSpriteFrameName(corner); + art_topRight_showcaseProj->setID("top-right-corner"); + art_topRight_showcaseProj->setAnchorPoint({ 1, 1 }); + art_topRight_showcaseProj->setPosition({ showcaseProjClippingNode->getScaledContentWidth(), showcaseProjClippingNode->getScaledContentHeight() }); + art_topRight_showcaseProj->setScale(0.75f); + art_topRight_showcaseProj->setFlipX(true); + art_topRight_showcaseProj->setFlipY(true); + art_topRight_showcaseProj->setZOrder(4); + + showcaseProjClippingNode->addChild(art_topRight_showcaseProj); + + // create thumbnail lazy sprite for showcase project + if (AVAL_GEODE_MOD->getSettingValue("show-proj-thumb")) { + AVAL_LOG_DEBUG("Creating thumbnail lazy sprite for showcase project '{}'", m_avalProject.name); + LazySprite* showcaseProjThumb = LazySprite::create(showcaseProjMenu->getScaledContentSize(), true); + showcaseProjThumb->setID("thumbnail"); + showcaseProjThumb->setAnchorPoint({ 0.5, 0.5 }); + showcaseProjThumb->ignoreAnchorPointForPosition(false); + showcaseProjThumb->setPosition({ showcaseProjMenu->getScaledContentWidth() / 2.f, showcaseProjMenu->getScaledContentHeight() / 2.f }); + showcaseProjThumb->setScale(0.5f); + + showcaseProjThumb->setLoadCallback([showcaseProjThumb, showcaseProjClippingNode](Result<> res) { + if (res.isOk()) { + AVAL_LOG_INFO("Showcase project thumbnail loaded successfully"); + + showcaseProjThumb->setScale(1.f); + showcaseProjThumb->setScale(showcaseProjClippingNode->getScaledContentHeight() / showcaseProjThumb->getScaledContentHeight()); + + showcaseProjThumb->setPosition(showcaseProjClippingNode->getPosition()); + showcaseProjThumb->ignoreAnchorPointForPosition(false); + showcaseProjThumb->setColor({ 125, 125, 125 }); + showcaseProjThumb->setOpacity(125); + } else { + AVAL_LOG_ERROR("Failed to load showcase project thumbnail: {}", res.unwrapErr()); + showcaseProjThumb->removeMeAndCleanup(); + }; + }); + + std::string encodedShowcaseUrl = url_encode(m_avalProject.showcase); // encode the showcase url for use in the thumbnail url + std::string showcaseProjThumbURL = fmt::format("https://api.cubicstudios.xyz/avalanche/v1/fetch/yt-thumbnails?url={}", (std::string)encodedShowcaseUrl); // custom thumbnail + + AVAL_LOG_DEBUG("Getting showcase project thumbnail at {}...", (std::string)showcaseProjThumbURL); + showcaseProjThumb->loadFromUrl(showcaseProjThumbURL, LazySprite::Format::kFmtUnKnown, false); + if (showcaseProjThumb) showcaseProjClippingNode->addChild(showcaseProjThumb); + } else { + AVAL_LOG_DEBUG("Showcase project thumbnail setting is disabled, not adding thumbnail to showcase containered project container"); + }; + + // set border + auto showcaseProjBorder = CCScale9Sprite::create("GJ_square07.png"); + showcaseProjBorder->setID("border"); + showcaseProjBorder->setPosition(showcaseProjClippingNode->getPosition()); + showcaseProjBorder->setContentSize(showcaseProjClippingNode->getScaledContentSize()); + showcaseProjBorder->ignoreAnchorPointForPosition(false); + showcaseProjBorder->setAnchorPoint({ 0.5f, 0.5f }); + showcaseProjBorder->setZOrder(3); + + // add border to clipping node + showcaseProjClippingNode->addChild(showcaseProjBorder); + + auto playShowcase_label = CCLabelBMFont::create("Watch the\nShowcase", "bigFont.fnt"); playShowcase_label->setID("play-showcase-label"); - playShowcase_label->ignoreAnchorPointForPosition(false); + playShowcase_label->setScale(0.5f); playShowcase_label->setAnchorPoint({ 0.5, 0.5 }); - playShowcase_label->setPosition({ m_mainLayer->getScaledContentWidth() / 2.f, 60.f }); - playShowcase_label->setScale(1.f); + playShowcase_label->ignoreAnchorPointForPosition(false); + playShowcase_label->setAlignment(CCTextAlignment::kCCTextAlignmentCenter); + playShowcase_label->setPosition({ showcaseProjMenu->getScaledContentWidth() / 2.f, 75.f }); + playShowcase_label->setZOrder(3); auto playShowcase_sprite = CCSprite::createWithSpriteFrameName("GJ_playBtn2_001.png"); playShowcase_sprite->setScale(0.5f); @@ -360,11 +532,12 @@ ProjectInfoPopup* ProjectInfoPopup::setProject(GJGameLevel* level) { playShowcase_sprite, this, menu_selector(ProjectInfoPopup::onPlayShowcase)); - playShowcase->setID("play-showcase-button"); - playShowcase->setPosition({ m_mainLayer->getScaledContentWidth() / 2.f, 30.f }); + playShowcase->setID("play-showcase"); + playShowcase->setPosition({ showcaseProjMenu->getScaledContentWidth() / 2.f, 37.5f }); + playShowcase->setZOrder(4); - m_overlayMenu->addChild(playShowcase_label); - m_overlayMenu->addChild(playShowcase); + showcaseProjMenu->addChild(playShowcase_label); + showcaseProjMenu->addChild(playShowcase); // project thumbnail if (AVAL_GEODE_MOD->getSettingValue("show-proj-thumb")) { @@ -377,18 +550,6 @@ ProjectInfoPopup* ProjectInfoPopup::setProject(GJGameLevel* level) { projThumb->ignoreAnchorPointForPosition(false); projThumb->setPosition({ m_clippingNode->getScaledContentWidth() / 2, m_clippingNode->getScaledContentHeight() / 2 }); - // extract video id from url - std::string videoId = m_avalProject.showcase_url; - - std::string prefix = "https://youtu.be/"; - - if (videoId.find(prefix) == 0) videoId = videoId.substr(prefix.length()); - - // format url - auto projThumbURL = fmt::format("https://img.youtube.com/vi/{}/maxresdefault.jpg", (std::string)videoId); - - AVAL_LOG_DEBUG("Getting thumbnail at {}...", projThumbURL); - projThumb->setLoadCallback([this, projThumb, bgSize](Result<> res) { if (res.isOk()) { AVAL_LOG_INFO("Sprite loaded successfully"); @@ -397,23 +558,216 @@ ProjectInfoPopup* ProjectInfoPopup::setProject(GJGameLevel* level) { float scale = bgSize.height / projThumb->getContentHeight(); projThumb->setScale(scale); - projThumb->setAnchorPoint({ 0, 0 }); projThumb->ignoreAnchorPointForPosition(false); projThumb->setColor({ 125, 125, 125 }); - projThumb->setPosition({ 0, 0 }); projThumb->setOpacity(125); } else { - AVAL_LOG_ERROR("{}", res.unwrapErr()); + AVAL_LOG_ERROR("Failed to get project thumbnail, {}", res.unwrapErr()); projThumb->removeMeAndCleanup(); }; }); + std::string projThumbURL = fmt::format("https://api.cubicstudios.xyz/avalanche/v1/fetch/thumbnails?id={}", (int)m_level->m_levelID.value()); // custom thumbnail + + AVAL_LOG_DEBUG("Getting thumbnail at {}...", (std::string)projThumbURL); projThumb->loadFromUrl(projThumbURL, LazySprite::Format::kFmtUnKnown, false); if (projThumb) m_clippingNode->addChild(projThumb); } else { AVAL_LOG_DEBUG("Project thumbnail setting is disabled, not adding thumbnail to project info popup"); }; + if (m_avalProject.link_to_main.enabled) { + AVAL_LOG_DEBUG("Project '{}' has a link to the main project", m_avalProject.name); + m_linkedProject = avalHandler->GetProject(m_avalProject.link_to_main.level_id); + + if (m_linkedProject.type == Project::Type::NONE) { + AVAL_LOG_ERROR("Failed to get linked project with ID {}", m_avalProject.link_to_main.level_id); + } else { + AVAL_LOG_INFO("Adding link to main project '{}'", m_linkedProject.name); + + // create linked project button + AVAL_LOG_DEBUG("Creating linked project button"); + auto linkedProjMenu = CCMenu::create(); + linkedProjMenu->setID("linked-project-menu"); + linkedProjMenu->setAnchorPoint({ 1, 1 }); + linkedProjMenu->ignoreAnchorPointForPosition(false); + linkedProjMenu->setPosition({ m_mainLayer->getScaledContentWidth() - 25.f, m_mainLayer->getScaledContentHeight() - 55.f }); + linkedProjMenu->setScaledContentSize({ 150.f, 81.f }); + linkedProjMenu->setZOrder(2); + + m_clippingNode->addChild(linkedProjMenu); + + // info button + auto linkedProjInfoBtnSprite = CCSprite::createWithSpriteFrameName("GJ_infoIcon_001.png"); + linkedProjInfoBtnSprite->setScale(0.375f); + + auto linkedProjInfoBtn = CCMenuItemSpriteExtra::create( + linkedProjInfoBtnSprite, + this, + menu_selector(ProjectInfoPopup::infoPopupLinked)); + linkedProjInfoBtn->setID("info-button"); + linkedProjInfoBtn->setPosition({ linkedProjMenu->getScaledContentWidth() - 6.25f, linkedProjMenu->getScaledContentHeight() - 6.25f }); + linkedProjInfoBtn->setZOrder(126); + + linkedProjMenu->addChild(linkedProjInfoBtn); + + // create clipping node for linked project + AVAL_LOG_DEBUG("Creating clipping node for linked project"); + auto linkedProjClippingNode = CCClippingNode::create(); + linkedProjClippingNode->setID("clipping-node"); + linkedProjClippingNode->setContentSize(linkedProjMenu->getScaledContentSize()); + linkedProjClippingNode->ignoreAnchorPointForPosition(false); + linkedProjClippingNode->setAnchorPoint({ 0.5f, 0.5f }); + linkedProjClippingNode->setPosition({ linkedProjMenu->getScaledContentWidth() / 2.f, linkedProjMenu->getScaledContentHeight() / 2.f }); + linkedProjClippingNode->setStencil(CCLayerColor::create({ 250, 250, 250 }, linkedProjMenu->getScaledContentWidth(), linkedProjMenu->getScaledContentHeight()));; + linkedProjClippingNode->setZOrder(-1); + + linkedProjMenu->addChild(linkedProjClippingNode); + + auto linkedProjClippingNodeBg = CCScale9Sprite::create("GJ_square02.png"); + linkedProjClippingNodeBg->setID("background"); + linkedProjClippingNodeBg->setContentSize(linkedProjClippingNode->getContentSize()); + linkedProjClippingNodeBg->setPosition(linkedProjClippingNode->getPosition()); + linkedProjClippingNodeBg->setAnchorPoint(linkedProjClippingNode->getAnchorPoint()); + linkedProjClippingNodeBg->setZOrder(-1); + + linkedProjClippingNode->addChild(linkedProjClippingNodeBg); + + // corner art deco for linked project + auto corner = "rewardCorner_001.png"; + + auto art_bottomLeft_linkedProj = CCSprite::createWithSpriteFrameName(corner); + art_bottomLeft_linkedProj->setID("bottom-left-corner"); + art_bottomLeft_linkedProj->setAnchorPoint({ 0, 0 }); + art_bottomLeft_linkedProj->setPosition({ 0, 0 }); + art_bottomLeft_linkedProj->setScale(0.5f); + art_bottomLeft_linkedProj->setFlipX(false); + art_bottomLeft_linkedProj->setFlipY(false); + art_bottomLeft_linkedProj->setZOrder(3); + + linkedProjClippingNode->addChild(art_bottomLeft_linkedProj); + + auto art_bottomRight_linkedProj = CCSprite::createWithSpriteFrameName(corner); + art_bottomRight_linkedProj->setID("bottom-right-corner"); + art_bottomRight_linkedProj->setAnchorPoint({ 1, 0 }); + art_bottomRight_linkedProj->setPosition({ linkedProjClippingNode->getScaledContentWidth(), 0 }); + art_bottomRight_linkedProj->setScale(0.5f); + art_bottomRight_linkedProj->setFlipX(true); + art_bottomRight_linkedProj->setFlipY(false); + art_bottomRight_linkedProj->setZOrder(3); + + linkedProjClippingNode->addChild(art_bottomRight_linkedProj); + + auto art_topLeft_linkedProj = CCSprite::createWithSpriteFrameName(corner); + art_topLeft_linkedProj->setID("top-left-corner"); + art_topLeft_linkedProj->setAnchorPoint({ 0, 1 }); + art_topLeft_linkedProj->setPosition({ 0, linkedProjClippingNode->getScaledContentHeight() }); + art_topLeft_linkedProj->setScale(0.5f); + art_topLeft_linkedProj->setFlipX(false); + art_topLeft_linkedProj->setFlipY(true); + art_topLeft_linkedProj->setZOrder(3); + + linkedProjClippingNode->addChild(art_topLeft_linkedProj); + + auto art_topRight_linkedProj = CCSprite::createWithSpriteFrameName(corner); + art_topRight_linkedProj->setID("top-right-corner"); + art_topRight_linkedProj->setAnchorPoint({ 1, 1 }); + art_topRight_linkedProj->setPosition({ linkedProjClippingNode->getScaledContentWidth(), linkedProjClippingNode->getScaledContentHeight() }); + art_topRight_linkedProj->setScale(0.5f); + art_topRight_linkedProj->setFlipX(true); + art_topRight_linkedProj->setFlipY(true); + art_topRight_linkedProj->setZOrder(3); + + linkedProjClippingNode->addChild(art_topRight_linkedProj); + + // create thumbnail lazy sprite for linked project + if (AVAL_GEODE_MOD->getSettingValue("show-proj-thumb")) { + AVAL_LOG_DEBUG("Creating thumbnail lazy sprite for linked project '{}'", m_linkedProject.name); + LazySprite* linkedProjThumb = LazySprite::create(linkedProjMenu->getScaledContentSize(), true); + linkedProjThumb->setID("thumbnail"); + linkedProjThumb->setAnchorPoint({ 0.5, 0.5 }); + linkedProjThumb->ignoreAnchorPointForPosition(false); + linkedProjThumb->setPosition({ linkedProjMenu->getScaledContentWidth() / 2.f, linkedProjMenu->getScaledContentHeight() / 2.f }); + linkedProjThumb->setScale(0.5f); + + linkedProjThumb->setLoadCallback([linkedProjThumb, linkedProjClippingNode](Result<> res) { + if (res.isOk()) { + AVAL_LOG_INFO("Linked project thumbnail loaded successfully"); + + linkedProjThumb->setScale(1.f); + linkedProjThumb->setScale(linkedProjClippingNode->getScaledContentHeight() / linkedProjThumb->getScaledContentHeight()); + + linkedProjThumb->setPosition(linkedProjClippingNode->getPosition()); + linkedProjThumb->ignoreAnchorPointForPosition(false); + linkedProjThumb->setColor({ 250, 250, 250 }); + linkedProjThumb->setOpacity(250); + } else { + AVAL_LOG_ERROR("Failed to load linked project thumbnail: {}", res.unwrapErr()); + linkedProjThumb->removeMeAndCleanup(); + }; + }); + + std::string encodedShowcaseUrl = url_encode(m_linkedProject.showcase); // encode the showcase url for use in the thumbnail url + std::string linkedProjThumbURL = fmt::format("https://api.cubicstudios.xyz/avalanche/v1/fetch/yt-thumbnails?url={}", (std::string)encodedShowcaseUrl); // custom thumbnail + + AVAL_LOG_DEBUG("Getting linked project thumbnail at {}...", (std::string)linkedProjThumbURL); + linkedProjThumb->loadFromUrl(linkedProjThumbURL, LazySprite::Format::kFmtUnKnown, false); + if (linkedProjThumb) linkedProjClippingNode->addChild(linkedProjThumb); + } else { + AVAL_LOG_DEBUG("Linked project thumbnail setting is disabled, not adding thumbnail to linked project container"); + }; + + // set border + auto linkedProjBorder = CCScale9Sprite::create("GJ_square07.png"); + linkedProjBorder->setID("border"); + linkedProjBorder->setPosition(linkedProjClippingNode->getPosition()); + linkedProjBorder->setContentSize(linkedProjClippingNode->getScaledContentSize()); + linkedProjBorder->ignoreAnchorPointForPosition(false); + linkedProjBorder->setAnchorPoint({ 0.5f, 0.5f }); + linkedProjBorder->setZOrder(2); + + // add border to clipping node + linkedProjClippingNode->addChild(linkedProjBorder); + + // create sprite for linked project showcase button + auto linkedProjShowcase_sprite = CCSprite::createWithSpriteFrameName("GJ_playBtn2_001.png"); + linkedProjShowcase_sprite->setScale(0.375f); + + // create button to play linked project showcase + AVAL_LOG_DEBUG("Creating button to play linked project showcase"); + auto linkedProjShowcase = CCMenuItemSpriteExtra::create( + linkedProjShowcase_sprite, + this, + menu_selector(ProjectInfoPopup::onPlayShowcaseLinked)); + linkedProjShowcase->setID("button"); + linkedProjShowcase->setPosition({ linkedProjMenu->getScaledContentWidth() / 2.f, 25.f }); + + linkedProjMenu->addChild(linkedProjShowcase); + + // create label for linked project showcase + AVAL_LOG_DEBUG("Creating label for linked project showcase"); + auto linkedProjLabel = CCLabelBMFont::create("Play Now!", "bigFont.fnt"); + linkedProjLabel->setID("label"); + linkedProjLabel->setAlignment(CCTextAlignment::kCCTextAlignmentCenter); + linkedProjLabel->setPosition({ linkedProjMenu->getScaledContentWidth() / 2.f, linkedProjMenu->getScaledContentHeight() - 10.f }); + linkedProjLabel->setScale(0.25f); + + linkedProjMenu->addChild(linkedProjLabel); + + // create name text label for linked project name + AVAL_LOG_DEBUG("Creating label for linked project name"); + auto linkedProjName = CCLabelBMFont::create(m_linkedProject.name.c_str(), "goldFont.fnt"); + linkedProjName->setID("name"); + linkedProjName->setAlignment(CCTextAlignment::kCCTextAlignmentCenter); + linkedProjName->setPosition({ linkedProjMenu->getScaledContentWidth() / 2.f, linkedProjMenu->getScaledContentHeight() - 25.f }); + linkedProjName->setScale(0.625f); + + linkedProjMenu->addChild(linkedProjName); + }; + } else { + AVAL_LOG_DEBUG("Project '{}' does not have a link to the main project", m_avalProject.name); + }; + // geode settings popup button auto settingsBtnSprite = CCSprite::createWithSpriteFrameName("GJ_optionsBtn_001.png"); settingsBtnSprite->setScale(0.75f); diff --git a/src/main.cpp b/src/main.cpp index b85c7e2..873ec30 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,8 @@ #include "./Debugger.hpp" +#include "./ParticleHelper.hpp" #include "../incl/Avalanche.hpp" -#include "./headers/ParticleHelper.hpp" #include "./headers/AvalancheFeatured.hpp" #include "./headers/ProjectInfoPopup.hpp" @@ -399,7 +399,7 @@ class $modify(LevelInfo, LevelInfoLayer) { if (showFame) { if (auto bgThumbnail = CCSprite::createWithSpriteFrameName("fame-bg.png"_spr)) { - bgThumbnail->setOpacity(75); + bgThumbnail->setOpacity(125); bgThumbnail->setAnchorPoint({ 0.5, 0 }); bgThumbnail->ignoreAnchorPointForPosition(false); bgThumbnail->setPosition({ this->getContentWidth() / 2, 0 }); @@ -977,7 +977,7 @@ class $modify(Menu, MenuLayer) { }; }); auto avalReq = web::WebRequest(); - m_fields->avalWebListener.setFilter(avalReq.get("https://gh.cubicstudios.xyz/WebLPS/aval-project/code.txt")); + m_fields->avalWebListener.setFilter(avalReq.get("https://api.cubicstudios.xyz/avalanche/v1/featured/code")); } else { AVAL_LOG_ERROR("Avalanche featured project button disabled"); };