package me.wiefferink.areashop.features.signs; import com.sk89q.worldguard.protection.managers.RegionManager; import com.sk89q.worldguard.protection.regions.ProtectedRegion; import me.wiefferink.areashop.AreaShop; import me.wiefferink.areashop.events.ask.AddingRegionEvent; import me.wiefferink.areashop.events.notify.UpdateRegionEvent; import me.wiefferink.areashop.features.RegionFeature; import me.wiefferink.areashop.managers.FileManager; import me.wiefferink.areashop.regions.BuyRegion; import me.wiefferink.areashop.regions.GeneralRegion; import me.wiefferink.areashop.regions.RentRegion; import me.wiefferink.areashop.tools.Materials; import me.wiefferink.areashop.tools.Utils; import me.wiefferink.bukkitdo.Do; import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.block.Action; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockPhysicsEvent; import org.bukkit.event.block.SignChangeEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.world.ChunkLoadEvent; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; public class SignsFeature extends RegionFeature { private static final Map<String, RegionSign> allSigns = Collections.synchronizedMap(new HashMap<>()); private static final Map<String, List<RegionSign>> signsByChunk = Collections.synchronizedMap(new HashMap<>()); private Map<String, RegionSign> signs; public SignsFeature() { } /** * Constructor. * @param region The region to bind to */ public SignsFeature(GeneralRegion region) { setRegion(region); signs = new HashMap<>(); // Setup current signs ConfigurationSection signSection = region.getConfig().getConfigurationSection("general.signs"); if(signSection != null) { for(String signKey : signSection.getKeys(false)) { RegionSign sign = new RegionSign(this, signKey); Location location = sign.getLocation(); if(location == null) { AreaShop.warn("Sign with key " + signKey + " of region " + region.getName() + " does not have a proper location"); continue; } signs.put(sign.getStringLocation(), sign); signsByChunk.computeIfAbsent(sign.getStringChunk(), key -> new ArrayList<>()) .add(sign); } allSigns.putAll(signs); } } @Override public void shutdown() { // Deregister signs from the registry if(signs != null) { for(Map.Entry<String, RegionSign> entry : signs.entrySet()) { allSigns.remove(entry.getKey()); signsByChunk.get(entry.getValue().getStringChunk()).remove(entry.getValue()); } } } @EventHandler(priority = EventPriority.HIGH) public void onSignBreak(BlockBreakEvent event) { if(event.isCancelled()) { return; } Block block = event.getBlock(); // Check if it is a sign if(Materials.isSign(block.getType())) { // Check if the rent sign is really the same as a saved rent RegionSign regionSign = SignsFeature.getSignByLocation(block.getLocation()); if(regionSign == null) { return; } // Remove the sign of the rental region if the player has permission if(event.getPlayer().hasPermission("areashop.delsign")) { regionSign.remove(); plugin.message(event.getPlayer(), "delsign-success", regionSign.getRegion()); } else { // Cancel the breaking of the sign event.setCancelled(true); plugin.message(event.getPlayer(), "delsign-noPermission", regionSign.getRegion()); } } } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onIndirectSignBreak(BlockPhysicsEvent event) { // Check if the block is a sign if(!Materials.isSign(event.getBlock().getType())) { return; } // Check if still attached to a block Block attachedBlock = plugin.getBukkitHandler().getSignAttachedTo(event.getBlock()); // TODO: signs cannot be placed on all blocks, improve this check to isSolid()? if (attachedBlock.getType() != Material.AIR) { return; } // Check if the sign is really the same as a saved rent RegionSign regionSign = SignsFeature.getSignByLocation(event.getBlock().getLocation()); if(regionSign == null) { return; } // Remove the sign so that it does not fall on the floor as an item (next region update will place it back when possible) AreaShop.debug("onIndirectSignBreak: Removed block of sign for", regionSign.getRegion().getName(), "at", regionSign.getStringLocation()); event.getBlock().setType(Material.AIR); event.setCancelled(true); } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onSignClick(PlayerInteractEvent event) { Block block = event.getClickedBlock(); if (block == null) { return; } // Only listen to left and right clicks on blocks if (!(event.getAction() == Action.RIGHT_CLICK_BLOCK || event.getAction() == Action.LEFT_CLICK_BLOCK)) { return; } // Only care about clicking blocks if(!Materials.isSign(block.getType())) { return; } // Check if this sign belongs to a region RegionSign regionSign = SignsFeature.getSignByLocation(block.getLocation()); if(regionSign == null) { return; } // Ignore players that are in sign link mode (which will handle the event itself) Player player = event.getPlayer(); if(plugin.getSignlinkerManager().isInSignLinkMode(player)) { return; } // Get the clicktype GeneralRegion.ClickType clickType = null; if(player.isSneaking() && event.getAction() == Action.LEFT_CLICK_BLOCK) { clickType = GeneralRegion.ClickType.SHIFTLEFTCLICK; } else if(!player.isSneaking() && event.getAction() == Action.LEFT_CLICK_BLOCK) { clickType = GeneralRegion.ClickType.LEFTCLICK; } else if(player.isSneaking() && event.getAction() == Action.RIGHT_CLICK_BLOCK) { clickType = GeneralRegion.ClickType.SHIFTRIGHTCLICK; } else if(!player.isSneaking() && event.getAction() == Action.RIGHT_CLICK_BLOCK) { clickType = GeneralRegion.ClickType.RIGHTCLICK; } boolean ran = regionSign.runSignCommands(player, clickType); // Only cancel event if at least one command has been executed event.setCancelled(ran); } @EventHandler(priority = EventPriority.MONITOR) public void onSignChange(SignChangeEvent event) { if(event.isCancelled()) { return; } Player player = event.getPlayer(); if(!plugin.isReady()) { plugin.message(player, "general-notReady"); return; } // Check if the sign is meant for this plugin if(event.getLine(0).contains(plugin.getConfig().getString("signTags.rent"))) { if(!player.hasPermission("areashop.createrent") && !player.hasPermission("areashop.createrent.member") && !player.hasPermission("areashop.createrent.owner")) { plugin.message(player, "setup-noPermissionRent"); return; } // Get the other lines String secondLine = event.getLine(1); String thirdLine = event.getLine(2); String fourthLine = event.getLine(3); // Get the regionManager for accessing regions RegionManager regionManager = plugin.getRegionManager(event.getPlayer().getWorld()); // If the secondLine does not contain a name try to find the region by location if(secondLine == null || secondLine.isEmpty()) { Set<ProtectedRegion> regions = plugin.getWorldGuardHandler().getApplicableRegionsSet(event.getBlock().getLocation()); if(regions != null) { boolean first = true; ProtectedRegion candidate = null; for(ProtectedRegion pr : regions) { if(first) { candidate = pr; first = false; } else { if(pr.getPriority() > candidate.getPriority()) { candidate = pr; } else if(pr.getPriority() < candidate.getPriority()) { // Already got the correct one } else if(pr.getParent() != null && pr.getParent().equals(candidate)) { candidate = pr; } else if(candidate.getParent() != null && candidate.getParent().equals(pr)) { // Already got the correct one } else { plugin.message(player, "setup-couldNotDetect", candidate.getId(), pr.getId()); return; } } } if(candidate != null) { secondLine = candidate.getId(); } } } boolean priceSet = fourthLine != null && !fourthLine.isEmpty(); boolean durationSet = thirdLine != null && !thirdLine.isEmpty(); // check if all the lines are correct if(secondLine == null || secondLine.isEmpty()) { plugin.message(player, "setup-noRegion"); return; } ProtectedRegion region = regionManager.getRegion(secondLine); if(region == null) { plugin.message(player, "cmd-noRegion", secondLine); return; } FileManager.AddResult addResult = plugin.getFileManager().checkRegionAdd(player, regionManager.getRegion(secondLine), event.getPlayer().getWorld(), GeneralRegion.RegionType.RENT); if(addResult == FileManager.AddResult.BLACKLISTED) { plugin.message(player, "setup-blacklisted", secondLine); } else if(addResult == FileManager.AddResult.ALREADYADDED) { plugin.message(player, "setup-alreadyRentSign"); } else if(addResult == FileManager.AddResult.ALREADYADDEDOTHERWORLD) { plugin.message(player, "setup-alreadyOtherWorld"); } else if(addResult == FileManager.AddResult.NOPERMISSION) { plugin.message(player, "setup-noPermission", secondLine); } else if(thirdLine != null && !thirdLine.isEmpty() && !Utils.checkTimeFormat(thirdLine)) { plugin.message(player, "setup-wrongDuration"); } else { double price = 0.0; if(priceSet) { // Check the fourth line try { price = Double.parseDouble(fourthLine); } catch(NumberFormatException e) { plugin.message(player, "setup-wrongPrice"); return; } } // Add rent to the FileManager final RentRegion rent = new RentRegion(secondLine, event.getPlayer().getWorld()); boolean isMember = plugin.getWorldGuardHandler().containsMember(rent.getRegion(), player.getUniqueId()); boolean isOwner = plugin.getWorldGuardHandler().containsOwner(rent.getRegion(), player.getUniqueId()); boolean landlord = (!player.hasPermission("areashop.createrent") && ((player.hasPermission("areashop.createrent.owner") && isOwner) || (player.hasPermission("areashop.createrent.member") && isMember))); if(landlord) { rent.setLandlord(player.getUniqueId(), player.getName()); } if(priceSet) { rent.setPrice(price); } if(durationSet) { rent.setDuration(thirdLine); } rent.getSignsFeature().addSign(event.getBlock().getLocation(), event.getBlock().getType(), plugin.getBukkitHandler().getSignFacing(event.getBlock()), null); AddingRegionEvent addingRegionEvent = plugin.getFileManager().addRegion(rent); if (addingRegionEvent.isCancelled()) { plugin.message(player, "general-cancelled", addingRegionEvent.getReason()); return; } rent.handleSchematicEvent(GeneralRegion.RegionEvent.CREATED); plugin.message(player, "setup-rentSuccess", rent); // Update the region after the event has written its lines Do.sync(rent::update); } } else if(event.getLine(0).contains(plugin.getConfig().getString("signTags.buy"))) { // Check for permission if(!player.hasPermission("areashop.createbuy") && !player.hasPermission("areashop.createbuy.member") && !player.hasPermission("areashop.createbuy.owner")) { plugin.message(player, "setup-noPermissionBuy"); return; } // Get the other lines String secondLine = event.getLine(1); String thirdLine = event.getLine(2); // Get the regionManager for accessing regions RegionManager regionManager = plugin.getRegionManager(event.getPlayer().getWorld()); // If the secondLine does not contain a name try to find the region by location if(secondLine == null || secondLine.isEmpty()) { Set<ProtectedRegion> regions = plugin.getWorldGuardHandler().getApplicableRegionsSet(event.getBlock().getLocation()); if(regions != null) { boolean first = true; ProtectedRegion candidate = null; for(ProtectedRegion pr : regions) { if(first) { candidate = pr; first = false; } else { if(pr.getPriority() > candidate.getPriority()) { candidate = pr; } else if(pr.getPriority() < candidate.getPriority()) { // Already got the correct one } else if(pr.getParent() != null && pr.getParent().equals(candidate)) { candidate = pr; } else if(candidate.getParent() != null && candidate.getParent().equals(pr)) { // Already got the correct one } else { plugin.message(player, "setup-couldNotDetect", candidate.getId(), pr.getId()); return; } } } if(candidate != null) { secondLine = candidate.getId(); } } } boolean priceSet = thirdLine != null && !thirdLine.isEmpty(); // Check if all the lines are correct if(secondLine == null || secondLine.isEmpty()) { plugin.message(player, "setup-noRegion"); return; } ProtectedRegion region = regionManager.getRegion(secondLine); if(region == null) { plugin.message(player, "cmd-noRegion", secondLine); return; } FileManager.AddResult addResult = plugin.getFileManager().checkRegionAdd(player, region, event.getPlayer().getWorld(), GeneralRegion.RegionType.BUY); if(addResult == FileManager.AddResult.BLACKLISTED) { plugin.message(player, "setup-blacklisted", secondLine); } else if(addResult == FileManager.AddResult.ALREADYADDED) { plugin.message(player, "setup-alreadyRentSign"); } else if(addResult == FileManager.AddResult.ALREADYADDEDOTHERWORLD) { plugin.message(player, "setup-alreadyOtherWorld"); } else if(addResult == FileManager.AddResult.NOPERMISSION) { plugin.message(player, "setup-noPermission", secondLine); } else { double price = 0.0; if(priceSet) { // Check the fourth line try { price = Double.parseDouble(thirdLine); } catch(NumberFormatException e) { plugin.message(player, "setup-wrongPrice"); return; } } // Add buy to the FileManager final BuyRegion buy = new BuyRegion(secondLine, event.getPlayer().getWorld()); boolean isMember = plugin.getWorldGuardHandler().containsMember(buy.getRegion(), player.getUniqueId()); boolean isOwner = plugin.getWorldGuardHandler().containsOwner(buy.getRegion(), player.getUniqueId()); boolean landlord = (!player.hasPermission("areashop.createbuy") && ((player.hasPermission("areashop.createbuy.owner") && isOwner) || (player.hasPermission("areashop.createbuy.member") && isMember))); if(landlord) { buy.setLandlord(player.getUniqueId(), player.getName()); } if(priceSet) { buy.setPrice(price); } buy.getSignsFeature().addSign(event.getBlock().getLocation(), event.getBlock().getType(), plugin.getBukkitHandler().getSignFacing(event.getBlock()), null); AddingRegionEvent addingRegionEvent = plugin.getFileManager().addRegion(buy); if (addingRegionEvent.isCancelled()) { plugin.message(player, "general-cancelled", addingRegionEvent.getReason()); return; } buy.handleSchematicEvent(GeneralRegion.RegionEvent.CREATED); plugin.message(player, "setup-buySuccess", buy); // Update the region after the event has written its lines Do.sync(buy::update); } } else if(event.getLine(0).contains(plugin.getConfig().getString("signTags.add"))) { // Check for permission if(!player.hasPermission("areashop.addsign")) { plugin.message(player, "addsign-noPermission"); return; } // Get the other lines String secondLine = event.getLine(1); String thirdLine = event.getLine(2); GeneralRegion region; if(secondLine != null && !secondLine.isEmpty()) { // Get region by secondLine of the sign region = plugin.getFileManager().getRegion(secondLine); if(region == null) { plugin.message(player, "addSign-notRegistered", secondLine); return; } } else { // Get region by sign position List<GeneralRegion> regions = Utils.getImportantRegions(event.getBlock().getLocation()); if(regions.isEmpty()) { plugin.message(player, "addsign-noRegions"); return; } else if(regions.size() > 1) { plugin.message(player, "addsign-couldNotDetectSign", regions.get(0).getName(), regions.get(1).getName()); return; } region = regions.get(0); } if(thirdLine == null || thirdLine.isEmpty()) { region.getSignsFeature().addSign(event.getBlock().getLocation(), event.getBlock().getType(), plugin.getBukkitHandler().getSignFacing(event.getBlock()), null); plugin.message(player, "addsign-success", region); } else { region.getSignsFeature().addSign(event.getBlock().getLocation(), event.getBlock().getType(), plugin.getBukkitHandler().getSignFacing(event.getBlock()), thirdLine); plugin.message(player, "addsign-successProfile", thirdLine, region); } // Update the region later because this event will do it first Do.sync(region::update); } } /** * Convert a location to a string to use as map key. * @param location The location to get the key for * @return A string to use in a map for a location */ public static String locationToString(Location location) { return location.getWorld().getName() + ";" + location.getBlockX() + ";" + location.getBlockY() + ";" + location.getBlockZ(); } /** * Convert a chunk to a string to use as map key. * @param location The location to get the key for * @return A string to use in a map for a chunk */ public static String chunkToString(Location location) { return location.getWorld().getName() + ";" + (location.getBlockX() >> 4) + ";" + (location.getBlockZ() >> 4); } /** * Convert a chunk to a string to use as map key. * Use a Location argument to prevent chunk loading! * @param chunk The location to get the key for * @return A string to use in a map for a chunk */ public static String chunkToString(Chunk chunk) { return chunk.getWorld().getName() + ";" + chunk.getX() + ";" + chunk.getZ(); } /** * Get a sign by a location. * @param location The location to get the sign for * @return The RegionSign that is at the location, or null if none */ public static RegionSign getSignByLocation(Location location) { return allSigns.get(locationToString(location)); } /** * Get the map with all signs. * @return Map with all signs: locationString -> RegionSign */ public static Map<String, RegionSign> getAllSigns() { return allSigns; } /** * Get the map with signs by chunk. * @return Map with signs by chunk: chunkString -> List<RegionSign> */ public static Map<String, List<RegionSign>> getSignsByChunk() { return signsByChunk; } @EventHandler public void regionUpdate(UpdateRegionEvent event) { event.getRegion().getSignsFeature().update(); } @EventHandler(priority = EventPriority.MONITOR) public void onChunkLoad(ChunkLoadEvent event) { List<RegionSign> chunkSigns = signsByChunk.get(chunkToString(event.getChunk())); if(chunkSigns == null) { return; } Do.forAll(chunkSigns, RegionSign::update); } /** * Update all signs connected to this region. * @return true if all signs are updated correctly, false if one or more updates failed */ public boolean update() { boolean result = true; for(RegionSign sign : signs.values()) { result &= sign.update(); } return result; } /** * Check if any of the signs need periodic updating. * @return true if one or more of the signs need periodic updating, otherwise false */ public boolean needsPeriodicUpdate() { boolean result = false; for(RegionSign sign : signs.values()) { result |= sign.needsPeriodicUpdate(); } return result; } /** * Get the signs of this region. * @return List of signs */ public List<RegionSign> getSigns() { return Collections.unmodifiableList(new ArrayList<>(signs.values())); } /** * Get the signs of this region. * @return Map with signs: locationString -> RegionSign */ Map<String, RegionSign> getSignsRef() { return signs; } /** * Get a list with all sign locations. * @return A List with all sign locations */ public List<Location> getSignLocations() { List<Location> result = new ArrayList<>(); for(RegionSign sign : signs.values()) { result.add(sign.getLocation()); } return result; } /** * Add a sign to this region. * @param location The location of the sign * @param signType The type of the sign (WALL_SIGN or SIGN_POST) * @param facing The orientation of the sign * @param profile The profile to use with this sign (null for default) */ public void addSign(Location location, Material signType, BlockFace facing, String profile) { int i = 0; while(getRegion().getConfig().isSet("general.signs." + i)) { i++; } String signPath = "general.signs." + i + "."; getRegion().setSetting(signPath + "location", Utils.locationToConfig(location)); getRegion().setSetting(signPath + "facing", facing != null ? facing.name() : null); getRegion().setSetting(signPath + "signType", signType != null ? signType.name() : null); if(profile != null && !profile.isEmpty()) { getRegion().setSetting(signPath + "profile", profile); } // Add to the map RegionSign sign = new RegionSign(this, i + ""); signs.put(sign.getStringLocation(), sign); allSigns.put(sign.getStringLocation(), sign); signsByChunk.computeIfAbsent(sign.getStringChunk(), key -> new ArrayList<>()) .add(sign); } }