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.
+
+
+
+## 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;
+ }
+};