/******************************************************************************* * Copyright 2015 Maximilian Stark | Dakror <mail@dakror.de> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package de.dakror.vloxlands.ai.path; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.math.collision.BoundingBox; import com.badlogic.gdx.utils.Array; import de.dakror.vloxlands.ai.job.WalkJob; import de.dakror.vloxlands.ai.path.node.AStarNode; import de.dakror.vloxlands.game.entity.Entity; import de.dakror.vloxlands.game.entity.creature.Creature; import de.dakror.vloxlands.game.entity.creature.Human; import de.dakror.vloxlands.game.entity.statics.StaticEntity; import de.dakror.vloxlands.game.voxel.Voxel; /** * @author Dakror */ public class AStar { static final Comparator<AStarNode> COMPARATOR = new Comparator<AStarNode>() { @Override public int compare(AStarNode o1, AStarNode o2) { return Float.compare(o1.F, o2.F); } }; static ArrayList<AStarNode> openList = new ArrayList<AStarNode>(); static ArrayList<AStarNode> closedList = new ArrayList<AStarNode>(); static AStarNode target; static Vector3 neighbor; static int targetedOthers; static boolean takeNeighbor; // because other human goes to target already public synchronized static Path findPath(Vector3 from, Vector3 to, Creature c, boolean useGhostTarget) { return findPath(from, to, c, 0, useGhostTarget); } public synchronized static Path findPath(Vector3 from, Vector3 to, Creature c, float maxRange, boolean useGhostTarget) { if (from == null || to == null) return null; if (maxRange == 0) maxRange = Math.max(from.dst(to), 1) * 5; if (!c.getIsland().isSpaceAbove(to.x, to.y, to.z, c.getHeight()) && !useGhostTarget) return null; takeNeighbor = false; targetedOthers = 0; for (Entity e : c.getIsland().getEntities()) { if (e instanceof Human && e != c) { if (((Human) e).firstJob() instanceof WalkJob || ((Human) e).path != null) { Path p = (WalkJob) ((Human) e).firstJob() instanceof WalkJob ? ((WalkJob) (((Human) e).firstJob())).getPath() : ((Human) e).path; if ((p.realTarget != null && p.realTarget.equals(to)) || p.getLast().equals(to)) { takeNeighbor = true; targetedOthers++; } } } } openList.clear(); closedList.clear(); target = null; neighbor = null; openList.add(new AStarNode(from.x, from.y, from.z, 0, from.dst(to), null)); if (useGhostTarget || takeNeighbor) { if (from.equals(to)) target = openList.get(0); else target = new AStarNode(to.x, to.y, to.z, 1, 0, null); } AStarNode selected = null; AStarNode ghostNode = null; while (true) { if (openList.size() == 0) return null; // no way Collections.sort(openList, COMPARATOR); selected = openList.get(0); openList.remove(0); while (selected.cantBeNeighborForGhostTarget && openList.size() > 0) { selected = openList.get(0); openList.remove(0); } closedList.add(selected); if (selected.H == 0 && !useGhostTarget) break; if ((ghostNode = addNeighbors(selected, from, to, c, maxRange, useGhostTarget)) != null) break; } Array<Vector3> v = new Array<Vector3>(); while (selected != null) { v.add(new Vector3(selected.x, selected.y, selected.z)); selected = (AStarNode) selected.parent; } v.reverse(); Vector3 firstNode = v.removeIndex(0); // remove start vector if (neighbor != null && !takeNeighbor) v.add(neighbor); Path p = new Path(v); p.removedFirstNode = firstNode; if (takeNeighbor) p.realTarget = to; if (ghostNode != null && useGhostTarget) p.setGhostTarget(new Vector3(ghostNode.x, ghostNode.y, ghostNode.z)); return p; } public static AStarNode addNeighbors(AStarNode selected, Vector3 from, Vector3 to, Creature c, float maxRange, boolean useGhostTarget) { int height = c.getHeight(); byte air = Voxel.get("AIR").getId(); final Vector3 v = new Vector3(); final BoundingBox b = new BoundingBox(); final BoundingBox b2 = new BoundingBox(); final float malus = 0.01f; for (int x = -1; x < 2; x++) { for (int z = -1; z < 2; z++) { for (int y = -1; y < 3; y++) { if (x != 0 && z != 0 && y != 0) continue; if (x == 0 && z == 0 && y == 0) continue; v.set(selected.x + x, selected.y + y, selected.z + z); if (!c.getIsland().isTargetable(v.x, v.y, v.z)) continue; if (c.getIsland().get(v.x, v.y, v.z) == air && !c.canFly()) continue; if (from.dst(v) > maxRange || to.dst(v) > maxRange) continue; float g = c.getIsland().get(v.x, v.y, v.z) == air ? 1.5f : 1; AStarNode node = new AStarNode(v.x, v.y, v.z, selected.G + g * v.dst(selected.x, selected.y, selected.z), v.dst(to), selected); if (closedList.contains(node)) continue; int index = openList.indexOf(node); boolean ctn = openList.contains(node); if (ctn && openList.get(index).G > node.G) { openList.get(index).G = node.G; openList.get(index).parent = selected; } else if (!ctn) { boolean free = true; if (!c.getIsland().isSpaceAbove(v.x, v.y, v.z, height)) free = false; if (x != 0 && z != 0 && free) { if (!c.getIsland().isSpaceAbove(selected.x, v.y, v.z, height)) free = false; else if (!c.getIsland().isSpaceAbove(v.x, v.y, selected.z, height)) free = false; } if (y != 0) { if (y < 0 && !c.getIsland().isSpaceAbove(v.x, v.y, v.z, height + 1)) free = false; else if (y > 0 && !c.getIsland().isSpaceAbove(selected.x, selected.y, selected.z, height + 1)) free = false; } if (free) { for (Entity e : c.getIsland().getEntities()) { if (e instanceof Human && e != c) { if (((Human) e).firstJob() instanceof WalkJob || ((Human) e).path != null) { Path p = (WalkJob) ((Human) e).firstJob() instanceof WalkJob ? ((WalkJob) (((Human) e).firstJob())).getPath() : ((Human) e).path; if (p.getLast().equals(v) || (p.ghostTarget != null && p.ghostTarget.equals(v))) { free = false; break; } } } else if (e instanceof StaticEntity) { e.getWorldBoundingBox(b); b2.min.set(v).add(c.getIsland().pos).add(malus, 1, malus); b2.max.set(b2.min).add(1 - 2 * malus, height, 1 - 2 * malus); b2.set(b2.min, b2.max); if (b.intersects(b2)) { free = false; break; } if (x != 0 && z != 0 && free) { b2.min.set(selected.x, v.y, v.z).add(c.getIsland().pos).add(malus, 1, malus); b2.max.set(b2.min).add(1 - 2 * malus, height, 1 - 2 * malus); b2.set(b2.min, b2.max); if (b.intersects(b2)) { free = false; break; } b2.min.set(v.x, v.y, selected.z).add(c.getIsland().pos).add(malus, 1, malus); b2.max.set(b2.min).add(1 - 2 * malus, height, 1 - 2 * malus); b2.set(b2.min, b2.max); if (b.intersects(b2)) { free = false; break; } } } } } if (useGhostTarget || takeNeighbor) { int cd = chebyshevDistance(to, v) + 1; boolean ringFull = targetedOthers % 9 == 0; boolean takeThisAsNeighbor = takeNeighbor && (cd == Math.ceil(targetedOthers / 9f) || (ringFull && cd == Math.ceil(targetedOthers / 9f) + 1)); boolean targetable = v.equals(to) || (from.equals(to) && v.dst(to) < Math.sqrt(3) && free) || takeThisAsNeighbor; boolean close = true; if (x == 0 && z == 0) close = false; if (targetable && close) { if (!v.equals(to)) neighbor = v; return v.equals(to) || takeThisAsNeighbor ? node : target; } else if (targetable) node.cantBeNeighborForGhostTarget = true; } if (free && y < 2) openList.add(node); } } } } return null; } public static int chebyshevDistance(Vector3 o1, Vector3 o2) { return (int) Math.max(Math.abs(o1.x - o2.x), Math.abs(o1.z - o2.z)); } }