/* * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT). * * Copyright (c) JCThePants (www.jcwhatever.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.jcwhatever.nucleus.internal.regions; import com.jcwhatever.nucleus.internal.NucMsg; import com.jcwhatever.nucleus.regions.IRegion; import com.jcwhatever.nucleus.regions.options.RegionEventPriority.PriorityType; import com.jcwhatever.nucleus.utils.CollectionUtils; import com.jcwhatever.nucleus.utils.PreCon; import com.jcwhatever.nucleus.utils.coords.ChunkCoords; import com.jcwhatever.nucleus.utils.coords.IChunkCoords; import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.World; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * A region manager responsible for storing a specific region type. */ class RegionTypeManager<R extends IRegion> { // Player watcher regions chunk map. private final Map<IChunkCoords, EventOrderedRegions<R>> _listenerRegionsMap = new HashMap<>(10); // All regions chunk map. private final Map<IChunkCoords, Set<R>> _allRegionsMap = new HashMap<>(15); // hash set of all registered regions private final Set<R> _regions = new RegionSet<>(10, false); // synchronization object private final Object _sync = new Object(); private final Class<R> _regionClass; /** * Constructor. * * @param regionClass The region type class. */ public RegionTypeManager (Class<R> regionClass) { PreCon.notNull(regionClass); _regionClass = regionClass; } /** * Get the region type class. */ public Class<R> getRegionClass() { return _regionClass; } /** * Get number of regions registered. */ public int getRegionCount() { return _regions.size(); } /** * Determine if there is a region at the specified location. * * @param location The location to check. */ public boolean hasRegion(Location location) { PreCon.notNull(location); return hasRegion(location.getWorld(), location.getBlockX(), location.getBlockY(), location.getBlockZ()); } /** * Determine if there is a region at the specified location. * * @param world The world to check in. * @param x The X coordinates to check. * @param y The Y coordinates to check. * @param z The Z coordinates to check. */ public boolean hasRegion(World world, int x, int y, int z) { synchronized(_sync) { if (_regions.size() == 0) return false; // calculate chunk location instead of getting it from chunk // to prevent asynchronous issues int chunkX = (int)Math.floor((double)x / 16); int chunkZ = (int)Math.floor((double)z / 16); IChunkCoords key = getChunkKey(world, chunkX, chunkZ); Set<R> regions = _allRegionsMap.get(key); if (regions == null) return false; for (R region : regions) { if (region.contains(x, y, z)) return true; } return false; } } /** * Get a set of regions that contain the specified location. * * @param location The location to check. */ public List<R> getRegions(Location location) { PreCon.notNull(location); return getRegion(location.getWorld(), location.getBlockX(), location.getBlockY(), location.getBlockZ(), PriorityType.ENTER, _allRegionsMap); } /** * Get a set of regions that contain the specified location. * * @param world The world to check. * @param x The x coordinates. * @param y The y coordinates. * @param z The z coordinates. */ public List<R> getRegions(World world, int x, int y, int z) { PreCon.notNull(world); return getRegion(world, x, y, z, PriorityType.ENTER, _allRegionsMap); } /** * Get a set of regions that the specified location * is inside of and are player watchers/listeners. * * @param location The location to check. */ public List<R> getListenerRegions(Location location) { return getRegion(location.getWorld(), location.getBlockX(), location.getBlockY(), location.getBlockZ(), PriorityType.ENTER, _listenerRegionsMap); } /** * Get a set of regions that the specified location * is inside of and are player watchers/listeners. * * @param world The world to check. * @param x The x coordinates. * @param y The y coordinates. * @param z The z coordinates. */ public List<R> getListenerRegions(World world, int x, int y, int z) { return getRegion(world, x, y, z, PriorityType.ENTER, _listenerRegionsMap); } /** * Get a set of regions that the specified location * is inside of and are player watchers/listeners. * * @param location The location to check. * @param priorityType The priority sorting type of the returned list. */ public List<R> getListenerRegions(Location location, PriorityType priorityType) { return getRegion(location.getWorld(), location.getBlockX(), location.getBlockY(), location.getBlockZ(), priorityType, _listenerRegionsMap); } /** * Get a set of regions that the specified location * is inside of and are player watchers/listeners. * * @param world The world to check. * @param x The X coordinates. * @param y The Y coordinates. * @param z The Z coordinates. * @param priorityType The priority sorting type of the returned list. */ public List<R> getListenerRegions(World world, int x, int y, int z, PriorityType priorityType) { return getRegion(world, x, y, z, priorityType, _listenerRegionsMap); } /** * Get all regions that intersect with the specified chunk. * * @param chunk The chunk to check. */ public List<R> getRegionsInChunk(Chunk chunk) { return getRegionsInChunk(chunk.getWorld(), chunk.getX(), chunk.getZ()); } /** * Get all regions that intersect with the specified chunk. * * @param world The world the chunk is in. * @param x The chunks X coordinates. * @param z The chunks Z coordinates. */ public List<R> getRegionsInChunk(World world, int x, int z) { synchronized(_sync) { IChunkCoords key = getChunkKey(world, x, z); Set<R> regions = _allRegionsMap.get(key); if (regions == null) return CollectionUtils.unmodifiableList(); return CollectionUtils.unmodifiableList(regions); } } /* * Get all regions contained in the specified location using * the supplied region map. */ private <T extends Set<R>> List<R> getRegion(World world, int x, int y, int z, PriorityType priorityType, Map<IChunkCoords, T> map) { synchronized(_sync) { List<R> results = new ArrayList<>(10); if (_regions.size() == 0) return results; // calculate chunk location instead of getting it from chunk // to prevent asynchronous issues int chunkX = (int)Math.floor((double)x / 16); int chunkZ = (int)Math.floor((double)z / 16); IChunkCoords key = getChunkKey(world, chunkX, chunkZ); Set<R> regions = map.get(key); if (regions == null) return results; Iterator<R> iterator; iterator = regions instanceof EventOrderedRegions ? ((EventOrderedRegions<R>) regions).iterator(priorityType) : regions.iterator(); while (iterator.hasNext()) { R region = iterator.next(); if (region.contains(x, y, z)) results.add(region); } return results; } } /** * Register a region so it can be found in searches * and its events called if it is a player watcher. * * @param region The Region to register. */ public void register(R region) { PreCon.notNull(region); if (!region.isDefined() || !region.isWorldLoaded()) { NucMsg.debug("Failed to register region '{0}' with RegionManager because " + "it's coords are undefined.", region.getName()); return; } _regions.add(region); synchronized(_sync) { boolean isFormerListener = false; int xMax = region.getChunkX() + region.getChunkXWidth(); int zMax = region.getChunkZ() + region.getChunkZWidth(); for (int x= region.getChunkX(); x < xMax; x++) { for (int z= region.getChunkZ(); z < zMax; z++) { //noinspection ConstantConditions IChunkCoords key = getChunkKey(region.getWorld(), x, z); if (region.isEventListener()) { // add to listener regions map EventOrderedRegions<R> regions = _listenerRegionsMap.get(key); if (regions == null) { regions = new EventOrderedRegions<R>(5); _listenerRegionsMap.put(key, regions); } regions.add(region); } else { isFormerListener = removeFromMap(_listenerRegionsMap, key, region); } // add to all regions map Set<R> regions = _allRegionsMap.get(key); if (regions == null) { regions = new RegionSet<>(5, true); _allRegionsMap.put(key, regions); } regions.add(region); } } onRegister(region, isFormerListener); } } /** * Unregister a region and its events completely. * * <p>Called when a region is disposed.</p> * * @param region The Region to unregister. */ public void unregister(R region) { PreCon.notNull(region); if (!region.isDefined() || !region.isWorldLoaded()) return; synchronized(_sync) { int xMax = region.getChunkX() + region.getChunkXWidth(); int zMax = region.getChunkZ() + region.getChunkZWidth(); for (int x= region.getChunkX(); x < xMax; x++) { for (int z= region.getChunkZ(); z < zMax; z++) { //noinspection ConstantConditions IChunkCoords key = getChunkKey(region.getWorld(), x, z); removeFromMap(_listenerRegionsMap, key, region); removeFromMap(_allRegionsMap, key, region); } } if (_regions.remove(region)) { onUnregister(region); } } } /** * Invoked after a region is registered. * * @param region The region that was registered. * @param isFormerListener True if the region was formerly registered as a listener * and has be re-registered as a non-listener. */ protected void onRegister(R region, boolean isFormerListener) {} /** * Invoked after a region is un-registered. * * @param region The region that was unregistered. */ protected void onUnregister(R region) {} /* * Remove a region from a region map. */ protected <T extends Set<R>> boolean removeFromMap(Map<IChunkCoords, T> map, IChunkCoords key, R region) { Set<R> regions = map.get(key); return regions != null && regions.remove(region); } /* * Get a regions chunk map key. */ protected IChunkCoords getChunkKey(World world, int x, int z) { return new ChunkCoords(world, x, z); } }