18 Commits

Author SHA1 Message Date
669fab22cd regionpostitemwatcher: rewrite region charge recipe to be cheaper 2022-09-18 12:31:41 +02:00
221539001e rip pl3xmap 2022-09-18 12:31:20 +02:00
61d0af1a05 pom: version bump 2022-09-18 12:26:11 +02:00
75087b23e1 region: jump cost to hub is always 1, but spokes are more expensive 2022-09-11 22:28:54 +02:00
8af0909110 playernotifier: fix 1.17 protocol support 2021-07-09 10:05:12 -07:00
9944a7bcdb dynmapeventrelay: fix method visibility 2021-07-08 07:56:53 -07:00
e21115cc65 geometry: bordermesh: fix bug with one-way routes, move route calculation into triangulate()
Fixes #19
2021-07-08 07:55:50 -07:00
51fdc80542 pl3xmap: use lantern icons for region posts 2021-07-08 07:49:45 -07:00
ff70dd255b plugin: Add some preliminary pl3xmap support 2021-07-08 00:34:12 -07:00
b5a28e40cf Release 0.3.0 2021-07-07 21:57:12 -07:00
4bfce1fd8d version: rc6 2021-06-15 14:46:57 -07:00
81960d14ce playernotifier: drop last usage of deprecated paperlib Title system 2021-06-15 14:46:31 -07:00
4bbada298f readme++ 2021-06-15 14:45:53 -07:00
e3afbdc3c7 plugin: add bstats telemetry before 0.3 release for feedback 2021-06-15 13:29:43 -07:00
034527c208 regionpostitemwatcher: implement crafting recipes for anchors, charges, and compasses 2021-06-15 13:08:10 -07:00
092ed9c6d0 geometry: bordermesh: fix region linking to prevent routes that run through unrelated regions 2021-06-15 13:07:29 -07:00
ac14a78d7f 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
eaa8313cd1 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
15 changed files with 308 additions and 114 deletions

View File

@ -1 +1,71 @@
# Regions
A plugin to carve up your minecraft world into named regions.
For most of minecraftian history, players, server owners, and content builders
have sought to find a way around one of the least exciting problems in
minecraft: How to travel long distances on a big world.
In 2010, Mojang gave us minecarts. Using some clever physics glitches,
minecrafters devised minecart boosters to send them through distant lands at a
modest speed.
Later that year, we all set sail for the infinite seas upon our new boats.
Travel was swift, provided water.
Soon after, we were blessed with the bright magicks of redstone and powered
rails. No longer did minecrafters need to rely on janky collision physics to
move ourselves through the non-aquatic world.
With the Beta 1.9 release, Ender pearls and speed potions were introduced. We
catapulted ourselves to terrifying new heights and found the world that much
smaller.
After the Beta era, Mojang bestowed upon us a terrifying and awesome power:
Nether portals. Soon long distance travel was a reasonable idea, if you didn't
mind losing your entire inventory to an errant ghast or lava pool.
The 1.9 update allowed us to take to the sky with Elytra, and 1.11 sent us into
the distant horizon with firework rockets. With enough determination, a compass,
and some gunpowder, the world was all that much smaller to us.
And yet, we remain unsatiated. Dissatisfied with the high cost of elytra and the
regular need to move great distances quickly, a great number of server plugins
that included teleportation proliferated the pages of spigotmc.org.
And yet still, we remain dissatisfied. Typing out a /warp or /home command is
trivial. Instantaneous teleportation at your fingertips might sound great, but
there remains a distinct un-minecraftian feel about it.
What if there was a more immersive way to add fast travel to your server?
What if your players didn't need to do impossible feats like scrying some runes
into a "chat box", something out of place from the minecraft world?
For your consideration: **Regions**
## Features
- Point-to-point teleportation with a GUI
![Teleportation GUI](docs/use-region-post.gif)
- Create a point of interest in your world, give it a name, apply a banner.
![Creation Animation](docs/create-region-post.gif)
- Fast travel routes between POIs are automatically established.
- Create World Hubs, accessable from any other POI on the world
- Players can only travel to POI's they've already explored
![Discovery Animation](docs/discover-region-post.gif)
- An incredibly cool and flashy teleportation effect
- Pay for your fast travel with XP levels
- Attempting to jump without enough XP might lead to a dangerous misfire,
dropping you an unexpected distance from your destination.
- Level up your region posts with craftable Region Post Charges
- Not enough XP? No worries, you can pay for the ticket with a post's stored
charges
- Craft a Region Compass to locate the nearest region post
- Wrap a latern in charges to create an anchor with which any player can create
their own local region post
![Crafting](docs/craft-region-items.gif)
- Restrict any of the above features using permissions
- Get a spiffy notification whenever you cross a region's border and enter a new
land
- Dynmap integration
- Asynchronous chunk loading and teleportation on Paper servers that all but
eliminates teleportation-induced lag

BIN
docs/craft-region-items.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

BIN
docs/create-region-post.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 MiB

BIN
docs/discover-region-post.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 MiB

BIN
docs/use-region-post.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 MiB

21
pom.xml
View File

@ -4,17 +4,23 @@
<groupId>us.camin.regions</groupId>
<artifactId>Regions</artifactId>
<packaging>jar</packaging>
<version>0.2.99-rc4</version>
<version>0.3.1</version>
<name>regions</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>
<version>2.2.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>4.6.0</version>
<version>4.7.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
@ -44,12 +50,6 @@
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@ -87,6 +87,10 @@
<pattern>io.papermc.lib</pattern>
<shadedPattern>us.camin.regions.paperlib</shadedPattern> <!-- Replace this -->
</relocation>
<relocation>
<pattern>org.bstats</pattern>
<shadedPattern>us.camin.regions.bstats</shadedPattern>
</relocation>
</relocations>
</configuration>
<executions>
@ -167,6 +171,7 @@
<id>papermc</id>
<url>http://papermc.io/repo/repository/maven-public/</url>
</repository>
<repository><id>pl3x-repo</id><url>http://repo.pl3x.net/</url></repository>
<repository><id>dynmap-repo</id><url>http://repo.mikeprimm.com/</url></repository>
<repository><id>imagej</id><url>http://maven.imagej.net/content/repositories/public/</url></repository>
</repositories>

View File

@ -71,7 +71,7 @@ public class DynmapEventRelay implements Listener {
}
}
public void updatePolygons(World world) {
private void updatePolygons(World world) {
List<GenericMarker> oldMarkers = m_borderMarkers.get(world);
if (oldMarkers != null) {
for(GenericMarker marker : oldMarkers) {
@ -91,17 +91,10 @@ public class DynmapEventRelay implements Listener {
log.info("Could not generate polygon for region " + region.name());
continue;
}
boolean isFrontier = geom.isFrontier(region);
if (!isFrontier) {
AreaMarker marker = m_borderSet.createAreaMarker(null, region.name(), false, world.getName(), polygon.x, polygon.z, false);
marker.setFillStyle(0.7, region.color().getColor().asRGB());
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
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 y[] = { 64, 64 };
double z[] = { neighbor.location().getBlockZ(), region.location().getBlockZ() };
PolyLineMarker marker = m_routesSet.createPolyLineMarker(null, null, false, world.getName(), x, y, z, false);
marker.setLineStyle((int)Math.ceil(thickness), 0.5, region.color().getColor().asRGB());
m_borderMarkers.get(world).add(marker);
String label = neighbor.name() + " / " + region.name();
String description = "<p>Travel Cost to " + neighbor.name() + ": " + region.getTravelCost(neighbor) + "</p>";
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) {
Location loc = region.location();
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);
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.setLineStyle(0, 0, 0);
String desc = "<h2>" + region.name() + "</h2>";
if (region.isHub()) {
desc += "<p><strong>World Hub</strong></p>";
}
marker.setDescription(desc);
circleMarker.setDescription(desc);
}

View File

@ -34,8 +34,8 @@ import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.wrappers.EnumWrappers.TitleAction;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
import com.destroystokyo.paper.Title;
import java.util.logging.Logger;
import us.camin.regions.events.PlayerMoveInEvent;
@ -50,9 +50,64 @@ public class PlayerNotifier implements Listener {
Logger log = Logger.getLogger("Regions.PlayerNotifier");
RegionManager m_manager;
Plugin m_plugin;
ProtocolManager m_protocolManager;
int m_protoVersion = 0;
public PlayerNotifier(Plugin plugin, RegionManager manager) {
m_manager = manager;
m_plugin = plugin;
m_protocolManager = ProtocolLibrary.getProtocolManager();
if (m_protocolManager != null) {
m_protoVersion = MinecraftProtocolVersion.getCurrentVersion();
}
}
private void sendTitle(Player player, WrappedChatComponent title, WrappedChatComponent subtitle) {
// Title packet format changed in 1.17
if (m_protoVersion < 755) {
PacketContainer setTitle = m_protocolManager.createPacket(PacketType.Play.Server.TITLE);
setTitle.getChatComponents().write(0, title);
PacketContainer setSubtitle = m_protocolManager.createPacket(PacketType.Play.Server.TITLE);
setSubtitle.getChatComponents().write(0, subtitle);
setSubtitle.getTitleActions().write(0, TitleAction.SUBTITLE);
try {
m_protocolManager.sendServerPacket(player, setTitle);
m_protocolManager.sendServerPacket(player, setSubtitle);
} catch (Exception e) {
}
} else {
PacketContainer setTitle = m_protocolManager.createPacket(PacketType.Play.Server.SET_TITLE_TEXT);
setTitle.getChatComponents().write(0, title);
PacketContainer setSubtitle = m_protocolManager.createPacket(PacketType.Play.Server.SET_SUBTITLE_TEXT);
setSubtitle.getChatComponents().write(0, subtitle);
try {
m_protocolManager.sendServerPacket(player, setTitle);
m_protocolManager.sendServerPacket(player, setSubtitle);
} catch (Exception e) {
}
}
}
private void sendActionBar(Player player, WrappedChatComponent text) {
// Title packet format changed in 1.17
if (m_protoVersion < 755) {
PacketContainer setActionBar = m_protocolManager.createPacket(PacketType.Play.Server.TITLE);
setActionBar.getChatComponents().write(0, text);
setActionBar.getTitleActions().write(0, TitleAction.ACTIONBAR);
try {
m_protocolManager.sendServerPacket(player, setActionBar);
} catch (Exception e) {
}
} else {
PacketContainer setActionBar = m_protocolManager.createPacket(PacketType.Play.Server.SET_ACTION_BAR_TEXT);
setActionBar.getChatComponents().write(0, text);
try {
m_protocolManager.sendServerPacket(player, setActionBar);
} catch (Exception e) {
}
}
}
@EventHandler
@ -72,30 +127,16 @@ public class PlayerNotifier implements Listener {
int pop = m_plugin.playerWatcher().playersInRegion(event.newRegion).size();
Location center = event.newRegion.location();
int altitude = center.getBlockY();
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
if (protocolManager != null) {
PacketContainer chatMessage = protocolManager.createPacket(PacketType.Play.Server.TITLE);
if (m_protocolManager != null) {
String colorHex = "#" + String.format("%06X", event.newRegion.color().getColor().asRGB());
chatMessage.getChatComponents().write(0, WrappedChatComponent.fromJson("{text:\"" + event.newRegion.name() + "\", color: \""+colorHex+"\"}"));
try {
protocolManager.sendServerPacket(event.player, chatMessage);
} catch (Exception e) {
}
chatMessage = protocolManager.createPacket(PacketType.Play.Server.TITLE);
chatMessage.getChatComponents().write(0, WrappedChatComponent.fromJson("{text:\"Population: " + pop + " Altitude: "+ altitude + "\", color: \"#ffffff\"}"));
chatMessage.getTitleActions().write(0, TitleAction.SUBTITLE);
try {
protocolManager.sendServerPacket(event.player, chatMessage);
} catch (Exception e) {
}
chatMessage = protocolManager.createPacket(PacketType.Play.Server.TITLE);
chatMessage.getChatComponents().write(0, WrappedChatComponent.fromJson("{text:\"Now entering " + event.newRegion.name() + "\", color: \""+colorHex+"\"}"));
chatMessage.getTitleActions().write(0, TitleAction.ACTIONBAR);
try {
protocolManager.sendServerPacket(event.player, chatMessage);
} catch (Exception e) {
}
WrappedChatComponent titleComponent = WrappedChatComponent.fromJson("{text:\"" + event.newRegion.name() + "\", color: \""+colorHex+"\"}");
WrappedChatComponent subtitleComponent = WrappedChatComponent.fromJson("{text:\"Population: " + pop + " Altitude: "+ altitude + "\", color: \"#ffffff\"}");
WrappedChatComponent actionBarComponent = WrappedChatComponent.fromJson("{text:\"Now entering " + event.newRegion.name() + "\", color: \""+colorHex+"\"}");
sendTitle(event.player, titleComponent, subtitleComponent);
sendActionBar(event.player, actionBarComponent);
} else {
// Fallback to approximated colors
event.player.sendTitle(event.newRegion.coloredName(), "Population: " + pop + " Altitude: " + altitude);
}
}
@ -107,28 +148,17 @@ public class PlayerNotifier implements Listener {
if (event.region.markSeenByPlayer(event.player)) {
event.player.playSound(event.region.location(), Sound.UI_TOAST_CHALLENGE_COMPLETE, (float)1, (float)1);
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
if (protocolManager != null) {
PacketContainer chatMessage = protocolManager.createPacket(PacketType.Play.Server.TITLE);
if (m_protocolManager != null) {
String colorHex = "#" + String.format("%06X", event.region.color().getColor().asRGB());
chatMessage.getChatComponents().write(0, WrappedChatComponent.fromJson("{text:\"Region discovered\", color: \""+colorHex+"\"}"));
try {
protocolManager.sendServerPacket(event.player, chatMessage);
} catch (Exception e) {
}
chatMessage = protocolManager.createPacket(PacketType.Play.Server.TITLE);
chatMessage.getChatComponents().write(0, WrappedChatComponent.fromJson("{text:\"You discovered the region " + event.region.name() + "\", color: \""+colorHex+"\"}"));
chatMessage.getTitleActions().write(0, TitleAction.SUBTITLE);
try {
protocolManager.sendServerPacket(event.player, chatMessage);
} catch (Exception e) {
}
WrappedChatComponent discoverTitle = WrappedChatComponent.fromJson("{text:\"Region discovered\", color: \""+colorHex+"\"}");
WrappedChatComponent discoverSubtitle = WrappedChatComponent.fromJson("{text:\"You discovered the region " + event.region.name() + "\", color: \""+colorHex+"\"}");
sendTitle(event.player, discoverTitle, discoverSubtitle);
} else {
//FIXME: also show pop/alt subtitle
Title title = new Title.Builder().title("Region Discovered").subtitle(event.region.name()).build();
event.player.sendMessage("You discovered the region " + event.region.name());
event.player.sendTitle(title);
// Fallback to approximated colors
// TODO: Apply region color to the rest of the title and subtitle
// text
event.player.sendTitle("Region Discovered", "You discovered the region " + event.region.coloredName());
}
// TODO: Make this configurable and disablable

View File

@ -23,8 +23,13 @@ import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.World;
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.RegionOpCommand;
import us.camin.regions.commands.RegionsCommand;
@ -73,6 +78,7 @@ public class Plugin extends JavaPlugin {
if (markerAPI != null) {
DynmapEventRelay regionHandler = new DynmapEventRelay (this, markerAPI);
getServer().getPluginManager().registerEvents(regionHandler, this);
log.info("Dynmap support enabled.");
} else {
log.info("Dynmap marker API not found. Disabling map support.");
}
@ -86,6 +92,17 @@ public class Plugin extends JavaPlugin {
getServer().getPluginManager().registerEvents(new PlayerInventoryTeleporter(this, m_regions), this);
getServer().getPluginManager().registerEvents(new RegionPostItemWatcher(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() {

View File

@ -151,8 +151,8 @@ public class Region {
public int getTravelCost(Region destination) {
int baseCost = getBaseTravelCost(destination);
if (destination.isHub()) {
// Travel *to* a hub is 50% cheaper, before charges applied
baseCost /= 2;
// Travel *to* a hub is always 1
baseCost = 1;
}
return Math.max(1, (int)(baseCost / (Math.min(4, m_charges + 1))));
}
@ -166,7 +166,7 @@ public class Region {
return 1;
}
double distance = teleportLocation().distance(destination.teleportLocation());
double blocksPerXP = 500;
double blocksPerXP = 768;
return Math.max(1, (int)(distance / blocksPerXP));
}

View File

@ -31,6 +31,9 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.CompassMeta;
import org.bukkit.inventory.ItemFlag;
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 us.camin.regions.ui.RegionPostBuilder;
@ -45,6 +48,32 @@ public class RegionPostItemWatcher implements Listener {
public RegionPostItemWatcher(Plugin plugin, RegionManager manager) {
m_manager = manager;
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.PURPUR_BLOCK);
chargeRecipe.setIngredient('G', Material.QUARTZ);
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 ", " G ", " ");
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) {
@ -53,6 +82,9 @@ public class RegionPostItemWatcher implements Listener {
List<String> lore = new ArrayList<String>();
lore.add("Right click to locate the nearest Region Post");
if (r == null) {
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 {
CompassMeta compassMeta = (CompassMeta)meta;
compassMeta.setDisplayName(ChatColor.DARK_PURPLE + "Region Compass (" + r.coloredName() + ChatColor.RESET + ChatColor.DARK_PURPLE + ")");

View File

@ -25,26 +25,10 @@ public class BorderMesh {
Map<Region, Set<Region>> m_neighbors;
public Collection<Region> neighbors(Region region) {
Collection<Region> ret = new ArrayList<Region>();
if (m_neighbors.containsKey(region)) {
Collection<Region> allNeighbors = m_neighbors.get(region);
if (false /*isFrontier(region)*/) {
//log.trace("Region " + region.name() + " is a frontier");
for(Region neighbor : allNeighbors) {
if (!isFrontier(neighbor)) {
ret.add(neighbor);
} else {
//log.trace("Not linking " + region.name() + " to " + neighbor.name() + " as it is also a frontier");
return m_neighbors.get(region);
}
}
} else {
//log.trace("Region " + region.name() + " is not a frontier");
for(Region neighbor : allNeighbors) {
ret.add(neighbor);
}
}
}
return ret;
return new ArrayList<Region>();
}
public BorderMesh(Collection<Region> regions) {
@ -58,6 +42,24 @@ public class BorderMesh {
public double y[];
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) {
x = new double[points.size()];
y = new double[points.size()];
@ -93,6 +95,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) {
return m_polygons.get(r);
}
@ -144,10 +167,12 @@ public class BorderMesh {
}
}
HashMap<Region, Set<Region>> allNeighborSet = new HashMap<Region, Set<Region>>();
// Now we go back through our region mesh to generate vornoi points
for(Region region : m_regions) {
ArrayList<Vector2D> points = new ArrayList<Vector2D>();
HashSet<Region> neighbors = new HashSet<Region>();
HashSet<Region> allNeighbors = new HashSet<Region>();
log.info("Executing voronoi transform...");
for(Triangle tri : triangleSoup) {
if (tri.region == region) {
@ -158,44 +183,56 @@ public class BorderMesh {
Location neighborLoc = neighbor.location();
Vector2D neighborCenter = new Vector2D(neighborLoc.getBlockX(), neighborLoc.getBlockZ());
if (vecEquals(tri.a, neighborCenter) || vecEquals(tri.b, neighborCenter) || vecEquals(tri.c, neighborCenter)) {
neighbors.add(neighbor);
allNeighbors.add(neighbor);
}
}
points.add(tri.circumcenter());
}
}
allNeighborSet.put(region, allNeighbors);
// Sort points into a renderable polygon based on direction to center
Location loc = region.location();
Vector2D regionCenter = new Vector2D(loc.getBlockX(), loc.getBlockZ());
points.sort((Vector2D a, Vector2D b) -> Double.compare(direction(a, regionCenter), direction(b, regionCenter)));
log.info("Border for " + region.name() + " is defined by " + points.size() + " points");
log.fine("Border for " + region.name() + " is defined by " + points.size() + " points");
Polygon polygon = new Polygon(points);
m_polygons.put(region, polygon);
m_neighbors.put(region, neighbors);
}
return true;
}
/*public boolean validNeighbors(Region regionA, Region regionB) {
Polygon polyA = polygonForRegion(regionA);
Polygon polyB = polygonForRegion(regionA);
Location center = region.location();
if (poly.contains(center.getBlockX(), center.getBlockZ())) {
return false;
} else {
return true;
// Now that we have the borders, we generate valid routes that cross
// zero or one border lines at most.
for(Region region : m_regions) {
Set<Region> routedNeighbors = new HashSet<Region>();
for(Region neighbor : allNeighborSet.get(region)) {
int crossings = 0;
Vector2D start = new Vector2D(region.location().getBlockX(), region.location().getBlockZ());
Vector2D neighborEnd = new Vector2D(neighbor.location().getBlockX(), neighbor.location().getBlockZ());
for(Region distantNeighbor : m_regions) {
if (distantNeighbor.equals(neighbor) || distantNeighbor.equals(region)) {
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)) {
log.fine("Intersect " + distantNeighbor.name());
crossings++;
}
}
}
log.fine("Route: " + region.name() + " -> " + neighbor.name() + "\t" + crossings);
if (crossings == 1 || crossings == 0) {
routedNeighbors.add(neighbor);
}
}
}*/
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;
m_neighbors.put(region, routedNeighbors);
}
return true;
}
private boolean vecEquals(Vector2D a, Vector2D b) {

View File

@ -41,7 +41,7 @@ public class PlayerTeleporter {
Random rand = new Random();
Vector travelVec = m_dest.toVector().subtract(m_src.toVector()).normalize();
double angleDelta = (Math.PI / 3) * (rand.nextGaussian() - 0.5) * (1-accuracy);
double angleDelta = (Math.PI / 5) * (rand.nextGaussian() - 0.5) * (1-accuracy);
travelVec.rotateAroundY(angleDelta);
double distanceToDest = m_src.distance(m_dest);

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

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