14 Commits

Author SHA1 Message Date
803c8ced51 plugin: add bstats telemetry before 0.3 release for feedback 2021-06-15 13:29:43 -07:00
342be770a1 regionpostitemwatcher: implement crafting recipes for anchors, charges, and compasses 2021-06-15 13:08:10 -07:00
0856b398eb geometry: bordermesh: fix region linking to prevent routes that run through unrelated regions 2021-06-15 13:07:29 -07:00
bb1d043fb2 ui: playerteleporter: reduce the rotation error from a misfire that is just plain mean on long jumps 2021-06-15 13:06:59 -07:00
f2253e6801 dynmap: update dynmap to fix borders, show costs on routes, and use a special icon for hubs 2021-06-15 13:06:09 -07:00
7d2a00c996 pom: fix jar loading in runtime 2021-06-15 07:01:37 -07:00
b856227e4a pom: bump version to rc3 2021-06-14 13:30:13 -07:00
e8f86289aa src: swap out region charges for XP levels as fuel, implement a mode where posts malfunction if you dont have enough levels 2021-06-14 13:30:04 -07:00
72611f7574 region: add item flags to banner items to hide pattern list in ui 2021-06-14 13:28:48 -07:00
43a499997d regionpostitemwatcher: fix compasses 2021-06-14 13:28:13 -07:00
d1045685e5 regionpostitemwatcher: remove more api that was removed in 1.17 2021-06-14 13:27:21 -07:00
fbfe174ed8 playernotifier: drop paper api that doesnt exist in 1.17 2021-06-14 13:25:43 -07:00
89830a1904 geometry: bordermesh: make sure we can still do travel even if a mesh cannot be generated 2021-06-14 13:24:51 -07:00
5ad959a02f plugin.yml: drop unused dep 2021-06-14 13:24:15 -07:00
11 changed files with 286 additions and 68 deletions

13
pom.xml
View File

@ -4,17 +4,24 @@
<groupId>us.camin.regions</groupId> <groupId>us.camin.regions</groupId>
<artifactId>Regions</artifactId> <artifactId>Regions</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<version>0.2.99-rc2</version> <version>0.2.99-rc5</version>
<name>regions</name> <name>regions</name>
<url>http://maven.apache.org</url> <url>http://maven.apache.org</url>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>
<version>2.2.1</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>com.comphenix.protocol</groupId> <groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId> <artifactId>ProtocolLib</artifactId>
<version>4.6.0</version> <version>4.6.0</version>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.papermc</groupId> <groupId>io.papermc</groupId>
@ -86,6 +93,10 @@
<pattern>io.papermc.lib</pattern> <pattern>io.papermc.lib</pattern>
<shadedPattern>us.camin.regions.paperlib</shadedPattern> <!-- Replace this --> <shadedPattern>us.camin.regions.paperlib</shadedPattern> <!-- Replace this -->
</relocation> </relocation>
<relocation>
<pattern>org.bstats</pattern>
<shadedPattern>us.camin.regions.bstats</shadedPattern>
</relocation>
</relocations> </relocations>
</configuration> </configuration>
<executions> <executions>

View File

@ -91,17 +91,10 @@ public class DynmapEventRelay implements Listener {
log.info("Could not generate polygon for region " + region.name()); log.info("Could not generate polygon for region " + region.name());
continue; continue;
} }
boolean isFrontier = geom.isFrontier(region); AreaMarker marker = m_borderSet.createAreaMarker(null, region.name(), false, world.getName(), polygon.x, polygon.z, false);
if (!isFrontier) { marker.setFillStyle(0.7, region.color().getColor().asRGB());
AreaMarker marker = m_borderSet.createAreaMarker(null, region.name(), false, world.getName(), polygon.x, polygon.z, false); marker.setLineStyle(2, 0.8, region.color().getColor().asRGB());
marker.setFillStyle(0.7, region.color().getColor().asRGB()); m_borderMarkers.get(world).add(marker);
marker.setLineStyle(2, 0.8, region.color().getColor().asRGB());
m_borderMarkers.get(world).add(marker);
} else {
PolyLineMarker marker = m_borderSet.createPolyLineMarker(null, region.name(), false, world.getName(), polygon.x, polygon.y, polygon.z, false);
marker.setLineStyle(2, 0.5, region.color().getColor().asRGB());
m_borderMarkers.get(world).add(marker);
}
// Add a line between each region, for teleportations // Add a line between each region, for teleportations
double thickness = Math.max(1, Math.log(geom.neighbors(region).size()) * 2.75); double thickness = Math.max(1, Math.log(geom.neighbors(region).size()) * 2.75);
@ -109,9 +102,13 @@ public class DynmapEventRelay implements Listener {
double x[] = { neighbor.location().getBlockX(), region.location().getBlockX() }; double x[] = { neighbor.location().getBlockX(), region.location().getBlockX() };
double y[] = { 64, 64 }; double y[] = { 64, 64 };
double z[] = { neighbor.location().getBlockZ(), region.location().getBlockZ() }; double z[] = { neighbor.location().getBlockZ(), region.location().getBlockZ() };
PolyLineMarker marker = m_routesSet.createPolyLineMarker(null, null, false, world.getName(), x, y, z, false); String label = neighbor.name() + " / " + region.name();
marker.setLineStyle((int)Math.ceil(thickness), 0.5, region.color().getColor().asRGB()); String description = "<p>Travel Cost to " + neighbor.name() + ": " + region.getTravelCost(neighbor) + "</p>";
m_borderMarkers.get(world).add(marker); description += "<p>Travel Cost to " + region.name() + ": " + neighbor.getTravelCost(region) + "</p>";
PolyLineMarker routeMarker = m_routesSet.createPolyLineMarker(null, label, true, world.getName(), x, y, z, false);
routeMarker.setDescription(description);
routeMarker.setLineStyle((int)Math.ceil(thickness), 0.5, region.color().getColor().asRGB());
m_borderMarkers.get(world).add(routeMarker);
} }
} }
} }
@ -119,11 +116,17 @@ public class DynmapEventRelay implements Listener {
private void createMarkerForRegion(Region region) { private void createMarkerForRegion(Region region) {
Location loc = region.location(); Location loc = region.location();
MarkerIcon icon = m_api.getMarkerIcon("compass"); MarkerIcon icon = m_api.getMarkerIcon("compass");
if (region.isHub()) {
icon = m_api.getMarkerIcon("world");
}
CircleMarker circleMarker = m_routesSet.createCircleMarker(null, region.name(), false, loc.getWorld().getName(), loc.getX(), loc.getY(), loc.getZ(), 60, 60, false); CircleMarker circleMarker = m_routesSet.createCircleMarker(null, region.name(), false, loc.getWorld().getName(), loc.getX(), loc.getY(), loc.getZ(), 60, 60, false);
Marker marker = m_centerSet.createMarker(null, region.name(), loc.getWorld().getName(), loc.getX(), loc.getY(), loc.getZ(), icon, false); Marker marker = m_centerSet.createMarker(null, region.name(), loc.getWorld().getName(), loc.getX(), loc.getY(), loc.getZ(), icon, false);
circleMarker.setFillStyle(0.75, region.color().getColor().asRGB()); circleMarker.setFillStyle(0.75, region.color().getColor().asRGB());
circleMarker.setLineStyle(0, 0, 0); circleMarker.setLineStyle(0, 0, 0);
String desc = "<h2>" + region.name() + "</h2>"; String desc = "<h2>" + region.name() + "</h2>";
if (region.isHub()) {
desc += "<p><strong>World Hub</strong></p>";
}
marker.setDescription(desc); marker.setDescription(desc);
circleMarker.setDescription(desc); circleMarker.setDescription(desc);
} }

View File

@ -96,8 +96,7 @@ public class PlayerNotifier implements Listener {
} catch (Exception e) { } catch (Exception e) {
} }
} else { } else {
Title title = new Title.Builder().title(event.newRegion.name()).subtitle("Population: "+pop+" Altitude: " + altitude).build(); event.player.sendTitle(event.newRegion.coloredName(), "Population: " + pop + " Altitude: " + altitude);
event.player.sendTitle(title);
} }
} }

View File

@ -23,8 +23,13 @@ import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.configuration.serialization.ConfigurationSerialization; import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.World;
import org.dynmap.markers.MarkerAPI; import org.dynmap.markers.MarkerAPI;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.SingleLineChart;
import us.camin.regions.commands.RegionCommand; import us.camin.regions.commands.RegionCommand;
import us.camin.regions.commands.RegionOpCommand; import us.camin.regions.commands.RegionOpCommand;
import us.camin.regions.commands.RegionsCommand; import us.camin.regions.commands.RegionsCommand;
@ -86,6 +91,17 @@ public class Plugin extends JavaPlugin {
getServer().getPluginManager().registerEvents(new PlayerInventoryTeleporter(this, m_regions), this); getServer().getPluginManager().registerEvents(new PlayerInventoryTeleporter(this, m_regions), this);
getServer().getPluginManager().registerEvents(new RegionPostItemWatcher(this, m_regions), this); getServer().getPluginManager().registerEvents(new RegionPostItemWatcher(this, m_regions), this);
getServer().getPluginManager().registerEvents(new RegionPostInteractionWatcher(this, m_regions), this); getServer().getPluginManager().registerEvents(new RegionPostInteractionWatcher(this, m_regions), this);
// PluginID is from bstats.org for CaminusRegions
Metrics metrics = new Metrics(this, 11705);
metrics.addCustomChart(new SingleLineChart("regions", () -> {
int allRegions = 0;
for(World w : getServer().getWorlds()) {
allRegions += m_regions.regionsForWorld(w).size();
}
return allRegions;
}
));
} }
public void loadRegions() { public void loadRegions() {

View File

@ -28,6 +28,7 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.inventory.meta.BannerMeta; import org.bukkit.inventory.meta.BannerMeta;
import org.bukkit.material.MaterialData; import org.bukkit.material.MaterialData;
import org.bukkit.inventory.ItemFlag;
import us.camin.regions.config.RegionConfiguration; import us.camin.regions.config.RegionConfiguration;
import us.camin.regions.ui.Colors; import us.camin.regions.ui.Colors;
@ -47,12 +48,12 @@ public class Region {
private boolean m_isHub = false; private boolean m_isHub = false;
public Region(String name, Location location) { public Region(String name, Location location) {
m_location = location.toBlockLocation(); m_location = location.getBlock().getLocation();
m_name = name; m_name = name;
} }
public Region(String name, Location location, int visits, int charges, DyeColor color) { public Region(String name, Location location, int visits, int charges, DyeColor color) {
m_location = location.toBlockLocation(); m_location = location.getBlock().getLocation();
m_name = name; m_name = name;
m_visits = visits; m_visits = visits;
m_charges = charges; m_charges = charges;
@ -147,6 +148,28 @@ public class Region {
m_charges += charges; m_charges += charges;
} }
public int getTravelCost(Region destination) {
int baseCost = getBaseTravelCost(destination);
if (destination.isHub()) {
// Travel *to* a hub is 50% cheaper, before charges applied
baseCost /= 2;
}
return Math.max(1, (int)(baseCost / (Math.min(4, m_charges + 1))));
}
public int getBaseTravelCost(Region destination) {
if (m_isHub && destination.isHub()) {
// Free travel between hubs
return 0;
} else if (m_isHub) {
// Max cost 1 level for travel *from* a hub
return 1;
}
double distance = teleportLocation().distance(destination.teleportLocation());
double blocksPerXP = 500;
return Math.max(1, (int)(distance / blocksPerXP));
}
int m_visits = 0; int m_visits = 0;
int m_charges = 0; int m_charges = 0;
@ -317,6 +340,8 @@ public class Region {
ItemStack item = new ItemStack(bannerIconMaterial()); ItemStack item = new ItemStack(bannerIconMaterial());
BannerMeta bannerMeta = (BannerMeta)item.getItemMeta(); BannerMeta bannerMeta = (BannerMeta)item.getItemMeta();
bannerMeta.setPatterns(bannerPatterns()); bannerMeta.setPatterns(bannerPatterns());
bannerMeta.addItemFlags(ItemFlag.HIDE_POTION_EFFECTS);
bannerMeta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES);
item.setItemMeta(bannerMeta); item.setItemMeta(bannerMeta);
return item; return item;
} }

View File

@ -89,12 +89,13 @@ public class RegionPostInteractionWatcher implements Listener {
Location lanternRegion = nearest.interactLocation().add(0, 1, 0); Location lanternRegion = nearest.interactLocation().add(0, 1, 0);
boolean isInteracted = false; boolean isInteracted = false;
if (clickedBlock != null) { if (clickedBlock != null) {
isInteracted |= clickedBlock.getBlockKey() == interactRegion.toBlockKey();
isInteracted |= clickedBlock.getBlockKey() == lanternRegion.toBlockKey(); isInteracted |= clickedBlock.getLocation().equals(interactRegion.getBlock().getLocation());
isInteracted |= nearest.interactLocation().add(1, 0, 0).toBlockKey() == clickedBlock.getBlockKey(); isInteracted |= clickedBlock.getLocation().equals(lanternRegion.getBlock().getLocation());
isInteracted |= nearest.interactLocation().add(-1, 0, 0).toBlockKey() == clickedBlock.getBlockKey(); isInteracted |= nearest.interactLocation().add(1, 0, 0).getBlock().getLocation().equals(clickedBlock.getLocation());
isInteracted |= nearest.interactLocation().add(0, 0, 1).toBlockKey() == clickedBlock.getBlockKey(); isInteracted |= nearest.interactLocation().add(-1, 0, 0).getBlock().getLocation().equals(clickedBlock.getLocation());
isInteracted |= nearest.interactLocation().add(0, 0, -1).toBlockKey() == clickedBlock.getBlockKey(); isInteracted |= nearest.interactLocation().add(0, 0, 1).getBlock().getLocation().equals(clickedBlock.getLocation());
isInteracted |= nearest.interactLocation().add(0, 0, -1).getBlock().getLocation().equals(clickedBlock.getLocation());
} }
if (isInteracted) { if (isInteracted) {
event.setCancelled(true); event.setCancelled(true);
@ -110,7 +111,8 @@ public class RegionPostInteractionWatcher implements Listener {
builder.updateLantern(); builder.updateLantern();
}); });
m_plugin.saveRegions(); m_plugin.saveRegions();
player.setItemInHand(handStack.subtract()); handStack.setAmount(handStack.getAmount()-1);
player.setItemInHand(handStack);
m_plugin.getServer().getPluginManager().callEvent(new PlayerAddRegionChargeEvent(player, nearest)); m_plugin.getServer().getPluginManager().callEvent(new PlayerAddRegionChargeEvent(player, nearest));
// TODO: Make this configurable and disablable // TODO: Make this configurable and disablable
player.giveExp(1); player.giveExp(1);
@ -126,10 +128,8 @@ public class RegionPostInteractionWatcher implements Listener {
RegionPostBuilder builder = new RegionPostBuilder(nearest, m_plugin); RegionPostBuilder builder = new RegionPostBuilder(nearest, m_plugin);
builder.build(); builder.build();
}); });
} else if (nearest.charges() > 0 || player.hasPermission("regions.bypass.charges")) {
m_plugin.getServer().getPluginManager().callEvent(new PlayerPostInteractEvent(player, nearest));
} else { } else {
player.sendMessage("This region post is not charged. Right click on it while sneaking and holding a Region Post Charge."); m_plugin.getServer().getPluginManager().callEvent(new PlayerPostInteractEvent(player, nearest));
} }
} }
} }

View File

@ -22,6 +22,7 @@ import org.bukkit.event.Listener;
import org.bukkit.event.Event; import org.bukkit.event.Event;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.ChatColor;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -30,6 +31,9 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.CompassMeta; import org.bukkit.inventory.meta.CompassMeta;
import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemFlag;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.RecipeChoice;
import org.bukkit.inventory.ShapedRecipe;
import org.bukkit.NamespacedKey;
import org.bukkit.event.block.Action; import org.bukkit.event.block.Action;
import us.camin.regions.ui.RegionPostBuilder; import us.camin.regions.ui.RegionPostBuilder;
@ -44,21 +48,49 @@ public class RegionPostItemWatcher implements Listener {
public RegionPostItemWatcher(Plugin plugin, RegionManager manager) { public RegionPostItemWatcher(Plugin plugin, RegionManager manager) {
m_manager = manager; m_manager = manager;
m_plugin = plugin; m_plugin = plugin;
// TODO: Make recipe-based creation of items configurable/disablable
NamespacedKey chargeKey = new NamespacedKey(m_plugin, "region_post_charge");
ShapedRecipe chargeRecipe = new ShapedRecipe(chargeKey, m_theChargeItem);
chargeRecipe.shape("DDD", "DGD", "DDD");
chargeRecipe.setIngredient('D', Material.GLOWSTONE_DUST);
chargeRecipe.setIngredient('G', Material.GHAST_TEAR);
NamespacedKey anchorKey = new NamespacedKey(m_plugin, "region_post_anchor");
ShapedRecipe anchorRecipe = new ShapedRecipe(anchorKey, m_theAnchor);
anchorRecipe.shape("DDD", "DGD", "DDD");
anchorRecipe.setIngredient('D', new RecipeChoice.ExactChoice(m_theChargeItem));
anchorRecipe.setIngredient('G', Material.LANTERN);
NamespacedKey compassKey = new NamespacedKey(m_plugin, "region_post_compass");
ShapedRecipe compassRecipe = new ShapedRecipe(compassKey, m_theCompass);
// Uses four fewer charges, slightly cheaper.
// TODO: Maybe we just want this to be glowstone instead of effectively 4
// ghast tears?
compassRecipe.shape(" D ", "DGD", " D ");
compassRecipe.setIngredient('D', new RecipeChoice.ExactChoice(m_theChargeItem));
compassRecipe.setIngredient('G', Material.COMPASS);
m_plugin.getServer().addRecipe(chargeRecipe);
m_plugin.getServer().addRecipe(anchorRecipe);
m_plugin.getServer().addRecipe(compassRecipe);
} }
static public ItemStack createCompass(Region r) { static public ItemStack createCompass(Region r) {
ItemStack stack = new ItemStack(Material.COMPASS); ItemStack stack = new ItemStack(Material.COMPASS);
ItemMeta meta = stack.getItemMeta(); ItemMeta meta = stack.getItemMeta();
List<String> lore = new ArrayList<String>(); List<String> lore = new ArrayList<String>();
lore.add("Right click to locate the nearest Region Post");
if (r == null) { if (r == null) {
lore.add("Right click to locate the nearest Region Post"); meta.setDisplayName(ChatColor.DARK_PURPLE + "Region Compass");
lore.add("Tracking: " + ChatColor.MAGIC + "NOWHERE IN PARTICULAR");
lore.add("Coordinates: " + ChatColor.MAGIC + "0000" + ChatColor.RESET + ", " + ChatColor.MAGIC + "0000");
} else { } else {
CompassMeta compassMeta = (CompassMeta)meta; CompassMeta compassMeta = (CompassMeta)meta;
compassMeta.setDisplayName(r.name() + " Region Compass"); compassMeta.setDisplayName(ChatColor.DARK_PURPLE + "Region Compass (" + r.coloredName() + ChatColor.RESET + ChatColor.DARK_PURPLE + ")");
compassMeta.setLodestone(r.location()); compassMeta.setLodestone(r.location());
compassMeta.setLodestoneTracked(false); compassMeta.setLodestoneTracked(false);
lore.add("Right click to locate the nearest Region Post"); lore.add("Tracking: " + r.coloredName());
lore.add("Tracking: " + r.name());
lore.add("Coordinates: " + r.location().getX() + ", " + r.location().getY()); lore.add("Coordinates: " + r.location().getX() + ", " + r.location().getY());
} }
meta.setLore(lore); meta.setLore(lore);
@ -110,7 +142,7 @@ public class RegionPostItemWatcher implements Listener {
if (stack.getType() == m_theCompass.getType()) { if (stack.getType() == m_theCompass.getType()) {
ItemMeta meta = stack.getItemMeta(); ItemMeta meta = stack.getItemMeta();
ItemMeta theItemMeta = m_theCompass.getItemMeta(); ItemMeta theItemMeta = m_theCompass.getItemMeta();
if (meta.getItemFlags() == theItemMeta.getItemFlags()) { if (meta.getLore() != null && meta.getLore().get(0).equals(theItemMeta.getLore().get(0))) {
return true; return true;
} }
} }
@ -169,7 +201,8 @@ public class RegionPostItemWatcher implements Listener {
RegionPostBuilder builder = new RegionPostBuilder(r, m_plugin); RegionPostBuilder builder = new RegionPostBuilder(r, m_plugin);
builder.build(); builder.build();
}); });
player.setItemInHand(handStack.subtract()); handStack.setAmount(handStack.getAmount()-1);
player.setItemInHand(handStack);
m_plugin.regionManager().addRegion(r); m_plugin.regionManager().addRegion(r);
m_plugin.saveRegions(); m_plugin.saveRegions();
player.sendMessage("You established the region "+r.coloredName()); player.sendMessage("You established the region "+r.coloredName());

View File

@ -24,23 +24,31 @@ public class BorderMesh {
Map<Region, Polygon> m_polygons; Map<Region, Polygon> m_polygons;
Map<Region, Set<Region>> m_neighbors; Map<Region, Set<Region>> m_neighbors;
// TODO: Probably need to cache the neighbors after doing all this
// intersection work! Should be generated during triangulate()
public Collection<Region> neighbors(Region region) { public Collection<Region> neighbors(Region region) {
Collection<Region> ret = new ArrayList<Region>(); Collection<Region> ret = new ArrayList<Region>();
if (m_neighbors.containsKey(region)) { if (m_neighbors.containsKey(region)) {
Collection<Region> allNeighbors = m_neighbors.get(region); Collection<Region> allNeighbors = m_neighbors.get(region);
if (false /*isFrontier(region)*/) { for(Region neighbor : allNeighbors) {
//log.trace("Region " + region.name() + " is a frontier"); int crossings = 0;
for(Region neighbor : allNeighbors) { Vector2D start = new Vector2D(region.location().getBlockX(), region.location().getBlockZ());
if (!isFrontier(neighbor)) { Vector2D neighborEnd = new Vector2D(neighbor.location().getBlockX(), neighbor.location().getBlockZ());
ret.add(neighbor); for(Region distantNeighbor : m_regions) {
} else { if (distantNeighbor.equals(neighbor)) {
//log.trace("Not linking " + region.name() + " to " + neighbor.name() + " as it is also a frontier"); continue;
}
Polygon poly = m_polygons.get(distantNeighbor);
for(Polygon.Segment edge : poly.segments()) {
// Check if the line from region->neighbor intersects with
// any polygon
if (doIntersect(start, neighborEnd, edge.start, edge.end)) {
crossings++;
} }
}
} }
} else { if (crossings == 1 || crossings == 0) {
//log.trace("Region " + region.name() + " is not a frontier"); ret.add(neighbor);
for(Region neighbor : allNeighbors) {
ret.add(neighbor);
} }
} }
} }
@ -58,6 +66,24 @@ public class BorderMesh {
public double y[]; public double y[];
public double z[]; public double z[];
public class Segment {
public Vector2D start;
public Vector2D end;
public Segment(Vector2D start, Vector2D end) {
this.start = start;
this.end = end;
}
}
public List<Segment> segments() {
List<Segment> ret = new ArrayList<Segment>();
for(int i = 0; i < x.length-1; i++) {
ret.add(new Segment(new Vector2D(x[i], z[i]), new Vector2D(x[i+1], z[i+1])));
}
ret.add(new Segment(new Vector2D(x[x.length-1], z[z.length-1]), new Vector2D(x[0], z[0])));
return ret;
}
public Polygon(List<Vector2D> points) { public Polygon(List<Vector2D> points) {
x = new double[points.size()]; x = new double[points.size()];
y = new double[points.size()]; y = new double[points.size()];
@ -93,6 +119,27 @@ public class BorderMesh {
} }
} }
private int orientation(Vector2D p, Vector2D q, Vector2D r) {
int val = (int)((q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y));
if (val == 0) return 0; // colinear
return (val > 0) ? 1 : 2; // Clockwise or counter-clockwise
}
private boolean doIntersect(Vector2D p1, Vector2D q1, Vector2D p2, Vector2D q2) {
int o1 = orientation(p1, q1, p2);
int o2 = orientation(p1, q1, q2);
int o3 = orientation(p2, q2, p1);
int o4 = orientation(p2, q2, q1);
if (o1 != o2 && o3 != o4) {
return true;
}
// It probably isn't possible to generate a route that is colinear with a
// region border, so assume it isn't intersecting.
return false;
}
public Polygon polygonForRegion(Region r) { public Polygon polygonForRegion(Region r) {
return m_polygons.get(r); return m_polygons.get(r);
} }
@ -113,7 +160,16 @@ public class BorderMesh {
log.warning("Got a null pointer when triangulating, skipping world."); log.warning("Got a null pointer when triangulating, skipping world.");
return false; return false;
} catch (NotEnoughPointsException e) { } catch (NotEnoughPointsException e) {
log.info("Not enough points to triangulate mesh. Add more regions!"); log.info("Not enough points to triangulate mesh, all regions will be connected to each other. Add more regions!");
for(Region region : m_regions) {
HashSet<Region> neighbors = new HashSet<Region>();
for(Region neighbor : m_regions) {
if (neighbor != region) {
neighbors.add(neighbor);
}
}
m_neighbors.put(region, neighbors);
}
return false; return false;
} }
@ -179,16 +235,6 @@ public class BorderMesh {
} }
}*/ }*/
public boolean isFrontier(Region region) {
Polygon poly = polygonForRegion(region);
Location center = region.location();
if (poly.contains(center.getBlockX(), center.getBlockZ())) {
return false;
} else {
return true;
}
}
private boolean vecEquals(Vector2D a, Vector2D b) { private boolean vecEquals(Vector2D a, Vector2D b) {
return a.x == b.x && a.y == b.y; return a.x == b.x && a.y == b.y;
} }

View File

@ -22,6 +22,7 @@ import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Optional; import java.util.Optional;
import java.util.Random;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
@ -29,7 +30,10 @@ import org.bukkit.event.Event.Result;
import org.bukkit.DyeColor; import org.bukkit.DyeColor;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.Sound; import org.bukkit.Sound;
import org.bukkit.Particle;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.block.BlockFace;
import org.bukkit.block.Block;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -40,6 +44,7 @@ import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemFlag;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.material.MaterialData; import org.bukkit.material.MaterialData;
import org.bukkit.util.Vector;
import us.camin.regions.Plugin; import us.camin.regions.Plugin;
import us.camin.regions.Region; import us.camin.regions.Region;
@ -99,22 +104,25 @@ public class PlayerInventoryTeleporter implements Listener {
.filter((r) -> r.name().equals(selectedName)) .filter((r) -> r.name().equals(selectedName))
.findFirst(); .findFirst();
if (destination.isPresent()) { if (destination.isPresent()) {
player.sendMessage("Teleporting you there now..."); Location targetLocation = destination.get().teleportLocation();
nearest.addCharges(-1); int cost = nearest.getTravelCost(destination.get());
destination.get().addCharges(1); int chargesConsumed = Math.min(nearest.charges(), (int)cost - player.getLevel());
double payment = player.getLevel() + chargesConsumed;
double accuracy = 0.8 + (payment / cost) * 0.2;
if (chargesConsumed > 0) {
nearest.addCharges(-chargesConsumed);
player.sendMessage("You don't have enough XP. "+ chargesConsumed + " charges were consumed.");
}
player.giveExpLevels(-(int)cost);
m_plugin.getServer().getScheduler().runTask(m_plugin, () -> { m_plugin.getServer().getScheduler().runTask(m_plugin, () -> {
RegionPostBuilder builder = new RegionPostBuilder(nearest, m_plugin); RegionPostBuilder builder = new RegionPostBuilder(nearest, m_plugin);
builder.updateLantern(); builder.updateLantern();
}); });
m_plugin.saveRegions(); m_plugin.saveRegions();
new PlayerTeleporter(player, nearest.teleportLocation(), destination.get().teleportLocation(), m_plugin).teleport().thenRun(() -> { new PlayerTeleporter(player, nearest.teleportLocation(), targetLocation, m_plugin, accuracy).teleport().thenRun(() -> {
RegionPostBuilder builder = new RegionPostBuilder(destination.get(), m_plugin); RegionPostBuilder builder = new RegionPostBuilder(destination.get(), m_plugin);
builder.updateLantern(); builder.updateLantern();
}); });
if (nearest.charges() == 0) {
nearest.location().getWorld().playSound(player.getLocation(), Sound.ENTITY_ITEM_BREAK, (float)1.0, (float)1.0);
//player.sendMessage("You've consumed this region post's last charge! You won't be able to return without recharging it.");
}
} else { } else {
player.sendMessage("There is no region with that name. This is a bug."); player.sendMessage("There is no region with that name. This is a bug.");
} }
@ -169,6 +177,15 @@ public class PlayerInventoryTeleporter implements Listener {
meta.addItemFlags(ItemFlag.HIDE_ENCHANTS); meta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
meta.addEnchant(Enchantment.SOUL_SPEED, 1, true); meta.addEnchant(Enchantment.SOUL_SPEED, 1, true);
} }
int cost = event.region.getTravelCost(region);
int baseCost = event.region.getBaseTravelCost(region);
if (cost != baseCost) {
lore.add(ChatColor.WHITE + "Travel cost: " + ChatColor.YELLOW + ChatColor.STRIKETHROUGH + Math.round(baseCost) + ChatColor.RESET + ChatColor.YELLOW + ChatColor.BOLD + " " + Math.round(cost) + " levels");
} else if (cost > 0) {
lore.add(ChatColor.WHITE + "Travel cost: " + ChatColor.YELLOW + Math.round(cost) + " levels");
} else {
lore.add(ChatColor.WHITE + "Travel cost: " + ChatColor.GREEN + "Free!");
}
lore.add(ChatColor.WHITE + "Distance: " + ChatColor.YELLOW + Math.round(region.location().distance(event.region.location()))); lore.add(ChatColor.WHITE + "Distance: " + ChatColor.YELLOW + Math.round(region.location().distance(event.region.location())));
lore.add(ChatColor.WHITE + "Population: " + ChatColor.YELLOW + m_plugin.playerWatcher().playersInRegion(region).size()); lore.add(ChatColor.WHITE + "Population: " + ChatColor.YELLOW + m_plugin.playerWatcher().playersInRegion(region).size());
lore.add(ChatColor.WHITE + "Altitude: " + ChatColor.YELLOW + altitude); lore.add(ChatColor.WHITE + "Altitude: " + ChatColor.YELLOW + altitude);
@ -180,6 +197,9 @@ public class PlayerInventoryTeleporter implements Listener {
} }
} }
lore.add("Nearby connections: " + String.join(", ", neighborNames)); lore.add("Nearby connections: " + String.join(", ", neighborNames));
if (event.player.getLevel() < cost) {
lore.add("" + ChatColor.RED + ChatColor.BOLD + "You don't have enough XP! Travel may be dangerous...");
}
} else { } else {
lore.add("" + ChatColor.BOLD + ChatColor.GOLD + "You haven't discovered this location yet!"); lore.add("" + ChatColor.BOLD + ChatColor.GOLD + "You haven't discovered this location yet!");
lore.add(ChatColor.WHITE + "Distance: " + ChatColor.YELLOW + ChatColor.MAGIC + Math.round(region.location().distance(event.region.location()))); lore.add(ChatColor.WHITE + "Distance: " + ChatColor.YELLOW + ChatColor.MAGIC + Math.round(region.location().distance(event.region.location())));

View File

@ -6,8 +6,12 @@ import org.bukkit.scheduler.BukkitTask;
import org.bukkit.Particle; import org.bukkit.Particle;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.ChatColor;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.Sound; import org.bukkit.Sound;
import org.bukkit.block.BlockFace;
import org.bukkit.block.Block;
import java.util.Random;
import io.papermc.lib.PaperLib; import io.papermc.lib.PaperLib;
import us.camin.regions.Plugin; import us.camin.regions.Plugin;
@ -15,6 +19,7 @@ import java.util.logging.Logger;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffect;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import org.bukkit.util.Vector;
public class PlayerTeleporter { public class PlayerTeleporter {
Logger log = Logger.getLogger("Regions.PlayerTeleporter"); Logger log = Logger.getLogger("Regions.PlayerTeleporter");
@ -23,12 +28,30 @@ public class PlayerTeleporter {
private Location m_src; private Location m_src;
private Location m_dest; private Location m_dest;
private Plugin m_plugin; private Plugin m_plugin;
private double m_accuracy;
public PlayerTeleporter(Player p, Location src, Location dest, Plugin plugin) { public PlayerTeleporter(Player p, Location src, Location dest, Plugin plugin, double accuracy) {
this.m_player = p; this.m_player = p;
this.m_src = src; this.m_src = src;
this.m_dest = dest; this.m_dest = dest;
this.m_plugin = plugin; this.m_plugin = plugin;
this.m_accuracy = accuracy;
if (m_accuracy < 1) {
Random rand = new Random();
Vector travelVec = m_dest.toVector().subtract(m_src.toVector()).normalize();
double angleDelta = (Math.PI / 5) * (rand.nextGaussian() - 0.5) * (1-accuracy);
travelVec.rotateAroundY(angleDelta);
double distanceToDest = m_src.distance(m_dest);
double distanceDelta = (distanceToDest/3) * (rand.nextGaussian() - 0.5) * (1 - accuracy);
double targetDistance = distanceToDest - distanceDelta;
travelVec.multiply(targetDistance);
m_dest = m_src.clone().add(travelVec);
}
} }
public CompletableFuture<Void> teleport() { public CompletableFuture<Void> teleport() {
@ -37,6 +60,22 @@ public class PlayerTeleporter {
BukkitTask hoverGenerator = scheduler.runTaskTimer(m_plugin, () -> applyHoverEffect(), 0, 10); BukkitTask hoverGenerator = scheduler.runTaskTimer(m_plugin, () -> applyHoverEffect(), 0, 10);
BukkitTask particleGenerator = scheduler.runTaskTimer(m_plugin, () -> spawnHoverParticles(), 0, 1); BukkitTask particleGenerator = scheduler.runTaskTimer(m_plugin, () -> spawnHoverParticles(), 0, 1);
BukkitTask noiseGenerator = scheduler.runTaskTimer(m_plugin, () -> {
if (m_accuracy < 1) {
m_player.getLocation().getWorld().playSound(m_player.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, (float)0.5, (float)1.0);
m_player.getLocation().getWorld().spawnParticle(Particle.EXPLOSION_LARGE, m_player.getLocation(), 3, 1, 1, 1);
}
}, 20, 15);
if (m_accuracy < 1) {
m_player.sendMessage("Teleporting you there-ish now...");
m_plugin.getServer().getScheduler().runTaskLater(m_plugin, () -> {
m_player.sendMessage("" + ChatColor.RED + ChatColor.BOLD + "Oh no!" + ChatColor.RESET + ChatColor.YELLOW + " The region post is malfunctioning!");
}, 40);
} else {
m_player.sendMessage("Teleporting you there now...");
}
playTeleportSound(); playTeleportSound();
m_plugin.getServer().getScheduler().runTaskLater(m_plugin, () -> { m_plugin.getServer().getScheduler().runTaskLater(m_plugin, () -> {
@ -46,11 +85,23 @@ public class PlayerTeleporter {
m_plugin.getServer().getScheduler().runTaskLater(m_plugin, () -> { m_plugin.getServer().getScheduler().runTaskLater(m_plugin, () -> {
spawnTeleportStartParticles(); spawnTeleportStartParticles();
PaperLib.teleportAsync(m_player, m_dest, TeleportCause.COMMAND).thenAccept(res -> { PaperLib.teleportAsync(m_player, m_dest, TeleportCause.COMMAND).thenAccept(res -> {
Block targetBlock = m_dest.getBlock();
if (!targetBlock.isPassable() && !targetBlock.getRelative(BlockFace.DOWN).isPassable()) {
while (!(targetBlock.isPassable() && targetBlock.getRelative(BlockFace.UP).isEmpty() && !targetBlock.getRelative(BlockFace.DOWN).isPassable())) {
targetBlock = targetBlock.getRelative(BlockFace.UP);
}
m_dest = targetBlock.getLocation().add(0.5, 0, 0.5);
m_player.teleport(m_dest, TeleportCause.COMMAND);
}
m_player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 60, 1, false, false, false)); m_player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 60, 1, false, false, false));
m_player.addPotionEffect(new PotionEffect(PotionEffectType.SLOW, 40, 1, false, false, false)); m_player.addPotionEffect(new PotionEffect(PotionEffectType.SLOW, 40, 1, false, false, false));
if (m_accuracy < 1) {
m_player.addPotionEffect(new PotionEffect(PotionEffectType.SLOW_FALLING, 20 * 5, 1, false, true, false));
}
spawnEndParticles(); spawnEndParticles();
particleGenerator.cancel(); particleGenerator.cancel();
hoverGenerator.cancel(); hoverGenerator.cancel();
noiseGenerator.cancel();
ret.complete(null); ret.complete(null);
}); });
}, 20 * 6); }, 20 * 6);
@ -63,11 +114,22 @@ public class PlayerTeleporter {
} }
void spawnEndParticles() { void spawnEndParticles() {
World world = m_player.getLocation().getWorld(); final World world = m_player.getLocation().getWorld();
world.playSound(m_dest, Sound.BLOCK_PORTAL_TRAVEL, (float)0.5, (float)1.0); world.playSound(m_dest, Sound.BLOCK_PORTAL_TRAVEL, (float)0.5, (float)1.0);
world.spawnParticle(Particle.CLOUD, m_dest, 70, 1, 1, 1); world.spawnParticle(Particle.CLOUD, m_dest, 70, 1, 1, 1);
world.spawnParticle(Particle.SPELL_MOB, m_dest, 5, 2, 2, 2); world.spawnParticle(Particle.SPELL_MOB, m_dest, 5, 2, 2, 2);
world.spawnParticle(Particle.PORTAL, m_player.getLocation(), 70, 1, 1, 1); world.spawnParticle(Particle.PORTAL, m_player.getLocation(), 70, 1, 1, 1);
if (m_accuracy < 1) {
BukkitTask fireGenerator = m_plugin.getServer().getScheduler().runTaskTimer(m_plugin, () -> {
world.spawnParticle(Particle.FLAME, m_player.getLocation(), 80, 1, 1, 1, 0);
}, 0, 8);
m_plugin.getServer().getScheduler().runTaskLater(m_plugin, () -> {
fireGenerator.cancel();
}, 20 * 8);
m_player.sendMessage("" + ChatColor.RED + ChatColor.BOLD + "Ouch!" + ChatColor.RESET + ChatColor.YELLOW + " You didn't have enough XP and the region post malfunctioned.");
m_player.damage(5 * (1.0-m_accuracy));
world.playSound(m_player.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, (float)1.0, (float)1.0);
}
} }
void applyHoverEffect() { void applyHoverEffect() {
@ -84,5 +146,8 @@ public class PlayerTeleporter {
m_src.getWorld().playSound(m_src, Sound.BLOCK_PORTAL_TRIGGER, (float)0.5, (float)0.6); m_src.getWorld().playSound(m_src, Sound.BLOCK_PORTAL_TRIGGER, (float)0.5, (float)0.6);
m_src.getWorld().playSound(m_src, Sound.BLOCK_RESPAWN_ANCHOR_DEPLETE, (float)0.5, (float)1.0); m_src.getWorld().playSound(m_src, Sound.BLOCK_RESPAWN_ANCHOR_DEPLETE, (float)0.5, (float)1.0);
m_src.getWorld().playSound(m_dest, Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, (float)0.5, (float)1.0); m_src.getWorld().playSound(m_dest, Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, (float)0.5, (float)1.0);
if (m_accuracy < 1) {
m_src.getWorld().playSound(m_src, Sound.ENTITY_ITEM_BREAK, (float)1.0, (float)1.0);
}
} }
} }

View File

@ -4,7 +4,7 @@ author: Torrie Fischer <tdfischer@hackerbots.net>
website: http://hackerbots.net/ website: http://hackerbots.net/
version: ${version} version: ${version}
api-version: 1.16 api-version: 1.16
softdepend: [dynmap, HolographicDisplays, ProtocolLib] softdepend: [dynmap, ProtocolLib]
commands: commands:
regions: regions:
description: "List available regions." description: "List available regions."