diff --git a/src/engine/gameManager/NavigationManager.java b/src/engine/gameManager/NavigationManager.java new file mode 100644 index 00000000..a129256d --- /dev/null +++ b/src/engine/gameManager/NavigationManager.java @@ -0,0 +1,116 @@ +package engine.gameManager; + +import engine.math.Vector3fImmutable; +import engine.objects.*; + +import java.util.ArrayList; + + +public class NavigationManager { + + private static final int cellGap = 1; + private static final int stepHeight = 2; + + public static void pathfind(AbstractCharacter character, Vector3fImmutable goal){ + try { + ArrayList path = getOptimizedPath(getPath(character.loc, goal), getPath(goal, character.loc)); + if (path.isEmpty()) { + character.destination = goal; + return; //no points to walk to + } + + 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); + } + + //optimize the path to its smallest possible amount of points + + + + return optimalPath; + } + + private static ArrayList getPath(Vector3fImmutable start, Vector3fImmutable goal) { + ArrayList path = new ArrayList<>(); + path.add(start); + Vector3fImmutable current = start; + boolean obstructed = false; + int count = 0; + while (current.distanceSquared(goal) > 9 && count < 250) + { + //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(Vector3fImmutable.ZERO); + for (Vector3fImmutable point : surroundingCells) + { + count++; + + if (path.contains(point)) + 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(start); + goalPath.add(goal); + 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) { + + Building building = BuildingManager.getBuildingAtLocation(point); + if(building != null) + for(Regions region : building.getBounds().getRegions()) + if(region.isPointInPolygon(point)) + if(Math.abs(region.lerpY(point) - point.y) > stepHeight) // get the height distance between current height and target location height + return true; + + Zone currentZone = ZoneManager.findSmallestZone(point); + if(currentZone == null) + return false; + else + return currentZone.navMesh.contains(point.x,point.z); + } +} diff --git a/src/engine/mobileAI/MobAI.java b/src/engine/mobileAI/MobAI.java index 779c0505..9d5d7efc 100644 --- a/src/engine/mobileAI/MobAI.java +++ b/src/engine/mobileAI/MobAI.java @@ -11,14 +11,15 @@ package engine.mobileAI; import engine.Enum; import engine.Enum.DispatchChannel; import engine.InterestManagement.WorldGrid; +import engine.exception.MsgSendException; import engine.gameManager.*; import engine.math.Vector3f; import engine.math.Vector3fImmutable; import engine.mobileAI.Threads.MobAIThread; import engine.mobileAI.Threads.Respawner; import engine.mobileAI.utilities.CombatUtilities; -import engine.mobileAI.utilities.MovementUtilities; import engine.net.DispatchMessage; +import engine.net.client.msg.MoveToPointMsg; import engine.net.client.msg.PerformActionMsg; import engine.net.client.msg.PowerProjectileMsg; import engine.objects.*; @@ -103,7 +104,7 @@ public class MobAI { if (mob.behaviourType.callsForHelp) MobCallForHelp(mob); - if (MovementUtilities.inRangeDropAggro(mob, target)) { + if (inRangeDropAggro(mob, target)) { mob.setCombatTarget(null); return; } @@ -287,10 +288,10 @@ public class MobAI { if (mob.agentType.equals(Enum.AIAgentType.GUARDMINION)) { Mob captain = (Mob) mob.guardCaptain; - mob.setDestination(captain.destination.add(Formation.getOffset(2, mob.guardCaptain.minions.indexOf(mob.getObjectUUID()) + 3))); + mob.destination = captain.destination.add(Formation.getOffset(2, mob.guardCaptain.minions.indexOf(mob.getObjectUUID()) + 3)); mob.lastPatrolPointIndex = captain.lastPatrolPointIndex; } else { - mob.setDestination(mob.patrolPoints.get(mob.lastPatrolPointIndex)); + NavigationManager.pathfind(mob, mob.patrolPoints.get(mob.lastPatrolPointIndex)); mob.lastPatrolPointIndex += 1; } @@ -303,7 +304,7 @@ public class MobAI { MobAI.Patrol(minion); } - MovementUtilities.aiMove(mob, mob.destination, true); + aiMove(mob, true,0); } catch (Exception e) { Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackTarget" + " " + e.getMessage()); @@ -689,7 +690,7 @@ public class MobAI { if (aiAgent.enemy.size() > 0 && aiAgent.enemy.contains(loadedPlayer.getRace().getRaceType().getMonsterType()) == false) continue; - if (MovementUtilities.inRangeToAggro(aiAgent, loadedPlayer)) { + if (inRangeToAggro(aiAgent, loadedPlayer)) { aiAgent.setCombatTarget(loadedPlayer); return; } @@ -722,7 +723,7 @@ public class MobAI { try { - if (!MovementUtilities.canMove(mob)) + if (!canMove(mob)) return; mob.updateLocation(); @@ -748,8 +749,8 @@ public class MobAI { if (CombatUtilities.inRange2D(mob, mob.guardCaptain, 6)) return; - mob.setDestination(mob.guardCaptain.getLoc()); - MovementUtilities.moveToLocation(mob, mob.destination, 5, false); + NavigationManager.pathfind(mob, mob.guardCaptain.getLoc()); + aiMove(mob, false,5); } else chaseTarget(mob); break; @@ -834,7 +835,7 @@ public class MobAI { if (mob.getCombatTarget() == null) return; - if (mob.getCombatTarget().getObjectType().equals(Enum.GameObjectType.PlayerCharacter) && MovementUtilities.inRangeDropAggro(mob, (PlayerCharacter) mob.getCombatTarget()) && + if (mob.getCombatTarget().getObjectType().equals(Enum.GameObjectType.PlayerCharacter) && inRangeDropAggro(mob, (PlayerCharacter) mob.getCombatTarget()) && mob.agentType.equals(Enum.AIAgentType.PET) == false) { mob.setCombatTarget(null); @@ -877,7 +878,7 @@ public class MobAI { } } } - } else if (MovementUtilities.inRangeOfBindLocation(mob) == false) { + } else if (inRangeOfBindLocation(mob) == false) { PowersBase recall = PowersManager.getPowerByToken(-1994153779); PowersManager.useMobPower(mob, mob, recall, 40); @@ -904,8 +905,8 @@ public class MobAI { if (CombatUtilities.inRange2D(mob, mob.getCombatTarget(), mob.getRange()) == false) { if (mob.getRange() > 15) { - mob.setDestination(mob.getCombatTarget().getLoc()); - MovementUtilities.moveToLocation(mob, mob.destination, 0, false); + NavigationManager.pathfind(mob, mob.getCombatTarget().getLoc()); + aiMove(mob, false,0); } else { //check if building @@ -913,12 +914,12 @@ public class MobAI { switch (mob.getCombatTarget().getObjectType()) { case PlayerCharacter: case Mob: - mob.setDestination(MovementUtilities.GetDestinationToCharacter(mob, (AbstractCharacter) mob.getCombatTarget())); - MovementUtilities.moveToLocation(mob, mob.destination, mob.getRange() + 1, false); + mob.destination = GetDestinationToCharacter(mob, (AbstractCharacter) mob.getCombatTarget()); + aiMove(mob, false,mob.getRange() + 1); break; case Building: - mob.setDestination(mob.getCombatTarget().getLoc()); - MovementUtilities.moveToLocation(mob, mob.getCombatTarget().getLoc(), 0, false); + mob.destination = mob.getCombatTarget().getLoc(); + aiMove(mob, false,0); break; } } @@ -1000,7 +1001,7 @@ public class MobAI { if (ZoneManager.seaFloor.zoneMobSet.contains(mob)) mob.killCharacter("no owner"); - if (MovementUtilities.canMove(mob) && mob.behaviourType.canRoam) + if (canMove(mob) && mob.behaviourType.canRoam) CheckMobMovement(mob); CheckForAttack(mob); @@ -1130,7 +1131,7 @@ public class MobAI { if (GuardCanAggro(mob, loadedPlayer) == false) continue; - if (MovementUtilities.inRangeToAggro(mob, loadedPlayer) && mob.getCombatTarget() == null) { + if (inRangeToAggro(mob, loadedPlayer) && mob.getCombatTarget() == null) { mob.setCombatTarget(loadedPlayer); return; } @@ -1240,9 +1241,9 @@ public class MobAI { float xPoint = ThreadLocalRandom.current().nextInt(400) - 200; float zPoint = ThreadLocalRandom.current().nextInt(400) - 200; Vector3fImmutable TreePos = mob.getGuild().getOwnedCity().getLoc(); - mob.setDestination(new Vector3fImmutable(TreePos.x + xPoint, TreePos.y, TreePos.z + zPoint)); + NavigationManager.pathfind(mob, new Vector3fImmutable(TreePos.x + xPoint, TreePos.y, TreePos.z + zPoint)); - MovementUtilities.aiMove(mob, mob.destination, true); + aiMove(mob, true,0); if (mob.agentType.equals(Enum.AIAgentType.GUARDCAPTAIN)) { for (Integer minionUUID : mob.minions) { @@ -1252,11 +1253,12 @@ public class MobAI { //make sure mob is out of combat stance if (minion.despawned == false) { - if (MovementUtilities.canMove(minion)) { + if (canMove(minion)) { Vector3f minionOffset = Formation.getOffset(2, mob.minions.indexOf(minionUUID) + 3); minion.updateLocation(); Vector3fImmutable formationPatrolPoint = new Vector3fImmutable(mob.destination.x + minionOffset.x, mob.destination.y, mob.destination.z + minionOffset.z); - MovementUtilities.aiMove(minion, formationPatrolPoint, true); + minion.destination = formationPatrolPoint; + aiMove(minion, true,0); } } } @@ -1284,7 +1286,7 @@ public class MobAI { if (potentialTarget.equals(mob.getCombatTarget())) continue; - if (potentialTarget != null && mob.playerAgroMap.get(potentialTarget.getObjectUUID()).floatValue() > CurrentHateValue && MovementUtilities.inRangeToAggro(mob, potentialTarget)) { + if (potentialTarget != null && mob.playerAgroMap.get(potentialTarget.getObjectUUID()).floatValue() > CurrentHateValue && inRangeToAggro(mob, potentialTarget)) { CurrentHateValue = mob.playerAgroMap.get(potentialTarget.getObjectUUID()).floatValue(); mostHatedTarget = potentialTarget; } @@ -1296,4 +1298,84 @@ public class MobAI { } return null; } + + public static boolean inRangeDropAggro(Mob agent, AbstractCharacter target) { + float rangeSquared = MobAIThread.AI_BASE_AGGRO_RANGE * MobAIThread.AI_BASE_AGGRO_RANGE; + if(agent.isPlayerGuard()) + rangeSquared = 62500; + if(agent.isSiege()) + rangeSquared = agent.getRange() * agent.getRange(); + + return agent.loc.distanceSquared(target.loc) > rangeSquared; + + } + + public static boolean inRangeToAggro(Mob agent, PlayerCharacter target) { + return MobAIThread.AI_BASE_AGGRO_RANGE * MobAIThread.AI_BASE_AGGRO_RANGE > agent.loc.distanceSquared(target.loc) ; + } + + public static boolean inRangeOfBindLocation(Mob agent) { + if(agent.isPlayerGuard() && agent.guardedCity.isLocationOnCityZone(agent.loc)) + return true; + float bindRangeSquared = (agent.parentZone.getBounds().getHalfExtents().x * agent.parentZone.getBounds().getHalfExtents().x) + (MobAIThread.AI_DROP_AGGRO_RANGE * MobAIThread.AI_DROP_AGGRO_RANGE); + return agent.loc.distanceSquared(agent.bindLoc) < bindRangeSquared; + } + + public static boolean canMove(Mob agent) { + if (!agent.behaviourType.canRoam) + return false; + + return (agent.isAlive() && !agent.getBonuses().getBool(Enum.ModType.Stunned, Enum.SourceType.None) && !agent.getBonuses().getBool(Enum.ModType.CannotMove, Enum.SourceType.None)); + } + + public static Vector3fImmutable GetDestinationToCharacter(Mob aiAgent, AbstractCharacter character) { + + if (!character.isMoving()) + return character.getLoc(); + + + float agentDistanceEndLoc = aiAgent.getLoc().distanceSquared2D(character.getEndLoc()); + float characterDistanceEndLoc = character.getLoc().distanceSquared2D(character.getEndLoc()); + + if (agentDistanceEndLoc > characterDistanceEndLoc) + return character.getEndLoc(); + + return character.getLoc(); + } + + public static void aiMove(Mob mob, boolean isWalking, float offset) { + + //update our walk/run state. + if (isWalking && !mob.isWalk()) { + mob.setWalkMode(true); + MovementManager.sendRWSSMsg(mob); + } else if (!isWalking && mob.isWalk()) { + mob.setWalkMode(false); + MovementManager.sendRWSSMsg(mob); + } + if(offset > 0){ + Vector3fImmutable newLoc = Vector3fImmutable.getRandomPointInCircle(mob.destination, offset); + mob.destination = newLoc; + } + + + MoveToPointMsg msg = new MoveToPointMsg(); + + msg.setSourceType(Enum.GameObjectType.Mob.ordinal()); + msg.setSourceID(mob.getObjectUUID()); + msg.setStartCoord(mob.loc); + msg.setEndCoord(mob.destination); + msg.setInBuildingFloor(-1); + msg.setInBuilding(-1); + msg.setStartLocType(0); + msg.setInBuildingUUID(0); + + + try { + MovementManager.movement(msg, mob); + } catch (MsgSendException e) { + // TODO Figure out how we want to handle the msg send exception + e.printStackTrace(); + } + } } \ No newline at end of file diff --git a/src/engine/mobileAI/utilities/MovementUtilities.java b/src/engine/mobileAI/utilities/MovementUtilities.java deleted file mode 100644 index 38f2cf93..00000000 --- a/src/engine/mobileAI/utilities/MovementUtilities.java +++ /dev/null @@ -1,364 +0,0 @@ -// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . -// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· -// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ -// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ -// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ -// Magicbane Emulator Project © 2013 - 2022 -// www.magicbane.com - - -package engine.mobileAI.utilities; - -import engine.Enum; -import engine.Enum.GameObjectType; -import engine.Enum.ModType; -import engine.Enum.SourceType; -import engine.InterestManagement.Terrain; -import engine.exception.MsgSendException; -import engine.gameManager.BuildingManager; -import engine.gameManager.MovementManager; -import engine.gameManager.ZoneManager; -import engine.math.Bounds; -import engine.math.Vector3fImmutable; -import engine.mobileAI.Threads.MobAIThread; -import engine.net.client.msg.MoveToPointMsg; -import engine.objects.*; -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 = 1; - private static final int stepHeight = 3; - - public static boolean inRangeOfBindLocation(Mob agent) { - - - if (agent.isPlayerGuard()) { - - Mob guardCaptain = null; - if (agent.getContract() != null) - guardCaptain = agent; - else - guardCaptain = (Mob) agent.guardCaptain; - - if (guardCaptain != null) { - Building barracks = guardCaptain.building; - - if (barracks != null) { - City city = barracks.getCity(); - - if (city != null) { - Building tol = city.getTOL(); - - //Guards recall distance = 814. - if (tol != null) { - return !(agent.getLoc().distanceSquared2D(tol.getLoc()) > sqr(Enum.CityBoundsType.ZONE.halfExtents)); - } - - } - } - } - - return true; - } - - Vector3fImmutable sl = new Vector3fImmutable(agent.getLoc().getX(), 0, agent.getLoc().getZ()); - Vector3fImmutable tl = new Vector3fImmutable(agent.getTrueBindLoc().x, 0, agent.getTrueBindLoc().z); - - float distanceSquaredToTarget = sl.distanceSquared2D(tl); //distance to center of target - float zoneRange = 250; - - if (agent.getParentZone() != null) { - if (agent.getParentZone().bounds != null) - zoneRange = agent.getParentZone().bounds.getHalfExtents().x * 2; - } - - if (zoneRange > 300) - zoneRange = 300; - - if (agent.getSpawnRadius() > zoneRange) - zoneRange = agent.getSpawnRadius(); - - - return distanceSquaredToTarget < sqr(MobAIThread.AI_DROP_AGGRO_RANGE + zoneRange); - - } - - public static boolean inRangeToAggro(Mob agent, PlayerCharacter target) { - - Vector3fImmutable sl = agent.getLoc(); - Vector3fImmutable tl = target.getLoc(); - - float distanceSquaredToTarget = sl.distanceSquared2D(tl) - sqr(agent.calcHitBox() + target.calcHitBox()); //distance to center of target - float range = MobAIThread.AI_BASE_AGGRO_RANGE; - - if (agent.isPlayerGuard()) - range = 150; - - return distanceSquaredToTarget < sqr(range); - - } - - public static boolean inRangeDropAggro(Mob agent, AbstractCharacter target) { - float range = MobAIThread.AI_BASE_AGGRO_RANGE * MobAIThread.AI_BASE_AGGRO_RANGE; - if(agent.isPlayerGuard()) - range = 62500; - if(agent.isSiege()) - range = agent.getRange() * agent.getRange(); - - return agent.loc.distanceSquared(target.loc) > range; - - } - - public static void moveToLocation(Mob agent, Vector3fImmutable newLocation, float offset, boolean isWalking) { - try { - - //don't move farther than 30 units from player. - if (offset > 30) - offset = 30; - Vector3fImmutable newLoc = Vector3fImmutable.getRandomPointInCircle(newLocation, offset); - - - agent.setFaceDir(newLoc.subtract2D(agent.getLoc()).normalize()); - - aiMove(agent, newLoc, isWalking); - } catch (Exception e) { - Logger.error(e.toString()); - } - } - - public static boolean canMove(Mob agent) { - if (agent.getMobBase() != null && Enum.MobFlagType.SENTINEL.elementOf(agent.getMobBase().getFlags())) - return false; - - return (agent.isAlive() && !agent.getBonuses().getBool(ModType.Stunned, SourceType.None) && !agent.getBonuses().getBool(ModType.CannotMove, SourceType.None)); - } - - public static void aiMove(Mob agent, Vector3fImmutable vect, boolean isWalking) { - - //update our walk/run state. - if (isWalking && !agent.isWalk()) { - agent.setWalkMode(true); - MovementManager.sendRWSSMsg(agent); - } else if (!isWalking && agent.isWalk()) { - agent.setWalkMode(false); - MovementManager.sendRWSSMsg(agent); - } - - MoveToPointMsg msg = new MoveToPointMsg(); - - -// Regions currentRegion = Mob.InsideBuildingRegion(agent); -// -// if (currentRegion != null){ -// -// -// if (currentRegion.isGroundLevel()){ -// agent.setInBuilding(0); -// agent.setInFloorID(-1); -// }else{ -// agent.setInBuilding(currentRegion.getLevel()); -// agent.setInFloorID(currentRegion.getRoom()); -// } -// }else{ -// agent.setInBuilding(-1); -// agent.setInFloorID(-1); -// agent.setInBuildingID(0); -// } -// agent.setLastRegion(currentRegion); - - - Vector3fImmutable startLoc = null; - Vector3fImmutable endLoc = null; - -// if (agent.getLastRegion() != null){ -// Building inBuilding = Building.getBuildingFromCache(agent.getInBuildingID()); -// if (inBuilding != null){ -// startLoc = ZoneManager.convertWorldToLocal(inBuilding, agent.getLoc()); -// endLoc = ZoneManager.convertWorldToLocal(inBuilding, vect); -// } -// }else{ -// agent.setBuildingID(0); -// agent.setInBuildingID(0); -// startLoc = agent.getLoc(); -// endLoc = vect; -// } - - startLoc = agent.getLoc(); - endLoc = vect; - - msg.setSourceType(GameObjectType.Mob.ordinal()); - msg.setSourceID(agent.getObjectUUID()); - msg.setStartCoord(startLoc); - msg.setEndCoord(endLoc); - msg.setInBuildingFloor(-1); - msg.setInBuilding(-1); - msg.setStartLocType(0); - msg.setInBuildingUUID(0); - - - try { - MovementManager.movement(msg, agent); - } catch (MsgSendException e) { - // TODO Figure out how we want to handle the msg send exception - e.printStackTrace(); - } - } - - public static Vector3fImmutable GetDestinationToCharacter(Mob aiAgent, AbstractCharacter character) { - - if (!character.isMoving()) - return character.getLoc(); - - - float agentDistanceEndLoc = aiAgent.getLoc().distanceSquared2D(character.getEndLoc()); - float characterDistanceEndLoc = character.getLoc().distanceSquared2D(character.getEndLoc()); - - if (agentDistanceEndLoc > characterDistanceEndLoc) - return character.getEndLoc(); - - return character.getLoc(); - } - - public static void pathfind(AbstractCharacter character, Vector3fImmutable goal){ - try { - - //if(character.region == null && Regions.getRegionAtLocation(goal) != null) {//mover not inside a building - // Building building = BuildingManager.getBuildingAtLocation(goal); - // ArrayList entrances = new ArrayList<>(); - //for (Regions region : building.getBounds().getRegions()) { - // if (region.exit && region.level == 0) - // entrances.add(region); - //} - // Regions cheapest = null; - // for(Regions entrance : entrances){ - // if(cheapest == null) { - // cheapest = entrance; - // continue; - // } -// if(getCost(new Vector3fImmutable(entrance.center),character.loc,goal) < getCost(new Vector3fImmutable(cheapest.center),character.loc,goal)) -// cheapest = entrance; -// } -// -// goal = new Vector3fImmutable(cheapest.center.x, cheapest.center.y, cheapest.center.z); - - //} - - ArrayList path = getOptimizedPath(getPath(character.loc, goal), getPath(goal, character.loc)); - if (path.isEmpty()) { - ((Mob) character).destination = goal; - 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; - int count = 0; - while (current.distanceSquared(goal) > 9 && count < 250) - { - //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(Vector3fImmutable.ZERO); - for (Vector3fImmutable point : surroundingCells) - { - count++; - - if (path.contains(point)) - continue; - - if (pointIsBlocked(point)) { - obstructed = true; - continue; - } - Building building = BuildingManager.getBuildingAtLocation(point); - Regions region = null; - if(building != null) - for(Regions reg : building.getBounds().getRegions()) - if(reg.isPointInPolygon(point)) - region = reg; - - if(region != null) - point = new Vector3fImmutable(point.x,region.lerpY(point),point.z); // adjust the Y value of the point to the region height it is in - else - point = new Vector3fImmutable(point.x, Terrain.getWorldHeight(point),point.z); - - 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) { - - Building building = BuildingManager.getBuildingAtLocation(point); - if(building == null) - return false;//no building at this location means nothing obstructing the walking path - - Regions region = Regions.getRegionAtLocation(point); - if(region != null && Math.abs(region.lerpY(point) - point.y) < stepHeight) { - return false; - } - - Zone currentZone = ZoneManager.findSmallestZone(point); - if(currentZone == null) - return false; - else - return currentZone.navMesh.contains(point.x,point.z); - //return false; - } - -} diff --git a/src/engine/mobileAI/utilities/PathingUtilities.java b/src/engine/mobileAI/utilities/PathingUtilities.java new file mode 100644 index 00000000..4741ab7d --- /dev/null +++ b/src/engine/mobileAI/utilities/PathingUtilities.java @@ -0,0 +1,80 @@ +package engine.mobileAI.utilities; +import engine.objects.Regions; +import java.awt.geom.Area; +import java.util.ArrayList; + +public class PathingUtilities { + public static class Point { + public int x; + public int y; + public Point previous; + + public Point(int x, int y, Point previous) { + this.x = x; + this.y = y; + this.previous = previous; + } + + public Point offset(int ox, int oy) { return new Point(x + ox, y + oy, this); } + } + + public static boolean IsWalkable(Area navMesh, Point point) { + if (navMesh.contains(point.x, point.y)) + return true; + + Regions region = Regions.getRegionAtPoint(point); + if (region != null) { + return !(region.getHeightAtPoint(point) - point.y > 2); + } + return false; + } + + public static ArrayList FindNeighbors(Area navMesh, Point point) { + ArrayList neighbors = new ArrayList<>(); + Point up = point.offset(0, 1); + Point down = point.offset(0, -1); + Point left = point.offset(-1, 0); + Point right = point.offset(1, 0); + if (IsWalkable(navMesh, up)) neighbors.add(up); + if (IsWalkable(navMesh, down)) neighbors.add(down); + if (IsWalkable(navMesh, left)) neighbors.add(left); + if (IsWalkable(navMesh, right)) neighbors.add(right); + return neighbors; + } + + public static ArrayList FindPath(Area navMesh, Point start, Point end) { + boolean finished = false; + ArrayList used = new ArrayList<>(); + used.add(start); + while (!finished) { + ArrayList newOpen = new ArrayList<>(); + for(int i = 0; i < used.size(); ++i){ + Point point = used.get(i); + for (Point neighbor : FindNeighbors(navMesh, point)) { + if (!used.contains(neighbor) && !newOpen.contains(neighbor)) { + newOpen.add(neighbor); + } + } + } + + for(Point point : newOpen) { + used.add(point); + if (end.equals(point)) { + finished = true; + break; + } + } + + if (!finished && newOpen.isEmpty()) + return null; + } + + ArrayList path = new ArrayList<>(); + Point point = used.get(used.size() - 1); + while(point.previous != null) { + path.add(0, point); + point = point.previous; + } + return path; + } +} diff --git a/src/engine/objects/AbstractCharacter.java b/src/engine/objects/AbstractCharacter.java index 0ff48126..4d6e3a92 100644 --- a/src/engine/objects/AbstractCharacter.java +++ b/src/engine/objects/AbstractCharacter.java @@ -120,6 +120,8 @@ public abstract class AbstractCharacter extends AbstractWorldObject { private long lastHateUpdate = 0; private byte aoecntr = 0; + public Vector3fImmutable destination = Vector3fImmutable.ZERO; + public int hidden = 0; // current rank of hide/sneak/invis public CopyOnWriteArrayList minions = new CopyOnWriteArrayList(); diff --git a/src/engine/objects/Mob.java b/src/engine/objects/Mob.java index 6346320c..9b6b8853 100644 --- a/src/engine/objects/Mob.java +++ b/src/engine/objects/Mob.java @@ -20,7 +20,6 @@ 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; @@ -54,7 +53,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed { public long nextCallForHelp = 0; public ReentrantReadWriteLock minionLock = new ReentrantReadWriteLock(); public boolean despawned = false; - public Vector3fImmutable destination = Vector3fImmutable.ZERO; + public MobBase mobBase; public int spawnDelay; public Zone parentZone; @@ -1901,7 +1900,4 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed { 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 12d54918..f612815a 100644 --- a/src/engine/objects/Regions.java +++ b/src/engine/objects/Regions.java @@ -9,12 +9,14 @@ package engine.objects; +import engine.InterestManagement.Terrain; import engine.InterestManagement.WorldGrid; import engine.gameManager.BuildingManager; import engine.math.Bounds; import engine.math.FastMath; import engine.math.Vector3f; import engine.math.Vector3fImmutable; +import engine.mobileAI.utilities.PathingUtilities; import engine.server.MBServerStatics; import java.awt.*; @@ -287,6 +289,22 @@ public class Regions { } return region; } + public static Regions getRegionAtPoint(PathingUtilities.Point point) { + Vector3fImmutable baseLoc = new Vector3fImmutable(point.x,0,point.y); + baseLoc.setY(Terrain.getWorldHeight(baseLoc)); + Building building = BuildingManager.getBuildingAtLocation(baseLoc); + if(building == null) + return null; + for(Regions region : building.getBounds().getRegions()) + if(region.isPointInPolygon(baseLoc)) + return region; + return null; + } + + public float getHeightAtPoint(PathingUtilities.Point point){ + Vector3fImmutable baseLoc = new Vector3fImmutable(point.x,0,point.y); + return this.lerpY(baseLoc); + } public int getRoom() { return room;