real autowalking
* smart autowalking using A* path finding algorithm
This commit is contained in:
parent
8bc63e25df
commit
fe86dc8050
|
@ -63,32 +63,11 @@ function UIGameMap:onMouseRelease(mousePosition, mouseButton)
|
||||||
if GameInterface.processMouseAction(mousePosition, mouseButton, nil, tile:getTopLookThing(), tile:getTopUseThing(), tile:getTopCreature(), tile:getTopMultiUseThing()) then
|
if GameInterface.processMouseAction(mousePosition, mouseButton, nil, tile:getTopLookThing(), tile:getTopUseThing(), tile:getTopCreature(), tile:getTopMultiUseThing()) then
|
||||||
return true
|
return true
|
||||||
elseif mouseButton == MouseLeftButton then
|
elseif mouseButton == MouseLeftButton then
|
||||||
local fromPos = g_game.getLocalPlayer():getPosition()
|
local dirs = g_map.findPath(g_game.getLocalPlayer():getPosition(), tile:getPosition(), 255)
|
||||||
local toPos = tile:getPosition()
|
if #dirs == 0 then
|
||||||
if fromPos.z ~= toPos.z then
|
|
||||||
TextMessage.displayStatus('There is no way.')
|
TextMessage.displayStatus('There is no way.')
|
||||||
return true
|
return true
|
||||||
end
|
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)
|
g_game.autoWalk(dirs)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
|
@ -372,15 +372,18 @@ void Game::walk(Otc::Direction direction)
|
||||||
forceWalk(direction);
|
forceWalk(direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::autoWalk(const std::vector<Otc::Direction>& dir)
|
void Game::autoWalk(const std::vector<Otc::Direction>& dirs)
|
||||||
{
|
{
|
||||||
if(!canPerformGameAction())
|
if(!canPerformGameAction())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if(dirs.size() >= 255)
|
||||||
|
return;
|
||||||
|
|
||||||
if(isFollowing())
|
if(isFollowing())
|
||||||
cancelFollow();
|
cancelFollow();
|
||||||
|
|
||||||
m_protocolGame->sendAutoWalk(dir);
|
m_protocolGame->sendAutoWalk(dirs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::forceWalk(Otc::Direction direction)
|
void Game::forceWalk(Otc::Direction direction)
|
||||||
|
|
|
@ -123,7 +123,7 @@ public:
|
||||||
|
|
||||||
// walk related
|
// walk related
|
||||||
void walk(Otc::Direction direction);
|
void walk(Otc::Direction direction);
|
||||||
void autoWalk(const std::vector<Otc::Direction>& dir);
|
void autoWalk(const std::vector<Otc::Direction>& dirs);
|
||||||
void forceWalk(Otc::Direction direction);
|
void forceWalk(Otc::Direction direction);
|
||||||
void turn(Otc::Direction direction);
|
void turn(Otc::Direction direction);
|
||||||
void stop();
|
void stop();
|
||||||
|
|
|
@ -411,3 +411,111 @@ int Map::getLastAwareFloor()
|
||||||
else
|
else
|
||||||
return Otc::SEA_FLOOR;
|
return Otc::SEA_FLOOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pathfinding using A* search algorithm
|
||||||
|
// as described in http://en.wikipedia.org/wiki/A*_search_algorithm
|
||||||
|
std::vector<Otc::Direction> 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<Node*, Node*, bool> {
|
||||||
|
bool operator()(Node* a, Node* b) const {
|
||||||
|
return b->totalCost < a->totalCost;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Otc::Direction> 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<Position, Node*, PositionHasher> nodes;
|
||||||
|
std::priority_queue<Node*, std::vector<Node*>, 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;
|
||||||
|
}
|
||||||
|
|
|
@ -76,6 +76,8 @@ public:
|
||||||
std::vector<AnimatedTextPtr> getAnimatedTexts() { return m_animatedTexts; }
|
std::vector<AnimatedTextPtr> getAnimatedTexts() { return m_animatedTexts; }
|
||||||
std::vector<StaticTextPtr> getStaticTexts() { return m_staticTexts; }
|
std::vector<StaticTextPtr> getStaticTexts() { return m_staticTexts; }
|
||||||
|
|
||||||
|
std::vector<Otc::Direction> findPath(const Position& start, const Position& goal, int maxSteps);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<Position, TilePtr, PositionHasher> m_tiles;
|
std::unordered_map<Position, TilePtr, PositionHasher> m_tiles;
|
||||||
std::map<uint32, CreaturePtr> m_knownCreatures;
|
std::map<uint32, CreaturePtr> m_knownCreatures;
|
||||||
|
|
|
@ -425,6 +425,7 @@ void MapView::setVisibleDimension(const Size& visibleDimension)
|
||||||
}
|
}
|
||||||
|
|
||||||
Point virtualCenterOffset = (drawDimension/2 - Size(1,1)).toPoint();
|
Point virtualCenterOffset = (drawDimension/2 - Size(1,1)).toPoint();
|
||||||
|
Point visibleCenterOffset = virtualCenterOffset;
|
||||||
|
|
||||||
ViewRange viewRange;
|
ViewRange viewRange;
|
||||||
if(tileSize >= 32 && visibleDimension.area() <= NEAR_VIEW_AREA)
|
if(tileSize >= 32 && visibleDimension.area() <= NEAR_VIEW_AREA)
|
||||||
|
@ -446,6 +447,7 @@ void MapView::setVisibleDimension(const Size& visibleDimension)
|
||||||
bool mustUpdate = (m_drawDimension != drawDimension ||
|
bool mustUpdate = (m_drawDimension != drawDimension ||
|
||||||
m_viewRange != viewRange ||
|
m_viewRange != viewRange ||
|
||||||
m_virtualCenterOffset != virtualCenterOffset ||
|
m_virtualCenterOffset != virtualCenterOffset ||
|
||||||
|
m_visibleCenterOffset != visibleCenterOffset ||
|
||||||
m_tileSize != tileSize);
|
m_tileSize != tileSize);
|
||||||
|
|
||||||
m_visibleDimension = visibleDimension;
|
m_visibleDimension = visibleDimension;
|
||||||
|
@ -453,6 +455,7 @@ void MapView::setVisibleDimension(const Size& visibleDimension)
|
||||||
m_tileSize = tileSize;
|
m_tileSize = tileSize;
|
||||||
m_viewRange = viewRange;
|
m_viewRange = viewRange;
|
||||||
m_virtualCenterOffset = virtualCenterOffset;
|
m_virtualCenterOffset = virtualCenterOffset;
|
||||||
|
m_visibleCenterOffset = visibleCenterOffset;
|
||||||
|
|
||||||
if(mustUpdate)
|
if(mustUpdate)
|
||||||
requestVisibleTilesCacheUpdate();
|
requestVisibleTilesCacheUpdate();
|
||||||
|
@ -550,7 +553,7 @@ TilePtr MapView::getTile(const Point& mousePos, const Rect& mapRect)
|
||||||
tilePos2D += m_followingCreature->getWalkOffset() * scaleFactor;
|
tilePos2D += m_followingCreature->getWalkOffset() * scaleFactor;
|
||||||
tilePos2D /= m_tileSize;
|
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())
|
if(!tilePos.isValid())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
|
|
|
@ -100,6 +100,7 @@ private:
|
||||||
Size m_drawDimension;
|
Size m_drawDimension;
|
||||||
Size m_visibleDimension;
|
Size m_visibleDimension;
|
||||||
Point m_virtualCenterOffset;
|
Point m_virtualCenterOffset;
|
||||||
|
Point m_visibleCenterOffset;
|
||||||
Position m_customCameraPosition;
|
Position m_customCameraPosition;
|
||||||
Boolean<true> m_mustUpdateVisibleTilesCache;
|
Boolean<true> m_mustUpdateVisibleTilesCache;
|
||||||
Boolean<true> m_mustDrawVisibleTilesCache;
|
Boolean<true> m_mustDrawVisibleTilesCache;
|
||||||
|
|
|
@ -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", "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", "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", "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.registerStaticClass("g_game");
|
||||||
g_lua.bindClassStaticFunction("g_game", "loginWorld", std::bind(&Game::loginWorld, &g_game, _1, _2, _3, _4, _5));
|
g_lua.bindClassStaticFunction("g_game", "loginWorld", std::bind(&Game::loginWorld, &g_game, _1, _2, _3, _4, _5));
|
||||||
|
|
|
@ -151,6 +151,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isValid() const { return !(x == 65535 && y == 65535 && z == 255); }
|
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; }
|
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; }
|
Position translated(int dx, int dy, short dz = 0) const { Position pos = *this; pos.x += dx; pos.y += dy; pos.z += dz; return pos; }
|
||||||
|
|
Loading…
Reference in New Issue