/*
* Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
* Copyright (C) 2021 cyoung06
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package kr.syeyoung.dungeonsguide.pathfinding;
import kr.syeyoung.dungeonsguide.dungeon.roomfinder.DungeonRoom;
import lombok.*;
import net.minecraft.util.*;
import net.minecraft.world.World;
import java.util.*;
public class ThetaStar {
private final BlockPos min, max;
private final World world;
int lastSx, lastSy, lastSz;
final int dx, dy, dz;
private DungeonRoom dungeonRoom;
@Getter
private AxisAlignedBB destinationBB;
public ThetaStar(DungeonRoom dungeonRoom, Vec3 destination) {
this.min = new BlockPos(dungeonRoom.getMinx(), 0, dungeonRoom.getMinz());
this.max = new BlockPos(dungeonRoom.getMaxx(), 255, dungeonRoom.getMaxz());
this.world = dungeonRoom.getCachedWorld();
this.dungeonRoom = dungeonRoom;
this.dx = (int) (destination.xCoord * 2);
this.dy = (int) (destination.yCoord * 2);
this.dz = (int) (destination.zCoord * 2);
destinationBB = AxisAlignedBB.fromBounds(dx-2, dy-2, dz-2, dx+2, dy+2, dz+2);
}
private Map nodeMap = new HashMap<>();
private Node openNode(int x, int y, int z)
{
Node.Coordinate coordinate = new Node.Coordinate(x,y,z);
Node node = this.nodeMap.get(coordinate);
if (node == null)
{
node = new Node(coordinate);
this.nodeMap.put(coordinate, node);
}
return node;
}
@Getter
private LinkedList route = new LinkedList<>();
@Getter
private PriorityQueue open = new PriorityQueue<>(Comparator.comparing((Node a) -> a == null ? Float.MAX_VALUE : a.f).thenComparing(a -> a == null ? Float.MAX_VALUE : a.coordinate.x).thenComparing(a -> a == null ? Float.MAX_VALUE : a.coordinate.y).thenComparing(a -> a == null ? Float.MAX_VALUE : a.coordinate.z));
private int pfindIdx = 0;
public boolean pathfind(Vec3 from, long timeout) {
pfindIdx ++;
if (lastSx != (int)Math.round(from.xCoord * 2) || lastSy != (int)Math.round(from.yCoord*2) || lastSz != (int)Math.round(from.zCoord * 2))
open.clear();
this.lastSx = (int) Math.round(from.xCoord * 2);
this.lastSy = (int) Math.round(from.yCoord * 2);
this.lastSz = (int) Math.round(from.zCoord * 2);
if (dungeonRoom.isBlocked(lastSx, lastSy, lastSz)) return false;
Node startNode = openNode(dx, dy, dz);
Node goalNode = openNode(lastSx, lastSy, lastSz);
startNode.g = 0;
startNode.f = 0;
goalNode.g = Integer.MAX_VALUE; goalNode.f = Integer.MAX_VALUE;
if (goalNode.parent != null) {
LinkedList route = new LinkedList<>();
Node curr =goalNode;
while(curr.parent != null) {
route.addLast(new Vec3(curr.coordinate.x / 2.0, curr.coordinate.y / 2.0 + 0.1, curr.coordinate.z/ 2.0));
curr = curr.parent;
}
route.addLast(new Vec3(curr.coordinate.x / 2.0, curr.coordinate.y / 2.0 + 0.1, curr.coordinate.z/ 2.0));
this.route = route;
return true;
}
open.add(startNode);
long end = System.currentTimeMillis() + timeout;
while (!open.isEmpty()) {
if (System.currentTimeMillis() > end) {
return false;
}
Node n = open.poll();
if (n.lastVisited == pfindIdx) continue;
n.lastVisited = pfindIdx;
if (n == goalNode) {
// route = reconstructPath(startNode)
LinkedList route = new LinkedList<>();
Node curr =goalNode;
while(curr.parent != null) {
route.addLast(new Vec3(curr.coordinate.x / 2.0, curr.coordinate.y / 2.0 + 0.1, curr.coordinate.z/ 2.0));
curr = curr.parent;
}
route.addLast(new Vec3(curr.coordinate.x / 2.0, curr.coordinate.y / 2.0 + 0.1, curr.coordinate.z/ 2.0));
this.route = route;
return true;
}
for (EnumFacing value : EnumFacing.VALUES) {
Node neighbor = openNode(n.coordinate.x + value.getFrontOffsetX(), n.coordinate.y + value.getFrontOffsetY(), n.coordinate.z + value.getFrontOffsetZ());
// check blocked.
if (!((destinationBB.minX <= neighbor.coordinate.x && neighbor.coordinate.x <= destinationBB.maxX &&
destinationBB.minY <= neighbor.coordinate.y && neighbor.coordinate.y <= destinationBB.maxY &&
destinationBB.minZ <= neighbor.coordinate.z && neighbor.coordinate.z <= destinationBB.maxZ) // near destination
|| !dungeonRoom.isBlocked(neighbor.coordinate.x, neighbor.coordinate.y, neighbor.coordinate.z))) { // not blocked
continue;
}
if (neighbor.lastVisited == pfindIdx) continue;
boolean flag = false;
if (n. parent != null) {
float tempGScore = n.parent.g + distSq(n.parent.coordinate.x - neighbor.coordinate.x, n.parent.coordinate.y - neighbor.coordinate.y, n.parent.coordinate.z - neighbor.coordinate.z);
if (tempGScore < neighbor.g && lineofsight(n.parent, neighbor)) {
neighbor.parent = n.parent;
neighbor.g = tempGScore;
neighbor.f = tempGScore + distSq(goalNode.coordinate.x - neighbor.coordinate.x, goalNode.coordinate.y - neighbor.coordinate.y, goalNode.coordinate.z - neighbor.coordinate.z);
open.add(neighbor);
flag = true;
}
}
if (!flag) {
float gScore = n.g + 1; // altho it's sq, it should be fine
if (gScore < neighbor.g) {
neighbor.parent = n;
neighbor.g = gScore;
neighbor.f = gScore +distSq(goalNode.coordinate.x - neighbor.coordinate.x, goalNode.coordinate.y - neighbor.coordinate.y, goalNode.coordinate.z - neighbor.coordinate.z);
open.add(neighbor);
} else if (neighbor.lastVisited != pfindIdx) {
neighbor.f = neighbor.g + distSq(goalNode.coordinate.x - neighbor.coordinate.x, goalNode.coordinate.y - neighbor.coordinate.y, goalNode.coordinate.z - neighbor.coordinate.z);
open.add(neighbor);
}
}
}
}
return true;
}
private boolean lineofsight(Node a, Node b) {
if (a == null || b == null) return false;
float sx = a.coordinate.x, sy = a.coordinate.y, sz = a.coordinate.z;
int ex = b.coordinate.x, ey = b.coordinate.y, ez = b.coordinate.z;
float dx = ex - sx, dy = ey - sy, dz = ez - sz;
float len = distSq(dx, dy, dz);
dx /= len; dy /= len; dz /= len;
for (int d = 0; d <= len; d += 1) {
if (dungeonRoom.isBlocked(Math.round(sx), (int) Math.ceil(sy), Math.round(sz))) return false;
if (dungeonRoom.isBlocked(Math.round(sx)+1, (int) Math.ceil(sy), Math.round(sz)+1)) return false;
if (dungeonRoom.isBlocked(Math.round(sx)-1, (int) Math.ceil(sy), Math.round(sz)-1)) return false;
if (dungeonRoom.isBlocked(Math.round(sx)+1, (int) Math.ceil(sy), Math.round(sz)-1)) return false;
if (dungeonRoom.isBlocked(Math.round(sx)-1, (int) Math.ceil(sy), Math.round(sz)+1)) return false;
sx += dx; sy += dy; sz += dz;
}
return true;
}
private int manhatten(int x, int y, int z) {return Math.abs(x)+ Math.abs(y)+ Math.abs(z);}
private float distSq(float x, float y, float z) {
return MathHelper.sqrt_float(x * x + y * y + z * z);
}
@RequiredArgsConstructor
@Data
public static final class Node {
@Data
@RequiredArgsConstructor
public static final class Coordinate {
private final int x, y, z;
}
private final Coordinate coordinate;
private float f = Float.MAX_VALUE, g = Float.MAX_VALUE;
private int lastVisited;
@EqualsAndHashCode.Exclude
@ToString.Exclude
private Node parent;
}
}