diff --git a/.gitignore b/.gitignore index beb171c..6a71e3a 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,5 @@ build-*/ # Visual Studio .vs/ + +bindings/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 7eb5ecc..f820fc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64") set(CMAKE_CXX_VISIBILITY_PRESET hidden) +set(GEODE_BINDINGS_REPO_PATH "D:\\Projects\\gay-wave-trail\\bindings\\") project(gay-wave-trail VERSION 1.0.0) @@ -14,6 +15,9 @@ file( src/trail_customization/*.cpp src/settings/*.cpp src/ui/*.cpp + src/types/*.cpp + src/types/*.hpp + src/*.hpp ) add_library(${PROJECT_NAME} SHARED ${SOURCES}) diff --git a/mod.json b/mod.json index 5c50151..1484a53 100644 --- a/mod.json +++ b/mod.json @@ -1,5 +1,5 @@ { - "geode": "3.8.1", + "geode": "3.4.0", "gd": { "win": "2.206", "android": "2.206", @@ -67,12 +67,6 @@ "type": "bool", "default": false }, - "rainbow-icon": { - "name": "First it was just the trail...", - "description": "NOW THE ICON TOO???\noh my fuckin sex, i\nam so dissapointed.\n(changes the wave icon color)", - "type": "bool", - "default": false - }, "saturation": { "name": "HOW GAY ARE YOU???", "type": "float", @@ -109,9 +103,11 @@ "default": false }, "gaydient-section": { - "type": "title", "name": "Gaydient Settings", - "description": "make kayla gay" + "type": "custom", + "description": "make kayla gay", + "scale": 300, + "posX": 80 }, "use-gradient": { "name": "Enable the GAYDIENT", @@ -119,34 +115,18 @@ "type": "bool", "default": false }, - "color-one": { - "name": "Wave trail color #1", - "description": "The first color the wave trail will become", - "type": "color", - "default": "#FFFFFF" - }, - "color-two": { - "name": "Wave trail color #2", - "description": "The second color the wave trail will become", - "type": "color", - "default": "#FFFFFF" - }, - "color-three": { - "name": "Wave trail color #3", - "description": "The third color the wave trail will become", - "type": "color", - "default": "#FFFFFF" - }, - "color-four": { - "name": "Wave trail color #4", - "description": "The fourth color the wave trail will become", - "type": "color", - "default": "#FFFFFF" + "colors-list": { + "name": "Colors", + "type": "custom", + "description": "the colors to use for the wave trail", + "default": ["#FFFFFF", "#FFFFFF", "#FFFFFF", "#FFFFFF"] }, "chaos-section": { "name": "The chaos emeralds (CHECK SETTING DESCRIPTIONS)", - "type": "title", - "description": "make skylar gay" + "type": "custom", + "description": "make skylar gay", + "scale": 300, + "posX": 160 }, "chaos": { "name": "THE CHAOS EMERALDS", diff --git a/resources/addIcon.png b/resources/addIcon.png new file mode 100644 index 0000000..3fc1816 Binary files /dev/null and b/resources/addIcon.png differ diff --git a/src/includes.hpp b/src/includes.hpp new file mode 100644 index 0000000..102c287 --- /dev/null +++ b/src/includes.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "Geode/loader/Log.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace geode::prelude; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index d607767..468cdbe 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,14 +6,15 @@ #include #include #include -#include #include #include "Geode/cocos/misc_nodes/CCMotionStreak.h" #include "Geode/loader/ModEvent.hpp" #include "Geode/modify/Modify.hpp" +#include "settings/multi_string_setting.hpp" #include "settings/section.hpp" #include "trail_customization/rainbow_trail.hpp" +#include "utils/color_utils.hpp" using namespace geode::prelude; using namespace cocos2d; @@ -27,19 +28,21 @@ struct MyPlayLayer : Modify { float speed = Mod::get()->getSettingValue("speed"); float saturation = Mod::get()->getSettingValue("saturation"); + std::vector color_strings = Mod::get()->getSettingValue("colors-list").m_strings; + + std::vector colors; + + for (const auto &color_string: color_strings) { + colors.push_back(ColorUtils::hex_to_rgb(color_string)); + } + bool mirror_players = Mod::get()->getSettingValue("mirror-players"); bool use_gradient = Mod::get()->getSettingValue("use-gradient"); bool enable = Mod::get()->getSettingValue("enable"); bool noRegularTrail = Mod::get()->getSettingValue("no-reg-trail"); - bool rainbow_icon = Mod::get()->getSettingValue("rainbow-icon"); bool disable_trail = Mod::get()->getSettingValue("disable-wave-trail"); bool infinite_trail = Mod::get()->getSettingValue("infinite-trail"); - auto color1 = Mod::get()->getSettingValue("color-one"); - auto color2 = Mod::get()->getSettingValue("color-two"); - auto color3 = Mod::get()->getSettingValue("color-three"); - auto color4 = Mod::get()->getSettingValue("color-four"); - if (ColorUtils::owo >= 360) { ColorUtils::owo = 0; } else { @@ -49,76 +52,30 @@ struct MyPlayLayer : Modify { phase = fmod(phase + speed, 360.f); bool p2 = true; - _ccColor3B rainbowColor = RainbowTrail::get_rainbow(0, saturation); - _ccColor3B rainbowColor2 = RainbowTrail::get_rainbow(180, saturation); - - _ccColor3B gradientColor = RainbowTrail::get_gradient(phase, 0.0f, false, color1, color2, color3, color4); - _ccColor3B gradientColor2 = RainbowTrail::get_gradient(phase, 0.0f, false, color4, color3, color2, color1); + RainbowTrail traill; - if (m_player1->m_isDart && noRegularTrail) { - m_player1->m_regularTrail - ->setVisible(false); - } + ccColor3B rainbowColor = RainbowTrail::get_rainbow(0, saturation); + ccColor3B rainbowColor2 = RainbowTrail::get_rainbow(180, saturation); + auto gradientColor = traill.get_gradient(phase, 0.0f, false, colors); + auto gradientColor2 = traill.get_gradient(phase + 180.0f, 0.0f, false, colors); - if (m_player2->m_isDart && noRegularTrail) { - m_player2->m_regularTrail - ->setVisible(false); - } - - if (enable) { - if (! use_gradient) { - if (m_player1->m_waveTrail) { - m_player1->m_waveTrail - ->setColor(rainbowColor); - - if (rainbow_icon && m_player1->m_isDart) { - m_player1->setColor(rainbowColor); - } - } - - if (m_player2->m_waveTrail) { - m_player2->m_waveTrail - ->setColor(! mirror_players - ? rainbowColor2 - : rainbowColor); - - if (rainbow_icon && m_player1->m_isDart) { - m_player2->setColor(rainbowColor2); - } - } - } else { + if (enable && m_player1->m_isDart) { + m_player1->m_regularTrail->setVisible(!noRegularTrail); if (m_player1->m_waveTrail) { - m_player1->m_waveTrail - ->setColor(gradientColor); - - if (rainbow_icon && m_player1->m_isDart) { - m_player1->setColor(gradientColor); - } else if (! rainbow_icon) { - reset_colors(); - } + m_player1->m_waveTrail->setColor(use_gradient ? gradientColor : rainbowColor); + m_player1->m_waveTrail->setVisible(!disable_trail); } + } + if (enable && m_player2->m_isDart) { + m_player2->m_regularTrail->setVisible(!noRegularTrail); if (m_player2->m_waveTrail) { - m_player2->m_waveTrail - ->setColor(! mirror_players - ? gradientColor - : gradientColor2); - - if (rainbow_icon && m_player2->m_isDart) { - m_player2->setColor(! mirror_players ? gradientColor : gradientColor2); - } else if (! rainbow_icon) { - reset_colors(); - } + ccColor3B p2Color = mirror_players + ? (use_gradient ? gradientColor : rainbowColor) + : (use_gradient ? gradientColor2 : rainbowColor2); + m_player2->m_waveTrail->setColor(p2Color); + m_player2->m_waveTrail->setVisible(!disable_trail); } - } - - if (disable_trail) { - this->m_player1->m_waveTrail->setVisible(false); - this->m_player2->m_waveTrail->setVisible(false); - } else { - this->m_player1->m_waveTrail->setVisible(true); - this->m_player2->m_waveTrail->setVisible(true); - } } } @@ -153,14 +110,14 @@ struct MyPlayLayer : Modify { /* persist wave trail */ struct MyMotionStreak : Modify { void fadeOutStreak2(float p0) { - if (!Mod::get()->getSettingValue("persist-wave-trail")) { + if (! Mod::get()->getSettingValue("persist-wave-trail")) { PlayerObject::fadeOutStreak2(p0); } } }; /* editor trail */ -class $modify(HardStreak) { +struct MyHardStreak : Modify { void updateStroke(float p0) { if (LevelEditorLayer::get() && Mod::get()->getSettingValue("editor-trail")) { m_drawStreak = true; @@ -170,7 +127,7 @@ class $modify(HardStreak) { } }; -class $modify(PlayerObject) { +struct MyPlayerObject : Modify { void placeStreakPoint() { if (LevelEditorLayer::get() && m_isDart && Mod::get()->getSettingValue("editor-trail")) { m_waveTrail->addPoint(this->getPosition()); @@ -191,4 +148,6 @@ class $modify(PlayerObject) { $on_mod(Loaded) { Mod::get()->addCustomSetting("gaydient-section", "none"); Mod::get()->addCustomSetting("chaos-section", "none"); -} + Mod::get()->addCustomSetting("colors-list", + Mod::get()->getSettingDefinition("colors-list")->get()->json->get>("default")); +} \ No newline at end of file diff --git a/src/settings/list_cell.cpp b/src/settings/list_cell.cpp new file mode 100644 index 0000000..2de5ae4 --- /dev/null +++ b/src/settings/list_cell.cpp @@ -0,0 +1,13 @@ +#include + +#include "list_cell.hpp" + +bool JBListCell::init(CCSize const &size) { + m_width = size.width; + m_height = size.height; + this->setContentSize(size); + this->setID("nong-list-cell"); + return true; +} + +void JBListCell::draw() { reinterpret_cast(this)->StatsCell::draw(); } \ No newline at end of file diff --git a/src/settings/list_cell.hpp b/src/settings/list_cell.hpp new file mode 100644 index 0000000..8f0178c --- /dev/null +++ b/src/settings/list_cell.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +using namespace geode::prelude; + +class JBListCell : public CCLayer, public FLAlertLayerProtocol { +protected: + float m_width; + float m_height; + + bool init(CCSize const &size); + void draw() override; +}; \ No newline at end of file diff --git a/src/settings/multi_string_setting.cpp b/src/settings/multi_string_setting.cpp new file mode 100644 index 0000000..5a2be6b --- /dev/null +++ b/src/settings/multi_string_setting.cpp @@ -0,0 +1,259 @@ +#include "multi_string_setting.hpp" +#include "../types/serializable_vector.hpp" +#include +#include + +using namespace geode::prelude; + +SettingNode *MultiStringSettingValue::createNode(float width) { + return MultiStringSettingNode::create(this, width); +} + +class MultiStringSettingPopup + : public geode::Popup, std::function)>> { +protected: + std::vector m_localValue; + std::function)> m_newStringsCallback; + CCMenu *m_listMenu; + ListView *m_list; + bool setup(std::vector localValue, + std::function)> newStringsCallback) override { + auto winSize = CCDirector::sharedDirector()->getWinSize(); + m_localValue = localValue; + m_newStringsCallback = newStringsCallback; + + m_listMenu = CCMenu::create(); + m_listMenu->setPosition(0, 0); + this->m_mainLayer->addChild(m_listMenu); + + createList(); + + handleTouchPriority(this); + return true; + } + + void onActionButton(CCObject *target) { + auto id = static_cast(target)->getID(); + auto index = std::stoi(id.substr(4)); + if (index == m_localValue.size() - 1) { + m_localValue.push_back(""); + } else { + m_localValue.erase(m_localValue.begin() + index); + } + m_newStringsCallback(m_localValue); + createList(); + } + + void createList() { + std::optional previousListPosition = {}; + if (m_list != nullptr) { + auto tableView = m_list->m_tableView; + float viewHeight = tableView->getContentHeight(); + float entireListHeight = tableView->m_contentLayer->getContentHeight(); + if (entireListHeight > viewHeight) { + float mostPossibleMovedHeightBottom = + entireListHeight - viewHeight; + float entireListYPosition = tableView->m_contentLayer->getPositionY(); + float mostPossibleMovedHeightTop = + -mostPossibleMovedHeightBottom - entireListYPosition; + previousListPosition = mostPossibleMovedHeightTop; + } + } + m_listMenu->removeAllChildren(); + m_list = nullptr; + + auto cells = CCArray::create(); + auto size = this->m_mainLayer->getContentSize(); + + for (int i = 0; i < m_localValue.size(); i++) { + auto last = i == m_localValue.size() - 1; + auto size2 = CCSize{size.width - 10.f, 40.f}; + + auto menu = CCMenu::create(); + menu->setPosition(0, 0); + + auto inputNode = TextInput::create(103.f, "Color Hex", "chatFont.fnt"); + inputNode->setScale(1.f); + inputNode->setPosition(size2.width / 2 - 15.f, size2.height / 2); + inputNode->setFilter("#0123456789abcdefABCDEF"); + inputNode->setMaxCharCount(300); + inputNode->setWidth(size2.width - 60.f); + inputNode->setString(m_localValue[i], false); + inputNode->setCallback([this, i](std::string const &str) { + m_localValue[i] = str; + m_newStringsCallback(m_localValue); + }); + menu->addChild(inputNode); + + auto spr = last ? CCSprite::create("addIcon.png"_spr) + : CCSprite::createWithSpriteFrameName("GJ_deleteIcon_001.png"); + spr->setScale(0.75f); + auto btn = CCMenuItemSpriteExtra::create( + spr, this, menu_selector(MultiStringSettingPopup::onActionButton)); + btn->setID(fmt::format("btn-{}", i)); + btn->setPosition(size2.width - 15.f, size2.height / 2); + menu->addChild(btn); + cells->addObject(menu); + } + + auto list = ListView::create(cells, 40.f, size.width - 8.f, size.height - 16.f); + list->setPosition(4.f, 10.f); + + std::optional newListPosition = {}; + if (previousListPosition.has_value()) { + auto tableView = list->m_tableView; + float viewHeight = tableView->getContentHeight(); + float entireListHeight = tableView->m_contentLayer->getContentHeight(); + // VV + float mostPossibleMovedHeightBottom = entireListHeight - viewHeight; + // VV + newListPosition = -previousListPosition.value() - mostPossibleMovedHeightBottom; + newListPosition = std::clamp(newListPosition.value(), -mostPossibleMovedHeightBottom, 0.f); + if (entireListHeight < viewHeight) { + newListPosition = viewHeight - entireListHeight; + } + } + if (newListPosition.has_value()) { + list->m_tableView->m_contentLayer->setPosition(CCPoint{0, newListPosition.value()}); + } + m_listMenu->addChild(list); + m_list = list; + } + +public: + static MultiStringSettingPopup * + create(std::vector localValue, + std::function)> newStringsCallback) { + auto ret = new MultiStringSettingPopup(); + if (ret && ret->initAnchored(420.f, 160.f, localValue, newStringsCallback)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; + } +}; + +bool MultiStringSettingNode::init(MultiStringSettingValue *value, float width) { + if (! SettingNode::init(value)) + return false; + this->m_value = value; + for (std::string &str: value->getStrings()) { + m_localValue.push_back(str); + } + + float height = 40.f; + this->setContentSize({width, height}); + + auto menu = CCMenu::create(); + menu->setPosition(0, 0); + menu->setID("inputs-menu"); + + // No way to get the JSON without hardcoding the setting ID... + auto settingJson = Mod::get()->getSettingDefinition("colors-list")->get()->json; + m_defaultValue = settingJson->get>("default"); + m_name = settingJson->get("name"); + m_description = settingJson->get("description"); + + m_label = CCLabelBMFont::create(m_name.c_str(), "bigFont.fnt"); + m_label->setAnchorPoint({0.f, 0.5f}); + m_label->setPosition(20.f, height / 2); + m_label->setScale(0.5f); + menu->addChild(m_label); + + auto infoSpr = CCSprite::createWithSpriteFrameName("GJ_infoIcon_001.png"); + infoSpr->setScale(.6f); + auto infoBtn = + CCMenuItemSpriteExtra::create(infoSpr, this, menu_selector(MultiStringSettingNode::onDesc)); + infoBtn->setPosition(m_label->getScaledContentSize().width + 40.f, height / 2); + menu->addChild(infoBtn); + + auto resetSpr = CCSprite::createWithSpriteFrameName("geode.loader/reset-gold.png"); + resetSpr->setScale(.5f); + m_resetBtn = + CCMenuItemSpriteExtra::create(resetSpr, this, menu_selector(MultiStringSettingNode::onReset)); + m_resetBtn->setPosition(m_label->getScaledContentSize().width + 40.f + 20.f, height / 2); + menu->addChild(m_resetBtn); + + auto viewSpr = ButtonSprite::create("View"); + viewSpr->setScale(0.72f); + auto viewBtn = + CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(MultiStringSettingNode::onView)); + viewBtn->setPosition(width - 40.f, height - 20.f); + menu->addChild(viewBtn); + + this->addChild(menu); + handleTouchPriority(this); + + updateVisuals(); + + return true; +} + +void MultiStringSettingNode::onView(CCObject *) { + auto popup = + MultiStringSettingPopup::create(m_localValue, [this](std::vector newStrings) { + m_localValue = newStrings; + updateVisuals(); + }); + popup->m_noElasticity = true; + popup->show(); +} + +void MultiStringSettingNode::onReset(CCObject *) { resetToDefault(); } + +void MultiStringSettingNode::onDesc(CCObject *) { + FLAlertLayer::create(m_name.c_str(), m_description.c_str(), "OK")->show(); +} + +void MultiStringSettingNode::updateVisuals() { + m_resetBtn->setVisible(hasNonDefaultValue()); + m_label->setColor(hasUncommittedChanges() ? ccColor3B{0x11, 0xdd, 0x00} + : ccColor3B{0xff, 0xff, 0xff}); + this->dispatchChanged(); +} + +void MultiStringSettingNode::commit() { + this->m_value->setStrings(m_localValue); + updateVisuals(); + this->dispatchCommitted(); +} + +bool MultiStringSettingNode::hasUncommittedChanges() { + if (m_localValue.size() != m_value->getStrings().size()) + return true; + for (int i = 0; i < m_localValue.size(); i++) { + if (m_localValue[i] != m_value->getStrings()[i]) + return true; + } + return false; +} + +bool MultiStringSettingNode::hasNonDefaultValue() { + if (m_localValue.size() != m_defaultValue.size()) + return true; + for (int i = 0; i < m_localValue.size(); i++) { + if (m_localValue[i] != m_defaultValue[i]) + return true; + } + return false; +} + +void MultiStringSettingNode::resetToDefault() { + m_localValue.clear(); + for (std::string &str: m_defaultValue) { + m_localValue.push_back(str); + } + updateVisuals(); +} + +MultiStringSettingNode *MultiStringSettingNode::create(MultiStringSettingValue *value, + float width) { + auto ret = new MultiStringSettingNode(); + if (ret && ret->init(value, width)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} \ No newline at end of file diff --git a/src/settings/multi_string_setting.hpp b/src/settings/multi_string_setting.hpp new file mode 100644 index 0000000..3e46f12 --- /dev/null +++ b/src/settings/multi_string_setting.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include "list_cell.hpp" +#include "../includes.hpp" + +using namespace geode::prelude; + +struct MultiStringSettingStruct { + std::vector m_strings; +}; + +class MultiStringSettingValue; + +class MultiStringSettingValue : public SettingValue { +protected: + std::vector m_strings; + +public: + MultiStringSettingValue(std::string const &key, std::string const &modID, + std::vector strings) + : SettingValue(key, modID), m_strings(strings) {} + + bool load(matjson::Value const &json) override { + m_strings.clear(); + auto array = json.as_array(); + for (auto const &elem : array) { + m_strings.push_back(elem.as_string()); + } + return true; + } + + bool save(matjson::Value &json) const override { + auto array = matjson::Array(); + for (auto const &string : m_strings) { + array.push_back(string); + } + json = array; + return true; + } + + SettingNode *createNode(float width) override; + + void setStrings(std::vector strings) { + this->m_strings = strings; + this->valueChanged(); + } + + std::vector getStrings() const { return this->m_strings; } +}; + +template <> struct SettingValueSetter { + static MultiStringSettingStruct get(SettingValue *setting) { + return MultiStringSettingStruct{static_cast(setting)->getStrings()}; + }; + static void set(MultiStringSettingValue *setting, MultiStringSettingStruct const &value) { + setting->setStrings(value.m_strings); + }; +}; + +class MultiStringSettingNode : public SettingNode { +protected: + MultiStringSettingValue *m_value; + CCMenuItemSpriteExtra *m_resetBtn; + CCLabelBMFont *m_label; + std::vector m_localValue; + std::string m_name; + std::string m_description; + std::vector m_defaultValue; + + bool init(MultiStringSettingValue *value, float width); + +public: + void updateVisuals(); + void onView(CCObject *); + void onReset(CCObject *); + void onDesc(CCObject *); + void commit() override; + bool hasUncommittedChanges() override; + bool hasNonDefaultValue() override; + void resetToDefault() override; + static MultiStringSettingNode *create(MultiStringSettingValue *value, float width); +}; \ No newline at end of file diff --git a/src/trail_customization/rainbow_trail.cpp b/src/trail_customization/rainbow_trail.cpp index 2d17389..925a0f2 100644 --- a/src/trail_customization/rainbow_trail.cpp +++ b/src/trail_customization/rainbow_trail.cpp @@ -1,7 +1,7 @@ #include "rainbow_trail.hpp" #include -#include #include +#include cocos2d::_ccColor3B RainbowTrail::get_rainbow(float offset, float saturation) { float R, G, B; @@ -18,30 +18,30 @@ cocos2d::_ccColor3B RainbowTrail::get_rainbow(float offset, float saturation) { return out; } -cocos2d::_ccColor3B RainbowTrail::get_gradient(float &phase, float offset, bool smooth, ccColor3B c1, ccColor3B c2, ccColor3B c3, ccColor3B c4) { - float t = fmodf(phase + offset, 360.0f); - float y = t / 90.0f; - int quadrant = static_cast(t / 90.0f); +cocos2d::_ccColor3B RainbowTrail::get_gradient(float phase, float offset, bool smooth, const std::vector &colors) { + float t = fmodf(phase + offset, 360.0f); + float y = t / 90.0f; + int quadrant = static_cast(t / 90.0f); - cocos2d::_ccColor3B out; + cocos2d::_ccColor3B out; - out.r = static_cast( - quadrant == 0 ? c1.r + (c2.r - c1.r) * y - : quadrant == 1 ? c2.r + (c3.r - c2.r) * (y - 1.0f) - : quadrant == 2 ? c3.r + (c4.r - c3.r) * (y - 2.0f) - : c4.r + (c1.r - c4.r) * (y - 3.0f)); + out.r = static_cast( + quadrant == 0 ? colors[0].r + (colors[1].r - colors[0].r) * y + : quadrant == 1 ? colors[1].r + (colors[2].r - colors[1].r) * (y - 1.0f) + : quadrant == 2 ? colors[2].r + (colors[3].r - colors[2].r) * (y - 2.0f) + : colors[3].r + (colors[0].r - colors[3].r) * (y - 3.0f)); - out.g = static_cast( - quadrant == 0 ? c1.g + (c2.g - c1.g) * y - : quadrant == 1 ? c2.g + (c3.g - c2.g) * (y - 1.0f) - : quadrant == 2 ? c3.g + (c4.g - c3.g) * (y - 2.0f) - : c4.g + (c1.g - c4.g) * (y - 3.0f)); + out.g = static_cast( + quadrant == 0 ? colors[0].g + (colors[1].g - colors[0].g) * y + : quadrant == 1 ? colors[1].g + (colors[2].g - colors[1].g) * (y - 1.0f) + : quadrant == 2 ? colors[2].g + (colors[3].g - colors[2].g) * (y - 2.0f) + : colors[3].g + (colors[0].g - colors[3].g) * (y - 3.0f)); - out.b = static_cast( - quadrant == 0 ? c1.b + (c2.b - c1.b) * y - : quadrant == 1 ? c2.b + (c3.b - c2.b) * (y - 1.0f) - : quadrant == 2 ? c3.b + (c4.b - c3.b) * (y - 2.0f) - : c4.b + (c1.b - c4.b) * (y - 3.0f)); + out.b = static_cast( + quadrant == 0 ? colors[0].b + (colors[1].b - colors[0].b) * y + : quadrant == 1 ? colors[1].b + (colors[2].b - colors[1].b) * (y - 1.0f) + : quadrant == 2 ? colors[2].b + (colors[3].b - colors[2].b) * (y - 2.0f) + : colors[3].b + (colors[0].b - colors[3].b) * (y - 3.0f)); - return out; -} + return out; +} \ No newline at end of file diff --git a/src/trail_customization/rainbow_trail.hpp b/src/trail_customization/rainbow_trail.hpp index a70e73d..b01d1fc 100644 --- a/src/trail_customization/rainbow_trail.hpp +++ b/src/trail_customization/rainbow_trail.hpp @@ -8,5 +8,5 @@ class RainbowTrail { public: static cocos2d::_ccColor3B get_rainbow(float offset, float saturation); cocos2d::_ccColor3B get_custom_rainbow(const std::vector &hex_colors, float offset, float saturation); - static cocos2d::_ccColor3B get_gradient(float &phase, float offset, bool smooth, ccColor3B c1, ccColor3B c2, ccColor3B c3, ccColor3B c4); + cocos2d::_ccColor3B get_gradient(float phase, float offset, bool smooth, const std::vector& colors); }; \ No newline at end of file diff --git a/src/types/serializable_vector.hpp b/src/types/serializable_vector.hpp new file mode 100644 index 0000000..736ad5b --- /dev/null +++ b/src/types/serializable_vector.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include + +template <> +struct matjson::Serialize> { + static bool is_json(matjson::Value const &value) { return value.is_array(); } + + static std::vector from_json(matjson::Value const &value) { + std::vector vec; + if (! is_json(value)) { + return vec; + } + auto array = value.as_array(); + for (auto const &elem: array) { + vec.push_back(elem.as_int()); + } + return vec; + } + + static matjson::Value to_json(std::vector const &vec) { + auto array = matjson::Array(); + for (auto const &elem: vec) { + array.push_back(matjson::Value(elem)); + } + return array; + } +}; + +template <> +struct matjson::Serialize> { + static bool is_json(matjson::Value const &value) { return value.is_array(); } + + static std::vector from_json(matjson::Value const &value) { + std::vector vec; + if (! is_json(value)) { + return vec; + } + auto array = value.as_array(); + for (auto const &elem: array) { + vec.push_back(elem.as_string()); + } + return vec; + } + + static matjson::Value to_json(std::vector const &vec) { + auto array = matjson::Array(); + for (auto const &elem: vec) { + array.push_back(matjson::Value(elem)); + } + return array; + } +}; \ No newline at end of file diff --git a/src/utils/color_utils.cpp b/src/utils/color_utils.cpp index 8a61189..98feb55 100644 --- a/src/utils/color_utils.cpp +++ b/src/utils/color_utils.cpp @@ -3,42 +3,42 @@ void ColorUtils::hsv_to_rgb(float &fR, float &fG, float &fB, float &fH, float &fS, float &fV) { float c = fV * fS; - float x = static_cast(static_cast(c) * ( 1 - std::abs(std::fmod(fH / 60.0f, 2) - 1))); + float x = static_cast(static_cast(c) * (1 - std::abs(std::fmod(fH / 60.0f, 2) - 1))); float m = fV - c; - fR = ( fH < 60.0f ) - ? c - : ( fH < 120.0f ) - ? x - : ( fH < 180.0f ) + fR = (fH < 60.0f) + ? c + : (fH < 120.0f) + ? x + : (fH < 180.0f) ? 0 - : ( fH < 240.0f ) - ? 0 - : ( fH < 300.0f ) - ? x - : c; - fG = ( fH < 60.0f ) - ? x - : ( fH < 120.0f ) - ? c - : ( fH < 180.0f ) + : (fH < 240.0f) + ? 0 + : (fH < 300.0f) + ? x + : c; + fG = (fH < 60.0f) + ? x + : (fH < 120.0f) + ? c + : (fH < 180.0f) ? c - : ( fH < 240.0f ) - ? x - : ( fH < 300.0f ) - ? 0 - : 0; - fB = ( fH < 60.0f ) - ? 0 - : ( fH < 120.0f ) - ? 0 - : ( fH < 180.0f ) + : (fH < 240.0f) ? x - : ( fH < 240.0f ) - ? c - : ( fH < 300.0f ) - ? c - : x; + : (fH < 300.0f) + ? 0 + : 0; + fB = (fH < 60.0f) + ? 0 + : (fH < 120.0f) + ? 0 + : (fH < 180.0f) + ? x + : (fH < 240.0f) + ? c + : (fH < 300.0f) + ? c + : x; fR += m; fG += m; @@ -48,16 +48,16 @@ void ColorUtils::hsv_to_rgb(float &fR, float &fG, float &fB, float &fH, float &f float ColorUtils::owo = 0; void ColorUtils::hex_to_hsv(uint32_t hex, float &h, float &s, float &v) { - float r = (( hex >> 16 ) & 0xFF ) / 255.0f; - float g = (( hex >> 8 ) & 0xFF ) / 255.0f; - float b = ( hex & 0xFF ) / 255.0f; + float r = ((hex >> 16) & 0xFF) / 255.0f; + float g = ((hex >> 8) & 0xFF) / 255.0f; + float b = (hex & 0xFF) / 255.0f; float max = std::max(std::max(r, g), b); float min = std::min(std::min(r, g), b); v = max; float delta = max - min; - if ( max != 0.0f ) { + if (max != 0.0f) { s = delta / max; } else { s = 0.0f; @@ -65,16 +65,65 @@ void ColorUtils::hex_to_hsv(uint32_t hex, float &h, float &s, float &v) { return; } - if ( r == max ) { - h = ( g - b ) / delta; - } else if ( g == max ) { - h = 2.0f + ( b - r ) / delta; + if (r == max) { + h = (g - b) / delta; + } else if (g == max) { + h = 2.0f + (b - r) / delta; } else { - h = 4.0f + ( r - g ) / delta; + h = 4.0f + (r - g) / delta; } h *= 60.0f; - if ( h < 0.0f ) { + if (h < 0.0f) { h += 360.0f; } } + +cocos2d::ccColor3B ColorUtils::hex_to_rgb(const std::string &hex) { + if (! is_valid_hex_code(hex)) { + return cocos2d::ccColor3B{0, 0, 0}; + } + + std::string clean_hex = hex; + if (clean_hex[0] == '#') { + clean_hex = clean_hex.substr(1); + } + + GLubyte r = hex_pair_to_dec(clean_hex.substr(0, 2)); + GLubyte g = hex_pair_to_dec(clean_hex.substr(2, 2)); + GLubyte b = hex_pair_to_dec(clean_hex.substr(4, 2)); + + return cocos2d::ccColor3B{r, g, b}; +} + +GLubyte ColorUtils::hex_pair_to_dec(const std::string &hex_pair) { + int val = 0; + + for (char c: hex_pair) { + val = val * 16; + + if (c >= '0' && c <= '9') { + val += c - '0'; + } else { + val += std::tolower(c) - 'a' + 10; + } + } + + return static_cast(val); +} + +bool ColorUtils::is_valid_hex_code(const std::string &hex_code) { + if (hex_code.empty()) + return false; + + size_t start_pos = (hex_code[0] == '#') ? 1 : 0; + + if (hex_code.length() != start_pos + 6) + return false; + + return std::all_of(hex_code.begin() + start_pos, hex_code.end(), + [](char c) { + c = std::tolower(c); + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); + }); +} \ No newline at end of file diff --git a/src/utils/color_utils.hpp b/src/utils/color_utils.hpp index b45041d..6da02ad 100644 --- a/src/utils/color_utils.hpp +++ b/src/utils/color_utils.hpp @@ -1,10 +1,17 @@ +#pragma once + #include using namespace cocos2d; class ColorUtils { public: + static cocos2d::ccColor3B hex_to_rgb(const std::string& hex); static void hsv_to_rgb(float &fR, float &fG, float &fB, float &fH, float &fS, float &fV); static void hex_to_hsv(uint32_t hex, float &h, float &s, float &v); static float owo; + +private: + static GLubyte hex_pair_to_dec(const std::string& hex_pair); + static bool is_valid_hex_code(const std::string& hex_code); }; \ No newline at end of file