diff --git a/src/engine/mobileAI/MobAI.java b/src/engine/mobileAI/MobAI.java index 1f233837..a0eb9f37 100644 --- a/src/engine/mobileAI/MobAI.java +++ b/src/engine/mobileAI/MobAI.java @@ -287,10 +287,10 @@ public class MobAI { if (mob.agentType.equals(Enum.AIAgentType.GUARDMINION)) { Mob captain = (Mob) mob.guardCaptain; - mob.destination = captain.destination.add(Formation.getOffset(2, mob.guardCaptain.minions.indexOf(mob.getObjectUUID()) + 3)); + mob.setDestination(captain.destination.add(Formation.getOffset(2, mob.guardCaptain.minions.indexOf(mob.getObjectUUID()) + 3))); mob.lastPatrolPointIndex = captain.lastPatrolPointIndex; } else { - mob.destination = mob.patrolPoints.get(mob.lastPatrolPointIndex); + mob.setDestination(mob.patrolPoints.get(mob.lastPatrolPointIndex)); mob.lastPatrolPointIndex += 1; } @@ -748,7 +748,7 @@ public class MobAI { if (CombatUtilities.inRange2D(mob, mob.guardCaptain, 6)) return; - mob.destination = mob.guardCaptain.getLoc(); + mob.setDestination(mob.guardCaptain.getLoc()); MovementUtilities.moveToLocation(mob, mob.destination, 5, false); } else chaseTarget(mob); @@ -904,7 +904,7 @@ public class MobAI { if (CombatUtilities.inRange2D(mob, mob.getCombatTarget(), mob.getRange()) == false) { if (mob.getRange() > 15) { - mob.destination = mob.getCombatTarget().getLoc(); + mob.setDestination(mob.getCombatTarget().getLoc()); MovementUtilities.moveToLocation(mob, mob.destination, 0, false); } else { @@ -913,11 +913,11 @@ public class MobAI { switch (mob.getCombatTarget().getObjectType()) { case PlayerCharacter: case Mob: - mob.destination = MovementUtilities.GetDestinationToCharacter(mob, (AbstractCharacter) mob.getCombatTarget()); + mob.setDestination(MovementUtilities.GetDestinationToCharacter(mob, (AbstractCharacter) mob.getCombatTarget())); MovementUtilities.moveToLocation(mob, mob.destination, mob.getRange() + 1, false); break; case Building: - mob.destination = mob.getCombatTarget().getLoc(); + mob.setDestination(mob.getCombatTarget().getLoc()); MovementUtilities.moveToLocation(mob, mob.getCombatTarget().getLoc(), 0, false); break; } @@ -1240,7 +1240,7 @@ public class MobAI { float xPoint = ThreadLocalRandom.current().nextInt(400) - 200; float zPoint = ThreadLocalRandom.current().nextInt(400) - 200; Vector3fImmutable TreePos = mob.getGuild().getOwnedCity().getLoc(); - mob.destination = new Vector3fImmutable(TreePos.x + xPoint, TreePos.y, TreePos.z + zPoint); + mob.setDestination(new Vector3fImmutable(TreePos.x + xPoint, TreePos.y, TreePos.z + zPoint)); MovementUtilities.aiMove(mob, mob.destination, true); diff --git a/src/engine/mobileAI/utilities/MovementUtilities.java b/src/engine/mobileAI/utilities/MovementUtilities.java index c00bfffa..e998961a 100644 --- a/src/engine/mobileAI/utilities/MovementUtilities.java +++ b/src/engine/mobileAI/utilities/MovementUtilities.java @@ -13,21 +13,27 @@ import engine.Enum; import engine.Enum.GameObjectType; import engine.Enum.ModType; import engine.Enum.SourceType; +import engine.InterestManagement.WorldGrid; import engine.exception.MsgSendException; +import engine.gameManager.BuildingManager; +import engine.gameManager.ChatManager; import engine.gameManager.MovementManager; +import engine.math.Bounds; import engine.math.Vector3fImmutable; import engine.mobileAI.Threads.MobAIThread; import engine.net.client.msg.MoveToPointMsg; import engine.objects.*; +import engine.server.MBServerStatics; import org.pmw.tinylog.Logger; +import java.util.ArrayList; import java.util.concurrent.ThreadLocalRandom; import static engine.math.FastMath.sqr; import static engine.math.FastMath.sqrt; public class MovementUtilities { - +private static final int cellGap = 4; public static boolean inRangeOfBindLocation(Mob agent) { @@ -291,5 +297,129 @@ public class MovementUtilities { return false; } + public static void pathfind(AbstractCharacter character, Vector3fImmutable goal){ + try { + ArrayList path = getOptimizedPath(getPath(character.loc, goal), getPath(goal, character.loc)); + if (path.isEmpty()) { + ((Mob) character).setDestination(character.loc); + return; //no points to walk to + } + + ((Mob) character).destination = path.get(0); + + } catch(Exception e){ + //something failed + } + } + + public static ArrayList getOptimizedPath(ArrayList startToGoal, ArrayList goalToStart) { + ArrayList optimalPath = new ArrayList<>(); + optimalPath.add(startToGoal.get(0)); + for(Vector3fImmutable point : startToGoal) + { + if(!goalToStart.contains(point)) + { + continue; + } + optimalPath.add(point); + } + return optimalPath; + } + + private static ArrayList getPath(Vector3fImmutable start, Vector3fImmutable goal) { + ArrayList path = new ArrayList<>(); + path.add(start); + Vector3fImmutable current = start; + boolean obstructed = false; + while (current.distanceSquared(goal) > 9) + { + //gather the 8 cells around the player + ArrayList surroundingCells = new ArrayList<>(); + surroundingCells.add(current.add(new Vector3fImmutable(cellGap, 0, 0))); + surroundingCells.add(current.add(new Vector3fImmutable(cellGap, 0, cellGap))); + surroundingCells.add(current.add(new Vector3fImmutable(0, 0, cellGap))); + surroundingCells.add(current.add(new Vector3fImmutable(-cellGap, 0, 0))); + surroundingCells.add(current.add(new Vector3fImmutable(-cellGap, 0, -cellGap))); + surroundingCells.add(current.add(new Vector3fImmutable(0, 0, -cellGap))); + surroundingCells.add(current.add(new Vector3fImmutable(-cellGap, 0, cellGap))); + surroundingCells.add(current.add(new Vector3fImmutable(cellGap, 0, -cellGap))); + Vector3fImmutable cheapest = new Vector3fImmutable(-10000, 0, -10000); + for (Vector3fImmutable point : surroundingCells) + { + if (path.contains(point)) + continue; + + Regions region = Regions.getRegionAtLocation(point); + if(region != null) { + //if (!region.stairs) + // point.setY(region.center.y); + //else + // point.setY(region.lerpY(point)); + path.add(new Vector3fImmutable(region.center.x,region.center.y,region.center.z)); //only use center points when travelling through regions + continue; + + } + if (pointIsBlocked(point)) { + obstructed = true; + continue; + } + if (getCost(cheapest, current, goal) > getCost(point, current, goal)) + cheapest = point; + + } + + current = cheapest; + path.add(cheapest); + } + if(obstructed) { + return path; + }else { + ArrayList goalPath = new ArrayList<>(); + goalPath.add(goal); + goalPath.add(start); + return goalPath; //if the path isn't obstructed we can walk directly from start to the goal + } + } + + public static float getCost(Vector3fImmutable point, Vector3fImmutable start, Vector3fImmutable goal) { + float gCost = start.distanceSquared(point); + float hCost = goal.distanceSquared(point); + return gCost + hCost; + } + + public static boolean pointIsBlocked(Vector3fImmutable point) { + + //TODO figure out best way to decide if a walking point intersects a mesh collider from a building + Building building = BuildingManager.getBuildingAtLocation(point); + + if(building == null) { + printToPlayers(point, "No Building Found At: " + point); + return false;//no building at this location means nothing obstructing the walking path + } + + if(!collidesWithBuilding(building,point)) { + printToPlayers(point, "No Building Collision At: " + point); + return false; + } + + if(Regions.getRegionAtLocation(point) != null) { + printToPlayers(point, "Region Found At: " + point); + return false; + } + printToPlayers(point, "Path Blocked At: " + point); + return true; + } + + private static boolean collidesWithBuilding(Building building, Vector3fImmutable end){ + MeshBounds mb = Bounds.meshBoundsCache.get(building.meshUUID); + return (end.x > mb.minX && end.x < mb.maxX && end.z > mb.minZ && end.z < mb.maxZ); + } + + private static void printToPlayers(Vector3fImmutable loc, String message){ + for(AbstractWorldObject awo : WorldGrid.getObjectsInRangePartial(loc, MBServerStatics.CHARACTER_LOAD_RANGE, MBServerStatics.MASK_PLAYER)){ + PlayerCharacter pc = (PlayerCharacter)awo; + ChatManager.chatSystemInfo(pc, message); + } + } } diff --git a/src/engine/objects/AbstractCharacter.java b/src/engine/objects/AbstractCharacter.java index 03aebf86..788ac476 100644 --- a/src/engine/objects/AbstractCharacter.java +++ b/src/engine/objects/AbstractCharacter.java @@ -987,18 +987,7 @@ public abstract class AbstractCharacter extends AbstractWorldObject { @Override public final void setLoc(final Vector3fImmutable value) { - Building building = BuildingManager.getBuildingAtLocation(this.loc); - Regions region = null; - if(building != null) { - //look for region in the building we are in - for (Regions regionCycle : building.getBounds().getRegions()) { - float regionHeight = regionCycle.highLerp.y - regionCycle.lowLerp.y; - if(regionHeight < 10) - regionHeight = 10; - if (regionCycle.isPointInPolygon(value) && Math.abs(regionCycle.highLerp.y - value.y) < regionHeight) - region = regionCycle; - } - } + Regions region = Regions.getRegionAtLocation(value); float regionHeightOffset = 0; if(region != null){ this.region = region; diff --git a/src/engine/objects/Mob.java b/src/engine/objects/Mob.java index 27f7d641..5dd267a2 100644 --- a/src/engine/objects/Mob.java +++ b/src/engine/objects/Mob.java @@ -20,6 +20,7 @@ import engine.jobs.DeferredPowerJob; import engine.jobs.UpgradeNPCJob; import engine.math.Bounds; import engine.math.Vector3fImmutable; +import engine.mobileAI.utilities.MovementUtilities; import engine.net.ByteBufferWriter; import engine.net.Dispatch; import engine.net.DispatchMessage; @@ -1899,4 +1900,6 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed { public int compareTo(@NotNull Delayed o) { return toIntExact(this.respawnTime - ((Mob) o).respawnTime); } + + public void setDestination(Vector3fImmutable destination) {MovementUtilities.pathfind(this,destination);} } diff --git a/src/engine/objects/Regions.java b/src/engine/objects/Regions.java index 1683003c..aa7a5866 100644 --- a/src/engine/objects/Regions.java +++ b/src/engine/objects/Regions.java @@ -270,32 +270,18 @@ public class Regions { return BuildingManager.getBuildingFromCache(region.parentBuildingID); } - public static Regions GetRegionForTeleport(Vector3fImmutable location) { + public static Regions getRegionAtLocation(Vector3fImmutable location) { Regions region = null; - - - //Find building - for (AbstractWorldObject awo : WorldGrid.getObjectsInRangePartial(location, 128, MBServerStatics.MASK_BUILDING)) { - Building building = (Building) awo; - if (!Bounds.collide(location, building.getBounds())) - continue; - if(building != null) { - region = BuildingManager.GetRegion(building, location.x, location.y, location.z); + Building building = BuildingManager.getBuildingAtLocation(location); + if(building != null) { + //look for region in the building we are in + for (Regions regionCycle : building.getBounds().getRegions()) { + float regionHeight = regionCycle.highLerp.y - regionCycle.lowLerp.y; + if(regionHeight < 10) + regionHeight = 10; + if (regionCycle.isPointInPolygon(location) && Math.abs(regionCycle.highLerp.y - location.y) < regionHeight) + region = regionCycle; } - //find regions that intersect x and z, check if object can enter. - //for (Regions toEnter : building.getBounds().getRegions()) { - // if (toEnter.isPointInPolygon(location)) { - - // if (region == null) - // region = toEnter; - // else // we're using a low level to high level tree structure, database not always in order low to high. - //check for highest level index. - // if (region != null && toEnter.highLerp.y > region.highLerp.y) - // region = toEnter; - - - // } - // } } return region; } diff --git a/src/engine/powers/poweractions/TeleportPowerAction.java b/src/engine/powers/poweractions/TeleportPowerAction.java index 5f0c6e31..301881d9 100644 --- a/src/engine/powers/poweractions/TeleportPowerAction.java +++ b/src/engine/powers/poweractions/TeleportPowerAction.java @@ -96,7 +96,7 @@ public class TeleportPowerAction extends AbstractPowerAction { //TODO verify target loc is valid loc - Regions region = Regions.GetRegionForTeleport(targetLoc); + Regions region = Regions.getRegionAtLocation(targetLoc); if (region != null && !region.isOutside()) return;