From c4ae7bfed3c37778b3f563515667c74ae01ae126 Mon Sep 17 00:00:00 2001 From: Jules Date: Sun, 6 Oct 2024 15:49:28 +0200 Subject: [PATCH] Fixed waypoint path calculation. Option to disable auto waypoint path calculation --- .../mmocore/api/util/MMOCoreUtils.java | 26 +++-- .../Indyuce/mmocore/gui/WaypointViewer.java | 71 ++++-------- .../mmocore/manager/ConfigManager.java | 3 +- .../java/net/Indyuce/mmocore/util/Pair.java | 47 ++++++++ .../Indyuce/mmocore/waypoint/Waypoint.java | 95 ++-------------- .../mmocore/waypoint/WaypointPath.java | 85 +++++--------- .../waypoint/WaypointPathCalculation.java | 107 ++++++++++++++++++ MMOCore-Dist/src/main/resources/config.yml | 12 ++ .../main/resources/default/gui/waypoints.yml | 18 +-- .../src/main/resources/default/messages.yml | 5 +- 10 files changed, 252 insertions(+), 217 deletions(-) create mode 100644 MMOCore-API/src/main/java/net/Indyuce/mmocore/util/Pair.java create mode 100644 MMOCore-API/src/main/java/net/Indyuce/mmocore/waypoint/WaypointPathCalculation.java diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/util/MMOCoreUtils.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/util/MMOCoreUtils.java index 8e0bd2f1..fa0fed43 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/util/MMOCoreUtils.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/util/MMOCoreUtils.java @@ -9,6 +9,7 @@ import io.lumine.mythic.lib.hologram.Hologram; import io.lumine.mythic.lib.version.VEnchantment; import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.util.Icon; +import org.apache.commons.lang.Validate; import org.bukkit.*; import org.bukkit.attribute.Attribute; import org.bukkit.entity.Entity; @@ -60,7 +61,7 @@ public class MMOCoreUtils { * @param current Current value of resource * @param maxStat Maximum value of resource * @return Clamped resource value. If the provided current value is 0, - * this function will return the maximum resource value. + * this function will return the maximum resource value. */ public static double fixResource(double current, double maxStat) { return current == 0 ? maxStat : Math.max(0, Math.min(current, maxStat)); @@ -218,12 +219,7 @@ public class MMOCoreUtils { return object.toString(); } - /** - * Method to get all entities surrounding a location. This method does not - * take every entity in the world but rather takes all the entities from the - * 9 chunks around the entity, so even if the location is at the border of a - * chunk (worst case border of 4 chunks), the entity will still be included - */ + @Deprecated public static List getNearbyChunkEntities(Location loc) { /* @@ -301,6 +297,22 @@ public class MMOCoreUtils { } } + @NotNull + public static Location readLocation(String string) { + String[] split = string.split(" "); + + World world = Bukkit.getWorld(split[0]); + Validate.notNull(world, "Could not find world with name '" + split[0] + "'"); + + double x = Double.parseDouble(split[1]); + double y = Double.parseDouble(split[2]); + double z = Double.parseDouble(split[3]); + float yaw = split.length > 4 ? (float) Double.parseDouble(split[4]) : 0; + float pitch = split.length > 5 ? (float) Double.parseDouble(split[5]) : 0; + + return new Location(world, x, y, z, yaw, pitch); + } + /** * @return Center location of an entity using its bounding box */ diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/gui/WaypointViewer.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/gui/WaypointViewer.java index 91a20dfe..489015c0 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/gui/WaypointViewer.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/gui/WaypointViewer.java @@ -13,6 +13,7 @@ import net.Indyuce.mmocore.gui.api.item.Placeholders; import net.Indyuce.mmocore.gui.api.item.SimplePlaceholderItem; import net.Indyuce.mmocore.waypoint.Waypoint; import net.Indyuce.mmocore.waypoint.WaypointPath; +import net.Indyuce.mmocore.waypoint.WaypointPathCalculation; import org.apache.commons.lang.Validate; import org.bukkit.ChatColor; import org.bukkit.Material; @@ -26,7 +27,6 @@ import org.bukkit.persistence.PersistentDataType; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -72,7 +72,7 @@ public class WaypointViewer extends EditableInventory { public class WaypointItem extends SimplePlaceholderItem { private final SimplePlaceholderItem noWaypoint, locked; - private final WaypointItemHandler availWaypoint, noStellium, notLinked, notDynamic, currentWayPoint; + private final WaypointItemHandler availWaypoint, noStellium, notLinked, currentWayPoint; public WaypointItem(ConfigurationSection config) { super(Material.BARRIER, config); @@ -80,7 +80,7 @@ public class WaypointViewer extends EditableInventory { Validate.notNull(config.getConfigurationSection("no-waypoint"), "Could not load 'no-waypoint' config"); Validate.notNull(config.getConfigurationSection("locked"), "Could not load 'locked' config"); Validate.notNull(config.getConfigurationSection("not-a-destination"), "Could not load 'not-a-destination' config"); - Validate.notNull(config.getConfigurationSection("not-dynamic"), "Could not load 'not-dynamic' config"); + //Validate.notNull(config.getConfigurationSection("not-dynamic"), "Could not load 'not-dynamic' config"); Validate.notNull(config.getConfigurationSection("current-waypoint"), "Could not load 'current-waypoint' config"); Validate.notNull(config.getConfigurationSection("not-enough-stellium"), "Could not load 'not-enough-stellium' config"); Validate.notNull(config.getConfigurationSection("display"), "Could not load 'display' config"); @@ -88,7 +88,7 @@ public class WaypointViewer extends EditableInventory { noWaypoint = new SimplePlaceholderItem(config.getConfigurationSection("no-waypoint")); locked = new SimplePlaceholderItem(config.getConfigurationSection("locked")); notLinked = new WaypointItemHandler(config.getConfigurationSection("not-a-destination"), true); - notDynamic = new WaypointItemHandler(config.getConfigurationSection("not-dynamic"), true); + //notDynamic = new WaypointItemHandler(config.getConfigurationSection("not-dynamic"), true); currentWayPoint = new WaypointItemHandler(config.getConfigurationSection("current-waypoint"), true); noStellium = new WaypointItemHandler(config.getConfigurationSection("not-enough-stellium"), false); availWaypoint = new WaypointItemHandler(config.getConfigurationSection("display"), false); @@ -106,23 +106,21 @@ public class WaypointViewer extends EditableInventory { if (index >= inv.waypoints.size()) return noWaypoint.display(inv, n); - // Locked waypoint? - Waypoint waypoint = inv.waypoints.get(index); + final Waypoint waypoint = inv.waypoints.get(index); + + // Current waypoint if (inv.current != null && inv.current.equals(waypoint)) return currentWayPoint.display(inv, n); + // Locked waypoint if (!inv.getPlayerData().hasWaypoint(waypoint)) return locked.display(inv, n); // Waypoints are not linked - if (inv.current != null && !inv.paths.containsKey(waypoint)) + if (!inv.paths.containsKey(waypoint)) return notLinked.display(inv, n); - // Not dynamic waypoint - if (inv.current == null && !inv.paths.containsKey(waypoint)) - return notDynamic.display(inv, n); - - //Normal cost + // Normal cost if (inv.paths.get(waypoint).getCost() > inv.getPlayerData().getStellium()) return noStellium.display(inv, n); @@ -132,10 +130,14 @@ public class WaypointViewer extends EditableInventory { public class WaypointItemHandler extends InventoryItem { private final boolean onlyName; + private final String splitter, none; public WaypointItemHandler(ConfigurationSection config, boolean onlyName) { super(config); + this.onlyName = onlyName; + this.splitter = config.getString("format_path.splitter", ", "); + this.none = config.getString("format_path.none", "None"); } @Override @@ -187,7 +189,7 @@ public class WaypointViewer extends EditableInventory { holders.register("current_cost", inv.paths.get(waypoint).getCost()); holders.register("normal_cost", decimal.format(inv.paths.containsKey(waypoint) ? inv.paths.get(waypoint).getCost() : Double.POSITIVE_INFINITY)); holders.register("dynamic_cost", decimal.format(waypoint.getDynamicCost())); - holders.register("intermediary_waypoints", inv.paths.containsKey(waypoint) ? inv.paths.get(waypoint).displayIntermediaryWayPoints(inv.isDynamicUse()) : "None"); + holders.register("intermediary_waypoints", inv.paths.containsKey(waypoint) ? inv.paths.get(waypoint).displayIntermediaryWayPoints(splitter, none) : none); } return holders; @@ -198,7 +200,8 @@ public class WaypointViewer extends EditableInventory { private final List waypoints = new ArrayList<>(MMOCore.plugin.waypointManager.getAll()); @Nullable private final Waypoint current; - private final Map paths = new HashMap<>(); + + private Map paths; private int page; @@ -206,29 +209,7 @@ public class WaypointViewer extends EditableInventory { super(playerData, editable); this.current = current; - if (current != null) - for (WaypointPath pathInfo : current.getAllPath()) - paths.put(pathInfo.getFinalWaypoint(), pathInfo); - - if (current == null) { - - //Iterate through all the dynamic points and find all the points it is linked to and the path - HashMap dynamicPoints = new HashMap<>(); - //We first check all the dynamic waypoints - for (Waypoint waypoint : waypoints) { - if (waypoint.mayBeUsedDynamically(playerData.getPlayer())) { - paths.put(waypoint, new WaypointPath(waypoint, waypoint.getDynamicCost())); - dynamicPoints.put(waypoint, waypoint.getDynamicCost()); - } - } - for (Waypoint source : dynamicPoints.keySet()) { - for (WaypointPath target : source.getAllPath()) { - if (!paths.containsKey(target.getFinalWaypoint()) || paths.get(target.getFinalWaypoint()).getCost() > target.getCost() + dynamicPoints.get(source)) { - paths.put(target.getFinalWaypoint(), target.addCost(dynamicPoints.get(source))); - } - } - } - } + paths = new WaypointPathCalculation(playerData).run(current).getPaths(); } @Override @@ -259,11 +240,10 @@ public class WaypointViewer extends EditableInventory { String tag = container.has(new NamespacedKey(MMOCore.plugin, "waypointId"), PersistentDataType.STRING) ? container.get(new NamespacedKey(MMOCore.plugin, "waypointId"), PersistentDataType.STRING) : ""; - if (tag.equals("")) - return; + if (tag.isEmpty()) return; // Locked waypoint? - Waypoint waypoint = MMOCore.plugin.waypointManager.get(tag); + final Waypoint waypoint = MMOCore.plugin.waypointManager.get(tag); if (!playerData.hasWaypoint(waypoint)) { ConfigMessage.fromKey("not-unlocked-waypoint").send(player); return; @@ -275,18 +255,12 @@ public class WaypointViewer extends EditableInventory { return; } - // Waypoint does not have target as destination - if (current != null && current.getPath(waypoint) == null) { + // No access to that waypoint + if (paths.get(waypoint) == null) { ConfigMessage.fromKey("cannot-teleport-to").send(player); return; } - // Not dynamic waypoint - if (current == null && !paths.containsKey(waypoint)) { - ConfigMessage.fromKey("not-dynamic-waypoint").send(player); - return; - } - // Stellium cost double withdraw = paths.get(waypoint).getCost(); double left = withdraw - playerData.getStellium(); @@ -300,7 +274,6 @@ public class WaypointViewer extends EditableInventory { player.closeInventory(); playerData.warp(waypoint, withdraw); - } } } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/ConfigManager.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/ConfigManager.java index 99225ae3..fa7b69ba 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/ConfigManager.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/ConfigManager.java @@ -26,7 +26,7 @@ public class ConfigManager { public final CommandVerbose commandVerbose = new CommandVerbose(); public boolean overrideVanillaExp, canCreativeCast, passiveSkillsNeedBinding, cobbleGeneratorXP, saveDefaultClassInfo, splitMainExp, splitProfessionExp, disableQuestBossBar, - pvpModeEnabled, pvpModeInvulnerabilityCanDamage, forceClassSelection, enableGlobalSkillTreeGUI, enableSpecificSkillTreeGUI; + pvpModeEnabled, pvpModeInvulnerabilityCanDamage, forceClassSelection, enableGlobalSkillTreeGUI, enableSpecificSkillTreeGUI, waypointAutoPathCalculation; public String partyChatPrefix, noSkillBoundPlaceholder; public ChatColor staminaFull, staminaHalf, staminaEmpty; public long combatLogTimer, lootChestExpireTime, lootChestPlayerCooldown, globalSkillCooldown; @@ -151,6 +151,7 @@ public class ConfigManager { disableQuestBossBar = MMOCore.plugin.getConfig().getBoolean("mmocore-quests.disable-boss-bar"); forceClassSelection = MMOCore.plugin.getConfig().getBoolean("force-class-selection"); waypointWarpTime = MMOCore.plugin.getConfig().getInt("waypoints.default-warp-time"); + waypointAutoPathCalculation = MMOCore.plugin.getConfig().getBoolean("waypoints.auto_path_calculation"); // Combat pvpModeEnabled = config.getBoolean("pvp_mode.enabled"); diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/util/Pair.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/util/Pair.java new file mode 100644 index 00000000..01fb1a87 --- /dev/null +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/util/Pair.java @@ -0,0 +1,47 @@ +package net.Indyuce.mmocore.util; + + +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public class Pair { + private final L left; + private final R right; + + private Pair(L left, R right) { + this.left = left; + this.right = right; + } + + public L getLeft() { + return left; + } + + public R getRight() { + return right; + } + + @NotNull + public String toString() { + return "(" + this.getLeft() + ',' + this.getRight() + ')'; + } + + @NotNull + public static Pair of(L left, R right) { + return new Pair<>(left, right); + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + Pair pair = (Pair) object; + return Objects.equals(left, pair.left) && Objects.equals(right, pair.right); + } + + @Override + public int hashCode() { + return Objects.hash(left, right); + } +} diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/waypoint/Waypoint.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/waypoint/Waypoint.java index 1729d4c2..62edfd13 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/waypoint/Waypoint.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/waypoint/Waypoint.java @@ -5,17 +5,14 @@ import io.lumine.mythic.lib.util.PostLoadAction; import io.lumine.mythic.lib.util.PreloadedObject; import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.util.MMOCoreUtils; import net.Indyuce.mmocore.loot.chest.condition.Condition; import net.Indyuce.mmocore.loot.chest.condition.ConditionInstance; import net.Indyuce.mmocore.player.Unlockable; -import org.apache.commons.lang.Validate; -import org.bukkit.Bukkit; import org.bukkit.Location; -import org.bukkit.World; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.logging.Level; @@ -59,7 +56,7 @@ public class Waypoint implements Unlockable, PreloadedObject { name = Objects.requireNonNull(config.getString("name"), "Could not load waypoint name"); lore = Objects.requireNonNullElse(config.getStringList("lore"), new ArrayList<>()); - loc = readLocation(Objects.requireNonNull(config.getString("location"), "Could not read location")); + loc = MMOCoreUtils.readLocation(Objects.requireNonNull(config.getString("location"), "Could not read location")); radiusSquared = Math.pow(config.getDouble("radius"), 2); warpTime = Math.max(0, config.getInt("warp-time", MMOCore.plugin.configManager.waypointWarpTime)); @@ -131,75 +128,13 @@ public class Waypoint implements Unlockable, PreloadedObject { } /** - * @return Integer.POSITIVE_INFINITY if the way point is not linked - * If it is, cost of the instant travel between the two waypoints. + * @return Double.POSITIVE_INFINITY if the way point is not linked + * If it is, cost of the instant travel between the two waypoints. */ - public double getDirectCost(Waypoint waypoint) { + public double getDirectCost(@NotNull Waypoint waypoint) { return destinations.isEmpty() ? normalCost : destinations.getOrDefault(waypoint, Double.POSITIVE_INFINITY); } - public List getAllPath() { - //All the WayPoints that have been registered - List checkedPoints = new ArrayList<>(); - //All the path - List paths = new ArrayList(); - List pointsToCheck = new ArrayList<>(); - pointsToCheck.add(new WaypointPath(this)); - - while (pointsToCheck.size() != 0) { - WaypointPath checked = pointsToCheck.get(0); - pointsToCheck.remove(0); - // If the point has already been checked, pass - if (checkedPoints.contains(checked.getFinalWaypoint())) - continue; - - paths.add(checked); - checkedPoints.add(checked.getFinalWaypoint()); - - for (Waypoint toCheck : checked.getFinalWaypoint().destinations.keySet()) - if (!checkedPoints.contains(toCheck)) { - WaypointPath toCheckInfo = checked.addWayPoint(toCheck); - // We keep pointsToCheck ordered - pointsToCheck = toCheckInfo.addInOrder(pointsToCheck); - } - } - return paths; - } - - @Nullable - public WaypointPath getPath(Waypoint targetWaypoint) { - //All the WayPoints that have been registered - List checkedPoints = new ArrayList<>(); - //All the path - List paths = new ArrayList(); - List pointsToCheck = new ArrayList<>(); - pointsToCheck.add(new WaypointPath(this)); - - while (pointsToCheck.size() != 0) { - WaypointPath checked = pointsToCheck.get(0); - pointsToCheck.remove(0); - // If the point has already been checked, pass - if (checkedPoints.contains(checked.getFinalWaypoint())) - continue; - - paths.add(checked); - checkedPoints.add(checked.getFinalWaypoint()); - - if (checked.getFinalWaypoint().equals(targetWaypoint)) - return checked; - - for (Waypoint toCheck : checked.getFinalWaypoint().destinations.keySet()) - if (!checkedPoints.contains(toCheck)) { - WaypointPath toCheckInfo = checked.addWayPoint(toCheck); - // We keep pointsToCheck ordered - pointsToCheck = toCheckInfo.addInOrder(pointsToCheck); - } - } - - //If no path has been found we return null - return null; - } - public boolean hasOption(WaypointOption option) { return options.get(option); } @@ -208,6 +143,11 @@ public class Waypoint implements Unlockable, PreloadedObject { return player.getWorld().equals(loc.getWorld()) && player.getLocation().distanceSquared(loc) < radiusSquared; } + @NotNull + public Map getDestinations() { + return destinations; + } + @Override public String toString() { return id; @@ -245,19 +185,4 @@ public class Waypoint implements Unlockable, PreloadedObject { Waypoint waypoint = (Waypoint) o; return id.equals(waypoint.id); } - - private Location readLocation(String string) { - String[] split = string.split(" "); - - World world = Bukkit.getWorld(split[0]); - Validate.notNull(world, "Could not find world with name '" + split[0] + "'"); - - double x = Double.parseDouble(split[1]); - double y = Double.parseDouble(split[2]); - double z = Double.parseDouble(split[3]); - float yaw = split.length > 4 ? (float) Double.parseDouble(split[4]) : 0; - float pitch = split.length > 5 ? (float) Double.parseDouble(split[5]) : 0; - - return new Location(world, x, y, z, yaw, pitch); - } } \ No newline at end of file diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/waypoint/WaypointPath.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/waypoint/WaypointPath.java index e987eff5..4723d3dd 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/waypoint/WaypointPath.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/waypoint/WaypointPath.java @@ -1,29 +1,25 @@ package net.Indyuce.mmocore.waypoint; +import org.jetbrains.annotations.NotNull; + import java.util.ArrayList; import java.util.List; public class WaypointPath { - private final List waypoints; - private double cost; + private final List waypoints = new ArrayList<>(); + private final double cost; - public WaypointPath(Waypoint waypoint) { - this.waypoints = new ArrayList<>(); - this.waypoints.add(waypoint); - cost = 0; + public static final WaypointPath INFINITE = new WaypointPath(Double.POSITIVE_INFINITY); + + public WaypointPath() { + this(0); } - public WaypointPath(Waypoint waypoint, double cost) { - this.waypoints = new ArrayList<>(); - this.waypoints.add(waypoint); - this.cost = cost; - } - - public WaypointPath(List waypoints, double cost) { - this.waypoints = new ArrayList<>(waypoints); + public WaypointPath(double cost) { this.cost = cost; } + @NotNull public List getWaypoints() { return waypoints; } @@ -32,54 +28,25 @@ public class WaypointPath { return cost; } - public WaypointPath addCost(double cost) { - this.cost += cost; - return this; - } + @NotNull + public String displayIntermediaryWayPoints(String splitter, String none) { + if (waypoints.isEmpty()) return none; - public List addInOrder(List pathInfos) { - int index = 0; - while (index < pathInfos.size()) { - if (cost < pathInfos.get(index).cost) { - pathInfos.set(index, this); - return pathInfos; - } - index++; + boolean b = false; + final StringBuilder result = new StringBuilder(); + for (Waypoint waypoint : waypoints) { + if (b) result.append(splitter); + result.append(waypoint.getName()); + if (!b) b = true; } - // If index == pathInfos.size() add the waypoint at the end - pathInfos.add(this); - return pathInfos; + + return result.toString(); } - - /** - * @param dynamic Display the first waypoint if it is dynamic as it is an intermediary point - * @return List with all - */ - public String displayIntermediaryWayPoints(boolean dynamic) { - int beginIndex = dynamic ? 0 : 1; - if (waypoints.size() <= beginIndex + 1) - return "None"; - - String result = ""; - for (int i = beginIndex; i < waypoints.size() - 1; i++) - result += waypoints.get(i).getName() + (i != waypoints.size() - 2 ? ", " : ""); - return result; - } - - public WaypointPath addWayPoint(Waypoint waypoint) { - List newWaypoints = new ArrayList<>(); - newWaypoints.addAll(waypoints); - newWaypoints.add(waypoint); - double cost = this.cost + getFinalWaypoint().getDirectCost(waypoint); - return new WaypointPath(newWaypoints, cost); - } - - public Waypoint getInitialWaypoint() { - return waypoints.get(0); - } - - public Waypoint getFinalWaypoint() { - return waypoints.get(waypoints.size() - 1); + @NotNull + public WaypointPath push(@NotNull Waypoint waypoint, double extraCost) { + final WaypointPath clone = new WaypointPath(this.cost + extraCost); + clone.waypoints.add(waypoint); + return clone; } } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/waypoint/WaypointPathCalculation.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/waypoint/WaypointPathCalculation.java new file mode 100644 index 00000000..ef138f3e --- /dev/null +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/waypoint/WaypointPathCalculation.java @@ -0,0 +1,107 @@ +package net.Indyuce.mmocore.waypoint; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.util.Pair; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class WaypointPathCalculation { + private final PlayerData playerData; + private final Map paths = new HashMap<>(); + + public WaypointPathCalculation(PlayerData playerData) { + this.playerData = playerData; + } + + @NotNull + public WaypointPathCalculation run(@Nullable Waypoint source) { + if (MMOCore.plugin.configManager.waypointAutoPathCalculation) runDijkstra(source); + else runSimple(source); + return this; + } + + public void runSimple(@Nullable Waypoint source) { + + // Direct adjacency + if (source != null) for (Map.Entry adjacent : source.getDestinations().entrySet()) + replaceIfLower(adjacent.getKey(), adjacent.getValue()); + + // Dynamic waypoints + for (Waypoint waypoint : MMOCore.plugin.waypointManager.getAll()) + if (waypoint.hasOption(WaypointOption.DYNAMIC)) replaceIfLower(waypoint, waypoint.getDynamicCost()); + } + + private void replaceIfLower(@NotNull Waypoint adjacent, double cost) { + paths.compute(adjacent, (ignored, path) -> path == null || cost < path.getCost() ? new WaypointPath(cost) : path); + } + + /** + * Runs the Dijkstra algorithm to compute the shortest paths between source and + * all available waypoints to a certain player. Paths using locked waypoints + * are not computed. + * + * @param source Source waypoint node + */ + public void runDijkstra(@Nullable Waypoint source) { + + final PriorityQueue> queue = new PriorityQueue<>(Comparator.comparingDouble(Pair::getRight)); + final Set visited = new HashSet<>(); + + // Initialization + for (Waypoint waypoint : MMOCore.plugin.waypointManager.getAll()) // Dynamic waypoints + if (waypoint.hasOption(WaypointOption.DYNAMIC) && waypoint.mayBeUsedDynamically(playerData.getPlayer())) + init(queue, waypoint, waypoint.getDynamicCost()); + if (source != null) init(queue, source, 0); // !! After dynamic waypoints !! + + // Run Dijkstra + do { + final Waypoint currentNode = queue.remove().getLeft(); + + // Mark as visited + visited.add(currentNode); + + // Iterate over neighbors + final WaypointPath nodePath = path(currentNode); + for (Map.Entry adjacentData : currentNode.getDestinations().entrySet()) { + + // Waypoint is usable? + final Waypoint adjacentNode = adjacentData.getKey(); + if (!(adjacentNode.isUnlockedByDefault() + || adjacentNode.isOnWaypoint(playerData.getPlayer()) + || playerData.hasWaypoint(adjacentNode))) + return; + + final double edgeWeight = adjacentData.getValue(); + + // Update distance if necessary + final WaypointPath adjacentPath = path(adjacentNode); + if (nodePath.getCost() + edgeWeight < adjacentPath.getCost()) + paths.put(adjacentNode, nodePath.push(currentNode, edgeWeight)); + + // Push to queue + if (!visited.contains(adjacentNode)) queue.add(Pair.of(adjacentNode, edgeWeight)); + } + + } while (!queue.isEmpty()); + } + + private void init(@NotNull PriorityQueue> queue, @NotNull Waypoint waypoint, double cost) { + queue.add(Pair.of(waypoint, cost)); + paths.put(waypoint, new WaypointPath(cost)); + } + + private WaypointPath path(Waypoint waypoint) { + return paths.getOrDefault(waypoint, WaypointPath.INFINITE); + } + + /** + * @return Result of the waypoint path calculation + */ + @NotNull + public Map getPaths() { + return paths; + } +} diff --git a/MMOCore-Dist/src/main/resources/config.yml b/MMOCore-Dist/src/main/resources/config.yml index b290d0e7..c3ede4ef 100644 --- a/MMOCore-Dist/src/main/resources/config.yml +++ b/MMOCore-Dist/src/main/resources/config.yml @@ -274,6 +274,18 @@ waypoints: # takes to use the waypoint and teleport to target location. default-warp-time: 100 + # By default, MMOCore runs path calculation to enable players + # to take shortcuts and directly teleport across distant waypoints. + # + # Example: + # - Player is standing at waypoint A. They want to go to waypoint C. + # - Waypoint A is linked to waypoint B, waypoint B is linked to waypoint C + # With this option toggled on, since A -> B -> C is a valid connection, + # MMOCore tells the player they can directly go to C without having to + # first teleport to B. The stellium cost is the sum of the individual + # stelium costs. MMOCore always finds the shortest path between two waypoints. + auto_path_calculation: true + # Change this to the name of the color you want for # the different resource bar placeholders resource-bar-colors: diff --git a/MMOCore-Dist/src/main/resources/default/gui/waypoints.yml b/MMOCore-Dist/src/main/resources/default/gui/waypoints.yml index 27831d4d..312b66e1 100644 --- a/MMOCore-Dist/src/main/resources/default/gui/waypoints.yml +++ b/MMOCore-Dist/src/main/resources/default/gui/waypoints.yml @@ -34,18 +34,7 @@ items: lore: - '{lore}' - - '&7You cannot teleport as the two waypoints are not linked.' - - - # When you cannot teleport to a non dynamic waypoint - not-dynamic: - name: '&a{name}' - item: ENDER_PEARL - - lore: - - '{lore}' - - '&7You cannot teleport as you are not standing on a waypoint.' - + - '&7You do not have access to this waypoint.' current-waypoint: name: '&a{name}' @@ -53,7 +42,7 @@ items: lore: - '{lore}' - - '&7The waypoint you are standing at.' + - '&7You are here.' # When you don't have enough stellium not-enough-stellium: @@ -69,6 +58,9 @@ items: display: name: '&a{name}' item: ENDER_EYE + path: + splitter: ', ' + none: 'None' lore: - '{lore}' - '&7You can teleport to this waypoint.' diff --git a/MMOCore-Dist/src/main/resources/default/messages.yml b/MMOCore-Dist/src/main/resources/default/messages.yml index fa49bfcc..e82f989f 100644 --- a/MMOCore-Dist/src/main/resources/default/messages.yml +++ b/MMOCore-Dist/src/main/resources/default/messages.yml @@ -115,11 +115,10 @@ new-waypoint-book: '%&eYou unlocked the &6{waypoint} &ewaypoint!' not-enough-stellium: '&cYou don''t have enough stellium: you need {more} more.' waypoint-cooldown: '&cPlease wait {cooldown} before using a waypoint again.' not-unlocked-waypoint: '&cYou have not unlocked that waypoint yet.' -not-dynamic-waypoint: '&cYou many only teleport to a non-dynamic waypoint while standing on another waypoint.' standing-on-waypoint: '&cYou are already standing on this waypoint.' warping-canceled: '%&cWaypoint warping canceled.' -warping-comencing: '%&cDO NOT MOVE!&e You will be warped in {left}sec.' -cannot-teleport-to: '&cThe two waypoints are not linked.' +warping-comencing: '%&cDO NOT MOVE!&e You will be teleported in {left}sec.' +cannot-teleport-to: '&cYou do not have access to this waypoint.' # Cash deposit: '&eYou successfully deposited &6{worth}g&e.'