#pragma once #include #include "casts.hpp" #include "general.hpp" #include "../DefaultInclude.hpp" #include #include #include #include "../loader/Event.hpp" #include #include // support converting ccColor3B / ccColor4B to / from json template <> struct matjson::Serialize { static geode::Result GEODE_DLL fromJson(Value const& value); static Value GEODE_DLL toJson(cocos2d::ccColor3B const& value); }; template <> struct matjson::Serialize { static geode::Result GEODE_DLL fromJson(Value const& value); static Value GEODE_DLL toJson(cocos2d::ccColor4B const& value); }; // operators for CC geometry namespace cocos2d { static constexpr cocos2d::CCPoint& operator*=(cocos2d::CCPoint& pos, float mul) { pos.x *= mul; pos.y *= mul; return pos; } static constexpr cocos2d::CCSize& operator*=(cocos2d::CCSize& size, float mul) { size.width *= mul; size.height *= mul; return size; } static constexpr cocos2d::CCSize operator*(cocos2d::CCSize const& size, cocos2d::CCPoint const& point) { return { size.width * point.x, size.height * point.y, }; } static constexpr cocos2d::CCRect operator*=(cocos2d::CCRect& rect, float mul) { rect.origin *= mul; rect.size *= mul; return rect; } static constexpr 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 constexpr cocos2d::CCPoint operator/=(cocos2d::CCPoint& pos, float div) { pos.x /= div; pos.y /= div; return pos; } static constexpr cocos2d::CCSize operator/=(cocos2d::CCSize& size, float div) { size.width /= div; size.height /= div; return size; } static constexpr cocos2d::CCRect operator/=(cocos2d::CCRect& rect, float div) { rect.origin /= div; rect.size /= div; return rect; } static constexpr cocos2d::CCPoint operator+=(cocos2d::CCPoint& pos, cocos2d::CCPoint const& add) { pos.x += add.x; pos.y += add.y; return pos; } static constexpr cocos2d::CCSize operator+=(cocos2d::CCSize& size, cocos2d::CCPoint const& add) { size.width += add.x; size.height += add.y; return size; } static constexpr cocos2d::CCSize operator+=(cocos2d::CCSize& size, cocos2d::CCSize const& add) { size.width += add.width; size.height += add.height; return size; } static constexpr cocos2d::CCRect operator+=(cocos2d::CCRect& rect, cocos2d::CCPoint const& add) { rect.origin += add; return rect; } static constexpr cocos2d::CCRect operator+=(cocos2d::CCRect& rect, cocos2d::CCSize const& add) { rect.size += add; return rect; } static constexpr cocos2d::CCRect operator+=(cocos2d::CCRect& rect, cocos2d::CCRect const& add) { rect.origin += add.origin; rect.size += add.size; return rect; } static constexpr cocos2d::CCPoint operator-=(cocos2d::CCPoint& pos, cocos2d::CCPoint const& add) { pos.x -= add.x; pos.y -= add.y; return pos; } static constexpr cocos2d::CCSize operator-=(cocos2d::CCSize& size, cocos2d::CCPoint const& add) { size.width -= add.x; size.height -= add.y; return size; } static constexpr cocos2d::CCSize operator-=(cocos2d::CCSize& size, cocos2d::CCSize const& add) { size.width -= add.width; size.height -= add.height; return size; } static constexpr cocos2d::CCRect operator-=(cocos2d::CCRect& rect, cocos2d::CCPoint const& add) { rect.origin -= add; return rect; } static constexpr cocos2d::CCRect operator-=(cocos2d::CCRect& rect, cocos2d::CCSize const& add) { rect.size -= add; return rect; } static constexpr cocos2d::CCRect operator-=(cocos2d::CCRect& rect, cocos2d::CCRect const& add) { rect.origin -= add.origin; rect.size -= add.size; return rect; } static constexpr cocos2d::CCSize operator-(cocos2d::CCSize const& size, float f) { return {size.width - f, size.height - f}; } static constexpr cocos2d::CCSize operator-(cocos2d::CCSize const& size) { return {-size.width, -size.height}; } static constexpr bool operator==(cocos2d::CCPoint const& p1, cocos2d::CCPoint const& p2) { return p1.x == p2.x && p1.y == p2.y; } static constexpr bool operator!=(cocos2d::CCPoint const& p1, cocos2d::CCPoint const& p2) { return p1.x != p2.x || p1.y != p2.y; } static constexpr bool operator==(cocos2d::CCSize const& s1, cocos2d::CCSize const& s2) { return s1.width == s2.width && s1.height == s2.height; } static constexpr bool operator!=(cocos2d::CCSize const& s1, cocos2d::CCSize const& s2) { return s1.width != s2.width || s1.height != s2.height; } static constexpr bool operator<(cocos2d::CCSize const& s1, cocos2d::CCSize const& s2) { return s1.width < s2.width && s1.height < s2.height; } static constexpr bool operator<=(cocos2d::CCSize const& s1, cocos2d::CCSize const& s2) { return s1.width <= s2.width && s1.height <= s2.height; } static constexpr bool operator>(cocos2d::CCSize const& s1, cocos2d::CCSize const& s2) { return s1.width > s2.width && s1.height > s2.height; } static constexpr bool operator>=(cocos2d::CCSize const& s1, cocos2d::CCSize const& s2) { return s1.width >= s2.width && s1.height >= s2.height; } static constexpr bool operator==(cocos2d::CCRect const& r1, cocos2d::CCRect const& r2) { return r1.origin == r2.origin && r1.size == r2.size; } static constexpr bool operator!=(cocos2d::CCRect const& r1, cocos2d::CCRect const& r2) { return r1.origin != r2.origin || r1.size != r2.size; } static constexpr 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 constexpr 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 constexpr bool operator==(cocos2d::ccColor3B const& c1, cocos2d::ccColor3B const& c2) { return c1.r == c2.r && c1.g == c2.g && c1.b == c2.b; } static constexpr bool operator!=(cocos2d::ccColor3B const& c1, cocos2d::ccColor3B const& c2) { return c1.r != c2.r || c1.g != c2.g || c1.b != c2.b; } static constexpr bool operator==(cocos2d::ccHSVValue const& c1, cocos2d::ccHSVValue const& c2) { return c1.h == c2.h && c1.s == c2.s && c1.v == c2.v && c1.absoluteSaturation == c2.absoluteSaturation && c1.absoluteBrightness == c2.absoluteBrightness; } static constexpr bool operator!=(cocos2d::ccHSVValue const& c1, cocos2d::ccHSVValue const& c2) { return !(c1 == c2); } } // 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. * * @tparam T A type that inherits from CCObject. * * @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(); * * bool init() { * if (!CCNode::init()) * return false; * * // No need to do m_list = CCArray::create() * // or m_list->retain() :3 * * return true; * } * }; * * @example * // Save a child from the current layer into a menu * Ref menu = static_cast(this->getChildByID("main-menu")); * * // Remove the menu from its parent * menu->removeFromParent(); * * // Menu will still point to a valid CCMenu as long as the menu variable exist */ 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 * @param obj Object to construct the Ref from */ Ref(T* obj) : m_obj(obj) { CC_SAFE_RETAIN(obj); } Ref(Ref const& other) : Ref(other.data()) {} Ref(Ref&& other) noexcept : 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) { m_obj = other.data(); other.m_obj = nullptr; 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; } bool operator>(Ref const& other) const { return m_obj > other.m_obj; } bool operator>=(Ref const& other) const { return m_obj >= other.m_obj; } }; class WeakRefPool; class GEODE_DLL WeakRefController final { private: cocos2d::CCObject* m_obj; WeakRefController(WeakRefController const&) = delete; WeakRefController(WeakRefController&&) = delete; friend class WeakRefPool; public: WeakRefController() = default; bool isManaged(); void swap(cocos2d::CCObject* other); cocos2d::CCObject* get() const; }; class GEODE_DLL WeakRefPool final { std::unordered_map> m_pool; void check(cocos2d::CCObject* obj); friend class WeakRefController; public: static WeakRefPool* get(); std::shared_ptr manage(cocos2d::CCObject* obj); }; /** * A smart pointer to a managed CCObject-deriving class. Like Ref, except * only holds a weak reference to the targeted object. When all non-weak * references (Refs, manual retain() calls) to the object are dropped, so * are all weak references. * * In essence, WeakRef is like a raw pointer, except that you can know if * the pointer is still valid or not, as WeakRef::lock() returns nullptr if * the pointed-to-object has already been freed. * * Note that an object pointed to by WeakRef is only released once some * WeakRef pointing to it checks for it after all other references to the * object have been dropped. If you store WeakRefs in a global map, you may * want to periodically lock all of them to make sure any memory that should * be freed is freed. * * @tparam T A type that inherits from CCObject. */ template class WeakRef final { static_assert( std::is_base_of_v, "WeakRef can only be used with a CCObject-inheriting class!" ); std::shared_ptr m_controller; WeakRef(std::shared_ptr obj) : m_controller(obj) {} friend class std::hash>; public: /** * Construct a WeakRef of an object. A weak reference is one that will * be valid as long as the object is referenced by other strong * references (such as Ref or manual retain calls), but once all strong * references are dropped, so are all weak references. The object is * freed once no strong references exist to it, and any WeakRef pointing * to it is freed or locked * @param obj Object to construct the WeakRef from */ WeakRef(T* obj) : m_controller(WeakRefPool::get()->manage(obj)) {} WeakRef(WeakRef const& other) : WeakRef(other.m_controller) {} WeakRef(WeakRef&& other) : m_controller(std::move(other.m_controller)) { other.m_controller = nullptr; } /** * Construct an empty WeakRef (the object will be null) */ WeakRef() = default; ~WeakRef() { // If the WeakRef is moved, m_controller is null if (m_controller) { m_controller->isManaged(); } } /** * Lock the WeakRef, returning a Ref if the pointed object is valid or * a null Ref if the object has been freed */ Ref lock() const { if (m_controller->isManaged()) { return Ref(static_cast(m_controller->get())); } return Ref(nullptr); } /** * Check if the WeakRef points to a valid object */ bool valid() const { return m_controller->isManaged(); } /** * 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) { m_controller->swap(other); } Ref operator=(T* obj) { this->swap(obj); return this->lock(); } WeakRef& operator=(WeakRef const& other) { this->swap(static_cast(other.m_controller->get())); return *this; } WeakRef& operator=(WeakRef&& other) { m_controller = std::move(other.m_controller); return *this; } explicit operator bool() const noexcept { return this->valid(); } bool operator==(T* other) const { return m_controller->get() == other; } bool operator==(WeakRef const& other) const { return m_controller->get() == other.m_controller->get(); } bool operator!=(T* other) const { return m_controller->get() != other; } bool operator!=(WeakRef const& other) const { return m_controller->get() != other.m_controller->get(); } // for containers bool operator<(WeakRef const& other) const { return m_controller->get() < other.m_controller->get(); } bool operator<=(WeakRef const& other) const { return m_controller->get() <= other.m_controller->get(); } bool operator>(WeakRef const& other) const { return m_controller->get() > other.m_controller->get(); } bool operator>=(WeakRef const& other) const { return m_controller->get() >= other.m_controller->get(); } }; template class EventListenerNode : public cocos2d::CCNode { protected: EventListener m_listener; EventListenerNode(EventListener&& listener) : m_listener(std::move(listener)) {} public: static EventListenerNode* create(EventListener listener) { auto ret = new EventListenerNode(std::move(listener)); if (ret->init()) { ret->autorelease(); return ret; } delete ret; return nullptr; } static EventListenerNode* create(typename Filter::Callback callback, Filter filter = Filter()) { auto ret = new EventListenerNode(EventListener(callback, filter)); if (ret->init()) { ret->autorelease(); return ret; } 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(EventListener(cls, callback)); if (ret->init()) { ret->autorelease(); return ret; } delete ret; return nullptr; } }; /** * A simple `CCObject` wrapper for a non-`CCObject` type */ template requires (!std::is_base_of_v) class ObjWrapper : public cocos2d::CCObject { protected: T m_value; ObjWrapper(T&& value) : m_value(std::forward(value)) { this->autorelease(); } ObjWrapper(T const& value) : m_value(value) { this->autorelease(); } public: /** * Construct an object wrapper */ static ObjWrapper* create(T&& value) { return new ObjWrapper(std::forward(value)); } /** * Construct an object wrapper */ static ObjWrapper* create(T const& value) { return new ObjWrapper(value); } // @note This returns a const& to allow move-only types to be returned! T const& getValue() const& { return m_value; } void setValue(T&& value) { m_value = std::forward(value); } }; } // 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)); } /** * 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...); } /** * 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); /** * Rescale node to fit inside given width * @param node Node to rescale * @param width Width to fit inside * @param def Default scale * @param min Minimum scale */ GEODE_DLL void limitNodeWidth(cocos2d::CCNode* node, float width, float def, float min); /** * Rescale node to fit inside given height * @param node Node to rescale * @param height Height to fit inside * @param def Default scale * @param min Minimum scale */ GEODE_DLL void limitNodeHeight(cocos2d::CCNode* node, float height, 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::typeinfo_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 node has the given sprite frame * name either in the sprite or in the sprite inside * the button. * * @param node Node to check * @param name Name of the sprite frame to search for * @returns True if the node has the given sprite frame * name */ GEODE_DLL bool isSpriteFrameName(cocos2d::CCNode* node, const char* name); /** * Get the first child that has the given sprite frame * name either in the sprite or in the sprite inside * the button. * * @param parent Parent node to search in * @param name Name of the sprite frame to search for * @returns Child with the given sprite frame name, or * nullptr if there is none */ GEODE_DLL cocos2d::CCNode* getChildBySpriteFrameName(cocos2d::CCNode* parent, const char* name); /** * Checks if a node has the given sprite name either * in the sprite or in the sprite inside the button. * * @param node Node to check * @param name Name of the sprite to search for * @returns True if the node has the given sprite name */ GEODE_DLL bool isSpriteName(cocos2d::CCNode* node, const char* name); /** * Get the first child that has the given sprite name * either in the sprite or in the sprite inside the * button. * * @param parent Parent node to search in * @param name Name of the sprite to search for * @returns Child with the given sprite name, or * nullptr if there is none */ GEODE_DLL cocos2d::CCNode* getChildBySpriteName(cocos2d::CCNode* parent, const char* name); /** * 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); 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}; } /** * Parse a ccColor3B from a hexadecimal string. The string may contain * a leading '#' * @param hexValue The string to parse into a color * @param permissive If true, strings like "f" are considered valid * representations of the color white. Useful for UIs that allow entering * a hex color. Empty strings evaluate to pure white * @returns A ccColor3B if it could be succesfully parsed, or an error * indicating the failure reason */ GEODE_DLL Result cc3bFromHexString(std::string const& hexValue, bool permissive = false); /** * Parse a ccColor4B from a hexadecimal string. The string may contain * a leading '#' * @param hexValue The string to parse into a color * @param requireAlpha Require the alpha component to be passed. If false, * alpha defaults to 255 * @param permissive If true, strings like "f" are considered valid * representations of the color white. Useful for UIs that allow entering * a hex color. Empty strings evaluate to pure white * @returns A ccColor4B if it could be succesfully parsed, or an error * indicating the failure reason */ GEODE_DLL Result cc4bFromHexString(std::string const& hexValue, bool requireAlpha = false, bool permissive = false); 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; } /** * Gets the mouse position in cocos2d coordinates. * On mobile platforms this will probably return (0, 0) * @returns The mouse position */ GEODE_DLL cocos2d::CCPoint getMousePos(); } // 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 hash> { size_t operator()(geode::WeakRef const& ref) const { // the explicit template argument is needed here because it would otherwise cast to WeakRef and recurse return std::hash>{}(ref.m_controller); } }; } // 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 concept CocosObjectPtr = std::is_pointer_v && std::is_convertible_v; template concept CocosDictionaryKey = std::same_as || std::same_as || std::same_as || std::same_as; /** * A templated wrapper over CCArray, providing easy iteration and indexing. * This will keep ownership of the given CCArray*. * * @tparam Type Pointer to a type that inherits CCObject. * * @example * CCArrayExt objects = PlayLayer::get()->m_objects; * // Easy indexing, giving you the type you assigned * GameObject* myObj = objects[2]; * * // Easy iteration using C++ range-based for loops * for (auto* obj : objects) { * log::info("{}", obj->m_objectID); * } */ template class CCArrayExt { protected: Ref m_arr; using T = std::remove_pointer_t; public: using value_type = T*; using iterator = T**; using const_iterator = const T**; 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() {} T** begin() const { if (!m_arr) { return nullptr; } return reinterpret_cast(m_arr->data->arr); } T** end() const { if (!m_arr) { return nullptr; } return reinterpret_cast(m_arr->data->arr) + m_arr->count(); } auto rbegin() const { return std::reverse_iterator(this->end()); } auto rend() const { return std::reverse_iterator(this->begin()); } 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 = static_cast(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_v || std::is_same_v) { 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; } }; /** * A templated wrapper over CCDictionary, providing easy iteration and indexing. * This will keep ownership of the given CCDictionary*. * * @tparam Key Type of the key. MUST only be int or gd::string or std::string. * @tparam ValuePtr Pointer to a type that inherits CCObject. * * @example * CCDictionaryExt levels = getSomeDict(); * // Easy indexing, giving you the type you assigned * GJGameLevel* myLvl = levels["Cube Adventures"]; * * // Easy iteration using C++ range-based for loops * for (auto [name, level] : levels) { * log::info("{}: {}", name, level->m_levelID); * } */ template struct CCDictionaryExt { protected: Ref m_dict; public: CCDictionaryExt() : m_dict(cocos2d::CCDictionary::create()) {} CCDictionaryExt(cocos2d::CCDictionary* dict) : m_dict(dict) {} CCDictionaryExt(CCDictionaryExt const& d) : m_dict(d.m_dict) {} CCDictionaryExt(CCDictionaryExt&& d) : m_dict(d.m_dict) { d.m_dict = nullptr; } 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[](const Key& key) { auto ret = static_cast(m_dict->objectForKey(key)); if (!ret) m_dict->setObject(cocos2d::CCNode::create(), key); return CCDictEntry(key, m_dict); } bool contains(const Key& key) { return m_dict->objectForKey(key) != nullptr; } size_t count(const Key& key) { return this->contains(key) ? 1 : 0; } cocos2d::CCDictionary* inner() { return m_dict; } }; struct CCMenuItemExt { private: template class LambdaCallback : public cocos2d::CCObject { public: std::function m_callback; static LambdaCallback* create(std::function callback) { auto ret = new (std::nothrow) LambdaCallback(); if (ret->init(std::move(callback))) { ret->autorelease(); return ret; } delete ret; return nullptr; } bool init(std::function callback) { m_callback = std::move(callback); return true; } void execute(cocos2d::CCNode* node) { m_callback(static_cast(node)); } }; public: static cocos2d::CCMenuItem* create( std::function callback ) { auto item = cocos2d::CCMenuItem::create(); assignCallback(item, std::move(callback)); return item; } static cocos2d::CCMenuItemSprite* createSprite( cocos2d::CCNode* normalSprite, cocos2d::CCNode* selectedSprite, std::function callback ) { auto item = cocos2d::CCMenuItemSprite::create(normalSprite, selectedSprite); assignCallback(item, std::move(callback)); return item; } static cocos2d::CCMenuItemSprite* createSprite( cocos2d::CCNode* normalSprite, cocos2d::CCNode* selectedSprite, cocos2d::CCNode* disabledSprite, std::function callback ) { auto item = cocos2d::CCMenuItemSprite::create(normalSprite, selectedSprite, disabledSprite); assignCallback(item, std::move(callback)); return item; } static CCMenuItemSpriteExtra* createSpriteExtra( cocos2d::CCNode* normalSprite, std::function callback ) { auto item = CCMenuItemSpriteExtra::create(normalSprite, nullptr, nullptr); assignCallback(item, std::move(callback)); return item; } static CCMenuItemSpriteExtra* createSpriteExtraWithFilename( std::string_view normalSpriteName, float scale, std::function callback ) { auto sprite = cocos2d::CCSprite::create(normalSpriteName.data()); sprite->setScale(scale); return createSpriteExtra(sprite, std::move(callback)); } static CCMenuItemSpriteExtra* createSpriteExtraWithFrameName( std::string_view normalSpriteName, float scale, std::function callback ) { auto sprite = cocos2d::CCSprite::createWithSpriteFrameName(normalSpriteName.data()); sprite->setScale(scale); return createSpriteExtra(sprite, std::move(callback)); } static CCMenuItemToggler* createToggler( cocos2d::CCNode* onSprite, cocos2d::CCNode* offSprite, std::function callback ) { auto item = CCMenuItemToggler::create(offSprite, onSprite, nullptr, nullptr); assignCallback(item, std::move(callback)); return item; } static CCMenuItemToggler* createTogglerWithStandardSprites( float scale, std::function callback ) { auto offSprite = cocos2d::CCSprite::createWithSpriteFrameName("GJ_checkOff_001.png"); auto onSprite = cocos2d::CCSprite::createWithSpriteFrameName("GJ_checkOn_001.png"); offSprite->setScale(scale); onSprite->setScale(scale); return createToggler(onSprite, offSprite, std::move(callback)); } static CCMenuItemToggler* createTogglerWithFilename( std::string_view onSpriteName, std::string_view offSpriteName, float scale, std::function callback ) { auto offSprite = cocos2d::CCSprite::create(offSpriteName.data()); auto onSprite = cocos2d::CCSprite::create(onSpriteName.data()); offSprite->setScale(scale); onSprite->setScale(scale); return createToggler(onSprite, offSprite, std::move(callback)); } static CCMenuItemToggler* createTogglerWithFrameName( std::string_view onSpriteName, std::string_view offSpriteName, float scale, std::function callback ) { auto offSprite = cocos2d::CCSprite::createWithSpriteFrameName(offSpriteName.data()); auto onSprite = cocos2d::CCSprite::createWithSpriteFrameName(onSpriteName.data()); offSprite->setScale(scale); onSprite->setScale(scale); return createToggler(onSprite, offSprite, std::move(callback)); } template static void assignCallback( cocos2d::CCMenuItem* item, std::function callback ) { auto lambda = LambdaCallback::create(std::move(callback)); item->setTarget(lambda, menu_selector(LambdaCallback::execute)); item->setUserObject("lambda-callback", lambda); } }; // CCCallFunc alternative that accepts a lambda (or any function object) template class CallFuncExtImpl : public cocos2d::CCActionInstant { public: static CallFuncExtImpl* create(const F& func) { auto ret = new CallFuncExtImpl; ret->m_func = func; ret->autorelease(); return ret; } static CallFuncExtImpl* create(F&& func) { auto ret = new CallFuncExtImpl; ret->m_func = std::move(func); ret->autorelease(); return ret; } private: F m_func; void update(float) override { if (m_func) this->m_func(); } }; // small hack to allow template deduction struct CallFuncExt { template static auto create(F&& func) { using Fd = std::decay_t; return CallFuncExtImpl::create(std::forward(func)); } }; void GEODE_DLL handleTouchPriorityWith(cocos2d::CCNode* node, int priority, bool force = false); void GEODE_DLL handleTouchPriority(cocos2d::CCNode* node, bool force = false); }