diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..fa3315c --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,91 @@ +name: Build Geode Mod + +permissions: + contents: write + +on: + workflow_dispatch: + push: + branches: + - '**' + +jobs: + + build: + continue-on-error: true # continue even if build failed for some platform + strategy: + + fail-fast: false + matrix: + config: + - name: Windows + os: windows-latest + + - name: macOS + os: macos-latest + + - name: Android64 + os: ubuntu-latest + target: Android64 + + - name: Android32 + os: ubuntu-latest + target: Android32 + + name: ${{ matrix.config.name }} + runs-on: ${{ matrix.config.os }} + steps: + - uses: actions/checkout@v3 + + - name: Build the mod + uses: geode-sdk/build-geode-mod@main + with: + target: ${{ matrix.config.target }} + combine: true + + upload: + name: Combine and upload builds + runs-on: ubuntu-latest + needs: ['build'] + steps: + + - name: "Combine builds" + uses: geode-sdk/build-geode-mod/combine@main + id: build + + - name: "Upload artifact" + uses: actions/upload-artifact@v4 + with: + name: Build Output + path: ${{ steps.build.outputs.build-output }} + + - name: "Set up Git repository" + uses: actions/checkout@v2 + + - name: "Development Release" + uses: ncipollo/release-action@v1 + with: + name: "Development Release" + body: | + Release of success build for latest commit on `${{ github.head_ref || github.ref_name }}`. + The build workflow run for this release goes in [#${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + tag: "nightly" + prerelease: true + allowUpdates: true + artifactErrorsFailBuild: true + artifacts: "${{steps.build.outputs.build-output}}/*" + + - name: "Get mod properties" + id: json_properties + uses: ActionsTools/read-json-action@main + with: + file_path: "mod.json" + + - name: "Try Release Version" + uses: ncipollo/release-action@v1 + with: + generateReleaseNotes: true + tag: "${{steps.json_properties.outputs.version}}" + artifacts: "${{steps.build.outputs.build-output}}/*" + artifactErrorsFailBuild: true + allowUpdates: false \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc40f45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,66 @@ +out + +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Macos be like +**/.DS_Store + +# Cache files for Sublime Text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# Ignore build folders +**/build +# Ignore platform specific build folders +build-*/ + +# Workspace files are user-specific +*.sublime-workspace + +# ILY vscode +**/.vscode + +# Local History for Visual Studio Code +.history/ + +# clangd +.cache/ + +# Visual Studio +.vs/ + +# CLion +.idea/ +/cmake-build-*/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e770bd4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.21) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64") +set(CMAKE_CXX_VISIBILITY_PRESET hidden) + +project(CubesSlasher VERSION 1.0.0) + +include_directories(src) +add_library(${PROJECT_NAME} SHARED + src/main.cpp + # Add any extra C++ source files here +) + +if (NOT DEFINED ENV{GEODE_SDK}) + message(FATAL_ERROR "Unable to find Geode SDK! Please define GEODE_SDK environment variable to point to Geode") +else() + message(STATUS "Found Geode: $ENV{GEODE_SDK}") +endif() + +# my bindings... https://github.com/user95401/bindings +include("$ENV{GEODE_SDK}/cmake/CPM.cmake") +CPMAddPackage(NAME "bindings" + GITHUB_REPOSITORY "user95401/bindings" + GIT_TAG "main" + DOWNLOAD_ONLY YES + NO_CACHE YES +) + +set(GEODE_BINDINGS_REPO_PATH ${bindings_SOURCE_DIR}) + +add_subdirectory($ENV{GEODE_SDK} ${CMAKE_CURRENT_BINARY_DIR}/geode) + +setup_geode_mod(${PROJECT_NAME}) diff --git a/CMakeSettings.json b/CMakeSettings.json new file mode 100644 index 0000000..2dd4a22 --- /dev/null +++ b/CMakeSettings.json @@ -0,0 +1,16 @@ +{ + "configurations": [ + { + "name": "x64-Clang-Release", + "generator": "Ninja", + "configurationType": "RelWithDebInfo", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_x64_x64" ], + "variables": [] + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..13e833d --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# Player Particles + +- __customize player particles for drag, land etc__ + +add a ui and use saved data system to make able u create own particles for basic player effects + +- __.txt file "particle from string" loader__ + +u can create `EFFECT_NAME.txt` instead of `EFFECT_NAME.plist` to use **string system** instead xml dict. and also u can edit any other effect by that way. + +the mod's logo + +## Build instructions +For more info, see [docs](https://docs.geode-sdk.org/getting-started/create-mod#build) +```sh +# Assuming you have the Geode CLI set up already +geode build +``` + +### Resources +* [Geode SDK Documentation](https://docs.geode-sdk.org/) +* [Geode SDK Source Code](https://github.com/geode-sdk/geode/) +* [Geode CLI](https://github.com/geode-sdk/cli) +* [Bindings](https://github.com/geode-sdk/bindings/) +* [Dev Tools](https://github.com/geode-sdk/DevTools) diff --git a/about.md b/about.md new file mode 100644 index 0000000..7509400 --- /dev/null +++ b/about.md @@ -0,0 +1,10 @@ +# Player Particles + +- __customize player particles for drag, land etc__ + +add a ui and use saved data system to make able u create own particles for basic player effects + +- __.txt file "particle from string" loader__ + +u can create `EFFECT_NAME.txt` instead of `EFFECT_NAME.plist` to use **string system** instead xml dict. and also u can edit any other effect by that way. + diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..566d376 Binary files /dev/null and b/logo.png differ diff --git a/mod.json b/mod.json new file mode 100644 index 0000000..aaebb0d --- /dev/null +++ b/mod.json @@ -0,0 +1,16 @@ +{ + "geode": "3.5.0", + "gd": { + "win": "2.206", + "android": "2.206", + "mac": "2.206", + "ios": "2.206" + }, + "id": "user95401.player_particles", + "name": "Player Particles", + "version": "v1.0.0-beta.1", + "developer": "user95401", + "description": "customize player particles for drag, land etc", + "tags": [ "gameplay", "offline", "enhancement", "interface", "utility", "customization", "developer" ], + +} diff --git a/src/_fs.hpp b/src/_fs.hpp new file mode 100644 index 0000000..12d17a5 --- /dev/null +++ b/src/_fs.hpp @@ -0,0 +1,25 @@ +#pragma once +namespace fs { +//#include "libs/glob.hpp" + using namespace std::filesystem; + inline std::error_code last_err_code; + template inline auto rtnWithErrLog(T rtn, std::string log) { log::error("{}", log); return rtn; } + inline auto exists(path path) { + return cocos::fileExistsInSearchPaths(path.string().c_str()); + } + inline auto read(path path) { + unsigned long file_size = 0; + auto buffer = CCFileUtils::sharedFileUtils()->getFileData(path.string().c_str(), "rb", &file_size); + std::string data = "read failed..."; + if (buffer && file_size != 0) data = std::string(reinterpret_cast(buffer), file_size); + return data; + } + inline auto rename(path old_path, path new_path) { + std::filesystem::rename(old_path, new_path, last_err_code); + log::debug( + "{}(\n\told_path \"{}\", \n\told_path \"{}\"\n): last_err_code={}, last_err_code.message={}", + __func__, old_path, new_path, last_err_code, last_err_code.message() + ); + return true; + } +} \ No newline at end of file diff --git a/src/_main.hpp b/src/_main.hpp new file mode 100644 index 0000000..1eacfa5 --- /dev/null +++ b/src/_main.hpp @@ -0,0 +1,108 @@ +#pragma once +#include +#include +using namespace geode::prelude; + +#include + +#include <_fs.hpp> + +//lol +#define SETTING(type, key_name) Mod::get()->getSettingValue(key_name) + +#define MEMBER_BY_OFFSET(type, class, offset) *reinterpret_cast(reinterpret_cast(class) + offset) +template constexpr size_t OFFSET_BY_MEMBER(U T::* member) { return (char*)&((T*)nullptr->*member) - (char*)nullptr; } + +#define public_cast(value, member) [](auto* v) { \ + class FriendClass__; \ + using T = std::remove_pointer::type; \ + class FriendeeClass__: public T { \ + protected: \ + friend FriendClass__; \ + }; \ + class FriendClass__ { \ + public: \ + auto& get(FriendeeClass__* v) { return v->member; } \ + } c; \ + return c.get(reinterpret_cast(v)); \ +}(value) + +namespace geode::cocos { + inline std::string frameName(CCNode* node) { + if (node == nullptr) return "NIL_NODE"; + if (auto textureProtocol = dynamic_cast(node)) { + if (auto texture = textureProtocol->getTexture()) { + if (auto spriteNode = dynamic_cast(node)) { + auto* cachedFrames = CCSpriteFrameCache::sharedSpriteFrameCache()->m_pSpriteFrames; + const auto rect = spriteNode->getTextureRect(); + for (auto [key, frame] : CCDictionaryExt(cachedFrames)) { + if (frame->getTexture() == texture && frame->getRect() == rect) { + return key.c_str(); + } + } + } + auto* cachedTextures = CCTextureCache::sharedTextureCache()->m_pTextures; + for (auto [key, obj] : CCDictionaryExt(cachedTextures)) { + if (obj == texture) { + return key.c_str(); + } + } + } + } + auto btnSpriteTry = frameName(getChild(node, 0)); + if ( + btnSpriteTry != "NIL_NODE" + and btnSpriteTry != "CANT_GET_FRAME_NAME" + ) return btnSpriteTry; + return "CANT_GET_FRAME_NAME"; + } + inline auto createDataNode(std::string id, std::string text = "", int tag = 0) { + auto node = CCLabelBMFont::create("", "chatFont.fnt"); + node->setID(id); + node->setString(text.c_str()); + if (tag != 0) node->setTag(tag); + node->setVisible(0); + return node; + } + inline auto findDataNode(CCNode* parent, std::string id) { + auto node = typeinfo_cast(parent->getChildByIDRecursive(id)); + if (node) log::warn("FAILED TO FIND DATA NODE! id: {}", id); + return node; + } +}; + +namespace geode::utils::string { + inline std::vector explode(std::string separator, std::string input) { + std::vector vec; + for (int i{ 0 }; i < input.length(); ++i) { + int pos = input.find(separator, i); + if (pos < 0) { vec.push_back(input.substr(i)); break; } + int count = pos - i; + vec.push_back(input.substr(i, count)); + i = pos + separator.length() - 1; + } + if (vec.size() == 0) vec.push_back(input); + return vec; + } +} + +#include +#include +namespace geode::utils { + template + Iter select_randomly(Iter start, Iter end, RandomGenerator& g) { + std::uniform_int_distribution<> dis(0, std::distance(start, end) - 1); + std::advance(start, dis(g)); + return start; + }; + template + Iter select_randomly(Iter start, Iter end) { + static std::random_device rd; + static std::mt19937 gen(rd()); + return select_randomly(start, end, gen); + } +}; + +#ifdef GEODE_IS_ANDROID +//#define debug error +#endif // GEODE_IS_ANDROID diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..8670d8a --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,484 @@ +#include <_main.hpp> + +std::map defaultEffects; +$execute{ + defaultEffects["dragEffect"] = "30a-1a0.3a0.15a99a90a45a75a20a5a1a0a-300a0a0a0a0a4a3a0a0a1a0.1a1a0.1a1a0.1a1a0a0a0a0a0a0a0a0a0a0a0a1a0a0a0a0a0a0a0a0a0a0a0a0a1a1a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0"; + defaultEffects["shipDragEffect"] = "40a-1a0.3a0.15a133a110a45a94a20a9a1a-350a-300a0a0a0a0a3a2a0a0a1a0.1a1a0.1a1a0.1a1a0a0a0a0a0a0a0a0a0a0a0a1a0a0a0a0a0a0a0a0a0a0a0a0a0a1a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0"; + defaultEffects["landEffect"] = "10a0.02a0a0.6a-1a90a60a150a25a10a0a0a-500a0a0a0a0a5a3a0a0a1a0.1a1a0.1a1a0.1a1a0.5a0a0a0a0a0a0a0a0a0a0a1a0a0a0a0a0a0a0a0a0a0a0a0a0a1a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0"; + defaultEffects["dashEffect"] = "50a-1a0.4a0.2a125a180a0a25a12a0a12a0a0a0a0a0a0a8a4a0a0a1a0a1a0a1a0a1a0a0a1a0a0a0a0a0a0a0a0a1a0a0a0a0a0a0a0a0a0a0a0a0a0a1a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0"; +}; + +class ParticlePopup : public CreateParticlePopup { +public: + inline static Ref sharedObject = nullptr; + static auto get(std::string data) { + if (!sharedObject.data()) sharedObject = CreateParticlePopup::create(nullptr, nullptr); + sharedObject->removeAllChildrenWithCleanup(1); + sharedObject->init(nullptr, nullptr, data); + return sharedObject; + } +}; + +class ParticlePreview : public CCLayerColor { +public: + Ref m_particleRef; + static ParticlePreview* create(std::string fileName) { + ParticlePreview* pRet = new ParticlePreview(); + if (pRet && pRet->init(CCParticleSystemQuad::create(fileName.c_str(), 0))) { + pRet->autorelease(); return pRet; + } + else { + delete pRet; pRet = 0; return 0; + } + }; + static ParticlePreview* createFromString(std::string data) { + ParticlePreview* pRet = new ParticlePreview(); + if (pRet && pRet->init(GameToolbox::particleFromString(data, CCParticleSystemQuad::create(), 0))) { + pRet->autorelease(); return pRet; + } + else { + delete pRet; pRet = 0; return 0; + } + }; + bool init(CCParticleSystemQuad* particleRef) { + + m_particleRef = particleRef; + + CCLayerColor::init(); + setContentSize({ 110.f, 90.f }); + setColor(cocos2d::ccBLACK); + setOpacity(cocos2d::ccWHITE.r); + + auto scroll = CCScrollView::create(this->getContentSize(), m_particleRef); + addChild(scroll); + + m_particleRef->setContentSize(this->getContentSize()); + m_particleRef->setAnchorPoint({ -0.5f, -0.5f }); + + return 1; + } +}; + +#include +class $modify(GJGarageLayerCustomPlayerParticles, GJGarageLayer) { + $override bool init() { + if (!GJGarageLayer::init()) return 0; + + if (auto shards_menu = typeinfo_cast(this->getChildByIDRecursive("shards-menu"))) { + auto openupbtn = CCMenuItemExt::createSpriteExtraWithFrameName( + "edit_eCParticleBtn_001.png", 1.f, [this](CCMenuItem* item) { + + auto createSavedEffectsPopup = [item](std::string effectName) + { + auto popup = createQuickPopup( + effectName.c_str(), + "\n \n \n \n \n \n \n \n ", + "Go Back", nullptr, + [item](auto, auto) { item->activate(); } + ); + popup->setZOrder(CCScene::get()->getChildrenCount()); + popup->m_mainLayer->setPositionX(56.f); + popup->m_buttonMenu->setPositionX(100.f + popup->m_buttonMenu->getPositionX()); + + auto subbg = CCScale9Sprite::create("GJ_square07.png"); + subbg->setPosition(popup->getContentSize() / 2); + subbg->setAnchorPoint({ 0.625f, 0.5f }); + subbg->setContentSize({ 479.f, 292.f }); + popup->m_mainLayer->addChild(subbg); + + auto subbgbackdrop = CCScale9Sprite::create("GJ_square06.png"); + subbgbackdrop->setPosition(subbg->getPosition()); + subbgbackdrop->setAnchorPoint(subbg->getAnchorPoint()); + subbgbackdrop->setContentSize(subbg->getContentSize()); + subbgbackdrop->setColor(ccBLACK); + subbgbackdrop->setOpacity(120); + popup->m_mainLayer->addChild(subbgbackdrop, -2); + + auto resetFileParticleImage = ButtonSprite::create( + "Restart", 141.000f, 1, "bigFont.fnt", "GJ_button_05.png", 0, 0.7f + ); + resetFileParticleImage->setScale(0.7f); + auto resetFileParticle = CCMenuItemExt::createSpriteExtra( + resetFileParticleImage, [popup, effectName](auto) { + popup->m_buttonMenu->removeChildByTag(5719); + popup->m_buttonMenu->removeChildByTag(5719); + + auto selectedPrev = ParticlePreview::create(effectName + ".plist"); + selectedPrev->setPosition({ -392.f, 4.f }); + popup->m_buttonMenu->addChild(selectedPrev, 0, 5719); + + auto selectedPrevTitle = CCLabelBMFont::create( + (effectName + ".plist").c_str(), "chatFont.fnt" + ); + selectedPrevTitle->setPosition({ -338.000f, 14.000f }); + selectedPrevTitle->limitLabelWidth(selectedPrev->getContentWidth() - 22.f, 0.850f, 0.1f); + popup->m_buttonMenu->addChild(selectedPrevTitle, 0, 5719); + + handleTouchPriority(popup); + } + ); + resetFileParticle->activate(); + resetFileParticle->setPosition({ -338.000f, -15.000f }); + popup->m_buttonMenu->addChild(resetFileParticle, 1); + + //card{ 142.000f, 182.000f } + auto scroll = ScrollLayer::create({ 292.000f, 182.000f }); + scroll->setPosition({ -242.000f, 24.000f }); + scroll->setTouchEnabled(0); + popup->m_buttonMenu->addChild(scroll); + + scroll->m_contentLayer->setOpacity(90.f); + + auto scrollOutline = CCScale9Sprite::create("GJ_square07.png"); + scrollOutline->setPositionX(scroll->getPosition().x + scroll->getContentWidth() / 2); + scrollOutline->setPositionY(scroll->getPosition().y + scroll->getContentHeight() / 2); + scrollOutline->setContentSize(scroll->getContentSize() + CCSizeMake(1,1) * 3); + popup->m_buttonMenu->addChild(scrollOutline); + + auto sbar = Scrollbar::create(scroll); + sbar->setTouchEnabled(0); + sbar->setPosition(-151.f, 11.f); + sbar->setRotation(-90.f); + popup->m_buttonMenu->addChild(sbar); + + auto goLeft = CCMenuItemExt::createSpriteExtra( + CCLabelBMFont::create(">", "bigFont.fnt"), [popup, scroll](auto) { + scroll->scrollLayer(scroll->getContentHeight()); + } + ); + goLeft->setSizeMult(1.3f); + goLeft->setPosition(56.000f, scrollOutline->getPositionY()); + popup->m_buttonMenu->addChild(goLeft); + + auto goRight = CCMenuItemExt::createSpriteExtra( + CCLabelBMFont::create("<", "bigFont.fnt"), [popup, scroll](auto) { + scroll->scrollLayer(-scroll->getContentHeight()); + } + ); + goRight->setSizeMult(1.3f); + goRight->setPosition(-246.000, scrollOutline->getPositionY()); + popup->m_buttonMenu->addChild(goRight); + + auto createParticleCard = [popup, scroll, resetFileParticle, effectName](std::string data, int id) + { + auto menu = CCMenu::create(); + menu->setContentSize({ 142.000f, 182.000f }); + menu->setPosition(CCPointZero); + + menu->addChild(cocos::createDataNode("data", data, id)); + + auto bg = CCScale9Sprite::create("geode.loader/black-square.png"); + bg->setAnchorPoint(CCPointZero); + bg->setContentSize(menu->getContentSize()); + menu->addChild(bg, -1); + + auto restartParticleImage = ButtonSprite::create( + "Restart", "bigFont.fnt", "GJ_button_05.png", 0.8f + ); + restartParticleImage->setScale(0.6f); + restartParticleImage->m_BGSprite->setContentWidth(184.000f); + restartParticleImage->m_BGSprite->setContentHeight(32.000f); + auto restartParticle = CCMenuItemExt::createSpriteExtra( + restartParticleImage, [popup, menu](auto) { + menu->removeChildByTag(13384); + auto data = std::string(findDataNode(menu, "data")->getString()); + auto selectedPrev = ParticlePreview::createFromString(data); + selectedPrev->setPosition({ 16.f, 80.f }); + menu->addChild(selectedPrev, 0, 13384); + handleTouchPriority(popup); + } + ); + restartParticle->activate(); + restartParticle->setPosition({ menu->getContentWidth() / 2, 66.f }); + menu->addChild(restartParticle, 1); + + auto idlabel = CCLabelBMFont::create(fmt::format("# {}", id).c_str(), "chatFont.fnt"); + idlabel->setPosition({ menu->getContentWidth() / 2, 96.f }); + idlabel->limitLabelWidth(110.f, 0.5f, 0.1f); + menu->addChild(idlabel, 1); + + auto copyParticleStrLabel = CCLabelBMFont::create("- copy string -", "chatFont.fnt"); + copyParticleStrLabel->setScale(0.6); + auto copyParticleStr = CCMenuItemExt::createSpriteExtra( + copyParticleStrLabel, [menu, restartParticle, resetFileParticle, effectName, id](auto) { + auto data = std::string(findDataNode(menu, "data")->getString()); + geode::utils::clipboard::write(data); + auto ntfy = Notification::create("particle string was wrote to clipboard!"); + ntfy->show(); + } + ); + copyParticleStr->setPosition({ menu->getContentWidth() / 2, 88.f }); + menu->addChild(copyParticleStr, 1); + + auto useParticleImage = ButtonSprite::create( + "Use This", "bigFont.fnt", "GJ_button_03.png", 0.8f + ); + useParticleImage->setScale(0.6f); + useParticleImage->m_BGSprite->setContentWidth(184.000f); + useParticleImage->m_BGSprite->setContentHeight(32.000f); + auto useParticle = CCMenuItemExt::createSpriteExtra( + useParticleImage, [menu, restartParticle, resetFileParticle, effectName](auto) { + auto data = std::string(findDataNode(menu, "data")->getString()); + Mod::get()->setSavedValue(effectName, data); + Mod::get()->saveData(); + restartParticle->activate();//menu, restartParticle, resetFileParticle + resetFileParticle->activate(); + } + ); + useParticle->setPosition({ menu->getContentWidth() / 2, 42.000f }); + menu->addChild(useParticle, 1); + + auto deleteParticleImage = ButtonSprite::create( + "Delete", "bigFont.fnt", "GJ_button_06.png", 0.8f + ); + deleteParticleImage->setScale(0.6f); + deleteParticleImage->m_BGSprite->setContentWidth(184.000f / 1.8f); + deleteParticleImage->m_BGSprite->setContentHeight(32.000f); + auto deleteParticle = CCMenuItemExt::createSpriteExtra( + deleteParticleImage, [menu, scroll, resetFileParticle, effectName, id](auto) { + + auto data = std::string(findDataNode(menu, "data")->getString()); + auto arr = Mod::get()->getSavedValue(effectName + ".saved"); + auto newarr = matjson::Array(); + for (auto val : arr) { + if (val.as_array()[0] != id) { + newarr.push_back(val); + } + } + if (Mod::get()->getSavedValue(effectName) == data) { + Mod::get()->getSaveContainer().try_erase(effectName); + resetFileParticle->activate(); + } + Mod::get()->setSavedValue(effectName + ".saved", newarr); + Mod::get()->saveData(); + + auto parent = menu->getParent(); + menu->removeMeAndCleanup(); + parent->updateLayout(); + scroll->scrollLayer(0.f); + } + ); + deleteParticle->setAnchorPoint({ 1.f, 0.5f }); + deleteParticle->setPosition({ menu->getContentWidth() / 1.8f, 18.000f }); + menu->addChild(deleteParticle, 1); + + auto editParticleImage = ButtonSprite::create( + "Edit", "bigFont.fnt", "GJ_button_02.png", 0.8f + ); + editParticleImage->setScale(0.6f); + editParticleImage->m_BGSprite->setContentWidth(184.000f / 2.5f); + editParticleImage->m_BGSprite->setContentHeight(32.000f); + auto editParticle = CCMenuItemExt::createSpriteExtra( + editParticleImage, [menu, restartParticle, resetFileParticle, effectName, id](auto) { + auto data = std::string(findDataNode(menu, "data")->getString()); + auto popup = ParticlePopup::get(data); + popup->show(); + popup->setZOrder(CCScene::get()->getChildrenCount()); + auto okBtn = cocos::findFirstChildRecursive(popup, [](auto) {return true; }); + CCMenuItemExt::assignCallback( + okBtn, [popup, menu, restartParticle, resetFileParticle, effectName, id](auto) { + findDataNode(menu, "data")->setString( + GameToolbox::saveParticleToString(popup->m_particle).c_str() + ); + popup->onClose(popup); + + auto data = std::string(findDataNode(menu, "data")->getString()); + auto arr = Mod::get()->getSavedValue(effectName + ".saved"); + auto newarr = matjson::Array(); + for (auto val : arr) { + if (val.as_array()[0] == id) { + val.as_array()[1] = (data); + } + newarr.push_back(val); + } + Mod::get()->setSavedValue(effectName + ".saved", newarr); + Mod::get()->saveData(); + + restartParticle->activate();//menu, restartParticle, resetFileParticle + resetFileParticle->activate(); + } + ); + //handleTouchPriority(popup); + //handleTouchPriority(menu); + } + ); + editParticle->setAnchorPoint({ 0.f, 0.5f }); + editParticle->setPosition({ menu->getContentWidth() / 1.74f, 18.000f }); + menu->addChild(editParticle, 1); + + return menu; + }; + + auto items = CCArray::create(); + + auto createDefaultLabel = CCLabelBMFont::create("From Default:", "goldFont.fnt"); + createDefaultLabel->limitLabelWidth(110.f, 0.6f, 0.1f); + items->addObject(createDefaultLabel); + + auto createnewImage = ButtonSprite::create( + "Create", 141.000f, 1, "bigFont.fnt", "GJ_button_04.png", 0, 0.7f + ); + createnewImage->setScale(0.7f); + auto createnew = CCMenuItemExt::createSpriteExtra( + createnewImage, [popup, effectName, scroll, createParticleCard](auto) { + + auto arr = Mod::get()->getSavedValue(effectName + ".saved"); + + auto freeID = 1; + for (auto particle_arr : arr) { + auto particle_id = particle_arr[0].as_int(); + if (particle_id >= freeID) freeID = (particle_id + 1); + } + + matjson::Array item = { freeID, defaultEffects[effectName] }; + arr.push_back(item); + Mod::get()->setSavedValue(effectName + ".saved", arr); + Mod::get()->saveData(); + + auto card = createParticleCard( + defaultEffects[effectName], + freeID + ); + scroll->m_contentLayer->addChild(card); + scroll->m_contentLayer->updateLayout(); + } + ); + items->addObject(createnew); + + auto createFromStringLabel = CCLabelBMFont::create("From String:", "goldFont.fnt"); + createFromStringLabel->limitLabelWidth(110.f, 0.6f, 0.1f); + items->addObject(createFromStringLabel); + + auto inputParticleStr = TextInput::create(110.000f, "particle\nstring here"); + inputParticleStr->setCommonFilter(CommonFilter::Any); + items->addObject(inputParticleStr); + + auto createFromStringImage = ButtonSprite::create( + "Create", 141.000f, 1, "bigFont.fnt", "GJ_button_04.png", 0, 0.7f + ); + createFromStringImage->setScale(0.7f); + auto createFromString = CCMenuItemExt::createSpriteExtra( + createFromStringImage, [popup, inputParticleStr, effectName, scroll, createParticleCard](auto) { + + auto arr = Mod::get()->getSavedValue(effectName + ".saved"); + + auto freeID = 1; + for (auto particle_arr : arr) { + auto particle_id = particle_arr[0].as_int(); + if (particle_id >= freeID) freeID = (particle_id + 1); + } + + matjson::Array item = { freeID, inputParticleStr->getString() }; + arr.push_back(item); + Mod::get()->setSavedValue(effectName + ".saved", arr); + Mod::get()->saveData(); + + auto card = createParticleCard( + inputParticleStr->getString(), + freeID + ); + scroll->m_contentLayer->addChild(card); + scroll->m_contentLayer->updateLayout(); + + } + ); + items->addObject(createFromString); + + CCMenu* menu = CCMenu::createWithArray(items); + menu->alignItemsVerticallyWithPadding(6.f); + menu->setPosition({ -338.000f, 174.000f }); + popup->m_buttonMenu->addChild(menu, 1); + + auto arr = Mod::get()->getSavedValue(effectName + ".saved"); + for (auto particle_arr : arr) { + auto card = createParticleCard( + particle_arr.as_array()[1].as_string(), + particle_arr.as_array()[0].as_int() + ); + scroll->m_contentLayer->addChild(card); + } + + scroll->m_contentLayer->setLayout(RowLayout::create() + ->setGap(0.f) + ->setGrowCrossAxis(1) + ->setAxisAlignment(AxisAlignment::Even) + ); + + handleTouchPriority(popup); + + }; + + auto selectorPopup = createQuickPopup( + "Select Effect", + "\n \n \n \n \n \n", + "Close", nullptr, + nullptr + ); + + auto items = CCArray::create(); + auto createAndAddItem = [selectorPopup, createSavedEffectsPopup, items](std::string title, std::string effectName) + { + auto image = ButtonSprite::create( + title.c_str(), "bigFont.fnt", "GJ_button_05.png", 0.7f + ); + image->m_BGSprite->setContentWidth(392.000f); + auto item = CCMenuItemExt::createSpriteExtra( + image, [selectorPopup, createSavedEffectsPopup, effectName](auto) { + selectorPopup->keyBackClicked(); + createSavedEffectsPopup(effectName); + } + ); + item->getNormalImage()->setScale(0.6f); + items->addObject(item); + }; + createAndAddItem("Drag Effect", "dragEffect"); + createAndAddItem("Ship Drag Effect", "shipDragEffect"); + createAndAddItem("Land Effect", "landEffect"); + createAndAddItem("Dash Effect", "dashEffect"); + + CCMenu* menu = CCMenu::createWithArray(items); + menu->alignItemsVerticallyWithPadding(-6.f); + selectorPopup->m_mainLayer->addChild(menu, 1); + + handleTouchPriority(selectorPopup); + + } + ); + shards_menu->addChild(openupbtn); + shards_menu->updateLayout(); + } + + return 1; + } +}; + +#include +class $modify(CCParticleSystemQuadCustomPlayerParticles, CCParticleSystemQuad) { + $override static CCParticleSystemQuad* create(const char* file, bool idk) { + auto orgone = CCParticleSystemQuad::create(file, idk); + + if (0) log::debug( + "defaultEffects[\"{}\"] = \"{}\";", file, + GameToolbox::saveParticleToString(orgone) + ); + + auto effectName = fs::path(file).filename().replace_extension("").string(); + + auto stringDataFileName = effectName + ".txt"; + if (fileExistsInSearchPaths(stringDataFileName.c_str())) { + //fs::read uses CCFileUtils::getFileData + auto data = fs::read(stringDataFileName.c_str()); + return GameToolbox::particleFromString(data, CCParticleSystemQuad::create(), idk); + } + + auto saves = Mod::get()->getSaveContainer(); + if (saves.contains(effectName)) { + return GameToolbox::particleFromString( + saves.try_get(effectName).value_or(defaultEffects[effectName]), + CCParticleSystemQuad::create(), idk + ); + } + + return orgone; + } +};