diff --git a/modules/game/widgets/uigamemap.lua b/modules/game/widgets/uigamemap.lua index 10c564da..a37e3ba0 100644 --- a/modules/game/widgets/uigamemap.lua +++ b/modules/game/widgets/uigamemap.lua @@ -63,32 +63,11 @@ function UIGameMap:onMouseRelease(mousePosition, mouseButton) if GameInterface.processMouseAction(mousePosition, mouseButton, nil, tile:getTopLookThing(), tile:getTopUseThing(), tile:getTopCreature(), tile:getTopMultiUseThing()) then return true elseif mouseButton == MouseLeftButton then - local fromPos = g_game.getLocalPlayer():getPosition() - local toPos = tile:getPosition() - if fromPos.z ~= toPos.z then + local dirs = g_map.findPath(g_game.getLocalPlayer():getPosition(), tile:getPosition(), 255) + if #dirs == 0 then TextMessage.displayStatus('There is no way.') return true end - - -- simple and stupid pathfinding algorithm - local dirs = {} - local pathPos = fromPos - while pathPos.x ~= toPos.x or pathPos.y ~= toPos.y do - if pathPos.x < toPos.x then - pathPos.x = pathPos.x + 1 - table.insert(dirs, East) - elseif pathPos.x > toPos.x then - pathPos.x = pathPos.x - 1 - table.insert(dirs, West) - elseif pathPos.y < toPos.y then - pathPos.y = pathPos.y + 1 - table.insert(dirs, South) - else --if pathPos.y > toPos.y then - pathPos.y = pathPos.y - 1 - table.insert(dirs, North) - end - end - g_game.autoWalk(dirs) return true end diff --git a/src/otclient/core/game.cpp b/src/otclient/core/game.cpp index da002c7d..3485e520 100644 --- a/src/otclient/core/game.cpp +++ b/src/otclient/core/game.cpp @@ -372,15 +372,18 @@ void Game::walk(Otc::Direction direction) forceWalk(direction); } -void Game::autoWalk(const std::vector& dir) +void Game::autoWalk(const std::vector& dirs) { if(!canPerformGameAction()) return; + if(dirs.size() >= 255) + return; + if(isFollowing()) cancelFollow(); - m_protocolGame->sendAutoWalk(dir); + m_protocolGame->sendAutoWalk(dirs); } void Game::forceWalk(Otc::Direction direction) diff --git a/src/otclient/core/game.h b/src/otclient/core/game.h index 007887c4..27ca9e1e 100644 --- a/src/otclient/core/game.h +++ b/src/otclient/core/game.h @@ -123,7 +123,7 @@ public: // walk related void walk(Otc::Direction direction); - void autoWalk(const std::vector& dir); + void autoWalk(const std::vector& dirs); void forceWalk(Otc::Direction direction); void turn(Otc::Direction direction); void stop(); diff --git a/src/otclient/core/map.cpp b/src/otclient/core/map.cpp index 4c8d04b1..c4aaa9a9 100644 --- a/src/otclient/core/map.cpp +++ b/src/otclient/core/map.cpp @@ -411,3 +411,111 @@ int Map::getLastAwareFloor() else return Otc::SEA_FLOOR; } + +// pathfinding using A* search algorithm +// as described in http://en.wikipedia.org/wiki/A*_search_algorithm +std::vector Map::findPath(const Position& startPos, const Position& goalPos, int maxSteps) +{ + struct Node { + Node(const Position& pos) : cost(0), totalCost(0), steps(0), pos(pos), prev(nullptr), dir(Otc::InvalidDirection), evaluated(false) { } + bool operator<(const Node& other) const { return totalCost < other.totalCost; } + float cost; + float totalCost; + int steps; + Position pos; + Node *prev; + Otc::Direction dir; + bool evaluated; + }; + + struct LessNode : std::binary_function { + bool operator()(Node* a, Node* b) const { + return b->totalCost < a->totalCost; + } + }; + + std::vector dirs; + + if(startPos == goalPos || startPos.z != goalPos.z || startPos.distance(goalPos) > maxSteps) { + return dirs; + } + + auto estimateCost = [=](const Position& pos) { return pos.distance(goalPos); }; + std::unordered_map nodes; + std::priority_queue, LessNode> searchList; + + Node *currentNode = new Node(startPos); + currentNode->pos = startPos; + Node *foundNode = nullptr; + while(currentNode && currentNode->steps < maxSteps) { + if(currentNode->pos == goalPos && (!foundNode || currentNode->cost < foundNode->cost)) + foundNode = currentNode; + + if(foundNode && currentNode->totalCost >= foundNode->cost) + break; + + for(int i=-1;i<=1;++i) { + for(int j=-1;j<=1;++j) { + if(i == 0 && j == 0) + continue; + + Position neighborPos = currentNode->pos.translated(i, j); + const TilePtr& tile = getTile(neighborPos); + if(!tile || !tile->isWalkable()) + continue; + + float walkFactor; + Otc::Direction walkDir = currentNode->pos.getDirectionFromPosition(neighborPos); + if(walkDir >= Otc::NorthEast) + walkFactor = 3.0f; + else + walkFactor = 1.0f; + + float cost = currentNode->cost + (tile->getGroundSpeed() * walkFactor) / 100.0f; + + Node *neighborNode; + if(nodes.find(neighborPos) == nodes.end()) { + neighborNode = new Node(neighborPos); + nodes[neighborPos] = neighborNode; + } else { + neighborNode = nodes[neighborPos]; + if(neighborNode->cost < cost) + continue; + } + + neighborNode->prev = currentNode; + neighborNode->cost = cost; + neighborNode->steps = currentNode->steps + 1; + neighborNode->totalCost = neighborNode->cost + estimateCost(neighborPos); + neighborNode->dir = walkDir; + neighborNode->evaluated = false; + searchList.push(neighborNode); + } + } + + currentNode->evaluated = true; + currentNode = nullptr; + while(searchList.size() > 0 && !currentNode) { + Node *node = searchList.top(); + searchList.pop(); + + if(!node->evaluated) + currentNode = node; + } + } + + if(foundNode) { + currentNode = foundNode; + while(currentNode) { + dirs.push_back(currentNode->dir); + currentNode = currentNode->prev; + } + dirs.pop_back(); + std::reverse(dirs.begin(), dirs.end()); + } + + for(auto it : nodes) + delete it.second; + + return dirs; +} diff --git a/src/otclient/core/map.h b/src/otclient/core/map.h index a0940501..0ab798f9 100644 --- a/src/otclient/core/map.h +++ b/src/otclient/core/map.h @@ -76,6 +76,8 @@ public: std::vector getAnimatedTexts() { return m_animatedTexts; } std::vector getStaticTexts() { return m_staticTexts; } + std::vector findPath(const Position& start, const Position& goal, int maxSteps); + private: std::unordered_map m_tiles; std::map m_knownCreatures; diff --git a/src/otclient/core/mapview.cpp b/src/otclient/core/mapview.cpp index 6273565c..a63e17de 100644 --- a/src/otclient/core/mapview.cpp +++ b/src/otclient/core/mapview.cpp @@ -425,6 +425,7 @@ void MapView::setVisibleDimension(const Size& visibleDimension) } Point virtualCenterOffset = (drawDimension/2 - Size(1,1)).toPoint(); + Point visibleCenterOffset = virtualCenterOffset; ViewRange viewRange; if(tileSize >= 32 && visibleDimension.area() <= NEAR_VIEW_AREA) @@ -446,6 +447,7 @@ void MapView::setVisibleDimension(const Size& visibleDimension) bool mustUpdate = (m_drawDimension != drawDimension || m_viewRange != viewRange || m_virtualCenterOffset != virtualCenterOffset || + m_visibleCenterOffset != visibleCenterOffset || m_tileSize != tileSize); m_visibleDimension = visibleDimension; @@ -453,6 +455,7 @@ void MapView::setVisibleDimension(const Size& visibleDimension) m_tileSize = tileSize; m_viewRange = viewRange; m_virtualCenterOffset = virtualCenterOffset; + m_visibleCenterOffset = visibleCenterOffset; if(mustUpdate) requestVisibleTilesCacheUpdate(); @@ -550,7 +553,7 @@ TilePtr MapView::getTile(const Point& mousePos, const Rect& mapRect) tilePos2D += m_followingCreature->getWalkOffset() * scaleFactor; tilePos2D /= m_tileSize; - Position tilePos = Position(1 + (int)tilePos2D.x - m_virtualCenterOffset.x, 1 + (int)tilePos2D.y - m_virtualCenterOffset.y, 0) + cameraPosition; + Position tilePos = Position(1 + (int)tilePos2D.x - m_visibleCenterOffset.x, 1 + (int)tilePos2D.y - m_visibleCenterOffset.y, 0) + cameraPosition; if(!tilePos.isValid()) return nullptr; diff --git a/src/otclient/core/mapview.h b/src/otclient/core/mapview.h index e23dc356..fb138ce8 100644 --- a/src/otclient/core/mapview.h +++ b/src/otclient/core/mapview.h @@ -100,6 +100,7 @@ private: Size m_drawDimension; Size m_visibleDimension; Point m_virtualCenterOffset; + Point m_visibleCenterOffset; Position m_customCameraPosition; Boolean m_mustUpdateVisibleTilesCache; Boolean m_mustDrawVisibleTilesCache; diff --git a/src/otclient/luafunctions.cpp b/src/otclient/luafunctions.cpp index 79dc5a4e..a160e3a5 100644 --- a/src/otclient/luafunctions.cpp +++ b/src/otclient/luafunctions.cpp @@ -75,6 +75,7 @@ void OTClient::registerLuaFunctions() g_lua.bindClassStaticFunction("g_map", "getCreatureById", std::bind(&Map::getCreatureById, &g_map, _1)); g_lua.bindClassStaticFunction("g_map", "removeCreatureById", std::bind(&Map::removeCreatureById, &g_map, _1)); g_lua.bindClassStaticFunction("g_map", "getSpectators", std::bind(&Map::getSpectators, &g_map, _1, _2)); + g_lua.bindClassStaticFunction("g_map", "findPath", std::bind(&Map::findPath, &g_map, _1, _2, _3)); g_lua.registerStaticClass("g_game"); g_lua.bindClassStaticFunction("g_game", "loginWorld", std::bind(&Game::loginWorld, &g_game, _1, _2, _3, _4, _5)); diff --git a/src/otclient/util/position.h b/src/otclient/util/position.h index 30335dd2..83897c96 100644 --- a/src/otclient/util/position.h +++ b/src/otclient/util/position.h @@ -151,6 +151,8 @@ public: } bool isValid() const { return !(x == 65535 && y == 65535 && z == 255); } + float distance(const Position& pos) const { return sqrt(pow((pos.x - x), 2) + pow((pos.y - y), 2)); } + int manhattanDistance(const Position& pos) const { return std::abs(pos.x - x) + std::abs(pos.y - y); } void translate(int dx, int dy, short dz = 0) { x += dx; y += dy; z += dz; } Position translated(int dx, int dy, short dz = 0) const { Position pos = *this; pos.x += dx; pos.y += dy; pos.z += dz; return pos; }