#pragma once #include "../external/json/json.hpp" #include "casts.hpp" #include "general.hpp" #include "../DefaultInclude.hpp" #include #include #include #include "../loader/Event.hpp" // support converting ccColor3B / ccColor4B to / from json namespace cocos2d { void GEODE_DLL to_json(nlohmann::json& json, cocos2d::ccColor3B const& color); void GEODE_DLL from_json(nlohmann::json const& json, cocos2d::ccColor3B& color); void GEODE_DLL to_json(nlohmann::json& json, cocos2d::ccColor4B const& color); void GEODE_DLL from_json(nlohmann::json const& json, cocos2d::ccColor4B& color); } // operators for CC geometry namespace geode { static cocos2d::CCPoint& operator*=(cocos2d::CCPoint& pos, float mul) { pos.x *= mul; pos.y *= mul; return pos; } static cocos2d::CCSize& operator*=(cocos2d::CCSize& size, float mul) { size.width *= mul; size.height *= mul; return size; } static cocos2d::CCSize operator*(cocos2d::CCSize const& size, cocos2d::CCPoint const& point) { return { size.width * point.x, size.height * point.y, }; } static cocos2d::CCRect operator*=(cocos2d::CCRect& rect, float mul) { rect.origin *= mul; rect.size *= mul; return rect; } static cocos2d::CCRect operator*(cocos2d::CCRect const& rect, float mul) { return { rect.origin.x * mul, rect.origin.y * mul, rect.size.width * mul, rect.size.height * mul, }; } static cocos2d::CCPoint operator/=(cocos2d::CCPoint& pos, float div) { pos.x /= div; pos.y /= div; return pos; } static cocos2d::CCSize operator/=(cocos2d::CCSize& size, float div) { size.width /= div; size.height /= div; return size; } static cocos2d::CCRect operator/=(cocos2d::CCRect& rect, float div) { rect.origin /= div; rect.size /= div; return rect; } static cocos2d::CCPoint operator+=(cocos2d::CCPoint& pos, cocos2d::CCPoint const& add) { pos.x += add.x; pos.y += add.y; return pos; } static cocos2d::CCSize operator+=(cocos2d::CCSize& size, cocos2d::CCPoint const& add) { size.width += add.x; size.height += add.y; return size; } static cocos2d::CCSize operator+=(cocos2d::CCSize& size, cocos2d::CCSize const& add) { size.width += add.width; size.height += add.height; return size; } static cocos2d::CCRect operator+=(cocos2d::CCRect& rect, cocos2d::CCPoint const& add) { rect.origin += add; return rect; } static cocos2d::CCRect operator+=(cocos2d::CCRect& rect, cocos2d::CCSize const& add) { rect.size += add; return rect; } static cocos2d::CCRect operator+=(cocos2d::CCRect& rect, cocos2d::CCRect const& add) { rect.origin += add.origin; rect.size += add.size; return rect; } static cocos2d::CCPoint operator-=(cocos2d::CCPoint& pos, cocos2d::CCPoint const& add) { pos.x -= add.x; pos.y -= add.y; return pos; } static cocos2d::CCSize operator-=(cocos2d::CCSize& size, cocos2d::CCPoint const& add) { size.width -= add.x; size.height -= add.y; return size; } static cocos2d::CCSize operator-=(cocos2d::CCSize& size, cocos2d::CCSize const& add) { size.width -= add.width; size.height -= add.height; return size; } static cocos2d::CCRect operator-=(cocos2d::CCRect& rect, cocos2d::CCPoint const& add) { rect.origin -= add; return rect; } static cocos2d::CCRect operator-=(cocos2d::CCRect& rect, cocos2d::CCSize const& add) { rect.size -= add; return rect; } static cocos2d::CCRect operator-=(cocos2d::CCRect& rect, cocos2d::CCRect const& add) { rect.origin -= add.origin; rect.size -= add.size; return rect; } static cocos2d::CCSize operator-(cocos2d::CCSize const& size, float f) { return {size.width - f, size.height - f}; } static cocos2d::CCSize operator-(cocos2d::CCSize const& size) { return {-size.width, -size.height}; } static bool operator==(cocos2d::CCPoint const& p1, cocos2d::CCPoint const& p2) { return p1.x == p2.x && p1.y == p2.y; } static bool operator!=(cocos2d::CCPoint const& p1, cocos2d::CCPoint const& p2) { return p1.x != p2.x || p1.y != p2.y; } static bool operator==(cocos2d::CCSize const& s1, cocos2d::CCSize const& s2) { return s1.width == s2.width && s1.height == s2.height; } static bool operator!=(cocos2d::CCSize const& s1, cocos2d::CCSize const& s2) { return s1.width != s2.width || s1.height != s2.height; } static bool operator==(cocos2d::CCRect const& r1, cocos2d::CCRect const& r2) { return r1.origin == r2.origin && r1.size == r2.size; } static bool operator!=(cocos2d::CCRect const& r1, cocos2d::CCRect const& r2) { return r1.origin != r2.origin || r1.size != r2.size; } static bool operator==(cocos2d::ccColor4B const& c1, cocos2d::ccColor4B const& c2) { return c1.r == c2.r && c1.g == c2.g && c1.b == c2.b && c1.a == c2.a; } static bool operator!=(cocos2d::ccColor4B const& c1, cocos2d::ccColor4B const& c2) { return c1.r != c2.r || c1.g != c2.g || c1.b != c2.b || c1.a != c2.a; } static bool operator!=(cocos2d::ccColor3B const& c1, cocos2d::ccColor3B const& c2) { return c1.r != c2.r || c1.g != c2.g || c1.b != c2.b; } } // Ref & Bug namespace geode { /** * A smart pointer to a managed CCObject-deriving class. Retains shared * ownership over the managed instance. Releases the object when the Ref * is destroyed, or assigned another object or nullptr. * * Use-cases include, for example, non-CCNode class members, or nodes that * are not always in the scene tree. * * @example class MyNode : public CCNode { * protected: * // no need to manually call retain or * // release on this array; Ref manages it * // for you :3 * Ref m_list = CCArray::create(); * }; */ template class Ref final { static_assert( std::is_base_of_v, "Ref can only be used with a CCObject-inheriting class!" ); T* m_obj = nullptr; public: /** * Construct a Ref of an object. The object will be retained and * managed until Ref goes out of scope */ Ref(T* obj) : m_obj(obj) { CC_SAFE_RETAIN(obj); } Ref(Ref const& other) : Ref(other.data()) {} Ref(Ref&& other) : m_obj(other.m_obj) { other.m_obj = nullptr; } /** * Construct an empty Ref (the managed object will be null) */ Ref() = default; ~Ref() { CC_SAFE_RELEASE(m_obj); } /** * Swap the managed object with another object. The managed object * will be released, and the new object retained * @param other The new object to swap to */ void swap(T* other) { CC_SAFE_RELEASE(m_obj); m_obj = other; CC_SAFE_RETAIN(other); } /** * Return the managed object * @returns The managed object */ T* data() const { return m_obj; } operator T*() const { return m_obj; } T* operator*() const { return m_obj; } T* operator->() const { return m_obj; } T* operator=(T* obj) { this->swap(obj); return obj; } Ref& operator=(Ref const& other) { this->swap(other.data()); return *this; } Ref& operator=(Ref&& other) { this->swap(other.data()); return *this; } bool operator==(T* other) const { return m_obj == other; } bool operator==(Ref const& other) const { return m_obj == other.m_obj; } bool operator!=(T* other) const { return m_obj != other; } bool operator!=(Ref const& other) const { return m_obj != other.m_obj; } // for containers bool operator<(Ref const& other) const { return m_obj < other.m_obj; } bool operator>(Ref const& other) const { return m_obj > other.m_obj; } }; template class EventListenerNode : public cocos2d::CCNode { protected: EventListener m_listener; public: static EventListenerNode* create(EventListener listener) { auto ret = new EventListenerNode(); if (ret && ret->init()) { ret->m_listener = listener; ret->autorelease(); return ret; } CC_SAFE_DELETE(ret); return nullptr; } static EventListenerNode* create(typename Filter::Callback callback) { auto ret = new EventListenerNode(); if (ret && ret->init()) { ret->m_listener = EventListener(callback); ret->autorelease(); return ret; } CC_SAFE_DELETE(ret); return nullptr; } template static EventListenerNode* create( C* cls, typename EventListener::template MemberFn callback ) { // for some reason msvc won't let me just call EventListenerNode::create... // it claims no return value... // despite me writing return EventListenerNode::create()...... auto ret = new EventListenerNode(); if (ret && ret->init()) { ret->m_listener.bind(cls, callback); ret->autorelease(); return ret; } CC_SAFE_DELETE(ret); return nullptr; } }; } // Cocos2d utils namespace geode::cocos { /** * Get child at index. Checks bounds. A negative * index will get the child starting from the end * @returns Child at index cast to the given type, * or nullptr if index exceeds bounds */ template static T* getChild(cocos2d::CCNode* x, int i) { // start from end for negative index if (i < 0) i = x->getChildrenCount() + i; // check if backwards index is out of bounds if (i < 0) return nullptr; // check if forwards index is out of bounds if (static_cast(x->getChildrenCount()) <= i) return nullptr; return static_cast(x->getChildren()->objectAtIndex(i)); } /** * Get nth child that is a given type. Checks bounds. * @returns Child at index cast to the given type, * or nullptr if index exceeds bounds */ template static Type* getChildOfType(cocos2d::CCNode* node, size_t index) { size_t indexCounter = 0; for (size_t i = 0; i < node->getChildrenCount(); ++i) { auto obj = cast::typeinfo_cast(node->getChildren()->objectAtIndex(i)); if (obj != nullptr) { if (indexCounter == index) { return obj; } ++indexCounter; } } return nullptr; } /** * Return a node, or create a default one if it's * nullptr. Syntactic sugar function */ template static T* nodeOrDefault(T* node, Args... args) { return node ? node : T::create(args...); } template struct SafeCreate final { T* result; SafeCreate& with(T* node) { result = node; return *this; } template SafeCreate& make(Args... args) { result = T::create(args...); return *this; } // convenience for CCSprite template SafeCreate& makeWithFrame(Args... args) { result = T::createWithSpriteFrameName(args...); return *this; } template SafeCreate& makeUsing(T* (*func)(Args...), Args... args) { result = func(args...); return *this; } template T* orMakeUsing(O* (*func)(Args...), Args... args) { if (result) return result; return func(args...); } template T* orMake(Args... args) { if (result) return result; return O::create(args...); } template T* orMakeWithFrame(Args... args) { if (result) return result; return O::createWithSpriteFrameName(args...); } }; /** * Get bounds for a set of nodes. Based on content * size * @param nodes Nodes to calculate coverage of * @returns Rectangle fitting all nodes. Origin * will be <= 0 and size will be >= 0 */ GEODE_DLL cocos2d::CCRect calculateNodeCoverage(std::vector const& nodes); /** * Get bounds for a set of nodes. Based on content * size * @param nodes Nodes to calculate coverage of * @returns Rectangle fitting all nodes. Origin * will be <= 0 and size will be >= 0 */ GEODE_DLL cocos2d::CCRect calculateNodeCoverage(cocos2d::CCArray* nodes); /** * Get bounds for a set of nodes. Based on content * size * @param parent Parent whose children to calculate * coverage of * @returns Rectangle fitting all the parent's children. * Origin will be <= 0 and size will be >= 0 */ GEODE_DLL cocos2d::CCRect calculateChildCoverage(cocos2d::CCNode* parent); /** * Create a CCScene from a layer and switch to it with the default fade * transition * @param layer Layer to create a scene from * @returns Created scene (not the fade transition) */ GEODE_DLL cocos2d::CCScene* switchToScene(cocos2d::CCLayer* layer); using CreateLayerFunc = std::function; /** * Reload textures, overwriting the scene to return to after the loading * screen is finished * @param returnTo A function that returns a new layer. After loading is * finished, the game switches to the given layer instead of MenuLayer. * Leave nullptr to enable default behaviour */ GEODE_DLL void reloadTextures(CreateLayerFunc returnTo = nullptr); /** * Rescale node to fit inside given size * @param node Node to rescale * @param size Size to fit inside * @param def Default size * @param min Minimum size */ GEODE_DLL void limitNodeSize(cocos2d::CCNode* node, cocos2d::CCSize const& size, float def, float min); /** * Checks if a node is visible (recursively * checks parent visibility) * @param node Node to check if visible * @returns True if node is visibile. Does * not take into account if node is off-screen */ GEODE_DLL bool nodeIsVisible(cocos2d::CCNode* node); /** * Gets a node by tag by traversing * children recursively * * @param node Parent node * @param tag Target tag * @return Child node with specified tag, or * null if there is none */ GEODE_DLL cocos2d::CCNode* getChildByTagRecursive(cocos2d::CCNode* node, int tag); /** * Get first node that conforms to the predicate * by traversing children recursively * * @param node Parent node * @param predicate Predicate used to evaluate nodes * @return Child node if one is found, or null if * there is none */ template Type* findFirstChildRecursive(cocos2d::CCNode* node, std::function predicate) { if (cast::safe_cast(node) && predicate(static_cast(node))) return static_cast(node); auto children = node->getChildren(); if (!children) return nullptr; for (int i = 0; i < children->count(); ++i) { auto newParent = static_cast(children->objectAtIndex(i)); auto child = findFirstChildRecursive(newParent, predicate); if (child) return child; } return nullptr; } /** * Checks if a given file exists in CCFileUtils * search paths. * @param filename File to check * @returns True if file exists * @example if (fileExistsInSearchPaths("mySprite.png"_spr)) { * CCSprite::create("mySprite.png"_spr); * } else { * CCSprite::create("fallback.png"); * } */ GEODE_DLL bool fileExistsInSearchPaths(char const* filename); template struct CCArrayIterator { public: CCArrayIterator(T* p) : m_ptr(p) {} T* m_ptr; auto& operator*() { return *m_ptr; } auto& operator*() const { return *m_ptr; } auto operator->() { return m_ptr; } auto operator->() const { return m_ptr; } auto& operator++() { ++m_ptr; return *this; } auto& operator--() { --m_ptr; return *this; } auto& operator+=(size_t val) { m_ptr += val; return *this; } auto& operator-=(size_t val) { m_ptr -= val; return *this; } auto operator+(size_t val) const { return CCArrayIterator(m_ptr + val); } auto operator-(size_t val) const { return CCArrayIterator(m_ptr - val); } auto operator-(CCArrayIterator const& other) const { return m_ptr - other.m_ptr; } bool operator<(CCArrayIterator const& other) const { return m_ptr < other.m_ptr; } bool operator>(CCArrayIterator const& other) const { return m_ptr > other.m_ptr; } bool operator<=(CCArrayIterator const& other) const { return m_ptr <= other.m_ptr; } bool operator>=(CCArrayIterator const& other) const { return m_ptr >= other.m_ptr; } bool operator==(CCArrayIterator const& other) const { return m_ptr == other.m_ptr; } bool operator!=(CCArrayIterator const& other) const { return m_ptr != other.m_ptr; } }; inline void ccDrawColor4B(cocos2d::ccColor4B const& color) { cocos2d::ccDrawColor4B(color.r, color.g, color.b, color.a); } inline cocos2d::ccColor4B invert4B(cocos2d::ccColor4B const& color) { return { static_cast(255 - color.r), static_cast(255 - color.g), static_cast(255 - color.b), color.a}; } inline cocos2d::ccColor3B invert3B(cocos2d::ccColor3B const& color) { return { static_cast(255 - color.r), static_cast(255 - color.g), static_cast(255 - color.b)}; } inline cocos2d::ccColor3B lighten3B(cocos2d::ccColor3B const& color, int amount) { return { static_cast(utils::clamp(color.r + amount, 0, 255)), static_cast(utils::clamp(color.g + amount, 0, 255)), static_cast(utils::clamp(color.b + amount, 0, 255)), }; } inline cocos2d::ccColor3B darken3B(cocos2d::ccColor3B const& color, int amount) { return lighten3B(color, -amount); } inline cocos2d::ccColor3B to3B(cocos2d::ccColor4B const& color) { return {color.r, color.g, color.b}; } inline cocos2d::ccColor4B to4B(cocos2d::ccColor3B const& color, GLubyte alpha = 255) { return {color.r, color.g, color.b, alpha}; } inline cocos2d::ccColor4F to4F(cocos2d::ccColor4B const& color) { return {color.r / 255.f, color.g / 255.f, color.b / 255.f, color.a / 255.f}; } constexpr cocos2d::ccColor3B cc3x(int hexValue) { if (hexValue <= 0xf) return cocos2d::ccColor3B{ static_cast(hexValue * 17), static_cast(hexValue * 17), static_cast(hexValue * 17)}; if (hexValue <= 0xff) return cocos2d::ccColor3B{ static_cast(hexValue), static_cast(hexValue), static_cast(hexValue)}; if (hexValue <= 0xfff) return cocos2d::ccColor3B{ static_cast((hexValue >> 8 & 0xf) * 17), static_cast((hexValue >> 4 & 0xf) * 17), static_cast((hexValue >> 0 & 0xf) * 17)}; else return cocos2d::ccColor3B{ static_cast(hexValue >> 16 & 0xff), static_cast(hexValue >> 8 & 0xff), static_cast(hexValue >> 0 & 0xff)}; } GEODE_DLL Result cc3bFromHexString(std::string const& hexValue); GEODE_DLL Result cc4bFromHexString(std::string const& hexValue); GEODE_DLL std::string cc3bToHexString(cocos2d::ccColor3B const& color); GEODE_DLL std::string cc4bToHexString(cocos2d::ccColor4B const& color); template >> static cocos2d::CCArray* vectorToCCArray(std::vector const& vec) { auto res = cocos2d::CCArray::createWithCapacity(vec.size()); for (auto const& item : vec) res->addObject(item); return res; } template >> static cocos2d::CCArray* vectorToCCArray(std::vector const& vec, std::function convFunc) { auto res = cocos2d::CCArray::createWithCapacity(vec.size()); for (auto const& item : vec) res->addObject(convFunc(item)); return res; } template >> std::vector ccArrayToVector(cocos2d::CCArray* arr) { return std::vector( reinterpret_cast(arr->data->arr), reinterpret_cast(arr->data->arr) + arr->data->num ); } template < typename K, typename V, typename = std::enable_if_t || std::is_same_v>> static cocos2d::CCDictionary* mapToCCDict(std::map const& map) { auto res = cocos2d::CCDictionary::create(); for (auto const& [key, value] : map) res->setObject(value, key); return res; } template < typename K, typename V, typename C, typename = std::enable_if_t || std::is_same_v>> static cocos2d::CCDictionary* mapToCCDict(std::map const& map, std::function convFunc) { auto res = cocos2d::CCDictionary::create(); for (auto const& [key, value] : map) res->setObject(value, convFunc(key)); return res; } // template || std::is_same_v> > // static std::map ccDictToMap(cocos2d::CCDictionary* dict) { // auto res = std::map(); // cocos2d::CCDictElement* element = nullptr; // CCDICT_FOREACH(dict, element) { // if constexpr (std::is_same_v) // res[element->getStrKey()] = element->getObject(); // if constexpr (std::is_same_v) // res[element->getIntKey()] = element->getObject(); // } // return res; // } } // std specializations namespace std { // enables using Ref as the key in unordered_map etc. template struct hash> { size_t operator()(geode::Ref const& ref) const { return std::hash()(ref.data()); } }; template struct iterator_traits> { using difference_type = ptrdiff_t; using value_type = T; using pointer = T*; using reference = T&; using iterator_category = std::random_access_iterator_tag; // its random access but im too lazy to implement it }; } // more utils namespace geode::cocos { struct GEODE_DLL CCArrayInserter { public: CCArrayInserter(cocos2d::CCArray* p) : m_array(p) {} cocos2d::CCArray* m_array; auto& operator=(cocos2d::CCObject* value) { m_array->addObject(value); return *this; } auto& operator*() { return *this; } auto& operator++() { return *this; } }; template class CCArrayExt { protected: Ref m_arr; using T = std::remove_pointer_t<_Type>; public: CCArrayExt() : m_arr(cocos2d::CCArray::create()) {} CCArrayExt(cocos2d::CCArray* arr) : m_arr(arr) {} CCArrayExt(CCArrayExt const& a) : m_arr(a.m_arr) {} CCArrayExt(CCArrayExt&& a) : m_arr(a.m_arr) { a.m_arr = nullptr; } ~CCArrayExt() {} auto begin() { if (!m_arr) { return CCArrayIterator(nullptr); } return CCArrayIterator(reinterpret_cast(m_arr->data->arr)); } auto end() { if (!m_arr) { return CCArrayIterator(nullptr); } return CCArrayIterator(reinterpret_cast(m_arr->data->arr) + m_arr->count()); } size_t size() const { return m_arr ? m_arr->count() : 0; } T operator[](size_t index) { return static_cast(m_arr->objectAtIndex(index)); } void push_back(T* item) { m_arr->addObject(item); } T* pop_back() { T ret = m_arr->lastObject(); m_arr->removeLastObject(); return ret; } cocos2d::CCArray* inner() { return m_arr; } }; template struct CCDictIterator { public: CCDictIterator(cocos2d::CCDictElement* p) : m_ptr(p) {} cocos2d::CCDictElement* m_ptr; std::pair operator*() { if constexpr (std::is_same::value) { return {m_ptr->getStrKey(), static_cast(m_ptr->getObject())}; } else { return {m_ptr->getIntKey(), static_cast(m_ptr->getObject())}; } } auto& operator++() { m_ptr = static_cast(m_ptr->hh.next); return *this; } friend bool operator==(CCDictIterator const& a, CCDictIterator const& b) { return a.m_ptr == b.m_ptr; }; friend bool operator!=(CCDictIterator const& a, CCDictIterator const& b) { return a.m_ptr != b.m_ptr; }; bool operator!=(int b) { return m_ptr != nullptr; } }; template struct CCDictEntry { K m_key; cocos2d::CCDictionary* m_dict; CCDictEntry(K key, cocos2d::CCDictionary* dict) : m_key(key), m_dict(dict) {} T operator->() { return static_cast(m_dict->objectForKey(m_key)); } operator T() { return static_cast(m_dict->objectForKey(m_key)); } CCDictEntry& operator=(T f) { m_dict->setObject(f, m_key); return *this; } }; template struct CCDictionaryExt { protected: cocos2d::CCDictionary* m_dict; public: CCDictionaryExt() : m_dict(cocos2d::CCDictionary::create()) { m_dict->retain(); } CCDictionaryExt(cocos2d::CCDictionary* dict) : m_dict(dict) { m_dict->retain(); } CCDictionaryExt(CCDictionaryExt const& d) : m_dict(d.m_dict) { m_dict->retain(); } CCDictionaryExt(CCDictionaryExt&& d) : m_dict(d.m_dict) { d.m_dict = nullptr; } ~CCDictionaryExt() { if (m_dict) m_dict->release(); } CCDictionaryExt const& operator=(cocos2d::CCDictionary* d) { m_dict->release(); m_dict = d; m_dict->retain(); } auto begin() { return CCDictIterator(m_dict->m_pElements); } // do not use this auto end() { return nullptr; } size_t size() { return m_dict->count(); } auto operator[](K key) { auto ret = static_cast(m_dict->objectForKey(key)); if (!ret) m_dict->setObject(cocos2d::CCNode::create(), key); return CCDictEntry(key, m_dict); } size_t count(K key) { return m_dict->allKeys(key)->count(); } }; }