real autowalking

* smart autowalking using A* path finding algorithm
This commit is contained in:
Eduardo Bart 2012-03-23 17:36:58 -03:00
parent 8bc63e25df
commit fe86dc8050
9 changed files with 126 additions and 27 deletions

View File

@ -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

View File

@ -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)

View File

@ -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();

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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));

View File

@ -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; }