void Game::autoWalk(const std::vector<Otc::Direction>& dirs) { if(!canPerformGameAction()) return; // protocol limits walk path up to 255 directions if(dirs.size() > 127) { g_logger.error("Auto walk path too great, the maximum number of directions is 127"); return; } if(dirs.size() == 0) return; // must cancel follow before any new walk if(isFollowing()) cancelFollow(); Otc::Direction direction = dirs.front(); if(m_localPlayer->canWalk(direction)) { TilePtr toTile = g_map.getTile(m_localPlayer->getPosition().translatedToDirection(direction)); if(toTile && toTile->isWalkable() && !m_localPlayer->isAutoWalking()) m_localPlayer->preWalk(direction); } m_protocolGame->sendAutoWalk(dirs); g_lua.callGlobalField("g_game", "onAutoWalk", dirs); }
void Game::autoWalk(std::vector<Otc::Direction> dirs) { if(!canPerformGameAction()) return; // protocol limits walk path up to 255 directions if(dirs.size() > 127) { g_logger.error("Auto walk path too great, the maximum number of directions is 127"); return; } if(dirs.size() == 0) return; // must cancel follow before any new walk if(isFollowing()) cancelFollow(); auto it = dirs.begin(); Otc::Direction direction = *it; if(!m_localPlayer->canWalk(direction)) return; TilePtr toTile = g_map.getTile(m_localPlayer->getPosition().translatedToDirection(direction)); if(toTile && toTile->isWalkable() && !m_localPlayer->isServerWalking()) { m_localPlayer->preWalk(direction); if(getFeature(Otc::GameForceFirstAutoWalkStep)) { forceWalk(direction); dirs.erase(it); } } g_lua.callGlobalField("g_game", "onAutoWalk", dirs); m_protocolGame->sendAutoWalk(dirs); }
void Game::walk(Otc::Direction direction) { if(!canPerformGameAction()) return; // must cancel follow before any new walk if(isFollowing()) cancelFollow(); // msut cancel auto walking and wait next try if(m_localPlayer->isAutoWalking()) { m_protocolGame->sendStop(); return; } if(!m_localPlayer->canWalk(direction)) return; Position toPos = m_localPlayer->getPosition().translatedToDirection(direction); TilePtr toTile = g_map.getTile(toPos); // only do prewalks to walkable tiles (like grounds and not walls) if(toTile && toTile->isWalkable()) m_localPlayer->preWalk(direction); // check walk to another floor (e.g: when above 3 parcels) else { // check if can walk to a lower floor auto canChangeFloorDown = [&]() -> bool { Position pos = toPos; if(!pos.down()) return false; TilePtr toTile = g_map.getTile(pos); if(toTile && toTile->hasElevation(3)) return true; return false; }; // check if can walk to a higher floor auto canChangeFloorUp = [&]() -> bool { TilePtr fromTile = m_localPlayer->getTile(); if(!fromTile || !fromTile->hasElevation(3)) return false; Position pos = toPos; if(!pos.up()) return false; TilePtr toTile = g_map.getTile(pos); if(!toTile || !toTile->isWalkable()) return false; return true; }; if(canChangeFloorDown() || canChangeFloorUp() || (!toTile || toTile->isEmpty())) { m_localPlayer->lockWalk(); } else return; } forceWalk(direction); g_lua.callGlobalField("g_game", "onWalk", direction); }
bool Game::walk(Otc::Direction direction, bool dash) { if(!canPerformGameAction()) return false; // must cancel follow before any new walk if(isFollowing()) cancelFollow(); // must cancel auto walking, and wait next try if(m_localPlayer->isAutoWalking() || m_localPlayer->isServerWalking()) { m_protocolGame->sendStop(); if(m_localPlayer->isAutoWalking()) m_localPlayer->stopAutoWalk(); return false; } if(dash) { if(m_localPlayer->isWalking() && m_dashTimer.ticksElapsed() < std::max<int>(m_localPlayer->getStepDuration(false, direction) - m_ping, 30)) return false; } else { // check we can walk and add new walk event if false if(!m_localPlayer->canWalk(direction)) { if(m_lastWalkDir != direction) { // must add a new walk event float ticks = m_localPlayer->getStepTicksLeft(); if(ticks <= 0) { ticks = 1; } if(m_walkEvent) { m_walkEvent->cancel(); m_walkEvent = nullptr; } m_walkEvent = g_dispatcher.scheduleEvent([=] { walk(direction, false); }, ticks); } return false; } } Position toPos = m_localPlayer->getPosition().translatedToDirection(direction); TilePtr toTile = g_map.getTile(toPos); // only do prewalks to walkable tiles (like grounds and not walls) if(toTile && toTile->isWalkable()) { m_localPlayer->preWalk(direction); // check walk to another floor (e.g: when above 3 parcels) } else { // check if can walk to a lower floor auto canChangeFloorDown = [&]() -> bool { Position pos = toPos; if(!pos.down()) return false; TilePtr toTile = g_map.getTile(pos); if(toTile && toTile->hasElevation(3)) return true; return false; }; // check if can walk to a higher floor auto canChangeFloorUp = [&]() -> bool { TilePtr fromTile = m_localPlayer->getTile(); if(!fromTile || !fromTile->hasElevation(3)) return false; Position pos = toPos; if(!pos.up()) return false; TilePtr toTile = g_map.getTile(pos); if(!toTile || !toTile->isWalkable()) return false; return true; }; if(canChangeFloorDown() || canChangeFloorUp() || (!toTile || toTile->isEmpty())) { m_localPlayer->lockWalk(); } else return false; } m_localPlayer->stopAutoWalk(); g_lua.callGlobalField("g_game", "onWalk", direction, dash); forceWalk(direction); if(dash) m_dashTimer.restart(); m_lastWalkDir = direction; return true; }
std::tuple<std::vector<Otc::Direction>, Otc::PathFindResult> Map::findPath(const Position& startPos, const Position& goalPos, int maxComplexity, int flags) { // pathfinding using A* search algorithm // as described in http://en.wikipedia.org/wiki/A*_search_algorithm struct Node { Node(const Position& pos) : cost(0), totalCost(0), pos(pos), prev(nullptr), dir(Otc::InvalidDirection) { } float cost; float totalCost; Position pos; Node *prev; Otc::Direction dir; }; struct LessNode : std::binary_function<std::pair<Node*, float>, std::pair<Node*, float>, bool> { bool operator()(std::pair<Node*, float> a, std::pair<Node*, float> b) const { return b.second < a.second; } }; std::tuple<std::vector<Otc::Direction>, Otc::PathFindResult> ret; std::vector<Otc::Direction>& dirs = std::get<0>(ret); Otc::PathFindResult& result = std::get<1>(ret); result = Otc::PathFindResultNoWay; if(startPos == goalPos) { result = Otc::PathFindResultSamePosition; return ret; } if(startPos.z != goalPos.z) { result = Otc::PathFindResultImpossible; return ret; } // check the goal pos is walkable if(g_map.isAwareOfPosition(goalPos)) { const TilePtr goalTile = getTile(goalPos); if(!goalTile || !goalTile->isWalkable()) { return ret; } } else { const MinimapTile& goalTile = g_minimap.getTile(goalPos); if(goalTile.hasFlag(MinimapTileNotWalkable)) { return ret; } } std::unordered_map<Position, Node*, PositionHasher> nodes; std::priority_queue<std::pair<Node*, float>, std::vector<std::pair<Node*, float>>, LessNode> searchList; Node *currentNode = new Node(startPos); currentNode->pos = startPos; nodes[startPos] = currentNode; Node *foundNode = nullptr; while(currentNode) { if((int)nodes.size() > maxComplexity) { result = Otc::PathFindResultTooFar; break; } // path found if(currentNode->pos == goalPos && (!foundNode || currentNode->cost < foundNode->cost)) foundNode = currentNode; // cost too high 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; bool wasSeen = false; bool hasCreature = false; bool isNotWalkable = true; bool isNotPathable = true; int speed = 100; Position neighborPos = currentNode->pos.translated(i, j); if(g_map.isAwareOfPosition(neighborPos)) { wasSeen = true; if(const TilePtr& tile = getTile(neighborPos)) { hasCreature = tile->hasCreature(); isNotWalkable = !tile->isWalkable(); isNotPathable = !tile->isPathable(); speed = tile->getGroundSpeed(); } } else { const MinimapTile& mtile = g_minimap.getTile(neighborPos); wasSeen = mtile.hasFlag(MinimapTileWasSeen); isNotWalkable = mtile.hasFlag(MinimapTileNotWalkable); isNotPathable = mtile.hasFlag(MinimapTileNotPathable); if(isNotWalkable || isNotPathable) wasSeen = true; speed = mtile.getSpeed(); } float walkFactor = 0; if(neighborPos != goalPos) { if(!(flags & Otc::PathFindAllowNotSeenTiles) && !wasSeen) continue; if(wasSeen) { if(!(flags & Otc::PathFindAllowCreatures) && hasCreature) continue; if(!(flags & Otc::PathFindAllowNonPathable) && isNotPathable) continue; if(!(flags & Otc::PathFindAllowNonWalkable) && isNotWalkable) continue; } } else { if(!(flags & Otc::PathFindAllowNotSeenTiles) && !wasSeen) continue; if(wasSeen) { if(!(flags & Otc::PathFindAllowNonWalkable) && isNotWalkable) continue; } } Otc::Direction walkDir = currentNode->pos.getDirectionFromPosition(neighborPos); if(walkDir >= Otc::NorthEast) walkFactor += 3.0f; else walkFactor += 1.0f; float cost = currentNode->cost + (speed * 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->totalCost = neighborNode->cost + neighborPos.distance(goalPos); neighborNode->dir = walkDir; searchList.push(std::make_pair(neighborNode, neighborNode->totalCost)); } } if(!searchList.empty()) { currentNode = searchList.top().first; searchList.pop(); } else currentNode = nullptr; } if(foundNode) { currentNode = foundNode; while(currentNode) { dirs.push_back(currentNode->dir); currentNode = currentNode->prev; } dirs.pop_back(); std::reverse(dirs.begin(), dirs.end()); result = Otc::PathFindResultOk; } for(auto it : nodes) delete it.second; return ret; }
bool Game::dashWalk(Otc::Direction direction) { if(!canPerformGameAction()) return false; // must cancel follow before any new walk if(isFollowing()) cancelFollow(); // must cancel auto walking if(m_localPlayer->isAutoWalking()) { m_protocolGame->sendStop(); m_localPlayer->stopAutoWalk(); } if(m_localPlayer->isWalking() && m_dashTimer.ticksElapsed() < std::max<int>(m_localPlayer->getStepDuration(false, direction) - m_ping, 30)) return false; Position toPos = m_localPlayer->getPosition().translatedToDirection(direction); TilePtr toTile = g_map.getTile(toPos); // only do prewalks to walkable tiles (like grounds and not walls) if(toTile && toTile->isWalkable()) { if(!m_localPlayer->isWalking() && m_localPlayer->getWalkTicksElapsed() >= m_localPlayer->getStepDuration() + 100) m_localPlayer->preWalk(direction); // check walk to another floor (e.g: when above 3 parcels) } else { // check if can walk to a lower floor auto canChangeFloorDown = [&]() -> bool { Position pos = toPos; if(!pos.down()) return false; TilePtr toTile = g_map.getTile(pos); if(toTile && toTile->hasElevation(3)) return true; return false; }; // check if can walk to a higher floor auto canChangeFloorUp = [&]() -> bool { TilePtr fromTile = m_localPlayer->getTile(); if(!fromTile || !fromTile->hasElevation(3)) return false; Position pos = toPos; if(!pos.up()) return false; TilePtr toTile = g_map.getTile(pos); if(!toTile || !toTile->isWalkable()) return false; return true; }; if(canChangeFloorDown() || canChangeFloorUp() || (!toTile || toTile->isEmpty())) { m_localPlayer->lockWalk(); } else return false; } forceWalk(direction); m_dashTimer.restart(); m_lastWalkDir = direction; return true; }