Skip to content

Loading…

Make portal checks more efficient; add a framematerials option. #143

Merged
merged 12 commits into from

3 participants

@gmcnew
Multiverse member

No description provided.

@FernFerret

Don't forget your "this" here. I was initially opposed (and used to use mChunkdsToPortals), but after working in python, I rather like the 'this's. Easily lets me know when something is a class method.

Multiverse member

Good catch -- chunksToPortals is actually a local variable, but worldChunkPortals isn't. Changing to "this.worldChunkPortals"...

Multiverse member

Ugh... Time for me to get some sleep... Sorry :(

Multiverse member

A user is reporting an NPE here when MVA resets a world at server start. I asked them to open an issue regarding this but just in case they don't I'm noting it here.

@FernFerret

Should prolly be private, but that's just a first impression. (With public getter, see future comment)

@FernFerret

Ah nice. Ok, so we need that other list to be accessible here (see previous comment), so maybe a getter? I always prefer that as they're easier to deprecate or if we decide to put validation on, we don't have to chant the caller here.

Multiverse member

Works for me. Also, instead of a getter (like List<Material> getPortalInteriorMaterials()), I think it'd be better to implement boolean isPortalInterior(Material type).

Multiverse member

Even better.

@gmcnew
Multiverse member

This needs to be modified to check frames on vehicle teleportation, too.

@gmcnew gmcnew merged commit 2cbf73f into Multiverse:master
@dumptruckman
Multiverse member

Oops, no. =| Good catch! I'll fix this later -- in the meantime, this bug was present when I did my performance testing (which indicated speed improvements, especially with large numbers of portals), so I guess storing portals by Z slice instead of by X/Z coordinates is still better than nothing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
View
157 src/main/java/com/onarandombox/MultiversePortals/MVPortal.java
@@ -7,20 +7,29 @@
package com.onarandombox.MultiversePortals;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
import java.util.logging.Level;
import org.bukkit.Location;
+import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
+import org.bukkit.util.Vector;
import com.onarandombox.MultiverseCore.api.MVDestination;
import com.onarandombox.MultiverseCore.api.MVWorldManager;
import com.onarandombox.MultiverseCore.api.MultiverseWorld;
import com.onarandombox.MultiverseCore.destination.ExactDestination;
import com.onarandombox.MultiverseCore.destination.InvalidDestination;
+import com.onarandombox.MultiversePortals.utils.MultiverseRegion;
public class MVPortal {
private String name;
@@ -39,6 +48,15 @@
private FileConfiguration config;
private boolean allowSave;
+ private static final Collection<Material> INTERIOR_MATERIALS = Arrays.asList(new Material[]{
+ Material.PORTAL, Material.LONG_GRASS, Material.VINE,
+ Material.SNOW, Material.AIR, Material.WATER,
+ Material.STATIONARY_WATER, Material.LAVA, Material.STATIONARY_LAVA });
+
+ public static boolean isPortalInterior(Material material) {
+ return INTERIOR_MATERIALS.contains(material);
+ }
+
public MVPortal(MultiversePortals instance, String name) {
init(instance, name, true);
}
@@ -348,6 +366,137 @@ private void removeFromUpperLists(Permission permission) {
}
}
+ /**
+ * Determines whether a point within the portal has a valid frame around it.
+ *
+ * @param location the location
+ * @return true if the frame around the location is valid; false otherwise
+ */
+ public boolean isFrameValid(Location l) {
+ List<Integer> validMaterialIds = MultiversePortals.FrameMaterials;
+ if (validMaterialIds == null || validMaterialIds.isEmpty()) {
+ // All frame materials are valid.
+ return true;
+ }
+
+ MultiversePortals.staticLog(Level.FINER, String.format("checking portal frame at %d,%d,%d",
+ l.getBlockX(), l.getBlockY(), l.getBlockZ()));
+
+ // Limit the search to the portal's region, extended by 1 block.
+ boolean frameValid = false;
+ {
+ MultiverseRegion r = getLocation().getRegion();
+ int useX = (r.getWidth() == 1) ? 0 : 1;
+ int useY = (r.getHeight() == 1) ? 0 : 1;
+ int useZ = (r.getDepth() == 1) ? 0 : 1;
+
+ // Search for a frame in each of the portal's "flat" (size 1)
+ // dimensions. If a portal's size is greater than 1 in all three
+ // dimensions, an invalid frame will be reported.
+
+ // Try a frame that's flat in the X dimension.
+ if (!frameValid && useX == 0) {
+ frameValid = isFrameValid(l, expandedRegion(r, 0, 1, 1));
+ }
+
+ // Try a frame that's flat in the Y dimension.
+ if (!frameValid && useY == 0) {
+ frameValid = isFrameValid(l, expandedRegion(r, 1, 0, 1));
+ }
+
+ // Try a frame that's flat in the Z dimension.
+ if (!frameValid && useZ == 0) {
+ frameValid = isFrameValid(l, expandedRegion(r, 1, 1, 0));
+ }
+ }
+ return frameValid;
+ }
+
+ /**
+ * Examines a frame around a location, bounded by a search region which has
+ * one dimension of size 1 and two dimensions which of size greater than
+ * one.
+ * @param location
+ * @param searchRegion
+ * @return
+ */
+ private boolean isFrameValid(Location location, MultiverseRegion searchRegion) {
+
+ int useX = (searchRegion.getWidth() == 1) ? 0 : 1;
+ int useY = (searchRegion.getHeight() == 1) ? 0 : 1;
+ int useZ = (searchRegion.getDepth() == 1) ? 0 : 1;
+
+ // Make sure the search region is flat in exactly one dimension.
+ if (useX + useY + useZ != 2) {
+ return false;
+ }
+
+ Level debugLevel = Level.INFO;
+
+ MultiversePortals.staticLog(debugLevel, String.format("checking portal around %d,%d,%d",
+ location.getBlockX(), location.getBlockY(), location.getBlockZ()));
+
+ int commonMaterialId = -1;
+
+ World world = location.getWorld();
+
+ Set<Location> visited = new HashSet<Location>();
+ Stack<Location> frontier = new Stack<Location>();
+ frontier.push(location);
+
+ while (!frontier.isEmpty()) {
+ Location toCheck = frontier.pop();
+ visited.add(toCheck);
+
+ MultiversePortals.staticLog(debugLevel, String.format(" ... block at %d,%d,%d",
+ toCheck.getBlockX(), toCheck.getBlockY(), toCheck.getBlockZ()));
+
+ if (isPortalInterior(toCheck.getBlock().getType())) {
+ // This is an empty block in the portal. Check each of the four
+ // neighboring locations.
+ for (int d1 = -1; d1 <= 1; d1++) {
+ for (int d2 = -1; d2 <= 1; d2++) {
+ if ((d1 == 0) ^ (d2 == 0)) {
+ int dx = useX * d1;
+ int dy = useY * (useX == 0 ? d1 : d2);
+ int dz = useZ * d2;
+
+ int newX = toCheck.getBlockX() + dx;
+ int newY = toCheck.getBlockY() + dy;
+ int newZ = toCheck.getBlockZ() + dz;
+
+ Location toVisit = new Location(world, newX, newY, newZ);
+ if (!searchRegion.containsVector(toVisit)) {
+ // This empty block is on the edge of the search
+ // region. Assume the frame is bad.
+ return false;
+ }
+ if (!visited.contains(toVisit)) {
+ frontier.add(toVisit);
+ }
+ }
+ }
+ }
+ }
+ else {
+ // This is a frame block. Check its material.
+ int materialId = toCheck.getBlock().getTypeId();
+ if (commonMaterialId == -1) {
+ // This is the first frame block we've found.
+ commonMaterialId = materialId;
+ }
+ else if (commonMaterialId != materialId) {
+ // This frame block doesn't match other frame blocks.
+ MultiversePortals.staticLog(debugLevel, "frame has multiple materials");
+ return false;
+ }
+ }
+ }
+ MultiversePortals.staticLog(debugLevel, String.format("frame has common material %d", commonMaterialId));
+
+ return MultiversePortals.FrameMaterials.contains(commonMaterialId);
+ }
+
public boolean isExempt(Player player) {
return false;
}
@@ -356,4 +505,12 @@ public Permission getExempt() {
return this.exempt;
}
+ private MultiverseRegion expandedRegion(MultiverseRegion r, int x, int y, int z) {
+ Vector min = new Vector().copy(r.getMinimumPoint());
+ Vector max = new Vector().copy(r.getMaximumPoint());
+ min.add(new Vector(-x, -y, -z));
+ max.add(new Vector( x, y, z));
+ return new MultiverseRegion(min, max, r.getWorld());
+ }
+
}
View
6 src/main/java/com/onarandombox/MultiversePortals/MultiversePortals.java
@@ -12,6 +12,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
@@ -77,6 +78,10 @@
public static boolean ClearOnRemove = false;
public static boolean TeleportVehicles = true;
+ // Restricts the materials that can be used for the frames of portals.
+ // An empty or null list means all materials are okay.
+ public static List<Integer> FrameMaterials = null;
+
public void onLoad() {
getDataFolder().mkdirs();
}
@@ -220,6 +225,7 @@ public void loadConfig() {
this.portalCooldown = this.MVPConfig.getInt("portalcooldown", 1000);
MultiversePortals.ClearOnRemove = this.MVPConfig.getBoolean("clearonremove", false);
MultiversePortals.TeleportVehicles = this.MVPConfig.getBoolean("teleportvehicles", true);
+ MultiversePortals.FrameMaterials = this.MVPConfig.getIntegerList("framematerials");
// Migrate useportalaccess -> enforceportalaccess
if (this.MVPConfig.get("useportalaccess") != null) {
this.MVPConfig.set("enforceportalaccess", this.MVPConfig.getBoolean("useportalaccess", true));
View
29 src/main/java/com/onarandombox/MultiversePortals/listeners/MVPPlayerListener.java
@@ -115,7 +115,7 @@ public void playerInteract(PlayerInteractEvent event) {
// Portal lighting stuff
if (event.getAction() == Action.RIGHT_CLICK_BLOCK && event.getMaterial() == Material.FLINT_AND_STEEL) {
// They're lighting somethin'
- this.plugin.log(Level.FINER, "Player is ligting block: " + this.plugin.getCore().getLocationManipulation().strCoordsRaw(event.getClickedBlock().getLocation()));
+ this.plugin.log(Level.FINER, "Player is lighting block: " + this.plugin.getCore().getLocationManipulation().strCoordsRaw(event.getClickedBlock().getLocation()));
PortalPlayerSession ps = this.plugin.getPortalSession(event.getPlayer());
Location translatedLocation = this.getTranslatedLocation(event.getClickedBlock(), event.getBlockFace());
if (!portalManager.isPortal(translatedLocation)) {
@@ -133,6 +133,13 @@ public void playerInteract(PlayerInteractEvent event) {
// Cancel the event if there was a portal.
if (portal != null) {
+
+ // Make sure the portal's frame around this point is made out of
+ // a valid material.
+ if (!portal.isFrameValid(translatedLocation)) {
+ return;
+ }
+
this.plugin.log(Level.FINER, "Right Clicked: ");
this.plugin.log(Level.FINER, "Block Clicked: " + event.getClickedBlock() + ":" + event.getClickedBlock().getType());
this.plugin.log(Level.FINER, "Translated Block: " + event.getPlayer().getWorld().getBlockAt(translatedLocation) + ":" + event.getPlayer().getWorld().getBlockAt(translatedLocation).getType());
@@ -224,6 +231,12 @@ public void playerMove(PlayerMoveEvent event) {
this.stateFailure(p.getDisplayName(), portal.getName());
return;
}
+
+ if (!portal.isFrameValid(loc)) {
+ //event.getPlayer().sendMessage("This portal's frame is made of an " + ChatColor.RED + "incorrect material." + ChatColor.RED + " You should exit it now.");
+ return;
+ }
+
GenericBank bank = plugin.getCore().getBank();
if (bank.hasEnough(event.getPlayer(), portal.getPrice(), portal.getCurrency(), "You need " + bank.getFormattedAmount(event.getPlayer(), portal.getPrice(), portal.getCurrency()) + " to enter the " + portal.getName() + " portal.")) {
// call event for other plugins
@@ -270,22 +283,28 @@ public void playerPortal(PlayerPortalEvent event) {
}
this.plugin.log(Level.FINER, "onPlayerPortal called!");
PortalManager pm = this.plugin.getPortalManager();
+ Location playerPortalLoc = event.getPlayer().getLocation();
// Determine if we're in a portal
- MVPortal portal = pm.getPortal(event.getPlayer(), event.getPlayer().getLocation());
+ MVPortal portal = pm.getPortal(event.getPlayer(), playerPortalLoc);
// Even if the location was null, we still have to see if
// someone wasn't exactly on (because they can do this).
if (portal == null) {
// Check around the player to make sure
- Location newLoc = this.plugin.getCore().getSafeTTeleporter().findPortalBlockNextTo(event.getFrom());
- if (newLoc != null) {
+ playerPortalLoc = this.plugin.getCore().getSafeTTeleporter().findPortalBlockNextTo(event.getFrom());
+ if (playerPortalLoc != null) {
this.plugin.log(Level.FINER, "Player was outside of portal, The location has been successfully translated.");
- portal = pm.getPortal(event.getPlayer(), newLoc);
+ portal = pm.getPortal(event.getPlayer(), playerPortalLoc);
}
}
if (portal != null) {
this.plugin.log(Level.FINER, "There was a portal found!");
MVDestination portalDest = portal.getDestination();
if (portalDest != null && !(portalDest instanceof InvalidDestination)) {
+ if (!portal.isFrameValid(playerPortalLoc)) {
+ event.getPlayer().sendMessage("This portal's frame is made of an " + ChatColor.RED + "incorrect material." + ChatColor.RED + " You should exit it now.");
+ event.setCancelled(true);
+ return;
+ }
PortalPlayerSession ps = this.plugin.getPortalSession(event.getPlayer());
if (!ps.allowTeleportViaCooldown(new Date())) {
event.getPlayer().sendMessage(ps.getFriendlyRemainingTimeMessage());
View
10 src/main/java/com/onarandombox/MultiversePortals/listeners/MVPVehicleListener.java
@@ -61,6 +61,11 @@ public void vehicleMove(VehicleMoveEvent event) {
if (dest == null || dest instanceof InvalidDestination)
return;
+ // Check the portal's frame.
+ if (!portal.isFrameValid(event.getVehicle().getLocation())) {
+ return;
+ }
+
Vector vehicleVec = event.getVehicle().getVelocity();
Location target = dest.getLocation(event.getVehicle());
if (dest instanceof PortalDestination) {
@@ -106,6 +111,11 @@ private boolean teleportVehicle(Player p, Vehicle v, Location to) {
return false;
}
+ // Check the portal's frame.
+ if (!portal.isFrameValid(v.getLocation())) {
+ return false;
+ }
+
Location l = d.getLocation(p);
Vector vehicleVec = v.getVelocity();
View
3 src/main/java/com/onarandombox/MultiversePortals/utils/PortalFiller.java
@@ -17,6 +17,7 @@
import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.utils.LocationManipulation;
+import com.onarandombox.MultiversePortals.MVPortal;
public class PortalFiller {
private MultiverseCore plugin;
@@ -105,6 +106,6 @@ private boolean isValidPortalRegion(Location l, Material portalType) {
if (l.getWorld().getBlockAt(l).getType() == portalType) {
return false;
}
- return (type == Material.PORTAL || type == Material.LONG_GRASS || type == Material.VINE || type == Material.SNOW || type == Material.AIR || type == Material.WATER || type == Material.STATIONARY_WATER || type == Material.LAVA || type == Material.STATIONARY_LAVA);
+ return MVPortal.isPortalInterior(type);
}
}
View
181 src/main/java/com/onarandombox/MultiversePortals/utils/PortalManager.java
@@ -8,12 +8,15 @@
package com.onarandombox.MultiversePortals.utils;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.bukkit.Location;
import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
@@ -33,13 +36,19 @@
*/
public class PortalManager {
private MultiversePortals plugin;
- private PortalFiller filler;
private Map<String, MVPortal> portals;
+ // For each world, keep a map of chunk hashes (see hashChunk()) to lists of
+ // portals in those chunks.
+ private Map<MultiverseWorld, Map<Integer, Collection<MVPortal>>> worldChunkPortals;
+
+ // getNearbyPortals() returns this instead of null. =)
+ private static final Collection<MVPortal> emptyPortalSet = new ArrayList<MVPortal>();
+
public PortalManager(MultiversePortals plugin) {
this.plugin = plugin;
- this.filler = new PortalFiller(plugin.getCore());
this.portals = new HashMap<String, MVPortal>();
+ this.worldChunkPortals = new HashMap<MultiverseWorld, Map<Integer, Collection<MVPortal>>>();
}
/**
* Method that checks to see if a player is inside a portal AND if they have perms to use it.
@@ -51,17 +60,19 @@ public MVPortal getPortal(Player sender, Location l) {
if (!this.plugin.getCore().getMVWorldManager().isMVWorld(l.getWorld().getName())) {
return null;
}
+
MultiverseWorld world = this.plugin.getCore().getMVWorldManager().getMVWorld(l.getWorld().getName());
- List<MVPortal> portalList = this.getPortals(sender, world);
- if (portalList == null || portalList.size() == 0) {
- return null;
- }
- for (MVPortal portal : portalList) {
- PortalLocation portalLoc = portal.getLocation();
- if (portalLoc.isValidLocation() && portalLoc.getRegion().containsVector(l)) {
- return portal;
+ for (MVPortal portal : getNearbyPortals(world, l)) {
+
+ // Ignore portals the player can't use.
+ if (!MultiversePortals.EnforcePortalAccess || portal.playerCanEnterPortal((Player) sender)) {
+ PortalLocation portalLoc = portal.getLocation();
+ if (portalLoc.isValidLocation() && portalLoc.getRegion().containsVector(l)) {
+ return portal;
+ }
}
}
+
return null;
}
/**
@@ -92,7 +103,7 @@ public boolean isPortal(Location l) {
*/
public MVPortal getPortal(Location l) {
MultiverseWorld world = this.plugin.getCore().getMVWorldManager().getMVWorld(l.getWorld().getName());
- for (MVPortal portal : this.getPortals(world)) {
+ for (MVPortal portal : getNearbyPortals(world, l)) {
MultiverseRegion r = portal.getLocation().getRegion();
if (r != null && r.containsVector(l)) {
return portal;
@@ -103,7 +114,8 @@ public MVPortal getPortal(Location l) {
public boolean addPortal(MVPortal portal) {
if (!this.portals.containsKey(portal.getName())) {
- this.portals.put(portal.getName(), portal);
+ MultiverseWorld world = this.plugin.getCore().getMVWorldManager().getMVWorld(portal.getWorld());
+ addUniquePortal(world, portal.getName(), portal);
return true;
}
return false;
@@ -111,11 +123,17 @@ public boolean addPortal(MVPortal portal) {
public boolean addPortal(MultiverseWorld world, String name, String owner, PortalLocation location) {
if (!this.portals.containsKey(name)) {
- this.portals.put(name, new MVPortal(this.plugin, name, owner, location));
+ addUniquePortal(world, name, new MVPortal(this.plugin, name, owner, location));
return true;
}
return false;
}
+
+ // Add a portal whose name is already known to be unique.
+ private void addUniquePortal(MultiverseWorld world, String name, MVPortal portal) {
+ this.portals.put(name, portal);
+ addToWorldChunkPortals(world, portal);
+ }
public MVPortal removePortal(String portalName, boolean removeFromConfigs) {
if (!isPortal(portalName)) {
@@ -128,6 +146,9 @@ public MVPortal removePortal(String portalName, boolean removeFromConfigs) {
}
MVPortal removed = this.portals.remove(portalName);
+ MultiverseWorld world = this.plugin.getCore().getMVWorldManager().getMVWorld(removed.getWorld());
+ removeFromWorldChunkPortals(world, removed);
+
removed.removePermission();
Permission portalAccess = this.plugin.getServer().getPluginManager().getPermission("multiverse.portal.access.*");
Permission exemptAccess = this.plugin.getServer().getPluginManager().getPermission("multiverse.portal.exempt.*");
@@ -140,22 +161,12 @@ public MVPortal removePortal(String portalName, boolean removeFromConfigs) {
this.plugin.getServer().getPluginManager().recalculatePermissionDefaults(portalAccess);
}
if (MultiversePortals.ClearOnRemove) {
- // Fill the interior of the portal with air. This means removing
- // a portal no longer leaves behind portal blocks (which would take
- // a player to the nether).
-
- Material clearMaterial = Material.AIR;
+ // Replace portal blocks in the portal with air. This keeps us from
+ // leaving behind portal blocks (which would take an unsuspecting
+ // player to the nether instead of their expected destination).
- // Start the fill at the region's center.
- MultiverseRegion removedRegion = removed.getLocation().getRegion();
- Vector fillPoint1 = removedRegion.getMinimumPoint();
- Vector fillPoint2 = removedRegion.getMaximumPoint();
- double fillX = (fillPoint1.getBlockX() + fillPoint2.getBlockX()) / 2;
- double fillY = (fillPoint1.getBlockY() + fillPoint2.getBlockY()) / 2;
- double fillZ = (fillPoint1.getBlockZ() + fillPoint2.getBlockZ()) / 2;
- Location fillLocation = new Location(removed.getWorld(), fillX, fillY, fillZ);
-
- this.filler.fillRegion(removedRegion, fillLocation, clearMaterial);
+ MultiverseRegion region = removed.getLocation().getRegion();
+ replaceInRegion(removed.getWorld(), region, Material.PORTAL, Material.AIR);
}
this.plugin.getServer().getPluginManager().removePermission(removed.getPermission());
this.plugin.getServer().getPluginManager().removePermission(removed.getExempt());
@@ -216,14 +227,12 @@ public MVPortal removePortal(String portalName, boolean removeFromConfigs) {
}
public MVPortal getPortal(String portalName) {
- if (this.portals.containsKey(portalName)) {
- return this.portals.get(portalName);
- }
- return null;
+ // Returns null if the portal doesn't exist.
+ return this.portals.get(portalName);
}
/**
- * Gets a portal with a commandsender and a name. Used as a convience for portal listing methods
+ * Gets a portal with a commandsender and a name. Used as a convenience for portal listing methods
*
* @param portalName
* @param sender
@@ -247,5 +256,111 @@ public void removeAll(boolean removeFromConfigs) {
this.removePortal(s, removeFromConfigs);
}
}
+
+ private void replaceInRegion(World world, MultiverseRegion removedRegion, Material oldMaterial, Material newMaterial) {
+ int oldMaterialId = oldMaterial.getId();
+ int newMaterialId = newMaterial.getId();
+
+ // Determine the bounds of the region.
+ Vector min = removedRegion.getMinimumPoint();
+ Vector max = removedRegion.getMaximumPoint();
+ int minX = min.getBlockX(), minY = min.getBlockY(), minZ = min.getBlockZ();
+ int maxX = max.getBlockX(), maxY = max.getBlockY(), maxZ = max.getBlockZ();
+
+ for (int x = minX; x <= maxX; x++) {
+ for (int y = minY; y <= maxY; y++) {
+ for (int z = minZ; z <= maxZ; z++) {
+ Block b = world.getBlockAt(x, y, z);
+ if (b.getTypeId() == oldMaterialId) {
+ b.setTypeId(newMaterialId, false);
+ }
+ }
+ }
+ }
+ }
+
+ private int blockToChunk(int b) {
+ // A block at -5 should be in chunk -1 instead of chunk 0.
+ if (b < 0) {
+ b -= 16;
+ }
+ return b / 16;
+ }
+
+ private int hashChunk(int cx, int cz) {
+ return (cz << 16) | (cz & 0xFFFF);
+ }
+
+ private void addToWorldChunkPortals(MultiverseWorld world, MVPortal portal) {
+
+ Map<Integer, Collection<MVPortal>> chunksToPortals = this.worldChunkPortals.get(world);
+ if (chunksToPortals == null) {
+ chunksToPortals = new HashMap<Integer, Collection<MVPortal>>();
+ this.worldChunkPortals.put(world, chunksToPortals);
+ }
+
+ // If this portal spans multiple chunks, we'll add it to each chunk that
+ // contains part of it.
+ PortalLocation location = portal.getLocation();
+ Vector min = location.getMinimum();
+ Vector max = location.getMaximum();
+ int c1x = blockToChunk(min.getBlockX()), c1z = blockToChunk(min.getBlockZ());
+ int c2x = blockToChunk(max.getBlockX()), c2z = blockToChunk(max.getBlockZ());
+ for (int cx = c1x; cx <= c2x; cx++) {
+ for (int cz = c1z; cz <= c2z; cz++) {
+ Integer hashCode = hashChunk(cx, cz);
+ Collection<MVPortal> portals = chunksToPortals.get(hashCode);
+ if (portals == null) {
+ // For this collection, iteration will be -much- more common
+ // than addition or removal. ArrayList has better iteration
+ // performance than HashSet.
+ portals = new ArrayList<MVPortal>();
+ chunksToPortals.put(hashCode, portals);
+ }
+ portals.add(portal);
+ }
+ }
+ }
+
+ private void removeFromWorldChunkPortals(MultiverseWorld world, MVPortal portal) {
+ Map<Integer, Collection<MVPortal>> chunksToPortals = this.worldChunkPortals.get(world);
+
+ PortalLocation location = portal.getLocation();
+ Vector min = location.getMinimum();
+ Vector max = location.getMaximum();
+ int c1x = blockToChunk(min.getBlockX()), c1z = blockToChunk(min.getBlockZ());
+ int c2x = blockToChunk(max.getBlockX()), c2z = blockToChunk(max.getBlockZ());
+
+ for (int cx = c1x; cx <= c2x; cx++) {
+ for (int cz = c1z; cz <= c2z; cz++) {
+ Integer hashCode = hashChunk(cx, cz);
+ chunksToPortals.get(hashCode).remove(portal);
+ }
+ }
+ }
+
+ /**
+ * Returns portals in the same chunk as the given location.
+ *
+ * @param location the location
+ * @return a collection of nearby portals; may be empty, but will not be null
+ */
+ private Collection<MVPortal> getNearbyPortals(MultiverseWorld world, Location location) {
+
+ Collection<MVPortal> nearbyPortals = null;
+
+ Map<Integer, Collection<MVPortal>> chunkMap = this.worldChunkPortals.get(world);
+ if (chunkMap != null) {
+ int cx = blockToChunk(location.getBlockX());
+ int cz = blockToChunk(location.getBlockZ());
+ Integer hash = hashChunk(cx, cz);
+
+ nearbyPortals = chunkMap.get(hash);
+ }
+
+ // Never return null. (This just keeps the caller from having to do a
+ // null check.)
+ return nearbyPortals != null ? nearbyPortals : emptyPortalSet;
+ }
}
View
3 src/main/resources/defaults/config.yml
@@ -12,4 +12,5 @@ enforceportalaccess: true
portalcooldown: 1000
clearonremove: false
teleportvehicles: true
-version: 2.6
+framematerials: []
+version: 2.7
Something went wrong with that request. Please try again.