Using multi-faceted strategy to better resolve UUIDs, and do it faster. Fixed dispensers putting fluids in a neighboring claim. Automatically deleting claims for worlds which no longer exist. Streamlined visualization code, hopefully will reduce or eliminate weird visualizations for VERY big land claims. Removed option to disallow un-claiming land in creative mode. Better default for last login date for new players or players who've had their data deleted or lost.
833 lines
32 KiB
Java
833 lines
32 KiB
Java
/*
|
|
GriefPrevention Server Plugin for Minecraft
|
|
Copyright (C) 2012 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.GriefPrevention;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
import org.bukkit.ChatColor;
|
|
import org.bukkit.GameMode;
|
|
import org.bukkit.Location;
|
|
import org.bukkit.Material;
|
|
import org.bukkit.OfflinePlayer;
|
|
import org.bukkit.World.Environment;
|
|
import org.bukkit.block.Block;
|
|
import org.bukkit.block.BlockFace;
|
|
import org.bukkit.block.BlockState;
|
|
import org.bukkit.block.Chest;
|
|
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.BlockBurnEvent;
|
|
import org.bukkit.event.block.BlockDamageEvent;
|
|
import org.bukkit.event.block.BlockDispenseEvent;
|
|
import org.bukkit.event.block.BlockFromToEvent;
|
|
import org.bukkit.event.block.BlockIgniteEvent;
|
|
import org.bukkit.event.block.BlockIgniteEvent.IgniteCause;
|
|
import org.bukkit.event.block.BlockPistonExtendEvent;
|
|
import org.bukkit.event.block.BlockPistonRetractEvent;
|
|
import org.bukkit.event.block.BlockPlaceEvent;
|
|
import org.bukkit.event.block.BlockSpreadEvent;
|
|
import org.bukkit.event.block.SignChangeEvent;
|
|
import org.bukkit.event.world.StructureGrowEvent;
|
|
import org.bukkit.inventory.Inventory;
|
|
import org.bukkit.inventory.ItemStack;
|
|
import org.bukkit.inventory.PlayerInventory;
|
|
import org.bukkit.material.Dispenser;
|
|
import org.bukkit.util.Vector;
|
|
|
|
//event handlers related to blocks
|
|
public class BlockEventHandler implements Listener
|
|
{
|
|
//convenience reference to singleton datastore
|
|
private DataStore dataStore;
|
|
|
|
private ArrayList<Material> trashBlocks;
|
|
|
|
//constructor
|
|
public BlockEventHandler(DataStore dataStore)
|
|
{
|
|
this.dataStore = dataStore;
|
|
|
|
//create the list of blocks which will not trigger a warning when they're placed outside of land claims
|
|
this.trashBlocks = new ArrayList<Material>();
|
|
this.trashBlocks.add(Material.COBBLESTONE);
|
|
this.trashBlocks.add(Material.TORCH);
|
|
this.trashBlocks.add(Material.DIRT);
|
|
this.trashBlocks.add(Material.SAPLING);
|
|
this.trashBlocks.add(Material.GRAVEL);
|
|
this.trashBlocks.add(Material.SAND);
|
|
this.trashBlocks.add(Material.TNT);
|
|
this.trashBlocks.add(Material.WORKBENCH);
|
|
}
|
|
|
|
//when a block is damaged...
|
|
@EventHandler(ignoreCancelled = true)
|
|
public void onBlockDamaged(BlockDamageEvent event)
|
|
{
|
|
//if placing items in protected chests isn't enabled, none of this code needs to run
|
|
if(!GriefPrevention.instance.config_addItemsToClaimedChests) return;
|
|
|
|
Block block = event.getBlock();
|
|
Player player = event.getPlayer();
|
|
|
|
//only care about player-damaged blocks
|
|
if(player == null) return;
|
|
|
|
//FEATURE: players may add items to a chest they don't have permission for by hitting it
|
|
|
|
//if it's a chest
|
|
if(block.getType() == Material.CHEST)
|
|
{
|
|
//only care about non-creative mode players, since those would outright break the box in one hit
|
|
if(player.getGameMode() == GameMode.CREATIVE) return;
|
|
|
|
//only care if the player has an itemstack in hand
|
|
PlayerInventory playerInventory = player.getInventory();
|
|
ItemStack stackInHand = playerInventory.getItemInHand();
|
|
if(stackInHand == null || stackInHand.getType() == Material.AIR) return;
|
|
|
|
//only care if the chest is in a claim, and the player does not have access to the chest
|
|
Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, null);
|
|
if(claim == null || claim.allowContainers(player) == null) return;
|
|
|
|
//if the player is under siege, he can't give away items
|
|
PlayerData playerData = this.dataStore.getPlayerData(event.getPlayer().getUniqueId());
|
|
if(playerData.siegeData != null)
|
|
{
|
|
GriefPrevention.sendMessage(player, TextMode.Err, Messages.SiegeNoDrop);
|
|
event.setCancelled(true);
|
|
return;
|
|
}
|
|
|
|
//if a player is in pvp combat, he can't give away items
|
|
if(playerData.inPvpCombat()) return;
|
|
|
|
//NOTE: to eliminate accidental give-aways, first hit on a chest displays a confirmation message
|
|
//subsequent hits donate item to the chest
|
|
|
|
//if first time damaging this chest, show confirmation message
|
|
if(playerData.lastChestDamageLocation == null || !block.getLocation().equals(playerData.lastChestDamageLocation))
|
|
{
|
|
//remember this location
|
|
playerData.lastChestDamageLocation = block.getLocation();
|
|
|
|
//give the player instructions
|
|
GriefPrevention.sendMessage(player, TextMode.Instr, Messages.DonateItemsInstruction);
|
|
}
|
|
|
|
//otherwise, try to donate the item stack in hand
|
|
else
|
|
{
|
|
//look for empty slot in chest
|
|
Chest chest = (Chest)block.getState();
|
|
Inventory chestInventory = chest.getInventory();
|
|
int availableSlot = chestInventory.firstEmpty();
|
|
|
|
//if there isn't one
|
|
if(availableSlot < 0)
|
|
{
|
|
//tell the player and stop here
|
|
GriefPrevention.sendMessage(player, TextMode.Err, Messages.ChestFull);
|
|
|
|
return;
|
|
}
|
|
|
|
//otherwise, transfer item stack from player to chest
|
|
//NOTE: Inventory.addItem() is smart enough to add items to existing stacks, making filling a chest with garbage as a grief very difficult
|
|
chestInventory.addItem(stackInHand);
|
|
playerInventory.setItemInHand(new ItemStack(Material.AIR));
|
|
|
|
//and confirm for the player
|
|
GriefPrevention.sendMessage(player, TextMode.Success, Messages.DonationSuccess);
|
|
}
|
|
}
|
|
}
|
|
|
|
//when a player breaks a block...
|
|
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
|
public void onBlockBreak(BlockBreakEvent breakEvent)
|
|
{
|
|
Player player = breakEvent.getPlayer();
|
|
PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId());
|
|
Block block = breakEvent.getBlock();
|
|
|
|
//optimization: breaking blocks directly underneath your last successfully broken block is always* safe
|
|
//*not when the new block was claimed after the last break
|
|
//*not in siege mode, where some types of claimed blocks may be broken
|
|
Location blockLocation = block.getLocation();
|
|
Location lastBreakLocation = playerData.lastSuccessfulBreak;
|
|
if(lastBreakLocation != null &&
|
|
!GriefPrevention.instance.siegeEnabledForWorld(block.getWorld()) &&
|
|
blockLocation.getBlockX() == lastBreakLocation.getBlockX() &&
|
|
blockLocation.getBlockZ() == lastBreakLocation.getBlockZ() &&
|
|
blockLocation.getBlockY() <= lastBreakLocation.getBlockY())
|
|
{
|
|
return;
|
|
}
|
|
|
|
//make sure the player is allowed to break at the location
|
|
String noBuildReason = GriefPrevention.instance.allowBreak(player, block.getLocation());
|
|
if(noBuildReason != null)
|
|
{
|
|
GriefPrevention.sendMessage(player, TextMode.Err, noBuildReason);
|
|
breakEvent.setCancelled(true);
|
|
return;
|
|
}
|
|
|
|
//make a note of any successful breaks
|
|
playerData.lastSuccessfulBreak = block.getLocation();
|
|
|
|
//FEATURE: automatically clean up hanging treetops
|
|
//if it's a log
|
|
if(block.getType() == Material.LOG && GriefPrevention.instance.config_trees_removeFloatingTreetops)
|
|
{
|
|
//run the specialized code for treetop removal (see below)
|
|
GriefPrevention.instance.handleLogBroken(block);
|
|
}
|
|
}
|
|
|
|
//when a player places a sign...
|
|
@EventHandler(ignoreCancelled = true)
|
|
public void onSignChanged(SignChangeEvent event)
|
|
{
|
|
Player player = event.getPlayer();
|
|
if(player == null) return;
|
|
|
|
StringBuilder lines = new StringBuilder();
|
|
boolean notEmpty = false;
|
|
for(int i = 0; i < event.getLines().length; i++)
|
|
{
|
|
if(event.getLine(i).length() != 0) notEmpty = true;
|
|
lines.append(event.getLine(i) + ";");
|
|
}
|
|
|
|
String signMessage = lines.toString();
|
|
|
|
//if not empty and wasn't the same as the last sign, log it and remember it for later
|
|
PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId());
|
|
if(notEmpty && playerData.lastMessage != null && !playerData.lastMessage.equals(signMessage))
|
|
{
|
|
GriefPrevention.AddLogEntry("[Sign Placement] <" + player.getName() + "> " + lines.toString() + " @ " + GriefPrevention.getfriendlyLocationString(event.getBlock().getLocation()));
|
|
playerData.lastMessage = signMessage;
|
|
|
|
if(!player.hasPermission("griefprevention.eavesdrop"))
|
|
{
|
|
Player [] players = GriefPrevention.instance.getServer().getOnlinePlayers();
|
|
for(int i = 0; i < players.length; i++)
|
|
{
|
|
Player otherPlayer = players[i];
|
|
if(otherPlayer.hasPermission("griefprevention.eavesdrop"))
|
|
{
|
|
otherPlayer.sendMessage(ChatColor.GRAY + player.getName() + "(sign): " + signMessage);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//when a player places a block...
|
|
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
|
public void onBlockPlace(BlockPlaceEvent placeEvent)
|
|
{
|
|
Player player = placeEvent.getPlayer();
|
|
Block block = placeEvent.getBlock();
|
|
|
|
//FEATURE: limit fire placement, to prevent PvP-by-fire
|
|
|
|
//if placed block is fire and pvp is off, apply rules for proximity to other players
|
|
if(block.getType() == Material.FIRE && !GriefPrevention.instance.config_pvp_enabledWorlds.contains(block.getWorld()) && !player.hasPermission("griefprevention.lava"))
|
|
{
|
|
List<Player> players = block.getWorld().getPlayers();
|
|
for(int i = 0; i < players.size(); i++)
|
|
{
|
|
Player otherPlayer = players.get(i);
|
|
Location location = otherPlayer.getLocation();
|
|
if(!otherPlayer.equals(player) && location.distanceSquared(block.getLocation()) < 9)
|
|
{
|
|
GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerTooCloseForFire, otherPlayer.getName());
|
|
placeEvent.setCancelled(true);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//make sure the player is allowed to build at the location
|
|
String noBuildReason = GriefPrevention.instance.allowBuild(player, block.getLocation());
|
|
if(noBuildReason != null)
|
|
{
|
|
GriefPrevention.sendMessage(player, TextMode.Err, noBuildReason);
|
|
placeEvent.setCancelled(true);
|
|
return;
|
|
}
|
|
|
|
//if the block is being placed within or under an existing claim
|
|
PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId());
|
|
Claim claim = this.dataStore.getClaimAt(block.getLocation(), true, playerData.lastClaim);
|
|
if(claim != null)
|
|
{
|
|
//FEATURE: prevent theft from container using a hopper when the container is at the very bottom of a land claim
|
|
|
|
if(block.getType() == Material.HOPPER)
|
|
{
|
|
//is the above block inside the land claim?
|
|
Block aboveBlock = block.getRelative(BlockFace.UP);
|
|
if(claim.contains(aboveBlock.getLocation(), false, false))
|
|
{
|
|
//then the player needs container trust to place the hopper
|
|
String failureMessage = claim.allowContainers(player);
|
|
if(failureMessage != null)
|
|
{
|
|
placeEvent.setCancelled(true);
|
|
GriefPrevention.sendMessage(player, TextMode.Err, failureMessage);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//rails can't be placed ANYWHERE under a claim, because they could be pushed up to the
|
|
//target container using pistons
|
|
if(block.getType().name().contains("RAIL"))
|
|
{
|
|
//then the player needs container trust to place the hopper
|
|
String failureMessage = claim.allowContainers(player);
|
|
if(failureMessage != null)
|
|
{
|
|
placeEvent.setCancelled(true);
|
|
GriefPrevention.sendMessage(player, TextMode.Err, failureMessage);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//warn about TNT not destroying claimed blocks
|
|
if(block.getType() == Material.TNT && !claim.areExplosivesAllowed)
|
|
{
|
|
GriefPrevention.sendMessage(player, TextMode.Warn, Messages.NoTNTDamageClaims);
|
|
GriefPrevention.sendMessage(player, TextMode.Instr, Messages.ClaimExplosivesAdvertisement);
|
|
}
|
|
|
|
//if the player has permission for the claim and he's placing UNDER the claim
|
|
if(block.getY() <= claim.lesserBoundaryCorner.getBlockY() && claim.allowBuild(player) == null)
|
|
{
|
|
//extend the claim downward
|
|
this.dataStore.extendClaim(claim, claim.getLesserBoundaryCorner().getBlockY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance);
|
|
}
|
|
|
|
//reset the counter for warning the player when he places outside his claims
|
|
playerData.unclaimedBlockPlacementsUntilWarning = 1;
|
|
}
|
|
|
|
//FEATURE: automatically create a claim when a player who has no claims places a chest
|
|
|
|
//otherwise if there's no claim, the player is placing a chest, and new player automatic claims are enabled
|
|
else if(block.getType() == Material.CHEST && GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius > -1 && GriefPrevention.instance.claimsEnabledForWorld(block.getWorld()))
|
|
{
|
|
//if the chest is too deep underground, don't create the claim and explain why
|
|
if(GriefPrevention.instance.config_claims_preventTheft && block.getY() < GriefPrevention.instance.config_claims_maxDepth)
|
|
{
|
|
GriefPrevention.sendMessage(player, TextMode.Warn, Messages.TooDeepToClaim);
|
|
return;
|
|
}
|
|
|
|
int radius = GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius;
|
|
|
|
//if the player doesn't have any claims yet, automatically create a claim centered at the chest
|
|
if(playerData.claims.size() == 0)
|
|
{
|
|
//radius == 0 means protect ONLY the chest
|
|
if(GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius == 0)
|
|
{
|
|
this.dataStore.createClaim(block.getWorld(), block.getX(), block.getX(), block.getY(), block.getY(), block.getZ(), block.getZ(), player.getUniqueId(), null, null);
|
|
GriefPrevention.sendMessage(player, TextMode.Success, Messages.ChestClaimConfirmation);
|
|
}
|
|
|
|
//otherwise, create a claim in the area around the chest
|
|
else
|
|
{
|
|
//as long as the automatic claim overlaps another existing claim, shrink it
|
|
//note that since the player had permission to place the chest, at the very least, the automatic claim will include the chest
|
|
while(radius >= 0 && !this.dataStore.createClaim(block.getWorld(),
|
|
block.getX() - radius, block.getX() + radius,
|
|
block.getY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance, block.getY(),
|
|
block.getZ() - radius, block.getZ() + radius,
|
|
player.getUniqueId(),
|
|
null, null).succeeded)
|
|
{
|
|
radius--;
|
|
}
|
|
|
|
//notify and explain to player
|
|
GriefPrevention.sendMessage(player, TextMode.Success, Messages.AutomaticClaimNotification);
|
|
|
|
//show the player the protected area
|
|
Claim newClaim = this.dataStore.getClaimAt(block.getLocation(), false, null);
|
|
Visualization visualization = Visualization.FromClaim(newClaim, block.getY(), VisualizationType.Claim, player.getLocation());
|
|
Visualization.Apply(player, visualization);
|
|
}
|
|
|
|
//instructions for using /trust
|
|
GriefPrevention.sendMessage(player, TextMode.Instr, Messages.TrustCommandAdvertisement);
|
|
|
|
//unless special permission is required to create a claim with the shovel, educate the player about the shovel
|
|
if(!GriefPrevention.instance.config_claims_creationRequiresPermission)
|
|
{
|
|
GriefPrevention.sendMessage(player, TextMode.Instr, Messages.GoldenShovelAdvertisement);
|
|
}
|
|
}
|
|
|
|
//check to see if this chest is in a claim, and warn when it isn't
|
|
if(GriefPrevention.instance.config_claims_preventTheft && this.dataStore.getClaimAt(block.getLocation(), false, playerData.lastClaim) == null)
|
|
{
|
|
GriefPrevention.sendMessage(player, TextMode.Warn, Messages.UnprotectedChestWarning);
|
|
}
|
|
}
|
|
|
|
//FEATURE: limit wilderness tree planting to grass, or dirt with more blocks beneath it
|
|
else if(block.getType() == Material.SAPLING && GriefPrevention.instance.config_blockSkyTrees && GriefPrevention.instance.claimsEnabledForWorld(player.getWorld()))
|
|
{
|
|
Block earthBlock = placeEvent.getBlockAgainst();
|
|
if(earthBlock.getType() != Material.GRASS)
|
|
{
|
|
if(earthBlock.getRelative(BlockFace.DOWN).getType() == Material.AIR ||
|
|
earthBlock.getRelative(BlockFace.DOWN).getRelative(BlockFace.DOWN).getType() == Material.AIR)
|
|
{
|
|
placeEvent.setCancelled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
//FEATURE: warn players when they're placing non-trash blocks outside of their claimed areas
|
|
else if(GriefPrevention.instance.config_claims_warnOnBuildOutside && !this.trashBlocks.contains(block.getType()) && GriefPrevention.instance.claimsEnabledForWorld(block.getWorld()) && playerData.claims.size() > 0)
|
|
{
|
|
if(--playerData.unclaimedBlockPlacementsUntilWarning <= 0)
|
|
{
|
|
GriefPrevention.sendMessage(player, TextMode.Warn, Messages.BuildingOutsideClaims);
|
|
playerData.unclaimedBlockPlacementsUntilWarning = 15;
|
|
|
|
if(playerData.lastClaim != null && playerData.lastClaim.allowBuild(player) == null)
|
|
{
|
|
Visualization visualization = Visualization.FromClaim(playerData.lastClaim, block.getY(), VisualizationType.Claim, player.getLocation());
|
|
Visualization.Apply(player, visualization);
|
|
}
|
|
}
|
|
}
|
|
|
|
//warn players when they place TNT above sea level, since it doesn't destroy blocks there
|
|
if( GriefPrevention.instance.config_blockSurfaceOtherExplosions && block.getType() == Material.TNT &&
|
|
block.getWorld().getEnvironment() != Environment.NETHER &&
|
|
block.getY() > GriefPrevention.instance.getSeaLevel(block.getWorld()) - 5)
|
|
{
|
|
GriefPrevention.sendMessage(player, TextMode.Warn, Messages.NoTNTDamageAboveSeaLevel);
|
|
}
|
|
|
|
//warn players about disabled pistons outside of land claims
|
|
if( GriefPrevention.instance.config_pistonsInClaimsOnly &&
|
|
(block.getType() == Material.PISTON_BASE || block.getType() == Material.PISTON_STICKY_BASE) &&
|
|
claim == null )
|
|
{
|
|
GriefPrevention.sendMessage(player, TextMode.Warn, Messages.NoPistonsOutsideClaims);
|
|
}
|
|
}
|
|
|
|
//blocks "pushing" other players' blocks around (pistons)
|
|
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
|
public void onBlockPistonExtend (BlockPistonExtendEvent event)
|
|
{
|
|
//pushing down is ALWAYS safe
|
|
if(event.getDirection() == BlockFace.DOWN) return;
|
|
|
|
Block pistonBlock = event.getBlock();
|
|
List<Block> blocks = event.getBlocks();
|
|
|
|
//if no blocks moving, then only check to make sure we're not pushing into a claim from outside
|
|
//this avoids pistons breaking non-solids just inside a claim, like torches, doors, and touchplates
|
|
if(blocks.size() == 0)
|
|
{
|
|
Block invadedBlock = pistonBlock.getRelative(event.getDirection());
|
|
|
|
//pushing "air" is harmless
|
|
if(invadedBlock.getType() == Material.AIR) return;
|
|
|
|
if( this.dataStore.getClaimAt(pistonBlock.getLocation(), false, null) == null &&
|
|
this.dataStore.getClaimAt(invadedBlock.getLocation(), false, null) != null)
|
|
{
|
|
event.setCancelled(true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//who owns the piston, if anyone?
|
|
String pistonClaimOwnerName = "_";
|
|
Claim claim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null);
|
|
if(claim != null) pistonClaimOwnerName = claim.getOwnerName();
|
|
|
|
//if pistons are limited to same-claim block movement
|
|
if(GriefPrevention.instance.config_pistonsInClaimsOnly)
|
|
{
|
|
//if piston is not in a land claim, cancel event
|
|
if(claim == null)
|
|
{
|
|
event.setCancelled(true);
|
|
return;
|
|
}
|
|
|
|
for(Block pushedBlock : event.getBlocks())
|
|
{
|
|
//if pushing blocks located outside the land claim it lives in, cancel the event
|
|
if(!claim.contains(pushedBlock.getLocation(), false, false))
|
|
{
|
|
event.setCancelled(true);
|
|
return;
|
|
}
|
|
|
|
//if pushing a block inside the claim out of the claim, cancel the event
|
|
//reason: could push into another land claim, don't want to spend CPU checking for that
|
|
//reason: push ice out, place torch, get water outside the claim
|
|
if(!claim.contains(pushedBlock.getRelative(event.getDirection()).getLocation(), false, false))
|
|
{
|
|
event.setCancelled(true);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//otherwise, consider ownership of piston and EACH pushed block
|
|
else
|
|
{
|
|
//which blocks are being pushed?
|
|
Claim cachedClaim = claim;
|
|
for(int i = 0; i < blocks.size(); i++)
|
|
{
|
|
//if ANY of the pushed blocks are owned by someone other than the piston owner, cancel the event
|
|
Block block = blocks.get(i);
|
|
claim = this.dataStore.getClaimAt(block.getLocation(), false, cachedClaim);
|
|
if(claim != null)
|
|
{
|
|
cachedClaim = claim;
|
|
if(!claim.getOwnerName().equals(pistonClaimOwnerName))
|
|
{
|
|
event.setCancelled(true);
|
|
event.getBlock().getWorld().createExplosion(event.getBlock().getLocation(), 0);
|
|
event.getBlock().getWorld().dropItem(event.getBlock().getLocation(), new ItemStack(event.getBlock().getType()));
|
|
event.getBlock().setType(Material.AIR);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//if any of the blocks are being pushed into a claim from outside, cancel the event
|
|
for(int i = 0; i < blocks.size(); i++)
|
|
{
|
|
Block block = blocks.get(i);
|
|
Claim originalClaim = this.dataStore.getClaimAt(block.getLocation(), false, cachedClaim);
|
|
String originalOwnerName = "";
|
|
if(originalClaim != null)
|
|
{
|
|
cachedClaim = originalClaim;
|
|
originalOwnerName = originalClaim.getOwnerName();
|
|
}
|
|
|
|
Claim newClaim = this.dataStore.getClaimAt(block.getRelative(event.getDirection()).getLocation(), false, cachedClaim);
|
|
String newOwnerName = "";
|
|
if(newClaim != null)
|
|
{
|
|
newOwnerName = newClaim.getOwnerName();
|
|
}
|
|
|
|
//if pushing this block will change ownership, cancel the event and take away the piston (for performance reasons)
|
|
if(!newOwnerName.equals(originalOwnerName) && !newOwnerName.isEmpty())
|
|
{
|
|
event.setCancelled(true);
|
|
event.getBlock().getWorld().createExplosion(event.getBlock().getLocation(), 0);
|
|
event.getBlock().getWorld().dropItem(event.getBlock().getLocation(), new ItemStack(event.getBlock().getType()));
|
|
event.getBlock().setType(Material.AIR);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//blocks theft by pulling blocks out of a claim (again pistons)
|
|
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
|
public void onBlockPistonRetract (BlockPistonRetractEvent event)
|
|
{
|
|
//we only care about sticky pistons retracting
|
|
if(!event.isSticky()) return;
|
|
|
|
//pulling up is always safe
|
|
if(event.getDirection() == BlockFace.UP) return;
|
|
|
|
//if pulling "air", always safe
|
|
if(event.getRetractLocation().getBlock().getType() == Material.AIR) return;
|
|
|
|
//if pistons limited to only pulling blocks which are in the same claim the piston is in
|
|
if(GriefPrevention.instance.config_pistonsInClaimsOnly)
|
|
{
|
|
//if piston not in a land claim, cancel event
|
|
Claim pistonClaim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null);
|
|
if(pistonClaim == null)
|
|
{
|
|
event.setCancelled(true);
|
|
return;
|
|
}
|
|
|
|
//if pulled block isn't in the same land claim, cancel the event
|
|
if(!pistonClaim.contains(event.getRetractLocation(), false, false))
|
|
{
|
|
event.setCancelled(true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//otherwise, consider ownership of both piston and block
|
|
else
|
|
{
|
|
//who owns the moving block, if anyone?
|
|
String movingBlockOwnerName = "_";
|
|
Claim movingBlockClaim = this.dataStore.getClaimAt(event.getRetractLocation(), false, null);
|
|
if(movingBlockClaim != null) movingBlockOwnerName = movingBlockClaim.getOwnerName();
|
|
|
|
//who owns the piston, if anyone?
|
|
String pistonOwnerName = "_";
|
|
Location pistonLocation = event.getBlock().getLocation();
|
|
Claim pistonClaim = this.dataStore.getClaimAt(pistonLocation, false, movingBlockClaim);
|
|
if(pistonClaim != null) pistonOwnerName = pistonClaim.getOwnerName();
|
|
|
|
//if there are owners for the blocks, they must be the same player
|
|
//otherwise cancel the event
|
|
if(!pistonOwnerName.equals(movingBlockOwnerName))
|
|
{
|
|
event.setCancelled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
//blocks are ignited ONLY by flint and steel (not by being near lava, open flames, etc), unless configured otherwise
|
|
@EventHandler(priority = EventPriority.LOWEST)
|
|
public void onBlockIgnite (BlockIgniteEvent igniteEvent)
|
|
{
|
|
if(!GriefPrevention.instance.config_fireSpreads && igniteEvent.getCause() != IgniteCause.FLINT_AND_STEEL && igniteEvent.getCause() != IgniteCause.LIGHTNING)
|
|
{
|
|
igniteEvent.setCancelled(true);
|
|
}
|
|
}
|
|
|
|
//fire doesn't spread unless configured to, but other blocks still do (mushrooms and vines, for example)
|
|
@EventHandler(priority = EventPriority.LOWEST)
|
|
public void onBlockSpread (BlockSpreadEvent spreadEvent)
|
|
{
|
|
if(spreadEvent.getSource().getType() != Material.FIRE) return;
|
|
|
|
if(!GriefPrevention.instance.config_fireSpreads)
|
|
{
|
|
spreadEvent.setCancelled(true);
|
|
|
|
Block underBlock = spreadEvent.getSource().getRelative(BlockFace.DOWN);
|
|
if(underBlock.getType() != Material.NETHERRACK)
|
|
{
|
|
spreadEvent.getSource().setType(Material.AIR);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//never spread into a claimed area, regardless of settings
|
|
if(this.dataStore.getClaimAt(spreadEvent.getBlock().getLocation(), false, null) != null)
|
|
{
|
|
spreadEvent.setCancelled(true);
|
|
|
|
//if the source of the spread is not fire on netherrack, put out that source fire to save cpu cycles
|
|
Block source = spreadEvent.getSource();
|
|
if(source.getType() == Material.FIRE && source.getRelative(BlockFace.DOWN).getType() != Material.NETHERRACK)
|
|
{
|
|
source.setType(Material.AIR);
|
|
}
|
|
}
|
|
}
|
|
|
|
//blocks are not destroyed by fire, unless configured to do so
|
|
@EventHandler(priority = EventPriority.LOWEST)
|
|
public void onBlockBurn (BlockBurnEvent burnEvent)
|
|
{
|
|
if(!GriefPrevention.instance.config_fireDestroys)
|
|
{
|
|
burnEvent.setCancelled(true);
|
|
Block block = burnEvent.getBlock();
|
|
Block [] adjacentBlocks = new Block []
|
|
{
|
|
block.getRelative(BlockFace.UP),
|
|
block.getRelative(BlockFace.DOWN),
|
|
block.getRelative(BlockFace.NORTH),
|
|
block.getRelative(BlockFace.SOUTH),
|
|
block.getRelative(BlockFace.EAST),
|
|
block.getRelative(BlockFace.WEST)
|
|
};
|
|
|
|
//pro-actively put out any fires adjacent the burning block, to reduce future processing here
|
|
for(int i = 0; i < adjacentBlocks.length; i++)
|
|
{
|
|
Block adjacentBlock = adjacentBlocks[i];
|
|
if(adjacentBlock.getType() == Material.FIRE && adjacentBlock.getRelative(BlockFace.DOWN).getType() != Material.NETHERRACK)
|
|
{
|
|
adjacentBlock.setType(Material.AIR);
|
|
}
|
|
}
|
|
|
|
Block aboveBlock = block.getRelative(BlockFace.UP);
|
|
if(aboveBlock.getType() == Material.FIRE)
|
|
{
|
|
aboveBlock.setType(Material.AIR);
|
|
}
|
|
return;
|
|
}
|
|
|
|
//never burn claimed blocks, regardless of settings
|
|
if(this.dataStore.getClaimAt(burnEvent.getBlock().getLocation(), false, null) != null)
|
|
{
|
|
burnEvent.setCancelled(true);
|
|
}
|
|
}
|
|
|
|
//ensures fluids don't flow out of claims, unless into another claim where the owner is trusted to build
|
|
private Claim lastSpreadClaim = null;
|
|
private Location lastSpreadSourceLocation = null;
|
|
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
|
public void onBlockFromTo (BlockFromToEvent spreadEvent)
|
|
{
|
|
//don't track fluid movement in worlds where claims are not enabled
|
|
if(!GriefPrevention.instance.config_claims_enabledWorlds.contains(spreadEvent.getBlock().getWorld())) return;
|
|
|
|
//always allow fluids to flow straight down
|
|
if(spreadEvent.getFace() == BlockFace.DOWN) return;
|
|
|
|
//from where?
|
|
Block fromBlock = spreadEvent.getBlock();
|
|
Claim fromClaim = null;
|
|
if(fromBlock.getLocation().equals(lastSpreadSourceLocation))
|
|
{
|
|
fromClaim = lastSpreadClaim;
|
|
}
|
|
else
|
|
{
|
|
fromClaim = this.dataStore.getClaimAt(fromBlock.getLocation(), false, this.lastSpreadClaim);
|
|
lastSpreadClaim = fromClaim;
|
|
lastSpreadSourceLocation = fromBlock.getLocation();
|
|
}
|
|
|
|
//where to?
|
|
Block toBlock = spreadEvent.getToBlock();
|
|
|
|
//if from a land claim, this is easy and cheap - just don't allow for flowing out of that claim
|
|
if(fromClaim != null)
|
|
{
|
|
if(!fromClaim.contains(toBlock.getLocation(), false, false))
|
|
{
|
|
spreadEvent.setCancelled(true);
|
|
}
|
|
}
|
|
|
|
//otherwise, just prevent spreading into a claim from outside
|
|
else
|
|
{
|
|
Claim toClaim = this.dataStore.getClaimAt(toBlock.getLocation(), false, fromClaim);
|
|
|
|
//if spreading into a claim
|
|
if(toClaim != null)
|
|
{
|
|
spreadEvent.setCancelled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
//ensures dispensers can't be used to dispense a block(like water or lava) or item across a claim boundary
|
|
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
|
public void onDispense(BlockDispenseEvent dispenseEvent)
|
|
{
|
|
//from where?
|
|
Block fromBlock = dispenseEvent.getBlock();
|
|
Dispenser dispenser = new Dispenser(fromBlock.getType(), fromBlock.getData());
|
|
|
|
//to where?
|
|
Block toBlock = fromBlock.getRelative(dispenser.getFacing());
|
|
Claim fromClaim = this.dataStore.getClaimAt(fromBlock.getLocation(), false, null);
|
|
Claim toClaim = this.dataStore.getClaimAt(toBlock.getLocation(), false, fromClaim);
|
|
|
|
//into wilderness is NOT OK when surface buckets are limited
|
|
Material materialDispensed = dispenseEvent.getItem().getType();
|
|
if((materialDispensed == Material.WATER_BUCKET || materialDispensed == Material.LAVA_BUCKET) && GriefPrevention.instance.config_blockWildernessWaterBuckets && GriefPrevention.instance.claimsEnabledForWorld(fromBlock.getWorld()) && toClaim == null)
|
|
{
|
|
dispenseEvent.setCancelled(true);
|
|
return;
|
|
}
|
|
|
|
//wilderness to wilderness is OK
|
|
if(fromClaim == null && toClaim == null) return;
|
|
|
|
//within claim is OK
|
|
if(fromClaim == toClaim) return;
|
|
|
|
//everything else is NOT OK
|
|
dispenseEvent.setCancelled(true);
|
|
}
|
|
|
|
@EventHandler(ignoreCancelled = true)
|
|
public void onTreeGrow (StructureGrowEvent growEvent)
|
|
{
|
|
//only take these potentially expensive steps if configured to do so
|
|
if(!GriefPrevention.instance.config_limitTreeGrowth) return;
|
|
|
|
Location rootLocation = growEvent.getLocation();
|
|
Claim rootClaim = this.dataStore.getClaimAt(rootLocation, false, null);
|
|
String rootOwnerName = null;
|
|
|
|
//who owns the spreading block, if anyone?
|
|
if(rootClaim != null)
|
|
{
|
|
//tree growth in subdivisions is dependent on who owns the top level claim
|
|
if(rootClaim.parent != null) rootClaim = rootClaim.parent;
|
|
|
|
//if an administrative claim, just let the tree grow where it wants
|
|
if(rootClaim.isAdminClaim()) return;
|
|
|
|
//otherwise, note the owner of the claim
|
|
rootOwnerName = rootClaim.getOwnerName();
|
|
}
|
|
|
|
//for each block growing
|
|
for(int i = 0; i < growEvent.getBlocks().size(); i++)
|
|
{
|
|
BlockState block = growEvent.getBlocks().get(i);
|
|
Claim blockClaim = this.dataStore.getClaimAt(block.getLocation(), false, rootClaim);
|
|
|
|
//if it's growing into a claim
|
|
if(blockClaim != null)
|
|
{
|
|
//if there's no owner for the new tree, or the owner for the new tree is different from the owner of the claim
|
|
if(rootOwnerName == null || !rootOwnerName.equals(blockClaim.getOwnerName()))
|
|
{
|
|
growEvent.getBlocks().remove(i--);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|