/*
PopulationDensity Server Plugin for Minecraft
Copyright (C) 2011 Ryan Hamshire
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package me.ryanhamshire.PopulationDensity;
import java.util.Arrays;
import java.util.List;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockDamageEvent;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.block.BlockPistonExtendEvent;
import org.bukkit.event.block.BlockPistonRetractEvent;
import org.bukkit.event.block.BlockPlaceEvent;
public class BlockEventHandler implements Listener
{
private static List<Material> alwaysBreakableMaterials = Arrays.asList(
Material.LONG_GRASS,
Material.DOUBLE_PLANT,
Material.LOG,
Material.LOG_2,
Material.LEAVES,
Material.LEAVES_2,
Material.RED_ROSE,
Material.YELLOW_FLOWER,
Material.SNOW_BLOCK
);
//when a player breaks a block...
@EventHandler(ignoreCancelled = true)
public void onBlockBreak(BlockBreakEvent breakEvent)
{
Player player = breakEvent.getPlayer();
PopulationDensity.instance.resetIdleTimer(player);
Block block = breakEvent.getBlock();
//if the player is not in managed world, do nothing (let vanilla code and other plugins do whatever)
if(!player.getWorld().equals(PopulationDensity.ManagedWorld)) return;
//otherwise figure out which region that block is in
Location blockLocation = block.getLocation();
//region posts are at sea level at the lowest, so no need to check build permissions under that
if(blockLocation.getBlockY() < PopulationDensity.instance.minimumRegionPostY) return;
//whitelist for blocks which can always be broken (grass cutting, tree chopping)
if(BlockEventHandler.alwaysBreakableMaterials.contains(block.getType())) return;
RegionCoordinates blockRegion = RegionCoordinates.fromLocation(blockLocation);
//if too close to (or above) region post, send an error message
//note the ONLY way to edit around a region post is to have special permission
if(!player.hasPermission("populationdensity.buildbreakanywhere") && this.nearRegionPost(blockLocation, blockRegion, PopulationDensity.instance.postProtectionRadius))
{
if(PopulationDensity.instance.buildRegionPosts)
PopulationDensity.sendMessage(player, TextMode.Err, Messages.NoBreakPost);
else
PopulationDensity.sendMessage(player, TextMode.Err, Messages.NoBreakSpawn);
breakEvent.setCancelled(true);
return;
}
}
private Location lastLocation = null;
private Boolean lastResult = null;
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onBlockFromTo(BlockFromToEvent event)
{
if(event.getFace() == BlockFace.DOWN) return;
Location from = event.getBlock().getLocation();
if(lastLocation != null && from.equals(lastLocation))
{
event.setCancelled(lastResult);
return;
}
//if not in managed world, do nothing
if(!from.getWorld().equals(PopulationDensity.ManagedWorld)) return;
//region posts are at sea level at the lowest, so no need to check build permissions under that
if(from.getY() < PopulationDensity.instance.minimumRegionPostY) return;
RegionCoordinates blockRegion = RegionCoordinates.fromLocation(from);
if(this.nearRegionPost(from, blockRegion, PopulationDensity.instance.postProtectionRadius + 1))
{
event.setCancelled(true);
lastResult = true;
}
else
{
lastResult = false;
}
lastLocation = from;
}
//when a player places a block
@EventHandler(ignoreCancelled = true)
public void onBlockPlace(BlockPlaceEvent placeEvent)
{
Player player = placeEvent.getPlayer();
PopulationDensity.instance.resetIdleTimer(player);
Block block = placeEvent.getBlock();
//if not in managed world, do nothing
if(!player.getWorld().equals(PopulationDensity.ManagedWorld)) return;
Location blockLocation = block.getLocation();
//region posts are at sea level at the lowest, so no need to check build permissions under that
if(blockLocation.getBlockY() < PopulationDensity.instance.minimumRegionPostY) return;
RegionCoordinates blockRegion = RegionCoordinates.fromLocation(blockLocation);
//if too close to (or above) region post, send an error message
if(!player.hasPermission("populationdensity.buildbreakanywhere") && this.nearRegionPost(blockLocation, blockRegion, PopulationDensity.instance.postProtectionRadius))
{
if(PopulationDensity.instance.buildRegionPosts)
PopulationDensity.sendMessage(player, TextMode.Err, Messages.NoBuildPost);
else
PopulationDensity.sendMessage(player, TextMode.Err, Messages.NoBuildSpawn);
placeEvent.setCancelled(true);
return;
}
//if over hopper limit for chunk, send error message
Material type = null;
if(!player.hasPermission("populationdensity.unlimitedhoppers"))
{
type = block.getType();
if(type == Material.HOPPER)
{
int hopperCount = 0;
BlockState [] tiles = block.getChunk().getTileEntities();
for(BlockState tile : tiles)
{
if(tile.getType() == Material.HOPPER && ++hopperCount >= PopulationDensity.instance.config_maximumHoppersPerChunk)
{
placeEvent.setCancelled(true);
PopulationDensity.sendMessage(player, TextMode.Err, Messages.HopperLimitReached, String.valueOf(PopulationDensity.instance.config_maximumHoppersPerChunk));
return;
}
}
}
}
//if bed or chest and player has not been reminded about /movein this play session
if(type == null) type = block.getType();
if(type == Material.BED || type == Material.CHEST)
{
PlayerData playerData = PopulationDensity.instance.dataStore.getPlayerData(player);
if(playerData.advertisedMoveInThisSession) return;
if(!playerData.homeRegion.equals(blockRegion))
{
PopulationDensity.sendMessage(player, TextMode.Warn, Messages.BuildingAwayFromHome);
playerData.advertisedMoveInThisSession = true;
}
}
}
//when a player damages a block...
@EventHandler(ignoreCancelled = true)
public void onBlockDamage(BlockDamageEvent damageEvent)
{
Player player = damageEvent.getPlayer();
Block block = damageEvent.getBlock();
if(player == null || (block.getType() != Material.WALL_SIGN && block.getType() != Material.SIGN_POST)) return;
//if the player is not in managed world, do nothing
if(!player.getWorld().equals(PopulationDensity.ManagedWorld)) return;
if(!this.nearRegionPost(block.getLocation(), RegionCoordinates.fromLocation(block.getLocation()), 1)) return;
PopulationDensity.sendMessage(player, TextMode.Instr, Messages.HelpMessage1, ChatColor.UNDERLINE + "" + ChatColor.AQUA + "http://bit.ly/mcregions");
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onBlockPistonExtend (BlockPistonExtendEvent event)
{
Block pistonBlock = event.getBlock();
if(!pistonBlock.getWorld().equals(PopulationDensity.ManagedWorld)) return;
RegionCoordinates pistonRegion = RegionCoordinates.fromLocation(pistonBlock.getLocation());
if(this.nearRegionPost(pistonBlock.getLocation(), pistonRegion, PopulationDensity.instance.postProtectionRadius + 6))
{
List<Block> blocks = event.getBlocks();
for(Block block : blocks)
{
if(this.nearRegionPost(block.getLocation(), pistonRegion, PopulationDensity.instance.postProtectionRadius + 1))
{
event.setCancelled(true);
return;
}
}
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onBlockPistonRetract (BlockPistonRetractEvent event)
{
Block pistonBlock = event.getBlock();
if(!pistonBlock.getWorld().equals(PopulationDensity.ManagedWorld)) return;
RegionCoordinates pistonRegion = RegionCoordinates.fromLocation(pistonBlock.getLocation());
if(this.nearRegionPost(pistonBlock.getLocation(), pistonRegion, PopulationDensity.instance.postProtectionRadius + 2))
{
List<Block> blocks = event.getBlocks();
for(Block block : blocks)
{
if(this.nearRegionPost(block.getLocation(), pistonRegion, PopulationDensity.instance.postProtectionRadius))
{
event.setCancelled(true);
return;
}
}
}
}
//determines whether or not you're "near" a region post
private boolean nearRegionPost(Location location, RegionCoordinates region, int howClose)
{
Location postLocation = PopulationDensity.getRegionCenter(region, false);
//NOTE! Why not use distance? Because I want a box to the sky, not a sphere.
//Why not round? Below calculation is cheaper than distance (needed for a cylinder or sphere).
//Why to the sky? Because if somebody builds a platform above the post, folks will teleport onto that platform by mistake.
//Also... lava from above would be bad.
//Why not below? Because I can't imagine mining beneath a post as an avenue for griefing.
return ( location.getBlockX() >= postLocation.getBlockX() - howClose &&
location.getBlockX() <= postLocation.getBlockX() + howClose &&
location.getBlockZ() >= postLocation.getBlockZ() - howClose &&
location.getBlockZ() <= postLocation.getBlockZ() + howClose &&
location.getBlockY() >= PopulationDensity.ManagedWorld.getHighestBlockYAt(postLocation) - 4
);
}
}