diff --git a/VERSION b/VERSION index 0b2eb36f..19811903 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.7.2 +3.8.0 diff --git a/loader/resources/mod.json.in b/loader/resources/mod.json.in index c4915538..0e7ef08d 100644 --- a/loader/resources/mod.json.in +++ b/loader/resources/mod.json.in @@ -60,6 +60,9 @@ ], "BlankSheet": [ "blanks/*.png" + ], + "EventSheet": [ + "modtober/*.png" ] } }, diff --git a/loader/resources/modtober/modtober24-banner-2.png b/loader/resources/modtober/modtober24-banner-2.png new file mode 100644 index 00000000..0cfed452 Binary files /dev/null and b/loader/resources/modtober/modtober24-banner-2.png differ diff --git a/loader/resources/modtober/modtober24-banner.png b/loader/resources/modtober/modtober24-banner.png new file mode 100644 index 00000000..867cef1f Binary files /dev/null and b/loader/resources/modtober/modtober24-banner.png differ diff --git a/loader/resources/modtober/modtober24-popup.png b/loader/resources/modtober/modtober24-popup.png new file mode 100644 index 00000000..fbcaaaeb Binary files /dev/null and b/loader/resources/modtober/modtober24-popup.png differ diff --git a/loader/resources/tag-modtober.png b/loader/resources/tag-modtober.png new file mode 100644 index 00000000..5b57a707 Binary files /dev/null and b/loader/resources/tag-modtober.png differ diff --git a/loader/src/ui/mods/GeodeStyle.cpp b/loader/src/ui/mods/GeodeStyle.cpp index 150ac8fb..9dfb9e85 100644 --- a/loader/src/ui/mods/GeodeStyle.cpp +++ b/loader/src/ui/mods/GeodeStyle.cpp @@ -184,21 +184,16 @@ CircleButtonSprite* createGeodeCircleButton(CCSprite* top, float scale, CircleBa return ret; } -ButtonSprite* createGeodeTagLabel(std::string const& text, std::optional> const& color) { +ButtonSprite* createTagLabel(std::string const& text, std::pair const& color) { auto label = ButtonSprite::create(text.c_str(), "bigFont.fnt", "white-square.png"_spr, .8f); - if (color) { - label->m_label->setColor(color->first); - label->m_BGSprite->setColor(color->second); - } - else { - auto def = geodeTagColor(text); - label->m_label->setColor(def.first); - label->m_BGSprite->setColor(def.second); - } + label->m_label->setColor(color.first); + label->m_BGSprite->setColor(color.second); return label; } - -std::pair geodeTagColor(std::string_view const& text) { +ButtonSprite* createGeodeTagLabel(std::string_view tag) { + return createTagLabel(geodeTagName(tag), geodeTagColors(tag)); +} +std::pair geodeTagColors(std::string_view tag) { static std::array TAG_COLORS { std::make_pair(ccc3(240, 233, 255), ccc3(130, 123, 163)), std::make_pair(ccc3(234, 255, 245), ccc3(123, 163, 136)), @@ -206,7 +201,20 @@ std::pair geodeTagColor(std::string_view const& text) { std::make_pair(ccc3(255, 253, 240), ccc3(163, 157, 123)), std::make_pair(ccc3(255, 242, 240), ccc3(163, 128, 123)), }; - return TAG_COLORS[hash(text) % 5932 % TAG_COLORS.size()]; + if (tag == "modtober24") { + return std::make_pair(ccc3(225, 236, 245), ccc3(82, 139, 201)); + } + return TAG_COLORS[hash(tag) % 5932 % TAG_COLORS.size()]; +} +std::string geodeTagName(std::string_view tag) { + // todo in v4: rework tags to use a server-provided display name instead + if (tag == "modtober24") { + return "Modtober 2024"; + } + // Everything else just capitalize and that's it + auto readable = std::string(tag); + readable[0] = std::toupper(readable[0]); + return readable; } ListBorders* createGeodeListBorders(CCSize const& size) { diff --git a/loader/src/ui/mods/GeodeStyle.hpp b/loader/src/ui/mods/GeodeStyle.hpp index 8a1b9c26..1583b7b8 100644 --- a/loader/src/ui/mods/GeodeStyle.hpp +++ b/loader/src/ui/mods/GeodeStyle.hpp @@ -80,8 +80,10 @@ ButtonSprite* createGeodeButton(std::string const& text, bool gold = false, Geod CircleButtonSprite* createGeodeCircleButton(CCSprite* top, float scale = 1.f, CircleBaseSize size = CircleBaseSize::Medium, bool altColor = false); -ButtonSprite* createGeodeTagLabel(std::string const& text, std::optional> const& color = std::nullopt); -std::pair geodeTagColor(std::string_view const& text); +ButtonSprite* createTagLabel(std::string const& text, std::pair const& color); +ButtonSprite* createGeodeTagLabel(std::string_view tag); +std::pair geodeTagColors(std::string_view tag); +std::string geodeTagName(std::string_view tag); ListBorders* createGeodeListBorders(CCSize const& size); diff --git a/loader/src/ui/mods/ModsLayer.cpp b/loader/src/ui/mods/ModsLayer.cpp index ce7562f8..1566440c 100644 --- a/loader/src/ui/mods/ModsLayer.cpp +++ b/loader/src/ui/mods/ModsLayer.cpp @@ -430,14 +430,15 @@ bool ModsLayer::init() { // Increment touch priority so the mods in the list don't override mainTabs->setTouchPriority(-150); - for (auto item : std::initializer_list> { - { "download.png"_spr, "Installed", InstalledModListSource::get(InstalledModListType::All), "installed-button" }, - { "GJ_starsIcon_001.png", "Featured", ServerModListSource::get(ServerModListType::Featured), "featured-button" }, - { "globe.png"_spr, "Download", ServerModListSource::get(ServerModListType::Download), "download-button" }, - { "GJ_timeIcon_001.png", "Recent", ServerModListSource::get(ServerModListType::Recent), "recent-button" }, + for (auto item : std::initializer_list> { + { "download.png"_spr, "Installed", InstalledModListSource::get(InstalledModListType::All), "installed-button", false }, + { "GJ_starsIcon_001.png", "Featured", ServerModListSource::get(ServerModListType::Featured), "featured-button", false }, + { "globe.png"_spr, "Download", ServerModListSource::get(ServerModListType::Download), "download-button", false }, + { "GJ_timeIcon_001.png", "Recent", ServerModListSource::get(ServerModListType::Recent), "recent-button", false }, + { "d_artCloud_03_001.png", "Modtober", ServerModListSource::get(ServerModListType::Modtober24), "modtober-button", true }, }) { auto btn = CCMenuItemSpriteExtra::create( - GeodeTabSprite::create(std::get<0>(item), std::get<1>(item), 120), + GeodeTabSprite::create(std::get<0>(item), std::get<1>(item), 100, std::get<4>(item)), this, menu_selector(ModsLayer::onTab) ); btn->setUserData(std::get<2>(item)); diff --git a/loader/src/ui/mods/list/ModItem.cpp b/loader/src/ui/mods/list/ModItem.cpp index 2d4835e3..570c89d7 100644 --- a/loader/src/ui/mods/list/ModItem.cpp +++ b/loader/src/ui/mods/list/ModItem.cpp @@ -84,12 +84,12 @@ bool ModItem::init(ModSource&& source) { ); m_infoContainer->addChild(m_developers); - m_restartRequiredLabel = createGeodeTagLabel( + m_restartRequiredLabel = createTagLabel( "Restart Required", - {{ + { to3B(ColorProvider::get()->color("mod-list-restart-required-label"_spr)), to3B(ColorProvider::get()->color("mod-list-restart-required-label-bg"_spr)) - }} + } ); m_restartRequiredLabel->setID("restart-required-label"); m_restartRequiredLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(std::nullopt, .75f)); @@ -208,6 +208,11 @@ bool ModItem::init(ModSource&& source) { paidModLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .8f)); m_titleContainer->addChild(paidModLabel); } + if (metadata.tags.contains("modtober24")) { + auto modtoberLabel = CCSprite::createWithSpriteFrameName("tag-modtober.png"_spr); + modtoberLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .8f)); + m_titleContainer->addChild(modtoberLabel); + } // Show mod download count here already so people can make informed decisions // on which mods to install @@ -363,7 +368,10 @@ void ModItem::updateState() { m_bg->setColor("mod-list-paid-color"_cc3b); m_bg->setOpacity(55); } - + if (metadata.tags.contains("modtober24")) { + m_bg->setColor(ccc3(63, 91, 138)); + m_bg->setOpacity(85); + } if (isGeodeTheme() && metadata.featured) { m_bg->setColor("mod-list-featured-color"_cc3b); m_bg->setOpacity(65); diff --git a/loader/src/ui/mods/list/ModList.cpp b/loader/src/ui/mods/list/ModList.cpp index 3335f7d7..9a52658c 100644 --- a/loader/src/ui/mods/list/ModList.cpp +++ b/loader/src/ui/mods/list/ModList.cpp @@ -4,6 +4,7 @@ #include "../popups/SortPopup.hpp" #include "../GeodeStyle.hpp" #include "../ModsLayer.hpp" +#include "../popups/ModtoberPopup.hpp" bool ModList::init(ModListSource* src, CCSize const& size) { if (!CCNode::init()) @@ -249,6 +250,34 @@ bool ModList::init(ModListSource* src, CCSize const& size) { m_topContainer->addChild(m_searchMenu); + // Modtober banner; this can be removed after Modtober 2024 is over! + if ( + auto src = typeinfo_cast(m_source); + src && src->getType() == ServerModListType::Modtober24 + ) { + auto menu = CCMenu::create(); + menu->setID("modtober-banner"); + menu->ignoreAnchorPointForPosition(false); + menu->setContentSize({ size.width, 30 }); + + auto banner = CCSprite::createWithSpriteFrameName("modtober24-banner.png"_spr); + limitNodeWidth(banner, size.width, 1.f, .1f); + menu->addChildAtPosition(banner, Anchor::Center); + + auto label = CCLabelBMFont::create("Modtober 2024 is Here!", "bigFont.fnt"); + label->setScale(.5f); + menu->addChildAtPosition(label, Anchor::Left, ccp(10, 0), ccp(0, .5f)); + + auto aboutSpr = createGeodeButton("About"); + aboutSpr->setScale(.5f); + auto aboutBtn = CCMenuItemSpriteExtra::create( + aboutSpr, this, menu_selector(ModList::onModtoberInfo) + ); + menu->addChildAtPosition(aboutBtn, Anchor::Right, ccp(-35, 0)); + + m_topContainer->addChild(menu); + } + m_topContainer->setLayout( ColumnLayout::create() ->setGap(0) @@ -659,6 +688,9 @@ void ModList::onToggleErrors(CCObject*) { void ModList::onUpdateAll(CCObject*) { server::ModDownloadManager::get()->startUpdateAll(); } +void ModList::onModtoberInfo(CCObject*) { + ModtoberPopup::create()->show(); +} size_t ModList::getPage() const { return m_page; diff --git a/loader/src/ui/mods/list/ModList.hpp b/loader/src/ui/mods/list/ModList.hpp index 21b72dcc..1183a11f 100644 --- a/loader/src/ui/mods/list/ModList.hpp +++ b/loader/src/ui/mods/list/ModList.hpp @@ -71,6 +71,7 @@ protected: void onToggleUpdates(CCObject*); void onToggleErrors(CCObject*); void onUpdateAll(CCObject*); + void onModtoberInfo(CCObject*); public: static ModList* create(ModListSource* src, CCSize const& size); diff --git a/loader/src/ui/mods/popups/ModPopup.cpp b/loader/src/ui/mods/popups/ModPopup.cpp index 03427bf3..9b0928f5 100644 --- a/loader/src/ui/mods/popups/ModPopup.cpp +++ b/loader/src/ui/mods/popups/ModPopup.cpp @@ -10,6 +10,7 @@ #include "../settings/ModSettingsPopup.hpp" #include "../../../internal/about.hpp" #include "../../GeodeUIEvent.hpp" +#include "../popups/ModtoberPopup.hpp" class FetchTextArea : public CCNode { public: @@ -300,12 +301,12 @@ bool ModPopup::setup(ModSource&& src) { manageTitle->setOpacity(195); manageContainer->addChildAtPosition(manageTitle, Anchor::Left, ccp(0, 0), ccp(0, .5f)); - m_restartRequiredLabel = createGeodeTagLabel( + m_restartRequiredLabel = createTagLabel( "Restart Required", - {{ + { to3B(ColorProvider::get()->color("mod-list-restart-required-label"_spr)), to3B(ColorProvider::get()->color("mod-list-restart-required-label-bg"_spr)) - }} + } ); m_restartRequiredLabel->setScale(.3f); manageContainer->addChildAtPosition(m_restartRequiredLabel, Anchor::Right, ccp(0, 0), ccp(1, .5f)); @@ -880,10 +881,7 @@ void ModPopup::onLoadTags(typename server::ServerRequestremoveAllChildren(); for (auto& tag : data) { - auto readable = tag; - readable[0] = std::toupper(readable[0]); - auto colors = geodeTagColor(tag); - m_tags->addChild(createGeodeTagLabel(readable)); + m_tags->addChild(createGeodeTagLabel(tag)); } if (data.empty()) { @@ -891,6 +889,47 @@ void ModPopup::onLoadTags(typename server::ServerRequestsetOpacity(120); m_tags->addChild(label); } + // This should probably be kept even after modtober ends, + // so the banner sprite must be kept + // If the build times from the cool popup become too long then we can + // probably move that to a normal FLAlert that explains "Modtober was + // this contest blah blah this mod was made for it" + else if (data.contains("modtober24")) { + auto menu = CCMenu::create(); + menu->setID("modtober-banner"); + menu->ignoreAnchorPointForPosition(false); + menu->setContentSize({ m_rightColumn->getContentWidth(), 25 }); + + auto banner = CCSprite::createWithSpriteFrameName("modtober24-banner-2.png"_spr); + limitNodeWidth(banner, m_rightColumn->getContentWidth(), 1.f, .1f); + menu->addChildAtPosition(banner, Anchor::Center); + + auto label = CCLabelBMFont::create("Entry for Modtober 2024", "bigFont.fnt"); + label->setScale(.35f); + menu->addChildAtPosition(label, Anchor::Left, ccp(10, 0), ccp(0, .5f)); + + auto aboutSpr = createGeodeButton("About"); + aboutSpr->setScale(.35f); + auto aboutBtn = CCMenuItemSpriteExtra::create( + aboutSpr, this, menu_selector(ModPopup::onModtoberInfo) + ); + menu->addChildAtPosition(aboutBtn, Anchor::Right, ccp(-25, 0)); + + m_rightColumn->addChildAtPosition(menu, Anchor::Bottom, ccp(0, 0), ccp(.5f, 0)); + + m_modtoberBanner = menu; + + // Force reload of all the tabs since otherwise their contents will overflow + for (auto& [_, tab] : m_tabs) { + if (tab.second && tab.second->getParent()) { + tab.second->removeFromParent(); + } + tab.second = nullptr; + } + // This might cause a minor inconvenience to someone who opens the popup and + // immediately switches to changelog but is then forced back into details + this->loadTab(Tab::Details); + } m_tags->updateLayout(); @@ -920,12 +959,17 @@ void ModPopup::loadTab(ModPopup::Tab tab) { btn.first->select(value == tab); } + float modtoberBannerHeight = 0; + if (m_modtoberBanner) { + modtoberBannerHeight = 30; + } + if (auto existing = m_tabs.at(tab).second) { m_currentTabPage = existing; - m_rightColumn->addChildAtPosition(existing, Anchor::Bottom); + m_rightColumn->addChildAtPosition(existing, Anchor::Bottom, ccp(0, modtoberBannerHeight)); } else { - const auto size = (m_rightColumn->getContentSize() - ccp(0, 30)); + const auto size = (m_rightColumn->getContentSize() - ccp(0, 30 + modtoberBannerHeight)); const float mdScale = .85f; switch (tab) { case Tab::Details: { @@ -955,7 +999,7 @@ void ModPopup::loadTab(ModPopup::Tab tab) { } break; } m_currentTabPage->setAnchorPoint({ .5f, .0f }); - m_rightColumn->addChildAtPosition(m_currentTabPage, Anchor::Bottom); + m_rightColumn->addChildAtPosition(m_currentTabPage, Anchor::Bottom, ccp(0, modtoberBannerHeight)); m_tabs.at(tab).second = m_currentTabPage; } } @@ -1030,6 +1074,12 @@ void ModPopup::onLink(CCObject* sender) { void ModPopup::onSupport(CCObject*) { openSupportPopup(m_source.getMetadata()); } +void ModPopup::onModtoberInfo(CCObject*) { + // todo: if we want to get rid of the modtober popup sprite (because it's fucking massive) + // then we can just replace this with a normal FLAlert explaining + // "this mod was an entry for modtober 2024 blah blah blah" + ModtoberPopup::create()->show(); +} ModPopup* ModPopup::create(ModSource&& src) { auto ret = new ModPopup(); diff --git a/loader/src/ui/mods/popups/ModPopup.hpp b/loader/src/ui/mods/popups/ModPopup.hpp index a6bbf789..c5689af3 100644 --- a/loader/src/ui/mods/popups/ModPopup.hpp +++ b/loader/src/ui/mods/popups/ModPopup.hpp @@ -35,6 +35,7 @@ protected: ButtonSprite* m_restartRequiredLabel; CCNode* m_rightColumn; CCNode* m_currentTabPage = nullptr; + CCNode* m_modtoberBanner = nullptr; std::unordered_map>> m_tabs; EventListener> m_statsListener; EventListener>> m_tagsListener; @@ -63,6 +64,7 @@ protected: void onSettings(CCObject*); void onLink(CCObject*); void onSupport(CCObject*); + void onModtoberInfo(CCObject*); public: void loadTab(Tab tab); diff --git a/loader/src/ui/mods/popups/ModtoberPopup.cpp b/loader/src/ui/mods/popups/ModtoberPopup.cpp new file mode 100644 index 00000000..95e86364 --- /dev/null +++ b/loader/src/ui/mods/popups/ModtoberPopup.cpp @@ -0,0 +1,44 @@ +#include "ModtoberPopup.hpp" +#include +#include +#include + +bool ModtoberPopup::setup() { + m_bgSprite->setVisible(false); + + auto bg = CCSprite::createWithSpriteFrameName("modtober24-popup.png"_spr); + m_mainLayer->addChildAtPosition(bg, Anchor::Center); + + auto supportSpr = createGeodeButton("Join"); + supportSpr->setScale(.8f); + auto supportBtn = CCMenuItemSpriteExtra::create( + supportSpr, this, menu_selector(ModtoberPopup::onDiscord) + ); + m_buttonMenu->addChildAtPosition(supportBtn, Anchor::BottomRight, ccp(-65, 50)); + + return true; +} + +void ModtoberPopup::onDiscord(CCObject*) { + createQuickPopup( + "Join Modtober", + "Modtober is being hosted on the GD Programming Discord Server.\n" + "To participate, join GDP and read the rules for the contest in #modtober-2024", + "Cancel", "Join Discord", + [](auto, bool btn2) { + if (btn2) { + web::openLinkInBrowser("https://discord.gg/gd-programming-646101505417674758"); + } + } + ); +} + +ModtoberPopup* ModtoberPopup::create() { + auto ret = new ModtoberPopup(); + if (ret && ret->init(410, 270)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} diff --git a/loader/src/ui/mods/popups/ModtoberPopup.hpp b/loader/src/ui/mods/popups/ModtoberPopup.hpp new file mode 100644 index 00000000..48bb0c85 --- /dev/null +++ b/loader/src/ui/mods/popups/ModtoberPopup.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include "../GeodeStyle.hpp" + +using namespace geode::prelude; + +class ModtoberPopup : public GeodePopup<> { +protected: + bool setup() override; + + void onDiscord(CCObject*); + +public: + static ModtoberPopup* create(); +}; diff --git a/loader/src/ui/mods/sources/ModListSource.hpp b/loader/src/ui/mods/sources/ModListSource.hpp index 8007a272..dccb058e 100644 --- a/loader/src/ui/mods/sources/ModListSource.hpp +++ b/loader/src/ui/mods/sources/ModListSource.hpp @@ -143,6 +143,7 @@ enum class ServerModListType { Featured, Trending, Recent, + Modtober24, }; class ServerModListSource : public ModListSource { @@ -165,6 +166,7 @@ public: server::ModsQuery const& getQuery() const; InvalidateQueryAfter getQueryMut(); bool isDefaultQuery() const override; + ServerModListType getType() const; }; class ModPackListSource : public ModListSource { diff --git a/loader/src/ui/mods/sources/ServerModListSource.cpp b/loader/src/ui/mods/sources/ServerModListSource.cpp index 9ae8d828..04991d7a 100644 --- a/loader/src/ui/mods/sources/ServerModListSource.cpp +++ b/loader/src/ui/mods/sources/ServerModListSource.cpp @@ -23,6 +23,12 @@ void ServerModListSource::resetQuery() { .sorting = server::ModsSort::RecentlyPublished, }; } break; + + case ServerModListType::Modtober24: { + m_query = server::ModsQuery { + .tags = { "modtober24" }, + }; + } break; } } @@ -76,6 +82,11 @@ ServerModListSource* ServerModListSource::get(ServerModListType type) { static auto inst = new ServerModListSource(ServerModListType::Recent); return inst; } break; + + case ServerModListType::Modtober24: { + static auto inst = new ServerModListSource(ServerModListType::Modtober24); + return inst; + } break; } } @@ -108,3 +119,7 @@ bool ServerModListSource::isDefaultQuery() const { m_query.tags.empty() && !m_query.developer.has_value(); } + +ServerModListType ServerModListSource::getType() const { + return m_type; +}