From 65355218d7012f67ed584c26f5178d2c4bf2a3d9 Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Tue, 1 Aug 2023 15:34:04 -0300 Subject: [PATCH 1/6] tile widget --- src/client/luafunctions.cpp | 5 ++++ src/client/tile.cpp | 40 ++++++++++++++++++++++++++ src/client/tile.h | 6 ++++ src/client/uimap.cpp | 2 ++ src/framework/core/application.cpp | 1 + src/framework/core/eventdispatcher.cpp | 2 +- src/framework/core/eventdispatcher.h | 2 +- src/framework/ui/uiwidget.h | 2 +- 8 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/client/luafunctions.cpp b/src/client/luafunctions.cpp index c31f56be66..e023a41784 100644 --- a/src/client/luafunctions.cpp +++ b/src/client/luafunctions.cpp @@ -788,6 +788,11 @@ void Client::registerLuaFunctions() g_lua.bindClassMemberFunction("select", &Tile::select); g_lua.bindClassMemberFunction("unselect", &Tile::unselect); g_lua.bindClassMemberFunction("isSelected", &Tile::isSelected); + + g_lua.bindClassMemberFunction("setWidget", &Tile::setWidget); + g_lua.bindClassMemberFunction("getWidget", &Tile::getWidget); + g_lua.bindClassMemberFunction("removeWidget", &Tile::removeWidget); + #ifdef FRAMEWORK_EDITOR g_lua.bindClassMemberFunction("isHouseTile", &Tile::isHouseTile); g_lua.bindClassMemberFunction("overwriteMinimapColor", &Tile::overwriteMinimapColor); diff --git a/src/client/tile.cpp b/src/client/tile.cpp index e9bc47e04d..794d3ac24e 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "effect.h" #include "game.h" #include "item.h" @@ -68,6 +69,7 @@ void Tile::draw(const Point& dest, const MapPosInfo& mapRect, int flags, bool is drawCreature(dest, mapRect, flags, isCovered, false, lightView); drawTop(dest, flags, false, lightView); + drawWidget(dest, mapRect); } void Tile::drawCreature(const Point& dest, const MapPosInfo& mapRect, int flags, bool isCovered, bool forceDraw, LightView* lightView) @@ -110,8 +112,46 @@ void Tile::drawTop(const Point& dest, int flags, bool forceDraw, LightView* ligh } } +void Tile::drawWidget(const Point& dest, const MapPosInfo& mapRect) +{ + if (!m_widget) + return; + + Point p = dest - mapRect.drawOffset; + p.x *= mapRect.horizontalStretchFactor; + p.y *= mapRect.verticalStretchFactor; + p += mapRect.rect.topLeft(); + + p.x += m_widget->getMarginLeft(); + p.x -= m_widget->getMarginRight(); + p.y += m_widget->getMarginTop(); + p.y -= m_widget->getMarginBottom(); + + const auto& widgetRect = m_widget->getRect(); + const auto& rect = Rect(p - Point(widgetRect.width() / 2 - g_gameConfig.getSpriteSize(), widgetRect.height() / 2 - g_gameConfig.getSpriteSize()), widgetRect.width(), widgetRect.height()); + + m_widget->setRect(rect); + + g_uiMapDispatcher.addEvent([rect, mapRect, widget = m_widget->static_self_cast()] { + const auto& oldClipRect = g_drawPool.getClipRect(); + g_drawPool.setClipRect(mapRect.rect); + widget->draw(rect, DrawPoolType::FOREGROUND); + g_drawPool.setClipRect(oldClipRect); + }); +} + +void Tile::removeWidget() +{ + if (m_widget) { + m_widget->destroy(); + m_widget = nullptr; + } +} + void Tile::clean() { + removeWidget(); + m_highlightThing = nullptr; while (!m_things.empty()) removeThing(m_things.front()); diff --git a/src/client/tile.h b/src/client/tile.h index a935b00877..20fb020e4d 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -115,6 +115,11 @@ class Tile : public LuaObject ThingPtr getTopMoveThing(); ThingPtr getTopMultiUseThing(); + void drawWidget(const Point& dest, const MapPosInfo& mapRect); + void setWidget(const UIWidgetPtr& widget) { m_widget = widget; } + UIWidgetPtr getWidget() { return m_widget; } + void removeWidget(); + int getDrawElevation() { return m_drawElevation; } const Position& getPosition() { return m_position; } const std::vector& getWalkingCreatures() { return m_walkingCreatures; } @@ -239,6 +244,7 @@ class Tile : public LuaObject std::vector m_tilesRedraw; ThingPtr m_highlightThing; + UIWidgetPtr m_widget; TileSelectType m_selectType{ TileSelectType::NONE }; diff --git a/src/client/uimap.cpp b/src/client/uimap.cpp index 45480a2bea..7710d9116e 100644 --- a/src/client/uimap.cpp +++ b/src/client/uimap.cpp @@ -22,6 +22,7 @@ #include "uimap.h" #include +#include #include #include #include "game.h" @@ -57,6 +58,7 @@ void UIMap::drawSelf(DrawPoolType drawPane) g_drawPool.addAction([] {glDisable(GL_BLEND); }); g_drawPool.addFilledRect(m_mapRect, Color::alpha); g_drawPool.addAction([] {glEnable(GL_BLEND); }); + g_uiMapDispatcher.poll(); return; } diff --git a/src/framework/core/application.cpp b/src/framework/core/application.cpp index 01c41e615b..d41f70a09f 100644 --- a/src/framework/core/application.cpp +++ b/src/framework/core/application.cpp @@ -110,6 +110,7 @@ void Application::deinit() // disable dispatcher events g_dispatcher.shutdown(); + g_uiMapDispatcher.shutdown(); g_textDispatcher.shutdown(); g_mainDispatcher.shutdown(); } diff --git a/src/framework/core/eventdispatcher.cpp b/src/framework/core/eventdispatcher.cpp index bd64997594..fb53fceb31 100644 --- a/src/framework/core/eventdispatcher.cpp +++ b/src/framework/core/eventdispatcher.cpp @@ -25,7 +25,7 @@ #include #include "timer.h" -EventDispatcher g_dispatcher, g_textDispatcher, g_mainDispatcher; +EventDispatcher g_dispatcher, g_uiMapDispatcher, g_textDispatcher, g_mainDispatcher; std::thread::id g_mainThreadId = std::this_thread::get_id(); void EventDispatcher::shutdown() diff --git a/src/framework/core/eventdispatcher.h b/src/framework/core/eventdispatcher.h index 0b1277ff81..82cd6ee054 100644 --- a/src/framework/core/eventdispatcher.h +++ b/src/framework/core/eventdispatcher.h @@ -48,5 +48,5 @@ class EventDispatcher std::priority_queue, ScheduledEvent::Compare> m_scheduledEventList; }; -extern EventDispatcher g_dispatcher, g_textDispatcher, g_mainDispatcher; +extern EventDispatcher g_dispatcher, g_uiMapDispatcher, g_textDispatcher, g_mainDispatcher; extern std::thread::id g_mainThreadId; diff --git a/src/framework/ui/uiwidget.h b/src/framework/ui/uiwidget.h index f70dea568b..680c1bbd1a 100644 --- a/src/framework/ui/uiwidget.h +++ b/src/framework/ui/uiwidget.h @@ -80,8 +80,8 @@ class UIWidget : public LuaObject UIWidget(); ~UIWidget() override; virtual void drawSelf(DrawPoolType drawPane); -protected: virtual void draw(const Rect& visibleRect, DrawPoolType drawPane); +protected: virtual void drawChildren(const Rect& visibleRect, DrawPoolType drawPane); friend class UIManager; From 41bf2c7a24b48c5979dcdd76e6a120ee49e48bf3 Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Tue, 1 Aug 2023 16:13:05 -0300 Subject: [PATCH 2/6] update --- src/framework/core/application.cpp | 1 - src/framework/core/eventdispatcher.cpp | 2 +- src/framework/core/eventdispatcher.h | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/framework/core/application.cpp b/src/framework/core/application.cpp index d41f70a09f..01c41e615b 100644 --- a/src/framework/core/application.cpp +++ b/src/framework/core/application.cpp @@ -110,7 +110,6 @@ void Application::deinit() // disable dispatcher events g_dispatcher.shutdown(); - g_uiMapDispatcher.shutdown(); g_textDispatcher.shutdown(); g_mainDispatcher.shutdown(); } diff --git a/src/framework/core/eventdispatcher.cpp b/src/framework/core/eventdispatcher.cpp index fb53fceb31..bd64997594 100644 --- a/src/framework/core/eventdispatcher.cpp +++ b/src/framework/core/eventdispatcher.cpp @@ -25,7 +25,7 @@ #include #include "timer.h" -EventDispatcher g_dispatcher, g_uiMapDispatcher, g_textDispatcher, g_mainDispatcher; +EventDispatcher g_dispatcher, g_textDispatcher, g_mainDispatcher; std::thread::id g_mainThreadId = std::this_thread::get_id(); void EventDispatcher::shutdown() diff --git a/src/framework/core/eventdispatcher.h b/src/framework/core/eventdispatcher.h index 82cd6ee054..0b1277ff81 100644 --- a/src/framework/core/eventdispatcher.h +++ b/src/framework/core/eventdispatcher.h @@ -48,5 +48,5 @@ class EventDispatcher std::priority_queue, ScheduledEvent::Compare> m_scheduledEventList; }; -extern EventDispatcher g_dispatcher, g_uiMapDispatcher, g_textDispatcher, g_mainDispatcher; +extern EventDispatcher g_dispatcher, g_textDispatcher, g_mainDispatcher; extern std::thread::id g_mainThreadId; From 9dc69e4f8d2bc9baabb322ffa727713341d4174c Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Tue, 1 Aug 2023 16:24:30 -0300 Subject: [PATCH 3/6] improve --- src/client/tile.cpp | 11 ++++------- src/client/tile.h | 1 + src/client/uimap.cpp | 12 +++++++++++- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 794d3ac24e..9b8b5e1f93 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -69,7 +69,6 @@ void Tile::draw(const Point& dest, const MapPosInfo& mapRect, int flags, bool is drawCreature(dest, mapRect, flags, isCovered, false, lightView); drawTop(dest, flags, false, lightView); - drawWidget(dest, mapRect); } void Tile::drawCreature(const Point& dest, const MapPosInfo& mapRect, int flags, bool isCovered, bool forceDraw, LightView* lightView) @@ -132,12 +131,10 @@ void Tile::drawWidget(const Point& dest, const MapPosInfo& mapRect) m_widget->setRect(rect); - g_uiMapDispatcher.addEvent([rect, mapRect, widget = m_widget->static_self_cast()] { - const auto& oldClipRect = g_drawPool.getClipRect(); - g_drawPool.setClipRect(mapRect.rect); - widget->draw(rect, DrawPoolType::FOREGROUND); - g_drawPool.setClipRect(oldClipRect); - }); + const auto& oldClipRect = g_drawPool.getClipRect(); + g_drawPool.setClipRect(mapRect.rect); + m_widget->draw(rect, DrawPoolType::FOREGROUND); + g_drawPool.setClipRect(oldClipRect); } void Tile::removeWidget() diff --git a/src/client/tile.h b/src/client/tile.h index 20fb020e4d..eb7dd5eb13 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -115,6 +115,7 @@ class Tile : public LuaObject ThingPtr getTopMoveThing(); ThingPtr getTopMultiUseThing(); + bool hasWidget() const { return m_widget != nullptr; } void drawWidget(const Point& dest, const MapPosInfo& mapRect); void setWidget(const UIWidgetPtr& widget) { m_widget = widget; } UIWidgetPtr getWidget() { return m_widget; } diff --git a/src/client/uimap.cpp b/src/client/uimap.cpp index 7710d9116e..ab3debcc3f 100644 --- a/src/client/uimap.cpp +++ b/src/client/uimap.cpp @@ -58,7 +58,17 @@ void UIMap::drawSelf(DrawPoolType drawPane) g_drawPool.addAction([] {glDisable(GL_BLEND); }); g_drawPool.addFilledRect(m_mapRect, Color::alpha); g_drawPool.addAction([] {glEnable(GL_BLEND); }); - g_uiMapDispatcher.poll(); + + if (m_mapView) { + for (const auto& tile : g_map.getTiles(m_mapView->m_lastCameraPosition.z)) { + if (!tile->hasWidget()) + continue; + + const auto& dest = m_mapView->transformPositionTo2D(tile->getPosition(), m_mapView->m_lastCameraPosition); + tile->drawWidget(dest, m_mapView->m_posInfo); + } + } + return; } From a3d87f8b2db6b0df66af0e591113f501b70fb04c Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Tue, 1 Aug 2023 17:50:25 -0300 Subject: [PATCH 4/6] update --- src/client/map.cpp | 3 ++ src/client/tile.cpp | 39 ++++++++++++++++----- src/client/tile.h | 3 +- src/client/uimap.cpp | 18 +++++++--- src/client/uimap.h | 5 +++ src/framework/core/graphicalapplication.cpp | 15 ++++---- src/framework/ui/declarations.h | 2 ++ src/framework/ui/uimanager.h | 3 ++ 8 files changed, 66 insertions(+), 22 deletions(-) diff --git a/src/client/map.cpp b/src/client/map.cpp index 7352e43b0d..1984077efb 100644 --- a/src/client/map.cpp +++ b/src/client/map.cpp @@ -519,7 +519,10 @@ void Map::removeUnawareThings() continue; } + tile->clean(); + block.remove(pos); + notificateTileUpdate(pos, nullptr, Otc::OPERATION_CLEAN); } if (blockEmpty) diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 9b8b5e1f93..e8e1c7bda7 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -24,12 +24,14 @@ #include #include #include +#include #include #include "effect.h" #include "game.h" #include "item.h" #include "lightview.h" #include "map.h" +#include "uimap.h" #include "protocolgame.h" Tile::Tile(const Position& position) : m_position(position) {} @@ -69,6 +71,7 @@ void Tile::draw(const Point& dest, const MapPosInfo& mapRect, int flags, bool is drawCreature(dest, mapRect, flags, isCovered, false, lightView); drawTop(dest, flags, false, lightView); + updateWidget(dest, mapRect); } void Tile::drawCreature(const Point& dest, const MapPosInfo& mapRect, int flags, bool isCovered, bool forceDraw, LightView* lightView) @@ -111,7 +114,7 @@ void Tile::drawTop(const Point& dest, int flags, bool forceDraw, LightView* ligh } } -void Tile::drawWidget(const Point& dest, const MapPosInfo& mapRect) +void Tile::updateWidget(const Point& dest, const MapPosInfo& mapRect) { if (!m_widget) return; @@ -130,19 +133,37 @@ void Tile::drawWidget(const Point& dest, const MapPosInfo& mapRect) const auto& rect = Rect(p - Point(widgetRect.width() / 2 - g_gameConfig.getSpriteSize(), widgetRect.height() / 2 - g_gameConfig.getSpriteSize()), widgetRect.width(), widgetRect.height()); m_widget->setRect(rect); +} + +void Tile::drawWidget(const Point& dest, const MapPosInfo& mapRect) +{ + if (!m_widget) + return; + + m_widget->draw(mapRect.rect, DrawPoolType::FOREGROUND); +} + +void Tile::setWidget(const UIWidgetPtr& widget) { + removeWidget(); - const auto& oldClipRect = g_drawPool.getClipRect(); - g_drawPool.setClipRect(mapRect.rect); - m_widget->draw(rect, DrawPoolType::FOREGROUND); - g_drawPool.setClipRect(oldClipRect); + m_widget = widget; + m_widget->setClipping(true); + g_dispatcher.scheduleEvent([tile = static_self_cast()] { + g_ui.getMapWidget()->addTileWidget(tile); + }, g_game.getServerBeat()); } void Tile::removeWidget() { - if (m_widget) { - m_widget->destroy(); - m_widget = nullptr; - } + if (!m_widget) + return; + + m_widget->destroy(); + m_widget = nullptr; + + g_dispatcher.scheduleEvent([tile = static_self_cast()] { + g_ui.getMapWidget()->removeTileWidget(tile); + }, g_game.getServerBeat()); } void Tile::clean() diff --git a/src/client/tile.h b/src/client/tile.h index eb7dd5eb13..73486987b4 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -117,7 +117,7 @@ class Tile : public LuaObject bool hasWidget() const { return m_widget != nullptr; } void drawWidget(const Point& dest, const MapPosInfo& mapRect); - void setWidget(const UIWidgetPtr& widget) { m_widget = widget; } + void setWidget(const UIWidgetPtr& widget); UIWidgetPtr getWidget() { return m_widget; } void removeWidget(); @@ -214,6 +214,7 @@ class Tile : public LuaObject void setThingFlag(const ThingPtr& thing); + void updateWidget(const Point& dest, const MapPosInfo& mapRect); void recalculateThingFlag() { m_thingTypeFlag = 0; diff --git a/src/client/uimap.cpp b/src/client/uimap.cpp index ab3debcc3f..97f4f69cbd 100644 --- a/src/client/uimap.cpp +++ b/src/client/uimap.cpp @@ -60,10 +60,7 @@ void UIMap::drawSelf(DrawPoolType drawPane) g_drawPool.addAction([] {glEnable(GL_BLEND); }); if (m_mapView) { - for (const auto& tile : g_map.getTiles(m_mapView->m_lastCameraPosition.z)) { - if (!tile->hasWidget()) - continue; - + for (const auto& tile : m_tiles) { const auto& dest = m_mapView->transformPositionTo2D(tile->getPosition(), m_mapView->m_lastCameraPosition); tile->drawWidget(dest, m_mapView->m_posInfo); } @@ -212,4 +209,17 @@ void UIMap::updateMapSize() updateVisibleDimension(); } +void UIMap::addTileWidget(const TilePtr& tile) { + std::scoped_lock l(g_drawPool.get(DrawPoolType::FOREGROUND)->getMutex()); + m_tiles.emplace_back(tile); +} +void UIMap::removeTileWidget(const TilePtr& tile) { + std::scoped_lock l(g_drawPool.get(DrawPoolType::FOREGROUND)->getMutex()); + const auto it = std::find(m_tiles.begin(), m_tiles.end(), tile); + if (it == m_tiles.end()) + return; + + m_tiles.erase(it); +} + /* vim: set ts=4 sw=4 et: */ \ No newline at end of file diff --git a/src/client/uimap.h b/src/client/uimap.h index fdee885201..7cc6979029 100644 --- a/src/client/uimap.h +++ b/src/client/uimap.h @@ -94,6 +94,9 @@ class UIMap : public UIWidget void setAntiAliasingMode(const MapView::AntialiasingMode mode) { m_mapView->setAntiAliasingMode(mode); } void setFloorFading(const uint16_t v) { m_mapView->setFloorFading(v); } + void addTileWidget(const TilePtr& tile); + void removeTileWidget(const TilePtr& tile); + protected: void onStyleApply(const std::string_view styleName, const OTMLNodePtr& styleNode) override; void onGeometryChange(const Rect& oldRect, const Rect& newRect) override; @@ -103,8 +106,10 @@ class UIMap : public UIWidget void updateVisibleDimension(); void updateMapSize(); + std::vector m_tiles; MapViewPtr m_mapView; Rect m_mapRect; + float m_aspectRatio; bool m_keepAspectRatio; diff --git a/src/framework/core/graphicalapplication.cpp b/src/framework/core/graphicalapplication.cpp index 684be96979..ee473bcbae 100644 --- a/src/framework/core/graphicalapplication.cpp +++ b/src/framework/core/graphicalapplication.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -139,8 +140,6 @@ void GraphicalApplication::run() std::condition_variable foreCondition, txtCondition; - UIWidgetPtr mapWidget; - // clang c++20 dont support jthread std::thread t1([&]() { while (!m_stopping) { @@ -161,17 +160,17 @@ void GraphicalApplication::run() foreCondition.notify_one(); if (g_game.isOnline()) { - if (!mapWidget) - mapWidget = g_ui.getRootWidget()->recursiveGetChildById("gameMapPanel"); + if (!g_ui.m_mapWidget) + g_ui.m_mapWidget = g_ui.getRootWidget()->recursiveGetChildById("gameMapPanel")->static_self_cast(); if (txt->canRepaint()) txtCondition.notify_one(); { std::scoped_lock l(map->getMutex()); - mapWidget->drawSelf(DrawPoolType::MAP); + g_ui.m_mapWidget->drawSelf(DrawPoolType::MAP); } - } else mapWidget = nullptr; + } else g_ui.m_mapWidget = nullptr; stdext::millisleep(1); } @@ -194,8 +193,8 @@ void GraphicalApplication::run() g_textDispatcher.poll(); txt->setEnable(canDrawTexts()); - if (mapWidget && txt->isEnabled()) - mapWidget->drawSelf(DrawPoolType::TEXT); + if (g_ui.m_mapWidget && txt->isEnabled()) + g_ui.m_mapWidget->drawSelf(DrawPoolType::TEXT); return m_stopping; }); diff --git a/src/framework/ui/declarations.h b/src/framework/ui/declarations.h index ec7c1a3ae1..4aebd615af 100644 --- a/src/framework/ui/declarations.h +++ b/src/framework/ui/declarations.h @@ -38,7 +38,9 @@ class UIAnchor; class UIAnchorGroup; class UIAnchorLayout; class UIParticles; +class UIMap; +using UIMapPtr = std::shared_ptr; using UIWidgetPtr = std::shared_ptr; using UIParticlesPtr = std::shared_ptr; using UITextEditPtr = std::shared_ptr; diff --git a/src/framework/ui/uimanager.h b/src/framework/ui/uimanager.h index bb8d4c0a91..a5a2e25374 100644 --- a/src/framework/ui/uimanager.h +++ b/src/framework/ui/uimanager.h @@ -50,6 +50,7 @@ class UIManager std::string getStyleClass(const std::string_view styleName); OTMLNodePtr findMainWidgetNode(const OTMLDocumentPtr& doc); + UIMapPtr getMapWidget() const { return m_mapWidget; } UIWidgetPtr loadUI(const std::string& file, const UIWidgetPtr& parent); OTMLNodePtr loadDeviceUI(const std::string& file, Platform::OperatingSystem os); OTMLNodePtr loadDeviceUI(const std::string& file, Platform::DeviceType deviceType); @@ -79,9 +80,11 @@ class UIManager void onWidgetDestroy(const UIWidgetPtr& widget); friend class UIWidget; + friend class GraphicalApplication; private: UIWidgetPtr m_rootWidget; + UIMapPtr m_mapWidget; UIWidgetPtr m_mouseReceiver; UIWidgetPtr m_keyboardReceiver; UIWidgetPtr m_draggingWidget; From 5585fe6a78c30f568cd3fd0b2b3f755c17bfca5e Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Tue, 1 Aug 2023 17:52:08 -0300 Subject: [PATCH 5/6] Update uimap.cpp --- src/client/uimap.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client/uimap.cpp b/src/client/uimap.cpp index 97f4f69cbd..38b9642c76 100644 --- a/src/client/uimap.cpp +++ b/src/client/uimap.cpp @@ -22,7 +22,6 @@ #include "uimap.h" #include -#include #include #include #include "game.h" From 844dc34d4bb958e5f7f4c626bdf7813c72d2cfde Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Tue, 1 Aug 2023 17:52:55 -0300 Subject: [PATCH 6/6] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 86e54a735e..97e5905f97 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ - Client 12.85 ~ 12.92, 13.00 ~ 13.14 support (protobuf) - Market has been rewritten to work only [Canary](https://github.com/opentibiabr/canary) - Async Texture Loading +- Tile Widget ##### Community (Features) - Mobile Support [@tuliomagalhaes](https://github.com/tuliomagalhaes) & [@BenDol](https://github.com/BenDol)