2013-01-25 03:45:39 +00:00
/ *
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 ;
2020-12-09 08:28:22 +00:00
import me.ryanhamshire.GriefPrevention.util.BoundingBox ;
2019-08-31 23:53:41 +00:00
import org.bukkit.Bukkit ;
2013-02-07 05:26:00 +00:00
import org.bukkit.ChatColor ;
2019-01-10 16:11:45 +00:00
import org.bukkit.GameMode ;
2013-01-25 03:45:39 +00:00
import org.bukkit.Location ;
import org.bukkit.Material ;
2019-08-11 18:49:15 +00:00
import org.bukkit.OfflinePlayer ;
2018-08-13 13:53:14 +00:00
import org.bukkit.Tag ;
2018-09-15 22:12:41 +00:00
import org.bukkit.World ;
2013-01-25 03:45:39 +00:00
import org.bukkit.World.Environment ;
import org.bukkit.block.Block ;
import org.bukkit.block.BlockFace ;
import org.bukkit.block.BlockState ;
2015-01-31 21:31:13 +00:00
import org.bukkit.block.Hopper ;
2020-09-14 23:45:08 +00:00
import org.bukkit.block.PistonMoveReaction ;
2020-05-16 04:11:43 +00:00
import org.bukkit.block.data.BlockData ;
2020-12-10 19:13:44 +00:00
import org.bukkit.block.data.type.Chest ;
2020-05-16 04:11:43 +00:00
import org.bukkit.block.data.type.Dispenser ;
2020-08-07 17:20:35 +00:00
import org.bukkit.entity.Fireball ;
2015-01-31 21:31:13 +00:00
import org.bukkit.entity.Item ;
2013-01-25 03:45:39 +00:00
import org.bukkit.entity.Player ;
2019-08-31 23:53:41 +00:00
import org.bukkit.entity.Projectile ;
2015-01-31 21:31:13 +00:00
import org.bukkit.entity.minecart.HopperMinecart ;
2013-01-25 03:45:39 +00:00
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.BlockDispenseEvent ;
2015-09-16 21:41:05 +00:00
import org.bukkit.event.block.BlockFormEvent ;
2013-01-25 03:45:39 +00:00
import org.bukkit.event.block.BlockFromToEvent ;
import org.bukkit.event.block.BlockIgniteEvent ;
import org.bukkit.event.block.BlockIgniteEvent.IgniteCause ;
2015-01-17 02:03:50 +00:00
import org.bukkit.event.block.BlockMultiPlaceEvent ;
2020-09-14 23:45:08 +00:00
import org.bukkit.event.block.BlockPistonEvent ;
2013-01-25 03:45:39 +00:00
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 ;
2019-08-31 23:53:41 +00:00
import org.bukkit.event.entity.ProjectileHitEvent ;
2020-09-26 19:54:19 +00:00
import org.bukkit.event.hanging.HangingBreakEvent ;
2015-01-31 21:31:13 +00:00
import org.bukkit.event.inventory.InventoryPickupItemEvent ;
2013-01-25 03:45:39 +00:00
import org.bukkit.event.world.StructureGrowEvent ;
2015-01-31 21:31:13 +00:00
import org.bukkit.inventory.InventoryHolder ;
2013-01-25 03:45:39 +00:00
import org.bukkit.inventory.ItemStack ;
2015-01-31 21:31:13 +00:00
import org.bukkit.metadata.MetadataValue ;
2020-08-07 17:20:35 +00:00
import org.bukkit.projectiles.BlockProjectileSource ;
import org.bukkit.projectiles.ProjectileSource ;
2013-01-25 03:45:39 +00:00
2020-05-29 04:25:34 +00:00
import java.util.ArrayList ;
import java.util.Collection ;
2020-10-03 00:35:45 +00:00
import java.util.EnumSet ;
2020-09-14 23:45:08 +00:00
import java.util.HashSet ;
2020-05-29 04:25:34 +00:00
import java.util.List ;
2020-09-15 04:05:51 +00:00
import java.util.Objects ;
2020-05-29 04:25:34 +00:00
import java.util.UUID ;
2020-12-09 15:59:35 +00:00
import java.util.function.BiPredicate ;
2021-06-24 08:28:59 +00:00
import java.util.function.Supplier ;
2020-05-29 04:25:34 +00:00
2013-01-25 03:45:39 +00:00
//event handlers related to blocks
2020-06-09 04:57:55 +00:00
public class BlockEventHandler implements Listener
2013-01-25 03:45:39 +00:00
{
2020-06-09 04:57:55 +00:00
//convenience reference to singleton datastore
2020-09-20 16:01:37 +00:00
private final DataStore dataStore ;
2020-06-09 04:57:55 +00:00
2020-10-03 00:35:45 +00:00
private final EnumSet < Material > trashBlocks ;
2020-06-09 04:57:55 +00:00
//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
2020-10-03 00:35:45 +00:00
this . trashBlocks = EnumSet . noneOf ( Material . class ) ;
2020-06-09 04:57:55 +00:00
this . trashBlocks . add ( Material . COBBLESTONE ) ;
this . trashBlocks . add ( Material . TORCH ) ;
this . trashBlocks . add ( Material . DIRT ) ;
this . trashBlocks . add ( Material . OAK_SAPLING ) ;
this . trashBlocks . add ( Material . SPRUCE_SAPLING ) ;
this . trashBlocks . add ( Material . BIRCH_SAPLING ) ;
this . trashBlocks . add ( Material . JUNGLE_SAPLING ) ;
this . trashBlocks . add ( Material . ACACIA_SAPLING ) ;
this . trashBlocks . add ( Material . DARK_OAK_SAPLING ) ;
this . trashBlocks . add ( Material . GRAVEL ) ;
this . trashBlocks . add ( Material . SAND ) ;
this . trashBlocks . add ( Material . TNT ) ;
this . trashBlocks . add ( Material . CRAFTING_TABLE ) ;
2021-07-10 19:31:12 +00:00
this . trashBlocks . add ( Material . TUFF ) ;
this . trashBlocks . add ( Material . COBBLED_DEEPSLATE ) ;
2020-06-09 04:57:55 +00:00
}
//when a player breaks a block...
@EventHandler ( ignoreCancelled = true , priority = EventPriority . LOWEST )
public void onBlockBreak ( BlockBreakEvent breakEvent )
{
Player player = breakEvent . getPlayer ( ) ;
Block block = breakEvent . getBlock ( ) ;
//make sure the player is allowed to break at the location
String noBuildReason = GriefPrevention . instance . allowBreak ( player , block , block . getLocation ( ) , breakEvent ) ;
if ( noBuildReason ! = null )
{
GriefPrevention . sendMessage ( player , TextMode . Err , noBuildReason ) ;
breakEvent . setCancelled ( true ) ;
return ;
}
}
2020-09-14 23:48:34 +00:00
//when a player changes the text of a sign...
2020-06-09 04:57:55 +00:00
@EventHandler ( ignoreCancelled = true )
public void onSignChanged ( SignChangeEvent event )
{
2020-09-14 23:48:34 +00:00
Player player = event . getPlayer ( ) ;
Block sign = event . getBlock ( ) ;
if ( player = = null | | sign = = null ) return ;
String noBuildReason = GriefPrevention . instance . allowBuild ( player , sign . getLocation ( ) , sign . getType ( ) ) ;
if ( noBuildReason ! = null )
{
GriefPrevention . sendMessage ( player , TextMode . Err , noBuildReason ) ;
event . setCancelled ( true ) ;
return ;
}
2020-06-09 04:57:55 +00:00
//send sign content to online administrators
if ( ! GriefPrevention . instance . config_signNotifications ) return ;
StringBuilder lines = new StringBuilder ( " placed a sign @ " + GriefPrevention . getfriendlyLocationString ( event . getBlock ( ) . getLocation ( ) ) ) ;
boolean notEmpty = false ;
for ( int i = 0 ; i < event . getLines ( ) . length ; i + + )
{
String withoutSpaces = event . getLine ( i ) . replace ( " " , " " ) ;
if ( ! withoutSpaces . isEmpty ( ) )
{
notEmpty = true ;
2020-09-20 16:06:53 +00:00
lines . append ( " \ n " ) . append ( event . getLine ( i ) ) ;
2020-06-09 04:57:55 +00:00
}
}
String signMessage = lines . toString ( ) ;
//prevent signs with blocked IP addresses
if ( ! player . hasPermission ( " griefprevention.spam " ) & & GriefPrevention . instance . containsBlockedIP ( signMessage ) )
2015-03-18 22:49:07 +00:00
{
event . setCancelled ( true ) ;
return ;
}
2020-06-09 04:57:55 +00:00
PlayerData playerData = this . dataStore . getPlayerData ( player . getUniqueId ( ) ) ;
//if not empty and wasn't the same as the last sign, log it and remember it for later
//This has been temporarily removed since `signMessage` includes location, not just the message. Waste of memory IMO
//if(notEmpty && (playerData.lastSignMessage == null || !playerData.lastSignMessage.equals(signMessage)))
if ( notEmpty )
{
GriefPrevention . AddLogEntry ( player . getName ( ) + lines . toString ( ) . replace ( " \ n " , " ; " ) , null ) ;
PlayerEventHandler . makeSocialLogEntry ( player . getName ( ) , signMessage ) ;
//playerData.lastSignMessage = signMessage;
if ( ! player . hasPermission ( " griefprevention.eavesdropsigns " ) )
{
@SuppressWarnings ( " unchecked " )
Collection < Player > players = ( Collection < Player > ) GriefPrevention . instance . getServer ( ) . getOnlinePlayers ( ) ;
for ( Player otherPlayer : players )
{
if ( otherPlayer . hasPermission ( " griefprevention.eavesdropsigns " ) )
{
otherPlayer . sendMessage ( ChatColor . GRAY + player . getName ( ) + signMessage ) ;
}
}
}
}
}
//when a player places multiple blocks...
@EventHandler ( ignoreCancelled = true , priority = EventPriority . LOWEST )
public void onBlocksPlace ( BlockMultiPlaceEvent placeEvent )
{
Player player = placeEvent . getPlayer ( ) ;
//don't track in worlds where claims are not enabled
if ( ! GriefPrevention . instance . claimsEnabledForWorld ( placeEvent . getBlock ( ) . getWorld ( ) ) ) return ;
2015-01-17 02:03:50 +00:00
//make sure the player is allowed to build at the location
2020-06-09 04:57:55 +00:00
for ( BlockState block : placeEvent . getReplacedBlockStates ( ) )
2015-01-17 02:03:50 +00:00
{
String noBuildReason = GriefPrevention . instance . allowBuild ( player , block . getLocation ( ) , block . getType ( ) ) ;
2020-06-09 04:57:55 +00:00
if ( noBuildReason ! = null )
2015-01-17 02:03:50 +00:00
{
GriefPrevention . sendMessage ( player , TextMode . Err , noBuildReason ) ;
placeEvent . setCancelled ( true ) ;
return ;
}
}
2020-06-09 04:57:55 +00:00
}
private boolean doesAllowFireProximityInWorld ( World world )
{
if ( GriefPrevention . instance . pvpRulesApply ( world ) )
{
return GriefPrevention . instance . config_pvp_allowFireNearPlayers ;
2020-06-09 06:15:52 +00:00
}
else
2020-06-09 04:57:55 +00:00
{
return GriefPrevention . instance . config_pvp_allowFireNearPlayers_NonPvp ;
}
}
//when a player places a block...
@SuppressWarnings ( " null " )
2020-02-02 21:30:56 +00:00
@EventHandler ( ignoreCancelled = true , priority = EventPriority . LOWEST )
2020-06-09 04:57:55 +00:00
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 & & ! doesAllowFireProximityInWorld ( block . getWorld ( ) ) )
{
List < Player > players = block . getWorld ( ) . getPlayers ( ) ;
2020-09-19 21:14:11 +00:00
for ( Player otherPlayer : players )
2020-06-09 04:57:55 +00:00
{
// Ignore players in creative or spectator mode to avoid users from checking if someone is spectating near them
if ( otherPlayer . getGameMode ( ) = = GameMode . CREATIVE | | otherPlayer . getGameMode ( ) = = GameMode . SPECTATOR )
{
continue ;
}
Location location = otherPlayer . getLocation ( ) ;
if ( ! otherPlayer . equals ( player ) & & location . distanceSquared ( block . getLocation ( ) ) < 9 & & player . canSee ( otherPlayer ) )
{
GriefPrevention . sendMessage ( player , TextMode . Err , Messages . PlayerTooCloseForFire2 ) ;
placeEvent . setCancelled ( true ) ;
return ;
}
}
}
//don't track in worlds where claims are not enabled
if ( ! GriefPrevention . instance . claimsEnabledForWorld ( placeEvent . getBlock ( ) . getWorld ( ) ) ) return ;
//make sure the player is allowed to build at the location
String noBuildReason = GriefPrevention . instance . allowBuild ( player , block . getLocation ( ) , block . getType ( ) ) ;
if ( noBuildReason ! = null )
{
// Allow players with container trust to place books in lecterns
PlayerData playerData = this . dataStore . getPlayerData ( player . getUniqueId ( ) ) ;
Claim claim = this . dataStore . getClaimAt ( block . getLocation ( ) , true , playerData . lastClaim ) ;
if ( block . getType ( ) = = Material . LECTERN & & placeEvent . getBlockReplacedState ( ) . getType ( ) = = Material . LECTERN )
{
if ( claim ! = null )
{
playerData . lastClaim = claim ;
2021-06-24 08:28:59 +00:00
Supplier < String > noContainerReason = claim . checkPermission ( player , ClaimPermission . Inventory , placeEvent ) ;
2020-06-09 04:57:55 +00:00
if ( noContainerReason = = null )
return ;
placeEvent . setCancelled ( true ) ;
2021-06-24 08:28:59 +00:00
GriefPrevention . sendMessage ( player , TextMode . Err , noContainerReason . get ( ) ) ;
2020-06-09 04:57:55 +00:00
return ;
}
}
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 ) ;
2020-12-10 19:13:44 +00:00
//If block is a chest, don't allow a DoubleChest to form across a claim boundary
denyConnectingDoubleChestsAcrossClaimBoundary ( claim , block , player ) ;
2020-06-09 04:57:55 +00:00
if ( claim ! = null )
{
playerData . lastClaim = claim ;
//warn about TNT not destroying claimed blocks
if ( block . getType ( ) = = Material . TNT & & ! claim . areExplosivesAllowed & & playerData . siegeData = = null )
2014-09-07 04:42:11 +00:00
{
GriefPrevention . sendMessage ( player , TextMode . Warn , Messages . NoTNTDamageClaims ) ;
GriefPrevention . sendMessage ( player , TextMode . Instr , Messages . ClaimExplosivesAdvertisement ) ;
}
2020-06-09 04:57:55 +00:00
//if the player has permission for the claim and he's placing UNDER the claim
2021-06-24 08:28:59 +00:00
if ( block . getY ( ) < = claim . lesserBoundaryCorner . getBlockY ( ) & & claim . checkPermission ( player , ClaimPermission . Build , placeEvent ) = = null )
2020-06-09 04:57:55 +00:00
{
//extend the claim downward
this . dataStore . extendClaim ( claim , block . getY ( ) - GriefPrevention . instance . config_claims_claimsExtendIntoGroundDistance ) ;
}
//allow for a build warning in the future
playerData . warnedAboutBuildingOutsideClaims = false ;
}
//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 ( GriefPrevention . instance . config_claims_automaticClaimsForNewPlayersRadius > - 1 & & player . hasPermission ( " griefprevention.createclaims " ) & & block . getType ( ) = = Material . CHEST )
{
//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 . getClaims ( ) . 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 , player ) ;
GriefPrevention . sendMessage ( player , TextMode . Success , Messages . ChestClaimConfirmation ) ;
}
//otherwise, create a claim in the area around the chest
else
{
//if failure due to insufficient claim blocks available
2020-10-02 16:02:37 +00:00
if ( playerData . getRemainingClaimBlocks ( ) < Math . pow ( 1 + 2 * GriefPrevention . instance . config_claims_automaticClaimsForNewPlayersRadiusMin , 2 ) )
2015-12-23 16:19:06 +00:00
{
GriefPrevention . sendMessage ( player , TextMode . Warn , Messages . NoEnoughBlocksForChestClaim ) ;
return ;
}
2020-06-09 04:57:55 +00:00
//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
CreateClaimResult result = null ;
2020-10-02 16:02:37 +00:00
while ( radius > = GriefPrevention . instance . config_claims_automaticClaimsForNewPlayersRadiusMin )
2020-06-09 04:57:55 +00:00
{
int area = ( radius * 2 + 1 ) * ( radius * 2 + 1 ) ;
if ( playerData . getRemainingClaimBlocks ( ) > = area )
{
result = 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 ,
player ) ;
if ( result . succeeded ) break ;
}
radius - - ;
}
2020-10-02 16:02:37 +00:00
if ( result ! = null & & result . claim ! = null )
2020-06-09 04:57:55 +00:00
{
2020-10-02 16:02:37 +00:00
if ( result . succeeded )
{
//notify and explain to player
GriefPrevention . sendMessage ( player , TextMode . Success , Messages . AutomaticClaimNotification ) ;
2020-06-09 04:57:55 +00:00
2020-10-02 16:02:37 +00:00
//show the player the protected area
Visualization visualization = Visualization . FromClaim ( result . claim , block . getY ( ) , VisualizationType . Claim , player . getLocation ( ) ) ;
Visualization . Apply ( player , visualization ) ;
}
else
{
//notify and explain to player
GriefPrevention . sendMessage ( player , TextMode . Err , Messages . AutomaticClaimOtherClaimTooClose ) ;
//show the player the protected area
Visualization visualization = Visualization . FromClaim ( result . claim , block . getY ( ) , VisualizationType . ErrorClaim , player . getLocation ( ) ) ;
Visualization . Apply ( player , visualization ) ;
}
2020-06-09 04:57:55 +00:00
}
}
GriefPrevention . sendMessage ( player , TextMode . Instr , Messages . SurvivalBasicsVideo2 , DataStore . SURVIVAL_VIDEO_URL ) ;
}
//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 ( Tag . SAPLINGS . isTagged ( block . getType ( ) ) & & 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 ( ! this . trashBlocks . contains ( block . getType ( ) ) & & GriefPrevention . instance . claimsEnabledForWorld ( block . getWorld ( ) ) )
{
if ( ! playerData . warnedAboutBuildingOutsideClaims & & ! player . hasPermission ( " griefprevention.adminclaims " )
& & player . hasPermission ( " griefprevention.createclaims " ) & & ( ( playerData . lastClaim = = null
& & playerData . getClaims ( ) . size ( ) = = 0 ) | | ( playerData . lastClaim ! = null
& & playerData . lastClaim . isNear ( player . getLocation ( ) , 15 ) ) ) )
{
Long now = null ;
if ( playerData . buildWarningTimestamp = = null | | ( now = System . currentTimeMillis ( ) ) - playerData . buildWarningTimestamp > 600000 ) //10 minute cooldown
{
GriefPrevention . sendMessage ( player , TextMode . Warn , Messages . BuildingOutsideClaims ) ;
playerData . warnedAboutBuildingOutsideClaims = true ;
if ( now = = null ) now = System . currentTimeMillis ( ) ;
playerData . buildWarningTimestamp = now ;
if ( playerData . getClaims ( ) . size ( ) < 2 )
{
GriefPrevention . sendMessage ( player , TextMode . Instr , Messages . SurvivalBasicsVideo2 , DataStore . SURVIVAL_VIDEO_URL ) ;
}
if ( playerData . lastClaim ! = 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 & &
claim = = null & &
playerData . siegeData = = null )
{
GriefPrevention . sendMessage ( player , TextMode . Warn , Messages . NoTNTDamageAboveSeaLevel ) ;
}
//warn players about disabled pistons outside of land claims
2020-09-14 23:45:08 +00:00
if ( GriefPrevention . instance . config_pistonMovement = = PistonMode . CLAIMS_ONLY & &
2020-06-09 04:57:55 +00:00
( block . getType ( ) = = Material . PISTON | | block . getType ( ) = = Material . STICKY_PISTON ) & &
claim = = null )
{
GriefPrevention . sendMessage ( player , TextMode . Warn , Messages . NoPistonsOutsideClaims ) ;
}
//limit active blocks in creative mode worlds
if ( ! player . hasPermission ( " griefprevention.adminclaims " ) & & GriefPrevention . instance . creativeRulesApply ( block . getLocation ( ) ) & & isActiveBlock ( block ) )
{
String noPlaceReason = claim . allowMoreActiveBlocks ( ) ;
if ( noPlaceReason ! = null )
2016-01-20 21:40:30 +00:00
{
GriefPrevention . sendMessage ( player , TextMode . Err , noPlaceReason ) ;
placeEvent . setCancelled ( true ) ;
return ;
}
2020-06-09 04:57:55 +00:00
}
}
static boolean isActiveBlock ( Block block )
{
return isActiveBlock ( block . getType ( ) ) ;
}
static boolean isActiveBlock ( BlockState state )
{
return isActiveBlock ( state . getType ( ) ) ;
}
static boolean isActiveBlock ( Material type )
{
if ( type = = Material . HOPPER | | type = = Material . BEACON | | type = = Material . SPAWNER ) return true ;
return false ;
}
2020-12-10 19:13:44 +00:00
private static final BlockFace [ ] HORIZONTAL_DIRECTIONS = new BlockFace [ ] {
BlockFace . NORTH ,
BlockFace . EAST ,
BlockFace . SOUTH ,
BlockFace . WEST
} ;
private void denyConnectingDoubleChestsAcrossClaimBoundary ( Claim claim , Block block , Player player )
{
UUID claimOwner = null ;
if ( claim ! = null )
claimOwner = claim . getOwnerID ( ) ;
// Check for double chests placed just outside the claim boundary
if ( block . getBlockData ( ) instanceof Chest )
{
for ( BlockFace face : HORIZONTAL_DIRECTIONS )
{
Block relative = block . getRelative ( face ) ;
if ( ! ( relative . getBlockData ( ) instanceof Chest ) ) continue ;
Claim relativeClaim = this . dataStore . getClaimAt ( relative . getLocation ( ) , true , claim ) ;
UUID relativeClaimOwner = relativeClaim = = null ? null : relativeClaim . getOwnerID ( ) ;
// Chests outside claims should connect (both null)
// and chests inside the same claim should connect (equal)
if ( Objects . equals ( claimOwner , relativeClaimOwner ) ) break ;
// Change both chests to singular chests
Chest chest = ( Chest ) block . getBlockData ( ) ;
chest . setType ( Chest . Type . SINGLE ) ;
block . setBlockData ( chest ) ;
Chest relativeChest = ( Chest ) relative . getBlockData ( ) ;
relativeChest . setType ( Chest . Type . SINGLE ) ;
relative . setBlockData ( relativeChest ) ;
// Resend relative chest block to prevent visual bug
player . sendBlockChange ( relative . getLocation ( ) , relativeChest ) ;
break ;
}
}
}
2020-09-14 23:45:08 +00:00
// Prevent pistons pushing blocks into or out of claims.
2020-06-09 04:57:55 +00:00
@EventHandler ( ignoreCancelled = true , priority = EventPriority . LOWEST )
public void onBlockPistonExtend ( BlockPistonExtendEvent event )
{
2020-10-12 04:15:54 +00:00
onPistonEvent ( event , event . getBlocks ( ) , false ) ;
2020-09-14 23:45:08 +00:00
}
// Prevent pistons pulling blocks into or out of claims.
@EventHandler ( ignoreCancelled = true , priority = EventPriority . LOWEST )
public void onBlockPistonRetract ( BlockPistonRetractEvent event )
{
2020-10-12 04:15:54 +00:00
onPistonEvent ( event , event . getBlocks ( ) , true ) ;
2020-09-14 23:45:08 +00:00
}
2020-06-09 04:57:55 +00:00
2020-09-14 23:45:08 +00:00
// Handle piston push and pulls.
2020-10-12 04:15:54 +00:00
private void onPistonEvent ( BlockPistonEvent event , List < Block > blocks , boolean isRetract )
2020-09-14 23:45:08 +00:00
{
PistonMode pistonMode = GriefPrevention . instance . config_pistonMovement ;
// Return if piston movements are ignored.
if ( pistonMode = = PistonMode . IGNORED ) return ;
2020-06-09 04:57:55 +00:00
2020-09-14 23:45:08 +00:00
// Don't check in worlds where claims are not enabled.
2020-06-09 04:57:55 +00:00
if ( ! GriefPrevention . instance . claimsEnabledForWorld ( event . getBlock ( ) . getWorld ( ) ) ) return ;
2020-09-14 23:45:08 +00:00
BlockFace direction = event . getDirection ( ) ;
2020-06-09 04:57:55 +00:00
Block pistonBlock = event . getBlock ( ) ;
2020-12-09 15:59:35 +00:00
Claim pistonClaim = this . dataStore . getClaimAt ( pistonBlock . getLocation ( ) , false ,
pistonMode ! = PistonMode . CLAIMS_ONLY , null ) ;
2020-06-09 04:57:55 +00:00
2020-09-14 23:45:08 +00:00
// A claim is required, but the piston is not inside a claim.
if ( pistonClaim = = null & & pistonMode = = PistonMode . CLAIMS_ONLY )
2020-06-09 04:57:55 +00:00
{
2020-09-14 23:45:08 +00:00
event . setCancelled ( true ) ;
return ;
}
2020-06-09 04:57:55 +00:00
2020-09-14 23:45:08 +00:00
// If no blocks are moving, quickly check if another claim's boundaries are violated.
if ( blocks . isEmpty ( ) )
{
2020-10-29 16:52:43 +00:00
// No block and retraction is always safe.
if ( isRetract ) return ;
2020-09-14 23:45:08 +00:00
Block invadedBlock = pistonBlock . getRelative ( direction ) ;
2020-12-09 15:59:35 +00:00
Claim invadedClaim = this . dataStore . getClaimAt ( invadedBlock . getLocation ( ) , false ,
pistonMode ! = PistonMode . CLAIMS_ONLY , pistonClaim ) ;
2020-10-12 04:21:43 +00:00
if ( invadedClaim ! = null & & ( pistonClaim = = null | | ! Objects . equals ( pistonClaim . getOwnerID ( ) , invadedClaim . getOwnerID ( ) ) ) )
2020-06-09 04:57:55 +00:00
{
event . setCancelled ( true ) ;
}
return ;
}
2020-12-09 08:28:22 +00:00
// Create bounding box for moved blocks.
BoundingBox movedBlocks = BoundingBox . ofBlocks ( blocks ) ;
// Expand to include invaded zone.
movedBlocks . resize ( direction , 1 ) ;
2020-09-14 23:45:08 +00:00
2020-12-09 15:59:35 +00:00
if ( pistonClaim ! = null )
2020-09-14 23:45:08 +00:00
{
2020-12-09 15:59:35 +00:00
// If blocks are all inside the same claim as the piston, allow.
if ( new BoundingBox ( pistonClaim ) . contains ( movedBlocks ) ) return ;
/ *
* In claims - only mode , all moved blocks must be inside of the owning claim .
* From BigScary :
* - Could push into another land claim , don ' t want to spend CPU checking for that
* - Push ice out , place torch , get water outside the claim
* /
if ( pistonMode = = PistonMode . CLAIMS_ONLY )
{
2020-09-14 23:45:08 +00:00
event . setCancelled ( true ) ;
2020-12-09 15:59:35 +00:00
return ;
}
2020-06-09 04:57:55 +00:00
}
2020-12-09 08:28:22 +00:00
// Check if blocks are in line vertically.
if ( movedBlocks . getLength ( ) = = 1 & & movedBlocks . getWidth ( ) = = 1 )
{
// Pulling up is always safe. The claim may not contain the area pulled from, but claims cannot stack.
if ( isRetract & & direction = = BlockFace . UP ) return ;
// Pushing down is always safe. The claim may not contain the area pushed into, but claims cannot stack.
if ( ! isRetract & & direction = = BlockFace . DOWN ) return ;
}
2020-09-14 23:45:08 +00:00
2020-12-09 15:59:35 +00:00
// Assemble list of potentially intersecting claims from chunks interacted with.
ArrayList < Claim > intersectable = new ArrayList < > ( ) ;
int chunkXMax = movedBlocks . getMaxX ( ) > > 4 ;
int chunkZMax = movedBlocks . getMaxZ ( ) > > 4 ;
2020-09-14 23:45:08 +00:00
2020-12-09 15:59:35 +00:00
for ( int chunkX = movedBlocks . getMinX ( ) > > 4 ; chunkX < = chunkXMax ; + + chunkX )
{
for ( int chunkZ = movedBlocks . getMinZ ( ) > > 4 ; chunkZ < = chunkZMax ; + + chunkZ )
2020-06-09 04:57:55 +00:00
{
2020-12-09 15:59:35 +00:00
ArrayList < Claim > chunkClaims = dataStore . chunksToClaimsMap . get ( DataStore . getChunkHash ( chunkX , chunkZ ) ) ;
if ( chunkClaims = = null ) continue ;
2020-11-15 14:15:00 +00:00
2020-12-09 15:59:35 +00:00
for ( Claim claim : chunkClaims )
{
// Ensure claim is not piston claim and is in same world.
if ( pistonClaim ! = claim & & pistonBlock . getWorld ( ) . equals ( claim . getLesserBoundaryCorner ( ) . getWorld ( ) ) )
intersectable . add ( claim ) ;
2020-06-09 04:57:55 +00:00
}
}
2020-12-09 15:59:35 +00:00
}
2020-06-09 04:57:55 +00:00
2020-12-09 15:59:35 +00:00
BiPredicate < Claim , BoundingBox > intersectionHandler ;
final Claim finalPistonClaim = pistonClaim ;
2020-09-14 23:45:08 +00:00
2020-12-09 15:59:35 +00:00
// Fast mode: Bounding box intersection always causes a conflict, even if blocks do not conflict.
if ( pistonMode = = PistonMode . EVERYWHERE_SIMPLE )
{
intersectionHandler = ( claim , claimBoundingBox ) - >
{
2020-09-14 23:45:08 +00:00
// If owners are different, cancel.
2020-12-09 15:59:35 +00:00
if ( finalPistonClaim = = null | | ! Objects . equals ( finalPistonClaim . getOwnerID ( ) , claim . getOwnerID ( ) ) )
2020-06-09 04:57:55 +00:00
{
event . setCancelled ( true ) ;
2020-12-09 15:59:35 +00:00
return true ;
2020-06-09 04:57:55 +00:00
}
2020-09-14 23:45:08 +00:00
2020-12-09 15:59:35 +00:00
// Otherwise, proceed to next claim.
return false ;
} ;
2020-06-09 04:57:55 +00:00
}
2020-12-09 15:59:35 +00:00
// Precise mode: Bounding box intersection may not yield a conflict. Individual blocks must be considered.
else
{
// Set up list of affected blocks.
HashSet < Block > checkBlocks = new HashSet < > ( blocks ) ;
2020-06-09 04:57:55 +00:00
2020-12-09 15:59:35 +00:00
// Add all blocks that will be occupied after the shift.
for ( Block block : blocks )
if ( block . getPistonMoveReaction ( ) ! = PistonMoveReaction . BREAK )
checkBlocks . add ( block . getRelative ( direction ) ) ;
2020-06-09 04:57:55 +00:00
2020-12-09 15:59:35 +00:00
intersectionHandler = ( claim , claimBoundingBox ) - >
2020-06-09 04:57:55 +00:00
{
2020-12-09 15:59:35 +00:00
// Ensure that the claim contains an affected block.
if ( checkBlocks . stream ( ) . noneMatch ( claimBoundingBox : : contains ) ) return false ;
// If pushing this block will change ownership, cancel the event and take away the piston (for performance reasons).
if ( finalPistonClaim = = null | | ! Objects . equals ( finalPistonClaim . getOwnerID ( ) , claim . getOwnerID ( ) ) )
2020-06-09 04:57:55 +00:00
{
2020-12-09 15:59:35 +00:00
event . setCancelled ( true ) ;
if ( GriefPrevention . instance . config_pistonExplosionSound )
2020-06-09 04:57:55 +00:00
{
2020-12-09 15:59:35 +00:00
pistonBlock . getWorld ( ) . createExplosion ( pistonBlock . getLocation ( ) , 0 ) ;
2020-06-09 04:57:55 +00:00
}
2020-12-09 15:59:35 +00:00
pistonBlock . getWorld ( ) . dropItem ( pistonBlock . getLocation ( ) , new ItemStack ( event . isSticky ( ) ? Material . STICKY_PISTON : Material . PISTON ) ) ;
pistonBlock . setType ( Material . AIR ) ;
return true ;
2020-06-09 04:57:55 +00:00
}
2020-12-09 15:59:35 +00:00
// Otherwise, proceed to next claim.
return false ;
} ;
}
2020-06-09 04:57:55 +00:00
2020-12-09 15:59:35 +00:00
for ( Claim claim : intersectable )
{
BoundingBox claimBoundingBox = new BoundingBox ( claim ) ;
2020-09-14 23:45:08 +00:00
2020-12-09 15:59:35 +00:00
// Ensure claim intersects with block bounding box.
if ( ! claimBoundingBox . intersects ( movedBlocks ) ) continue ;
// Do additional mode-based handling.
if ( intersectionHandler . test ( claim , claimBoundingBox ) ) return ;
2020-06-09 04:57:55 +00:00
}
}
//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 )
{
//don't track in worlds where claims are not enabled
if ( ! GriefPrevention . instance . claimsEnabledForWorld ( igniteEvent . getBlock ( ) . getWorld ( ) ) ) return ;
if ( igniteEvent . getCause ( ) = = IgniteCause . LIGHTNING & & GriefPrevention . instance . dataStore . getClaimAt ( igniteEvent . getIgnitingEntity ( ) . getLocation ( ) , false , null ) ! = null )
{
2021-01-20 01:29:53 +00:00
igniteEvent . setCancelled ( true ) ; //BlockIgniteEvent is called before LightningStrikeEvent. See #532. However, see #1125 for further discussion on detecting trident-caused lightning.
2018-11-22 09:06:51 +00:00
}
2020-06-09 04:57:55 +00:00
2020-08-07 17:20:35 +00:00
// If a fire is started by a fireball from a dispenser, allow it if the dispenser is in the same claim.
if ( igniteEvent . getCause ( ) = = IgniteCause . FIREBALL & & igniteEvent . getIgnitingEntity ( ) instanceof Fireball )
{
ProjectileSource shooter = ( ( Fireball ) igniteEvent . getIgnitingEntity ( ) ) . getShooter ( ) ;
if ( shooter instanceof BlockProjectileSource )
{
Claim claim = GriefPrevention . instance . dataStore . getClaimAt ( igniteEvent . getBlock ( ) . getLocation ( ) , false , null ) ;
if ( claim ! = null & & GriefPrevention . instance . dataStore . getClaimAt ( ( ( BlockProjectileSource ) shooter ) . getBlock ( ) . getLocation ( ) , false , claim ) = = claim )
{
return ;
}
}
}
// Handle arrows igniting TNT.
if ( igniteEvent . getCause ( ) = = IgniteCause . ARROW )
{
Claim claim = GriefPrevention . instance . dataStore . getClaimAt ( igniteEvent . getBlock ( ) . getLocation ( ) , false , null ) ;
if ( claim = = null )
{
// Only TNT can be ignited by arrows, so the targeted block will be destroyed by completion.
if ( ! GriefPrevention . instance . config_fireDestroys | | ! GriefPrevention . instance . config_fireSpreads )
igniteEvent . setCancelled ( true ) ;
return ;
}
if ( igniteEvent . getIgnitingEntity ( ) instanceof Projectile )
{
ProjectileSource shooter = ( ( Projectile ) igniteEvent . getIgnitingEntity ( ) ) . getShooter ( ) ;
// Allow ignition if arrow was shot by a player with build permission.
2021-06-24 08:28:59 +00:00
if ( shooter instanceof Player & & claim . checkPermission ( ( Player ) shooter , ClaimPermission . Build , igniteEvent ) = = null ) return ;
2020-08-07 17:20:35 +00:00
// Allow ignition if arrow was shot by a dispenser in the same claim.
if ( shooter instanceof BlockProjectileSource & &
GriefPrevention . instance . dataStore . getClaimAt ( ( ( BlockProjectileSource ) shooter ) . getBlock ( ) . getLocation ( ) , false , claim ) = = claim )
return ;
}
// Block all other ignition by arrows in claims.
igniteEvent . setCancelled ( true ) ;
return ;
}
2020-06-09 04:57:55 +00:00
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 ;
//don't track in worlds where claims are not enabled
if ( ! GriefPrevention . instance . claimsEnabledForWorld ( spreadEvent . getBlock ( ) . getWorld ( ) ) ) 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 )
{
if ( GriefPrevention . instance . config_claims_firespreads ) return ;
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 . 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 )
{
//don't track in worlds where claims are not enabled
if ( ! GriefPrevention . instance . claimsEnabledForWorld ( burnEvent . getBlock ( ) . getWorld ( ) ) ) return ;
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
2020-09-19 21:14:11 +00:00
for ( Block adjacentBlock : adjacentBlocks )
2020-06-09 04:57:55 +00:00
{
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 )
{
if ( GriefPrevention . instance . config_claims_firedamages ) return ;
burnEvent . setCancelled ( true ) ;
}
}
//ensures fluids don't flow into land claims from outside
private Claim lastSpreadClaim = null ;
@EventHandler ( ignoreCancelled = true , priority = EventPriority . LOWEST )
public void onBlockFromTo ( BlockFromToEvent spreadEvent )
{
//always allow fluids to flow straight down
if ( spreadEvent . getFace ( ) = = BlockFace . DOWN ) return ;
//don't track in worlds where claims are not enabled
if ( ! GriefPrevention . instance . claimsEnabledForWorld ( spreadEvent . getBlock ( ) . getWorld ( ) ) ) return ;
//where to?
2014-11-11 23:52:09 +00:00
Block toBlock = spreadEvent . getToBlock ( ) ;
Location toLocation = toBlock . getLocation ( ) ;
Claim toClaim = this . dataStore . getClaimAt ( toLocation , false , lastSpreadClaim ) ;
2020-06-09 04:57:55 +00:00
2014-11-11 23:52:09 +00:00
//if into a land claim, it must be from the same land claim
2020-06-09 04:57:55 +00:00
if ( toClaim ! = null )
2014-11-11 23:52:09 +00:00
{
this . lastSpreadClaim = toClaim ;
2020-06-09 04:57:55 +00:00
if ( ! toClaim . contains ( spreadEvent . getBlock ( ) . getLocation ( ) , false , true ) )
2014-11-11 23:52:09 +00:00
{
2014-12-23 18:52:17 +00:00
//exception: from parent into subdivision
2020-06-09 04:57:55 +00:00
if ( toClaim . parent = = null | | ! toClaim . parent . contains ( spreadEvent . getBlock ( ) . getLocation ( ) , false , false ) )
2014-12-23 18:52:17 +00:00
{
spreadEvent . setCancelled ( true ) ;
}
2014-11-11 23:52:09 +00:00
}
}
2020-06-09 04:57:55 +00:00
2015-09-16 21:41:05 +00:00
//otherwise if creative mode world, don't flow
2020-06-09 04:57:55 +00:00
else if ( GriefPrevention . instance . creativeRulesApply ( toLocation ) )
2015-09-16 21:41:05 +00:00
{
spreadEvent . setCancelled ( true ) ;
}
2020-06-09 04:57:55 +00:00
}
@EventHandler ( ignoreCancelled = true , priority = EventPriority . LOWEST )
public void onForm ( BlockFormEvent event )
{
Block block = event . getBlock ( ) ;
Location location = block . getLocation ( ) ;
if ( GriefPrevention . instance . creativeRulesApply ( location ) )
{
Material type = block . getType ( ) ;
if ( type = = Material . COBBLESTONE | | type = = Material . OBSIDIAN | | type = = Material . LAVA | | type = = Material . WATER )
{
Claim claim = GriefPrevention . instance . dataStore . getClaimAt ( location , false , null ) ;
if ( claim = = null )
{
event . setCancelled ( true ) ;
}
}
}
}
//Stop projectiles from destroying blocks that don't fire a proper event
@EventHandler ( ignoreCancelled = true )
private void chorusFlower ( ProjectileHitEvent event )
{
//don't track in worlds where claims are not enabled
if ( ! GriefPrevention . instance . claimsEnabledForWorld ( event . getEntity ( ) . getWorld ( ) ) ) return ;
if ( event . getHitBlock ( ) = = null | | event . getHitBlock ( ) . getType ( ) ! = Material . CHORUS_FLOWER )
return ;
Block block = event . getHitBlock ( ) ;
Claim claim = dataStore . getClaimAt ( block . getLocation ( ) , false , null ) ;
if ( claim = = null )
return ;
Player shooter = null ;
Projectile projectile = event . getEntity ( ) ;
if ( projectile . getShooter ( ) instanceof Player )
shooter = ( Player ) projectile . getShooter ( ) ;
if ( shooter = = null )
{
event . getHitBlock ( ) . setType ( Material . AIR ) ;
Bukkit . getScheduler ( ) . runTask ( GriefPrevention . instance , ( ) - > event . getHitBlock ( ) . setBlockData ( block . getBlockData ( ) ) ) ;
return ;
}
2021-06-24 08:28:59 +00:00
Supplier < String > allowContainer = claim . checkPermission ( shooter , ClaimPermission . Inventory , event ) ;
2020-06-09 04:57:55 +00:00
if ( allowContainer ! = null )
{
event . getHitBlock ( ) . setType ( Material . AIR ) ;
Bukkit . getScheduler ( ) . runTask ( GriefPrevention . instance , ( ) - > event . getHitBlock ( ) . setBlockData ( block . getBlockData ( ) ) ) ;
2021-06-24 08:28:59 +00:00
GriefPrevention . sendMessage ( shooter , TextMode . Err , allowContainer . get ( ) ) ;
2020-06-09 04:57:55 +00:00
return ;
}
}
//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 )
{
//don't track in worlds where claims are not enabled
if ( ! GriefPrevention . instance . claimsEnabledForWorld ( dispenseEvent . getBlock ( ) . getWorld ( ) ) ) return ;
//from where?
Block fromBlock = dispenseEvent . getBlock ( ) ;
BlockData fromData = fromBlock . getBlockData ( ) ;
if ( ! ( fromData instanceof Dispenser ) ) return ;
Dispenser dispenser = ( Dispenser ) fromData ;
//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 in creative mode worlds
Material materialDispensed = dispenseEvent . getItem ( ) . getType ( ) ;
if ( ( materialDispensed = = Material . WATER_BUCKET | | materialDispensed = = Material . LAVA_BUCKET ) & & GriefPrevention . instance . creativeRulesApply ( dispenseEvent . getBlock ( ) . getLocation ( ) ) & & 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 )
2014-10-01 01:28:18 +00:00
{
//only take these potentially expensive steps if configured to do so
2020-06-09 04:57:55 +00:00
if ( ! GriefPrevention . instance . config_limitTreeGrowth ) return ;
//don't track in worlds where claims are not enabled
if ( ! GriefPrevention . instance . claimsEnabledForWorld ( growEvent . getWorld ( ) ) ) return ;
Location rootLocation = growEvent . getLocation ( ) ;
2014-10-01 01:28:18 +00:00
Claim rootClaim = this . dataStore . getClaimAt ( rootLocation , false , null ) ;
String rootOwnerName = null ;
2020-06-09 04:57:55 +00:00
2014-10-01 01:28:18 +00:00
//who owns the spreading block, if anyone?
2020-06-09 04:57:55 +00:00
if ( rootClaim ! = null )
2014-10-01 01:28:18 +00:00
{
//tree growth in subdivisions is dependent on who owns the top level claim
2020-06-09 04:57:55 +00:00
if ( rootClaim . parent ! = null ) rootClaim = rootClaim . parent ;
2014-10-01 01:28:18 +00:00
//if an administrative claim, just let the tree grow where it wants
2020-06-09 04:57:55 +00:00
if ( rootClaim . isAdminClaim ( ) ) return ;
2014-10-01 01:28:18 +00:00
//otherwise, note the owner of the claim
rootOwnerName = rootClaim . getOwnerName ( ) ;
}
2020-06-09 04:57:55 +00:00
2014-10-01 01:28:18 +00:00
//for each block growing
2020-06-09 04:57:55 +00:00
for ( int i = 0 ; i < growEvent . getBlocks ( ) . size ( ) ; i + + )
2014-10-01 01:28:18 +00:00
{
BlockState block = growEvent . getBlocks ( ) . get ( i ) ;
Claim blockClaim = this . dataStore . getClaimAt ( block . getLocation ( ) , false , rootClaim ) ;
2020-06-09 04:57:55 +00:00
2014-10-01 01:28:18 +00:00
//if it's growing into a claim
2020-06-09 04:57:55 +00:00
if ( blockClaim ! = null )
2014-10-01 01:28:18 +00:00
{
//if there's no owner for the new tree, or the owner for the new tree is different from the owner of the claim
2020-06-09 04:57:55 +00:00
if ( rootOwnerName = = null | | ! rootOwnerName . equals ( blockClaim . getOwnerName ( ) ) )
2014-10-01 01:28:18 +00:00
{
growEvent . getBlocks ( ) . remove ( i - - ) ;
}
}
}
}
2020-06-09 04:57:55 +00:00
@EventHandler ( ignoreCancelled = true )
public void onInventoryPickupItem ( InventoryPickupItemEvent event )
2019-08-11 18:49:15 +00:00
{
2020-06-09 04:57:55 +00:00
//prevent hoppers from picking-up items dropped by players on death
InventoryHolder holder = event . getInventory ( ) . getHolder ( ) ;
if ( holder instanceof HopperMinecart | | holder instanceof Hopper )
{
Item item = event . getItem ( ) ;
List < MetadataValue > data = item . getMetadata ( " GP_ITEMOWNER " ) ;
//if this is marked as belonging to a player
if ( data ! = null & & data . size ( ) > 0 )
{
UUID ownerID = ( UUID ) data . get ( 0 ) . value ( ) ;
//has that player unlocked his drops?
OfflinePlayer owner = GriefPrevention . instance . getServer ( ) . getOfflinePlayer ( ownerID ) ;
if ( owner . isOnline ( ) )
{
PlayerData playerData = this . dataStore . getPlayerData ( ownerID ) ;
//if locked, don't allow pickup
if ( ! playerData . dropsAreUnlocked )
{
event . setCancelled ( true ) ;
}
}
}
}
}
2020-10-12 03:59:17 +00:00
2020-09-26 19:54:19 +00:00
@EventHandler ( ignoreCancelled = true )
public void onItemFrameBrokenByBoat ( final HangingBreakEvent event )
{
// Checks if the event is caused by physics - 90% of cases caused by a boat (other 10% would be block,
// however since it's in a claim, unless you use a TNT block we don't need to worry about it).
if ( event . getCause ( ) ! = HangingBreakEvent . RemoveCause . PHYSICS )
{
return ;
}
// Cancels the event if in a claim, as we can not efficiently retrieve the person/entity who broke the Item Frame/Hangable Item.
if ( this . dataStore . getClaimAt ( event . getEntity ( ) . getLocation ( ) , false , null ) ! = null )
{
event . setCancelled ( true ) ;
}
}
2013-01-25 03:45:39 +00:00
}