#ifndef __INCLUDE_HEADER_PYTHON_OFFLINESHO__ #define __INCLUDE_HEADER_PYTHON_OFFLINESHO__ #ifdef ENABLE_IKASHOP_RENEWAL #include #include "PythonBackground.h" #include "Packet.h" #include namespace ikashop { template class S, typename S::iterator > void ForEach(S& container, std::function func){ std::for_each(container.begin(), container.end(), func); } template class S, typename S::iterator > void ForEach(S& container, std::function func){ std::for_each(container.begin(), container.end(), [&](auto&& iter){ func(iter.second); }); } template void DeletePointersContainer(T& obj) { ForEach(obj, [](auto&& iter){ delete(iter); }); } enum eConstOfflineshop { OFFLINESHOP_DURATION_MAX_DAYS = 8, OFFLINESHOP_DURATION_MAX_HOURS = 23, OFFLINESHOP_DURATION_MAX_MINUTES = OFFLINESHOP_DURATION_MAX_DAYS * 24 * 60, OFFLINESHOP_MAX_FILTER_HISTORY_SIZE = 25, OFFLINESHOP_GRID_WIDTH = 15, OFFLINESHOP_GRID_HEIGHT = 10, OFFLINESHOP_GRID_CELL_COUNT = OFFLINESHOP_GRID_WIDTH * OFFLINESHOP_GRID_HEIGHT, #ifdef EXTEND_IKASHOP_ULTIMATE OFFLINESHOP_PAGE_COUNT = 3, OFFLINESHOP_PAGE_SIZE = OFFLINESHOP_GRID_WIDTH * OFFLINESHOP_GRID_HEIGHT, #endif SAFEBOX_GRID_WIDTH = 9, SAFEBOX_GRID_HEIGHT = 11, SAFEBOX_PAGE_COUNT = 3, }; template class CShopGrid { static constexpr auto CELL_COUNT = WIDTH * HEIGHT; public: void Clear() { m_cells = {}; } void RegisterItem(int cell, int size) { const auto [col, row] = _DecomposeCell(cell); for (int i = 0; i < size; i++) _SetCellState(_ComposeCell(col, row + i), true); } void UnregisterItem(int cell, int size) { const auto [col, row] = _DecomposeCell(cell); for (int i = 0; i < size; i++) _SetCellState(_ComposeCell(col, row + i), false); } bool CheckSpace(int cell, int size) { const auto [col, row] = _DecomposeCell(cell); return std::ranges::all_of(std::views::iota(row, row + size), [&](auto&& value) { return !_GetCellState(_ComposeCell(col, value)); }); } std::optional FindSpace(int size) { for (int cell = 0; cell < CELL_COUNT; cell++) if (CheckSpace(cell, size)) return cell; return std::nullopt; } constexpr int GetCellCount() { return WIDTH * HEIGHT; } private: std::pair _DecomposeCell(int cell) { return { cell % WIDTH, cell / WIDTH }; } int _ComposeCell(int col, int row) { return row * WIDTH + col; } void _SetCellState(int cell, bool state) { if (cell >= 0 && cell < CELL_COUNT) m_cells[cell] = state; } bool _GetCellState(int cell) { return cell >= 0 && cell < CELL_COUNT ? m_cells[cell] : true; } private: std::array m_cells{}; }; DWORD GetItemHash(const TShopPlayerItem& item); namespace python { struct PythonReference { public: static PythonReference steal(PyObject* obj){ PythonReference ref; ref.m_obj = obj; return ref; } private: PythonReference() = default; public: PythonReference(PyObject* obj) : m_obj{ obj } { Py_XINCREF(obj); } PythonReference(const PythonReference& r) : m_obj{ r.m_obj } { Py_XINCREF(r.m_obj); } PythonReference(PythonReference&& r) : m_obj{ r.m_obj } { r.m_obj = nullptr; } ~PythonReference() { Py_XDECREF(m_obj); } operator PyObject* () { return m_obj; } PyObject* weakRef() const { return m_obj; } operator bool() const { return m_obj != nullptr && m_obj != Py_None && PyObject_IsTrue(m_obj); } private: PyObject* m_obj = nullptr; }; struct PythonGarbageCollector { // integer values PyObject* makeInteger(int val) { return _Register(PyInt_FromLong(static_cast(val))); } PyObject* makeInteger(long val) { return _Register(PyInt_FromLong(val)); } PyObject* makeInteger(DWORD val) { return _Register(PyInt_FromSize_t(val)); } PyObject* makeInteger(int64_t val) { return _Register(PyLong_FromLongLong(val)); } PyObject* makeInteger(uint64_t val) { return _Register(PyLong_FromUnsignedLongLong(val)); } // floating values PyObject* makeFloat(float value) { return _Register(PyFloat_FromDouble(static_cast(value))); } PyObject* makeFloat(double value) { return _Register(PyFloat_FromDouble(value)); } // boolean PyObject* makeBool(bool value) { return _Register(PyBool_FromLong(value ? 1 : 0)); } // string PyObject* makeString(const std::string& str) { return _Register(PyString_FromString(str.c_str())); } PyObject* makeString(const char* str) { return _Register(PyString_FromString(str)); } // objects PyObject* makeDict() { return _Register(PyDict_New()); } PyObject* makeList(size_t count = 0) { return _Register(PyList_New(count)); } PyObject* makeTuple(size_t count = 0) { return _Register(PyTuple_New(count)); } PyObject* makeValue(const std::string& str, const auto& ...arg) { return _Register(Py_BuildValue(str.c_str(), arg...)); } PyObject* registerObject(PyObject* obj){ objects.emplace_back(obj); return obj; } ~PythonGarbageCollector(){ std::ranges::for_each(objects, [](auto&& obj){ Py_XDECREF(obj); }); } private: PyObject* _Register(PyObject* ptr) { objects.emplace_back(ptr); return ptr; } private: std::vector objects; }; struct PythonSafeDict; struct PythonSafeList; template concept PythonSafeType = std::is_same_v || std::is_same_v || std::is_same_v; struct PythonSafeDict { PythonSafeDict() { dict = collector.makeDict(); } PythonSafeDict(const PythonSafeDict& r) { dict = collector.registerObject(r.incRef()); } // integers void set(const std::string& key, bool value) { PyDict_SetItemString(dict, key.c_str(), collector.makeBool(value)); } void set(const std::string& key, int value) { PyDict_SetItemString(dict, key.c_str(), collector.makeInteger(value)); } void set(const std::string& key, long value) { PyDict_SetItemString(dict, key.c_str(), collector.makeInteger(value)); } void set(const std::string& key, DWORD value) { PyDict_SetItemString(dict, key.c_str(), collector.makeInteger(value)); } void set(const std::string& key, int64_t value) { PyDict_SetItemString(dict, key.c_str(), collector.makeInteger(value)); } void set(const std::string& key, uint64_t value) { PyDict_SetItemString(dict, key.c_str(), collector.makeInteger(value)); } // floats void set(const std::string& key, float value) { PyDict_SetItemString(dict, key.c_str(), collector.makeFloat(value)); } void set(const std::string& key, double value) { PyDict_SetItemString(dict, key.c_str(), collector.makeFloat(value)); } // strings void set(const std::string& key, const std::string& value) { PyDict_SetItemString(dict, key.c_str(), collector.makeString(value)); } void set(const std::string& key, const char* value) { PyDict_SetItemString(dict, key.c_str(), collector.makeString(value)); } // objects void set(const std::string& key, PyObject* obj) { PyDict_SetItemString(dict, key.c_str(), obj); } void set(const std::string& key, const PythonSafeType auto& value) { PyDict_SetItemString(dict, key.c_str(), value.weakRef()); } // references PyObject* weakRef() const { return dict; } PythonReference ref() const { return { dict }; } PyObject* incRef() const { Py_XINCREF(dict); return dict; } private: PyObject* dict = nullptr; PythonGarbageCollector collector; }; struct PythonSafeList { PythonSafeList() { list = collector.makeList(); } PythonSafeList(const PythonSafeList& r) { list = collector.registerObject(r.incRef()); } // integers void append(bool value) { PyList_Append(list, collector.makeBool(value)); } void append(int value) { PyList_Append(list, collector.makeInteger(value)); } void append(long value) { PyList_Append(list, collector.makeInteger(value)); } void append(DWORD value) { PyList_Append(list, collector.makeInteger(value)); } void append(int64_t value) { PyList_Append(list, collector.makeInteger(value)); } void append(uint64_t value) { PyList_Append(list, collector.makeInteger(value)); } // floats void append(float value) { PyList_Append(list, collector.makeFloat(value)); } void append(double value) { PyList_Append(list, collector.makeFloat(value)); } // strings void append(const std::string& value) { PyList_Append(list, collector.makeString(value)); } void append(const char* value) { PyList_Append(list, collector.makeString(value)); } // objects void append(PyObject* obj) { PyList_Append(list, obj); } void append(const PythonSafeType auto& value) { PyList_Append(list, value.weakRef()); } // references PyObject* weakRef() const { return list; } PythonReference ref() const { return { list }; } PyObject* incRef() const { Py_XINCREF(list); return list; } private: PyObject* list = nullptr; PythonGarbageCollector collector; }; inline PythonSafeDict ExtractPythonDictPlayerItem(const auto& info) { // appending item attributes PythonGarbageCollector collector; PythonSafeList attrList; for (int j = 0; j < ITEM_ATTRIBUTE_SLOT_MAX_NUM; j++) attrList.append(collector.makeValue("(ii)", info.aAttr[j].bType, info.aAttr[j].sValue)); // appending item sockets PythonSafeList socketList; for (int j = 0; j < ITEM_SOCKET_SLOT_MAX_NUM; j++) socketList.append(info.alSockets[j]); // making item dict PythonSafeDict itemDict; itemDict.set("id", info.id); itemDict.set("owner", info.owner); itemDict.set("vnum", info.vnum); itemDict.set("count", info.count); #ifdef ENABLE_CHANGELOOK_SYSTEM itemDict.set("look", info.id); #endif itemDict.set("attrs", attrList.weakRef()); itemDict.set("sockets", socketList.weakRef()); itemDict.set("window", info.window); itemDict.set("cell", info.pos); #ifdef EXTEND_IKASHOP_PRO itemDict.set("hash", GetItemHash(info)); #endif return itemDict; } inline PythonSafeDict ExtractPythonDictShopItem(const auto& info) { auto itemDict = ExtractPythonDictPlayerItem(info); itemDict.set("price", info.price.yang); #ifdef ENABLE_CHEQUE_SYSTEM itemDict.set("cheque", info.price.cheque); #endif return itemDict; } #ifdef EXTEND_IKASHOP_ULTIMATE inline PythonSafeDict ExtractPythonDictShopItemView(const auto& info) { auto itemDict = ExtractPythonDictShopItem(info); itemDict.set("priceavg", info.priceavg); return itemDict; } #endif #ifdef EXTEND_IKASHOP_PRO inline PythonSafeDict ExtractPythonDictNotification(const ikashop::TNotificationInfo& info) { PythonSafeDict dict; dict.set("id", info.id); dict.set("type", (int)info.type); dict.set("who", info.who); dict.set("what", info.what); dict.set("format", info.format); dict.set("datetime", info.datetime); dict.set("seen", info.seenflag); return dict; }; #endif inline PythonSafeList ExtractPythonListFromContainer(const auto& container, auto func) { PythonSafeList list; for(auto& element : container) list.append(func(element)); return list; } inline PythonSafeList ExtractPythonPlayerItemList(const auto& container) { static auto func = [](auto&& value) { return ExtractPythonDictPlayerItem(value); }; return ExtractPythonListFromContainer(container, func); } inline PythonSafeList ExtractPythonShopItemList(const auto& container) { static auto func = [](auto&& value) { return ExtractPythonDictShopItem(value); }; return ExtractPythonListFromContainer(container, func); } #ifdef EXTEND_IKASHOP_ULTIMATE inline PythonSafeList ExtractPythonShopItemViewList(const auto& container) { static auto func = [](auto&& value) { return ExtractPythonDictShopItemView(value); }; return ExtractPythonListFromContainer(container, func); } #endif } } #ifdef ENABLE_IKASHOP_ENTITIES namespace ikashop { class ShopInstance { using SkinType = int; using ShopID = uint32_t; using SignString = std::string; #ifdef EXTEND_IKASHOP_ULTIMATE using EffectID = DWORD; using EffectFile = std::string; using BoneIndex = int; private: static DWORD _FindEffectCRC(const EffectFile& name); static inline std::map s_RegisterEffectMap; public: static void LoadShopSkinSettings(); private: struct SkinEffectInfo { std::string script{}; std::string bone{}; D3DXVECTOR3 position{}; D3DXVECTOR3 rotation{}; }; struct SkinInfo { std::vector effects; std::string folder; std::string model; }; static inline std::map s_SkinMap; static void _LoadShopSkin(int index, const std::string& folder, const std::string& msm); #endif public: ShopInstance(SkinType Skin); void SetVID(ShopID dwVID) { m_VirtualID = dwVID; } ShopID GetVID() const { return m_VirtualID; } void SetShopType(SkinType iType) { m_Skin = iType; } SkinType GetType() const { return m_Skin; } bool IsSkin() const { return m_Skin != 0; } CGraphicThingInstance* GetThingInstancePtr() { return &m_GraphicThing; } void SetName(const SignString& str); const SignString& GetName(); void Show(float x, float y, float z); void Clear(); void Render(); void RenderText(); void BlendRender(); void Update(); bool IsPickable(); bool IsPicked(); #ifdef EXTEND_IKASHOP_ULTIMATE void HideAllEffects(); void ShowAllEffects(); #endif private: CGraphicThingInstance m_GraphicThing{}; CGraphicTextInstance m_Text; float m_TextHeight; ShopID m_VirtualID = 0; SkinType m_Skin = 0; bool m_shownEffects = true; #ifdef EXTEND_IKASHOP_ULTIMATE struct AttachedEffectInfo { EffectID id; BoneIndex bone; D3DXVECTOR3 position; D3DXVECTOR3 rotation; }; std::vector m_AttachedEffect{}; #endif }; } #endif class CPythonIkarusShop : public CSingleton { public: struct TFilterHistoryElement : public ikashop::TFilterInfo { int64_t datetime; DWORD count; friend auto operator <=> (const TFilterHistoryElement& left, const TFilterHistoryElement& right) { if(left.datetime == right.datetime) return std::strong_ordering::equal; if(left.datetime < right.datetime) return std::strong_ordering::less; return std::strong_ordering::greater; } }; typedef std::vector FILTERHISTORY; typedef FILTERHISTORY::iterator FILTERHISTORY_ITER; static void NowDatetime(int64_t& datetime) { datetime = static_cast(std::time(nullptr)); } static constexpr auto PRICE_HISTORY_EXPIRATION = 7 * 24 * 60 * 60; static constexpr auto NOTIFICATION_EXPIRATION = 10 * 24 * 60 * 60; #ifdef EXTEND_IKASHOP_ULTIMATE static constexpr auto OFFSHOP_LOCK_INDEX_INIT = 15 * 10; // first page unlocked by default #endif #ifdef EXTEND_IKASHOP_PRO struct PriceHistoryElement { int64_t expiration; int64_t price; DWORD count = 1; #ifdef ENABLE_CHEQUE_SYSTEM int cheque = 0; #endif }; #endif public: CPythonIkarusShop(); ~CPythonIkarusShop(); //starts void SetSearchShopBoard(PyObject* poWindow); void SetBusinessBoard(PyObject* poWindow); PyObject* GetBusinessBoard(); #ifdef EXTEND_IKASHOP_ULTIMATE void SetShopSkinBoard(PyObject* poWindow); PyObject* GetShopSkinBoard(); #endif #ifdef EXTEND_IKASHOP_ULTIMATE void OpenShop(const ikashop::TShopInfo& shop, const std::vector& vec); void OpenShopOwner(const ikashop::TShopInfo& shop, const std::vector& vec); #else void OpenShop(const ikashop::TShopInfo& shop, const std::vector& vec); void OpenShopOwner(const ikashop::TShopInfo& shop, const std::vector& vec); #endif void OpenShopOwnerNoShop(); void ShopClose(); void ShopExpiredGuesting(DWORD ownerid); void ShopRemoveItem(bool owner, DWORD itemid); void ShopGuestEditItem(DWORD itemid, const ikashop::TPriceInfo& price); void ShopOwnerEditItem(DWORD itemid, const ikashop::TPriceInfo& price); void ShopFilterResult(const std::vector& vec); void OfferListReceived(const std::vector& offers); void SafeboxRefresh(const ikashop::TValutesInfo& valute, const std::vector& item); void SafeboxRemoveItem(DWORD itemid); void SafeboxAddItem(const ikashop::TShopPlayerItem& item); void AuctionList(const std::vector& auctions); #ifdef EXTEND_IKASHOP_ULTIMATE void MyAuctionOpen(const ikashop::TAuctionInfo& auction, const std::vector& offers, long long priceavg); void AuctionOpen(const ikashop::TAuctionInfo& auction, const std::vector& offers, long long priceavg); #else void MyAuctionOpen(const ikashop::TAuctionInfo& auction, const std::vector& offers); void AuctionOpen(const ikashop::TAuctionInfo& auction, const std::vector& offers); #endif void OpenMyAuctionNoAuction(); //filter caching void LoadFilterHistory(); void SaveFilterHistory(); void AddNewFilterToHistory(TFilterHistoryElement& element); void PopOldestFilterHistoryElement(); #ifdef EXTEND_IKASHOP_PRO // price caching void LoadPriceHistory(); void SavePriceHistory(); void RegisterPriceHistory(DWORD vnum, DWORD count, DWORD hash, const ikashop::TPriceInfo& price); std::optional GetPriceHistory(DWORD vnum, DWORD hash, DWORD count); #endif //interfaces methods ikashop::python::PythonSafeList GetFilterHistory(); std::vector GetItemNames(); //create shop methods void ShopBuilding_AddItem(int win, int slot); void AuctionBuilding_AddItem(int win, int slot); #ifdef EXTEND_IKASHOP_PRO void RefreshBoardCounters(int safebox, int auction, int offers, int notifications); #else void RefreshBoardCounters(int safebox, int auction, int offers); #endif #ifdef EXTEND_IKASHOP_PRO void RegisterNotification(const ikashop::TNotificationInfo& info); void LoadNotifications(); void SaveNotifications(); const std::deque& GetNotifications(); void SetNotificationList(const std::vector& notifications); #endif #ifdef EXTEND_IKASHOP_ULTIMATE void PriceAverageResponse(long long average); #endif #ifdef ENABLE_IKASHOP_ENTITIES void InsertEntity(DWORD vid , int type, const char* name, long x, long y, long z); void RemoveEntity(DWORD vid); void RenderEntities(); void UpdateEntities(); bool GetShowNameFlag(); void SetShowNameFlag(bool flag); void DeleteEntities(); bool CheckPickedShopEntity(DWORD* ret); #endif void PopupMessage(const std::string& message); void DeleteSearchResult(DWORD itemid); private: bool CallOnBusinessBoard(const std::string& func, PyObject* args = nullptr); bool CallOnSearchShopBoard(const std::string& func, PyObject* args = nullptr); private: PyObject* m_businessBoard = nullptr; PyObject* m_searchShopBoard = nullptr; #ifdef EXTEND_IKASHOP_ULTIMATE PyObject* m_shopSkinBoard = nullptr; #endif FILTERHISTORY m_filterHistory; // price history #ifdef EXTEND_IKASHOP_PRO std::map m_priceByVnum; std::map m_priceByHash; #endif #ifdef ENABLE_IKASHOP_ENTITIES std::vector> m_shopInstances; bool m_bIsShowName; #endif #ifdef EXTEND_IKASHOP_PRO std::deque m_notifications; #endif }; extern void initikashop(); #endif #endif //__INCLUDE_HEADER_PYTHON_OFFLINESHO__