Compare commits
9 Commits
v0.2.99-rc
...
v0.3.0
Author | SHA1 | Date | |
---|---|---|---|
b5a28e40cf | |||
4bfce1fd8d | |||
81960d14ce | |||
4bbada298f | |||
e3afbdc3c7 | |||
034527c208 | |||
092ed9c6d0 | |||
ac14a78d7f | |||
eaa8313cd1 |
70
README.md
70
README.md
@ -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
|
||||

|
||||
- Create a point of interest in your world, give it a name, apply a banner.
|
||||

|
||||
- 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
|
||||

|
||||
- 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
|
||||

|
||||
- 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
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
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
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
BIN
docs/use-region-post.gif
Executable file
Binary file not shown.
After Width: | Height: | Size: 34 MiB |
18
pom.xml
18
pom.xml
@ -4,13 +4,19 @@
|
||||
<groupId>us.camin.regions</groupId>
|
||||
<artifactId>Regions</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>0.2.99-rc4</version>
|
||||
<version>0.3.0</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>
|
||||
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -35,7 +35,6 @@ import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.wrappers.EnumWrappers.TitleAction;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
|
||||
import com.destroystokyo.paper.Title;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import us.camin.regions.events.PlayerMoveInEvent;
|
||||
@ -126,9 +125,7 @@ public class PlayerNotifier implements Listener {
|
||||
}
|
||||
} 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);
|
||||
event.player.sendTitle("Region Discovered", "You discovered the region " + event.region.coloredName());
|
||||
}
|
||||
|
||||
// TODO: Make this configurable and disablable
|
||||
|
@ -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;
|
||||
@ -86,6 +91,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() {
|
||||
|
@ -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.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) {
|
||||
@ -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 + ")");
|
||||
|
@ -24,22 +24,30 @@ public class BorderMesh {
|
||||
Map<Region, Polygon> m_polygons;
|
||||
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) {
|
||||
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");
|
||||
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)) {
|
||||
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 {
|
||||
//log.trace("Region " + region.name() + " is not a frontier");
|
||||
for(Region neighbor : allNeighbors) {
|
||||
}
|
||||
if (crossings == 1 || crossings == 0) {
|
||||
ret.add(neighbor);
|
||||
}
|
||||
}
|
||||
@ -58,6 +66,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 +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) {
|
||||
return m_polygons.get(r);
|
||||
}
|
||||
@ -188,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) {
|
||||
return a.x == b.x && a.y == b.y;
|
||||
}
|
||||
|
@ -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);
|
||||
|
Reference in New Issue
Block a user