#include "stdafx.h" #include "net.minecraft.world.entity.h" #include "net.minecraft.world.level.h" #include "net.minecraft.world.level.material.h" #include "net.minecraft.world.level.tile.h" #include "net.minecraft.world.phys.h" #include "BinaryHeap.h" #include "Node.h" #include "Path.h" #include "PathFinder.h" PathFinder::PathFinder(LevelSource *level, bool canPassDoors, bool canOpenDoors, bool avoidWater, bool canFloat) { neighbors = new NodeArray(32); this->canPassDoors = canPassDoors; this->canOpenDoors = canOpenDoors; this->avoidWater = avoidWater; this->canFloat = canFloat; this->level = level; } PathFinder::~PathFinder() { // All the nodes should be uniquely referenced in the nodes map, and everything else should just be duplicate // references to the same things, so just need to destroy their containers delete [] neighbors->data; delete neighbors; AUTO_VAR(itEnd, nodes.end()); for( AUTO_VAR(it, nodes.begin()); it != itEnd; it++ ) { delete it->second; } } Path *PathFinder::findPath(Entity *from, Entity *to, float maxDist) { return findPath(from, to->x, to->bb->y0, to->z, maxDist); } Path *PathFinder::findPath(Entity *from, int x, int y, int z, float maxDist) { return findPath(from, x + 0.5f, y + 0.5f, z + 0.5f, maxDist); } Path *PathFinder::findPath(Entity *e, double xt, double yt, double zt, float maxDist) { openSet.clear(); nodes.clear(); bool resetAvoidWater = avoidWater; int startY = Mth::floor(e->bb->y0 + 0.5f); if (canFloat && e->isInWater()) { startY = (int) (e->bb->y0); int tileId = level->getTile((int) Mth::floor(e->x), startY, (int) Mth::floor(e->z)); while (tileId == Tile::water_Id || tileId == Tile::calmWater_Id) { ++startY; tileId = level->getTile((int) Mth::floor(e->x), startY, (int) Mth::floor(e->z)); } resetAvoidWater = avoidWater; avoidWater = false; } else startY = Mth::floor(e->bb->y0 + 0.5f); Node *from = getNode((int) floor(e->bb->x0), startY, (int) floor(e->bb->z0)); Node *to = getNode((int) floor(xt - e->bbWidth / 2), (int) floor(yt), (int) floor(zt - e->bbWidth / 2)); Node *size = new Node((int) floor(e->bbWidth + 1), (int) floor(e->bbHeight + 1), (int) floor(e->bbWidth + 1)); Path *path = findPath(e, from, to, size, maxDist); delete size; avoidWater = resetAvoidWater; return path; } // function A*(start,goal) Path *PathFinder::findPath(Entity *e, Node *from, Node *to, Node *size, float maxDist) { from->g = 0; from->h = from->distanceToSqr(to); from->f = from->h; openSet.clear(); openSet.insert(from); Node *closest = from; while (!openSet.isEmpty()) { Node *x = openSet.pop(); if (x->equals(to)) { return reconstruct_path(from, to); } if (x->distanceToSqr(to) < closest->distanceToSqr(to)) { closest = x; } x->closed = true; int neighborCount = getNeighbors(e, x, size, to, maxDist); for (int i = 0; i < neighborCount; i++) { Node *y = neighbors->data[i]; float tentative_g_score = x->g + x->distanceToSqr(y); if (!y->inOpenSet() || tentative_g_score < y->g) { y->cameFrom = x; y->g = tentative_g_score; y->h = y->distanceToSqr(to); if (y->inOpenSet()) { openSet.changeCost(y, y->g + y->h); } else { y->f = y->g + y->h; openSet.insert(y); } } } } if (closest == from) return NULL; return reconstruct_path(from, closest); } int PathFinder::getNeighbors(Entity *entity, Node *pos, Node *size, Node *target, float maxDist) { int p = 0; int jumpSize = 0; if (isFree(entity, pos->x, pos->y + 1, pos->z, size) == TYPE_OPEN) jumpSize = 1; Node *n = getNode(entity, pos->x, pos->y, pos->z + 1, size, jumpSize); Node *w = getNode(entity, pos->x - 1, pos->y, pos->z, size, jumpSize); Node *e = getNode(entity, pos->x + 1, pos->y, pos->z, size, jumpSize); Node *s = getNode(entity, pos->x, pos->y, pos->z - 1, size, jumpSize); if (n != NULL && !n->closed && n->distanceTo(target) < maxDist) neighbors->data[p++] = n; if (w != NULL && !w->closed && w->distanceTo(target) < maxDist) neighbors->data[p++] = w; if (e != NULL && !e->closed && e->distanceTo(target) < maxDist) neighbors->data[p++] = e; if (s != NULL && !s->closed && s->distanceTo(target) < maxDist) neighbors->data[p++] = s; return p; } Node *PathFinder::getNode(Entity *entity, int x, int y, int z, Node *size, int jumpSize) { Node *best = NULL; int pathType = isFree(entity, x, y, z, size); if (pathType == TYPE_WALKABLE) return getNode(x, y, z); if (pathType == TYPE_OPEN) best = getNode(x, y, z); if (best == NULL && jumpSize > 0 && pathType != TYPE_FENCE && pathType != TYPE_TRAP && isFree(entity, x, y + jumpSize, z, size) == TYPE_OPEN) { best = getNode(x, y + jumpSize, z); y += jumpSize; } if (best != NULL) { int drop = 0; int cost = 0; while (y > 0) { cost = isFree(entity, x, y - 1, z, size); if (avoidWater && cost == TYPE_WATER) return NULL; if (cost != TYPE_OPEN) break; // fell too far? if (++drop >= 4) return NULL; y--; if (y > 0) best = getNode(x, y, z); } // fell into lava? if (cost == TYPE_LAVA) return NULL; } return best; } /*final*/ Node *PathFinder::getNode(int x, int y, int z) { int i = Node::createHash(x, y, z); Node *node; AUTO_VAR(it, nodes.find(i)); if ( it == nodes.end() ) { MemSect(54); node = new Node(x, y, z); MemSect(0); nodes.insert( unordered_map::value_type(i, node) ); } else { node = (*it).second; } return node; } int PathFinder::isFree(Entity *entity, int x, int y, int z, Node *size) { return isFree(entity, x, y, z, size, avoidWater, canOpenDoors, canPassDoors); } int PathFinder::isFree(Entity *entity, int x, int y, int z, Node *size, bool avoidWater, bool canOpenDoors, bool canPassDoors) { bool walkable = false; for (int xx = x; xx < x + size->x; xx++) for (int yy = y; yy < y + size->y; yy++) for (int zz = z; zz < z + size->z; zz++) { int tileId = entity->level->getTile(xx, yy, zz); if(tileId <= 0) continue; if (tileId == Tile::trapdoor_Id) walkable = true; else if (tileId == Tile::water_Id || tileId == Tile::calmWater_Id) { if (avoidWater) return TYPE_WATER; else walkable = true; } else if (!canPassDoors && tileId == Tile::door_wood_Id) { return TYPE_BLOCKED; } Tile *tile = Tile::tiles[tileId]; if (tile->isPathfindable(entity->level, xx, yy, zz)) continue; if (canOpenDoors && tileId == Tile::door_wood_Id) continue; int renderShape = tile->getRenderShape(); if (renderShape == Tile::SHAPE_FENCE || tileId == Tile::fenceGate_Id || renderShape == Tile::SHAPE_WALL) return TYPE_FENCE; if (tileId == Tile::trapdoor_Id) return TYPE_TRAP; Material *m = tile->material; if (m == Material::lava) { if (entity->isInLava()) continue; return TYPE_LAVA; } return TYPE_BLOCKED; } return walkable ? TYPE_WALKABLE : TYPE_OPEN; } // function reconstruct_path(came_from,current_node) Path *PathFinder::reconstruct_path(Node *from, Node *to) { int count = 1; Node *n = to; while (n->cameFrom != NULL) { count++; n = n->cameFrom; } NodeArray nodes = NodeArray(count); n = to; nodes.data[--count] = n; while (n->cameFrom != NULL) { n = n->cameFrom; nodes.data[--count] = n; } Path *ret = new Path(nodes); delete [] nodes.data; return ret; }