Fixed waypoint path calculation. Option to disable auto waypoint path calculation

This commit is contained in:
Jules 2024-10-06 15:49:28 +02:00
parent f24a1b0a6a
commit c4ae7bfed3
10 changed files with 252 additions and 217 deletions

View File

@ -9,6 +9,7 @@ import io.lumine.mythic.lib.hologram.Hologram;
import io.lumine.mythic.lib.version.VEnchantment; import io.lumine.mythic.lib.version.VEnchantment;
import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.util.Icon; import net.Indyuce.mmocore.util.Icon;
import org.apache.commons.lang.Validate;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.attribute.Attribute; import org.bukkit.attribute.Attribute;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
@ -218,12 +219,7 @@ public class MMOCoreUtils {
return object.toString(); return object.toString();
} }
/** @Deprecated
* 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
*/
public static List<Entity> getNearbyChunkEntities(Location loc) { public static List<Entity> 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 * @return Center location of an entity using its bounding box
*/ */

View File

@ -13,6 +13,7 @@ import net.Indyuce.mmocore.gui.api.item.Placeholders;
import net.Indyuce.mmocore.gui.api.item.SimplePlaceholderItem; import net.Indyuce.mmocore.gui.api.item.SimplePlaceholderItem;
import net.Indyuce.mmocore.waypoint.Waypoint; import net.Indyuce.mmocore.waypoint.Waypoint;
import net.Indyuce.mmocore.waypoint.WaypointPath; import net.Indyuce.mmocore.waypoint.WaypointPath;
import net.Indyuce.mmocore.waypoint.WaypointPathCalculation;
import org.apache.commons.lang.Validate; import org.apache.commons.lang.Validate;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
@ -26,7 +27,6 @@ import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -72,7 +72,7 @@ public class WaypointViewer extends EditableInventory {
public class WaypointItem extends SimplePlaceholderItem<WaypointViewerInventory> { public class WaypointItem extends SimplePlaceholderItem<WaypointViewerInventory> {
private final SimplePlaceholderItem noWaypoint, locked; private final SimplePlaceholderItem noWaypoint, locked;
private final WaypointItemHandler availWaypoint, noStellium, notLinked, notDynamic, currentWayPoint; private final WaypointItemHandler availWaypoint, noStellium, notLinked, currentWayPoint;
public WaypointItem(ConfigurationSection config) { public WaypointItem(ConfigurationSection config) {
super(Material.BARRIER, 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("no-waypoint"), "Could not load 'no-waypoint' config");
Validate.notNull(config.getConfigurationSection("locked"), "Could not load 'locked' 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-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("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("not-enough-stellium"), "Could not load 'not-enough-stellium' config");
Validate.notNull(config.getConfigurationSection("display"), "Could not load 'display' 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")); noWaypoint = new SimplePlaceholderItem(config.getConfigurationSection("no-waypoint"));
locked = new SimplePlaceholderItem(config.getConfigurationSection("locked")); locked = new SimplePlaceholderItem(config.getConfigurationSection("locked"));
notLinked = new WaypointItemHandler(config.getConfigurationSection("not-a-destination"), true); 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); currentWayPoint = new WaypointItemHandler(config.getConfigurationSection("current-waypoint"), true);
noStellium = new WaypointItemHandler(config.getConfigurationSection("not-enough-stellium"), false); noStellium = new WaypointItemHandler(config.getConfigurationSection("not-enough-stellium"), false);
availWaypoint = new WaypointItemHandler(config.getConfigurationSection("display"), false); availWaypoint = new WaypointItemHandler(config.getConfigurationSection("display"), false);
@ -106,23 +106,21 @@ public class WaypointViewer extends EditableInventory {
if (index >= inv.waypoints.size()) if (index >= inv.waypoints.size())
return noWaypoint.display(inv, n); return noWaypoint.display(inv, n);
// Locked waypoint? final Waypoint waypoint = inv.waypoints.get(index);
Waypoint waypoint = inv.waypoints.get(index);
// Current waypoint
if (inv.current != null && inv.current.equals(waypoint)) if (inv.current != null && inv.current.equals(waypoint))
return currentWayPoint.display(inv, n); return currentWayPoint.display(inv, n);
// Locked waypoint
if (!inv.getPlayerData().hasWaypoint(waypoint)) if (!inv.getPlayerData().hasWaypoint(waypoint))
return locked.display(inv, n); return locked.display(inv, n);
// Waypoints are not linked // Waypoints are not linked
if (inv.current != null && !inv.paths.containsKey(waypoint)) if (!inv.paths.containsKey(waypoint))
return notLinked.display(inv, n); return notLinked.display(inv, n);
// Not dynamic waypoint // Normal cost
if (inv.current == null && !inv.paths.containsKey(waypoint))
return notDynamic.display(inv, n);
//Normal cost
if (inv.paths.get(waypoint).getCost() > inv.getPlayerData().getStellium()) if (inv.paths.get(waypoint).getCost() > inv.getPlayerData().getStellium())
return noStellium.display(inv, n); return noStellium.display(inv, n);
@ -132,10 +130,14 @@ public class WaypointViewer extends EditableInventory {
public class WaypointItemHandler extends InventoryItem<WaypointViewerInventory> { public class WaypointItemHandler extends InventoryItem<WaypointViewerInventory> {
private final boolean onlyName; private final boolean onlyName;
private final String splitter, none;
public WaypointItemHandler(ConfigurationSection config, boolean onlyName) { public WaypointItemHandler(ConfigurationSection config, boolean onlyName) {
super(config); super(config);
this.onlyName = onlyName; this.onlyName = onlyName;
this.splitter = config.getString("format_path.splitter", ", ");
this.none = config.getString("format_path.none", "None");
} }
@Override @Override
@ -187,7 +189,7 @@ public class WaypointViewer extends EditableInventory {
holders.register("current_cost", inv.paths.get(waypoint).getCost()); 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("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("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; return holders;
@ -198,7 +200,8 @@ public class WaypointViewer extends EditableInventory {
private final List<Waypoint> waypoints = new ArrayList<>(MMOCore.plugin.waypointManager.getAll()); private final List<Waypoint> waypoints = new ArrayList<>(MMOCore.plugin.waypointManager.getAll());
@Nullable @Nullable
private final Waypoint current; private final Waypoint current;
private final Map<Waypoint, WaypointPath> paths = new HashMap<>();
private Map<Waypoint, WaypointPath> paths;
private int page; private int page;
@ -206,29 +209,7 @@ public class WaypointViewer extends EditableInventory {
super(playerData, editable); super(playerData, editable);
this.current = current; this.current = current;
if (current != null) paths = new WaypointPathCalculation(playerData).run(current).getPaths();
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<Waypoint, Double> 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)));
}
}
}
}
} }
@Override @Override
@ -259,11 +240,10 @@ public class WaypointViewer extends EditableInventory {
String tag = container.has(new NamespacedKey(MMOCore.plugin, "waypointId"), PersistentDataType.STRING) ? String tag = container.has(new NamespacedKey(MMOCore.plugin, "waypointId"), PersistentDataType.STRING) ?
container.get(new NamespacedKey(MMOCore.plugin, "waypointId"), PersistentDataType.STRING) : ""; container.get(new NamespacedKey(MMOCore.plugin, "waypointId"), PersistentDataType.STRING) : "";
if (tag.equals("")) if (tag.isEmpty()) return;
return;
// Locked waypoint? // Locked waypoint?
Waypoint waypoint = MMOCore.plugin.waypointManager.get(tag); final Waypoint waypoint = MMOCore.plugin.waypointManager.get(tag);
if (!playerData.hasWaypoint(waypoint)) { if (!playerData.hasWaypoint(waypoint)) {
ConfigMessage.fromKey("not-unlocked-waypoint").send(player); ConfigMessage.fromKey("not-unlocked-waypoint").send(player);
return; return;
@ -275,18 +255,12 @@ public class WaypointViewer extends EditableInventory {
return; return;
} }
// Waypoint does not have target as destination // No access to that waypoint
if (current != null && current.getPath(waypoint) == null) { if (paths.get(waypoint) == null) {
ConfigMessage.fromKey("cannot-teleport-to").send(player); ConfigMessage.fromKey("cannot-teleport-to").send(player);
return; return;
} }
// Not dynamic waypoint
if (current == null && !paths.containsKey(waypoint)) {
ConfigMessage.fromKey("not-dynamic-waypoint").send(player);
return;
}
// Stellium cost // Stellium cost
double withdraw = paths.get(waypoint).getCost(); double withdraw = paths.get(waypoint).getCost();
double left = withdraw - playerData.getStellium(); double left = withdraw - playerData.getStellium();
@ -300,7 +274,6 @@ public class WaypointViewer extends EditableInventory {
player.closeInventory(); player.closeInventory();
playerData.warp(waypoint, withdraw); playerData.warp(waypoint, withdraw);
} }
} }
} }

View File

@ -26,7 +26,7 @@ public class ConfigManager {
public final CommandVerbose commandVerbose = new CommandVerbose(); public final CommandVerbose commandVerbose = new CommandVerbose();
public boolean overrideVanillaExp, canCreativeCast, passiveSkillsNeedBinding, cobbleGeneratorXP, saveDefaultClassInfo, splitMainExp, splitProfessionExp, disableQuestBossBar, 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 String partyChatPrefix, noSkillBoundPlaceholder;
public ChatColor staminaFull, staminaHalf, staminaEmpty; public ChatColor staminaFull, staminaHalf, staminaEmpty;
public long combatLogTimer, lootChestExpireTime, lootChestPlayerCooldown, globalSkillCooldown; public long combatLogTimer, lootChestExpireTime, lootChestPlayerCooldown, globalSkillCooldown;
@ -151,6 +151,7 @@ public class ConfigManager {
disableQuestBossBar = MMOCore.plugin.getConfig().getBoolean("mmocore-quests.disable-boss-bar"); disableQuestBossBar = MMOCore.plugin.getConfig().getBoolean("mmocore-quests.disable-boss-bar");
forceClassSelection = MMOCore.plugin.getConfig().getBoolean("force-class-selection"); forceClassSelection = MMOCore.plugin.getConfig().getBoolean("force-class-selection");
waypointWarpTime = MMOCore.plugin.getConfig().getInt("waypoints.default-warp-time"); waypointWarpTime = MMOCore.plugin.getConfig().getInt("waypoints.default-warp-time");
waypointAutoPathCalculation = MMOCore.plugin.getConfig().getBoolean("waypoints.auto_path_calculation");
// Combat // Combat
pvpModeEnabled = config.getBoolean("pvp_mode.enabled"); pvpModeEnabled = config.getBoolean("pvp_mode.enabled");

View File

@ -0,0 +1,47 @@
package net.Indyuce.mmocore.util;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public class Pair<L, R> {
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 <L, R> Pair<L, R> 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);
}
}

View File

@ -5,17 +5,14 @@ import io.lumine.mythic.lib.util.PostLoadAction;
import io.lumine.mythic.lib.util.PreloadedObject; import io.lumine.mythic.lib.util.PreloadedObject;
import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.PlayerData; 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.Condition;
import net.Indyuce.mmocore.loot.chest.condition.ConditionInstance; import net.Indyuce.mmocore.loot.chest.condition.ConditionInstance;
import net.Indyuce.mmocore.player.Unlockable; import net.Indyuce.mmocore.player.Unlockable;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
import java.util.logging.Level; 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"); name = Objects.requireNonNull(config.getString("name"), "Could not load waypoint name");
lore = Objects.requireNonNullElse(config.getStringList("lore"), new ArrayList<>()); 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); radiusSquared = Math.pow(config.getDouble("radius"), 2);
warpTime = Math.max(0, config.getInt("warp-time", MMOCore.plugin.configManager.waypointWarpTime)); 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 * @return Double.POSITIVE_INFINITY if the way point is not linked
* If it is, cost of the instant travel between the two waypoints. * 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); return destinations.isEmpty() ? normalCost : destinations.getOrDefault(waypoint, Double.POSITIVE_INFINITY);
} }
public List<WaypointPath> getAllPath() {
//All the WayPoints that have been registered
List<Waypoint> checkedPoints = new ArrayList<>();
//All the path
List<WaypointPath> paths = new ArrayList();
List<WaypointPath> 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<Waypoint> checkedPoints = new ArrayList<>();
//All the path
List<WaypointPath> paths = new ArrayList();
List<WaypointPath> 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) { public boolean hasOption(WaypointOption option) {
return options.get(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; return player.getWorld().equals(loc.getWorld()) && player.getLocation().distanceSquared(loc) < radiusSquared;
} }
@NotNull
public Map<Waypoint, Double> getDestinations() {
return destinations;
}
@Override @Override
public String toString() { public String toString() {
return id; return id;
@ -245,19 +185,4 @@ public class Waypoint implements Unlockable, PreloadedObject {
Waypoint waypoint = (Waypoint) o; Waypoint waypoint = (Waypoint) o;
return id.equals(waypoint.id); 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);
}
} }

View File

@ -1,29 +1,25 @@
package net.Indyuce.mmocore.waypoint; package net.Indyuce.mmocore.waypoint;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class WaypointPath { public class WaypointPath {
private final List<Waypoint> waypoints; private final List<Waypoint> waypoints = new ArrayList<>();
private double cost; private final double cost;
public WaypointPath(Waypoint waypoint) { public static final WaypointPath INFINITE = new WaypointPath(Double.POSITIVE_INFINITY);
this.waypoints = new ArrayList<>();
this.waypoints.add(waypoint); public WaypointPath() {
cost = 0; this(0);
} }
public WaypointPath(Waypoint waypoint, double cost) { public WaypointPath(double cost) {
this.waypoints = new ArrayList<>();
this.waypoints.add(waypoint);
this.cost = cost;
}
public WaypointPath(List<Waypoint> waypoints, double cost) {
this.waypoints = new ArrayList<>(waypoints);
this.cost = cost; this.cost = cost;
} }
@NotNull
public List<Waypoint> getWaypoints() { public List<Waypoint> getWaypoints() {
return waypoints; return waypoints;
} }
@ -32,54 +28,25 @@ public class WaypointPath {
return cost; return cost;
} }
public WaypointPath addCost(double cost) { @NotNull
this.cost += cost; public String displayIntermediaryWayPoints(String splitter, String none) {
return this; if (waypoints.isEmpty()) return none;
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;
} }
public List<WaypointPath> addInOrder(List<WaypointPath> pathInfos) { return result.toString();
int index = 0;
while (index < pathInfos.size()) {
if (cost < pathInfos.get(index).cost) {
pathInfos.set(index, this);
return pathInfos;
}
index++;
}
// If index == pathInfos.size() add the waypoint at the end
pathInfos.add(this);
return pathInfos;
} }
@NotNull
/** public WaypointPath push(@NotNull Waypoint waypoint, double extraCost) {
* @param dynamic Display the first waypoint if it is dynamic as it is an intermediary point final WaypointPath clone = new WaypointPath(this.cost + extraCost);
* @return List with all clone.waypoints.add(waypoint);
*/ return clone;
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<Waypoint> 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);
} }
} }

View File

@ -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<Waypoint, WaypointPath> 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<Waypoint, Double> 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<Pair<Waypoint, Double>> queue = new PriorityQueue<>(Comparator.comparingDouble(Pair::getRight));
final Set<Waypoint> 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<Waypoint, Double> 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<Pair<Waypoint, Double>> 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<Waypoint, WaypointPath> getPaths() {
return paths;
}
}

View File

@ -274,6 +274,18 @@ waypoints:
# takes to use the waypoint and teleport to target location. # takes to use the waypoint and teleport to target location.
default-warp-time: 100 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 # Change this to the name of the color you want for
# the different resource bar placeholders # the different resource bar placeholders
resource-bar-colors: resource-bar-colors:

View File

@ -34,18 +34,7 @@ items:
lore: lore:
- '{lore}' - '{lore}'
- '&7You cannot teleport as the two waypoints are not linked.' - '&7You do not have access to this waypoint.'
# 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.'
current-waypoint: current-waypoint:
name: '&a{name}' name: '&a{name}'
@ -53,7 +42,7 @@ items:
lore: lore:
- '{lore}' - '{lore}'
- '&7The waypoint you are standing at.' - '&7You are here.'
# When you don't have enough stellium # When you don't have enough stellium
not-enough-stellium: not-enough-stellium:
@ -69,6 +58,9 @@ items:
display: display:
name: '&a{name}' name: '&a{name}'
item: ENDER_EYE item: ENDER_EYE
path:
splitter: ', '
none: 'None'
lore: lore:
- '{lore}' - '{lore}'
- '&7You can teleport to this waypoint.' - '&7You can teleport to this waypoint.'

View File

@ -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.' not-enough-stellium: '&cYou don''t have enough stellium: you need {more} more.'
waypoint-cooldown: '&cPlease wait {cooldown} before using a waypoint again.' waypoint-cooldown: '&cPlease wait {cooldown} before using a waypoint again.'
not-unlocked-waypoint: '&cYou have not unlocked that waypoint yet.' 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.' standing-on-waypoint: '&cYou are already standing on this waypoint.'
warping-canceled: '%&cWaypoint warping canceled.' warping-canceled: '%&cWaypoint warping canceled.'
warping-comencing: '%&cDO NOT MOVE!&e You will be warped in {left}sec.' warping-comencing: '%&cDO NOT MOVE!&e You will be teleported in {left}sec.'
cannot-teleport-to: '&cThe two waypoints are not linked.' cannot-teleport-to: '&cYou do not have access to this waypoint.'
# Cash # Cash
deposit: '&eYou successfully deposited &6{worth}g&e.' deposit: '&eYou successfully deposited &6{worth}g&e.'