/* * This file is part of GriefDefender, licensed under the MIT License (MIT). * * Copyright (c) bloodmc * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.griefdefender.listener; import com.flowpowered.math.vector.Vector3i; import com.google.common.collect.ImmutableMap; import com.google.common.reflect.TypeToken; import com.griefdefender.GDPlayerData; import com.griefdefender.GDTimings; import com.griefdefender.GriefDefenderPlugin; import com.griefdefender.api.GriefDefender; import com.griefdefender.api.Tristate; import com.griefdefender.api.claim.Claim; import com.griefdefender.api.claim.ClaimResult; import com.griefdefender.api.claim.ClaimTypes; import com.griefdefender.api.claim.ClaimVisualTypes; import com.griefdefender.api.claim.TrustTypes; import com.griefdefender.api.economy.PaymentType; import com.griefdefender.api.permission.flag.Flags; import com.griefdefender.api.permission.option.Options; import com.griefdefender.cache.EventResultCache; import com.griefdefender.cache.MessageCache; import com.griefdefender.cache.PermissionHolderCache; import com.griefdefender.claim.GDClaim; import com.griefdefender.claim.GDClaimManager; import com.griefdefender.configuration.GriefDefenderConfig; import com.griefdefender.configuration.MessageStorage; import com.griefdefender.event.GDCauseStackManager; import com.griefdefender.internal.registry.BlockTypeRegistryModule; import com.griefdefender.internal.registry.GDBlockType; import com.griefdefender.internal.util.BlockUtil; import com.griefdefender.internal.util.NMSUtil; import com.griefdefender.internal.visual.GDClaimVisual; import com.griefdefender.permission.GDPermissionManager; import com.griefdefender.permission.GDPermissionUser; import com.griefdefender.permission.GDPermissions; import com.griefdefender.permission.flag.GDFlags; import com.griefdefender.storage.BaseStorage; import com.griefdefender.util.BlockPosCache; import com.griefdefender.util.CauseContextHelper; import com.griefdefender.util.SignUtil; import com.griefdefender.util.SpongeUtil; import net.kyori.text.Component; import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; import net.kyori.text.serializer.legacy.LegacyComponentSerializer; import org.spongepowered.api.Sponge; import org.spongepowered.api.block.BlockSnapshot; import org.spongepowered.api.block.BlockState; import org.spongepowered.api.block.BlockType; import org.spongepowered.api.block.BlockTypes; import org.spongepowered.api.block.tileentity.Piston; import org.spongepowered.api.block.tileentity.Sign; import org.spongepowered.api.block.tileentity.TileEntity; import org.spongepowered.api.block.tileentity.carrier.Chest; import org.spongepowered.api.data.Transaction; import org.spongepowered.api.data.key.Keys; import org.spongepowered.api.entity.Entity; import org.spongepowered.api.entity.FallingBlock; import org.spongepowered.api.entity.Item; import org.spongepowered.api.entity.hanging.ItemFrame; import org.spongepowered.api.entity.living.player.Player; import org.spongepowered.api.entity.living.player.User; import org.spongepowered.api.event.Listener; import org.spongepowered.api.event.Order; import org.spongepowered.api.event.block.ChangeBlockEvent; import org.spongepowered.api.event.block.CollideBlockEvent; import org.spongepowered.api.event.block.NotifyNeighborBlockEvent; import org.spongepowered.api.event.block.tileentity.ChangeSignEvent; import org.spongepowered.api.event.cause.Cause; import org.spongepowered.api.event.cause.EventContext; import org.spongepowered.api.event.cause.EventContextKeys; import org.spongepowered.api.event.filter.cause.Root; import org.spongepowered.api.event.world.ExplosionEvent; import org.spongepowered.api.item.ItemTypes; import org.spongepowered.api.item.inventory.ItemStackSnapshot; import org.spongepowered.api.text.Text; import org.spongepowered.api.util.Direction; import org.spongepowered.api.world.DimensionTypes; import org.spongepowered.api.world.LocatableBlock; import org.spongepowered.api.world.Location; import org.spongepowered.api.world.World; import org.spongepowered.api.world.explosion.Explosion; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Set; //event handlers related to blocks public class BlockEventHandler { private int lastBlockPreTick = -1; private boolean lastBlockPreCancelled = false; // convenience reference to singleton datastore private final BaseStorage dataStore; // constructor public BlockEventHandler(BaseStorage dataStore) { this.dataStore = dataStore; } @Listener(order = Order.FIRST, beforeModifications = true) public void onBlockPre(ChangeBlockEvent.Pre event) { lastBlockPreTick = Sponge.getServer().getRunningTimeTicks(); if (GriefDefenderPlugin.isSourceIdBlacklisted("block-pre", event.getSource(), event.getLocations().get(0).getExtent().getProperties())) { return; } final World world = event.getLocations().get(0).getExtent(); if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); return; } final Cause cause = event.getCause(); final EventContext context = event.getContext(); final User user = CauseContextHelper.getEventUser(event); final boolean hasFakePlayer = context.containsKey(EventContextKeys.FAKE_PLAYER); if (user != null) { if (context.containsKey(EventContextKeys.PISTON_RETRACT)) { return; } } final LocatableBlock locatableBlock = cause.first(LocatableBlock.class).orElse(null); final TileEntity tileEntity = cause.first(TileEntity.class).orElse(null); Entity sourceEntity = null; // Always use TE as source if available final Object source = tileEntity != null ? tileEntity : cause.root(); Location sourceLocation = locatableBlock != null ? locatableBlock.getLocation() : tileEntity != null ? tileEntity.getLocation() : null; if (sourceLocation == null && source instanceof Entity) { // check entity sourceEntity = ((Entity) source); sourceLocation = sourceEntity.getLocation(); } final boolean pistonExtend = context.containsKey(EventContextKeys.PISTON_EXTEND); boolean isLiquidSource = context.containsKey(EventContextKeys.LIQUID_FLOW); if (!isLiquidSource && locatableBlock != null && locatableBlock.getBlockState().getType() == BlockTypes.FLOWING_WATER) { isLiquidSource = true; } final boolean isFireSource = isLiquidSource ? false : context.containsKey(EventContextKeys.FIRE_SPREAD); final boolean isLeafDecay = context.containsKey(EventContextKeys.LEAVES_DECAY); if (!GDFlags.LEAF_DECAY && isLeafDecay) { return; } if (!GDFlags.LIQUID_FLOW && isLiquidSource) { return; } if (!GDFlags.BLOCK_SPREAD && isFireSource) { return; } lastBlockPreCancelled = false; final boolean isForgePlayerBreak = context.containsKey(EventContextKeys.PLAYER_BREAK); GDTimings.BLOCK_PRE_EVENT.startTimingIfSync(); // Handle player block breaks separately if (isForgePlayerBreak && !hasFakePlayer && source instanceof Player) { final Player player = (Player) source; GDClaim targetClaim = null; final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getPlayerData(world, player.getUniqueId()); for (Location location : event.getLocations()) { if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.BLOCK_BREAK.getName(), location.getBlock(), world.getProperties())) { GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); return; } targetClaim = this.dataStore.getClaimAt(location, targetClaim); if (location.getBlockType() == BlockTypes.AIR) { continue; } if (!checkSurroundings(event, location, player, playerData, targetClaim)) { event.setCancelled(true); GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); return; } // check overrides final Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, Flags.BLOCK_BREAK, source, location.getBlock(), player, TrustTypes.BUILDER, true); if (result != Tristate.TRUE) { final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_BUILD, ImmutableMap.of( "player", targetClaim.getOwnerName())); GriefDefenderPlugin.sendClaimDenyMessage(targetClaim, player, message); event.setCancelled(true); lastBlockPreCancelled = true; GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); return; } } GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); return; } if (sourceLocation != null) { GDPlayerData playerData = null; if (user != null) { playerData = GriefDefenderPlugin.getInstance().dataStore.getPlayerData(world, user.getUniqueId()); } GDClaim sourceClaim = this.dataStore.getClaimAt(sourceLocation); GDClaim targetClaim = null; List> sourceLocations = event.getLocations(); if (pistonExtend) { // check next block in extend direction sourceLocations = new ArrayList<>(event.getLocations()); Location location = sourceLocations.get(sourceLocations.size() - 1); final Direction direction = locatableBlock.getLocation().getBlock().get(Keys.DIRECTION).get(); final Location dirLoc = location.getBlockRelative(direction); sourceLocations.add(dirLoc); } for (Location location : sourceLocations) { // Mods such as enderstorage will send chest updates to itself // We must ignore cases like these to avoid issues with mod if (tileEntity != null) { if (location.getPosition().equals(tileEntity.getLocation().getPosition())) { continue; } } final BlockState blockState = location.getBlock(); targetClaim = this.dataStore.getClaimAt(location, targetClaim); // If a player successfully interacted with a block recently such as a pressure plate, ignore check // This fixes issues such as pistons not being able to extend if (user != null && !isForgePlayerBreak && playerData != null && playerData.eventResultCache != null && playerData.eventResultCache.checkEventResultCache(targetClaim, "block-pre") == Tristate.TRUE) { if (!isLiquidSource && !isFireSource && !isLeafDecay) { GDPermissionManager.getInstance().processEventLog(event, location, targetClaim, Flags.BLOCK_BREAK.getPermission(), source, blockState, user, playerData.eventResultCache.lastTrust, Tristate.TRUE); } continue; } if (user != null && targetClaim.isUserTrusted(user, TrustTypes.BUILDER)) { if (!isLiquidSource && !isFireSource && !isLeafDecay) { GDPermissionManager.getInstance().processEventLog(event, location, targetClaim, Flags.BLOCK_BREAK.getPermission(), source, blockState, user, TrustTypes.BUILDER.getName().toLowerCase(), Tristate.TRUE); } continue; } if (sourceClaim.getOwnerUniqueId().equals(targetClaim.getOwnerUniqueId()) && user == null && sourceEntity == null && !isFireSource && !isLeafDecay) { if (!isLiquidSource) { GDPermissionManager.getInstance().processEventLog(event, location, targetClaim, Flags.BLOCK_BREAK.getPermission(), source, blockState, user, "owner", Tristate.TRUE); } continue; } if (user != null && pistonExtend) { if (targetClaim.isUserTrusted(user, TrustTypes.ACCESSOR)) { GDPermissionManager.getInstance().processEventLog(event, location, targetClaim, Flags.BLOCK_BREAK.getPermission(), source, blockState, user, TrustTypes.ACCESSOR.getName().toLowerCase(), Tristate.TRUE); continue; } } if (isLeafDecay) { if (GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, Flags.LEAF_DECAY, source, blockState, user) == Tristate.FALSE) { event.setCancelled(true); GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); return; } } else if (isFireSource) { if (GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, Flags.BLOCK_SPREAD, source, blockState, user) == Tristate.FALSE) { event.setCancelled(true); GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); return; } } else if (isLiquidSource) { if (GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, Flags.LIQUID_FLOW, source, blockState, user) == Tristate.FALSE) { event.setCancelled(true); lastBlockPreCancelled = true; GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); return; } continue; } else if (GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, Flags.BLOCK_BREAK, source, blockState, user) == Tristate.FALSE) { // PRE events can be spammy so we need to avoid sending player messages here. event.setCancelled(true); lastBlockPreCancelled = true; GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); return; } } } else if (user != null) { final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getPlayerData(world, user.getUniqueId()); GDClaim targetClaim = null; for (Location location : event.getLocations()) { // Mods such as enderstorage will send chest updates to itself // We must ignore cases like these to avoid issues with mod if (tileEntity != null) { if (location.getPosition().equals(tileEntity.getLocation().getPosition())) { continue; } } final BlockState blockState = location.getBlock(); targetClaim = this.dataStore.getClaimAt(location, targetClaim); // If a player successfully interacted with a block recently such as a pressure plate, ignore check // This fixes issues such as pistons not being able to extend if (!isForgePlayerBreak && playerData != null && playerData.eventResultCache != null && playerData.eventResultCache.checkEventResultCache(targetClaim, "block-pre") == Tristate.TRUE) { GDPermissionManager.getInstance().processEventLog(event, location, targetClaim, Flags.BLOCK_BREAK.getPermission(), source, blockState, user, playerData.eventResultCache.lastTrust, Tristate.TRUE); continue; } if (targetClaim.isUserTrusted(user, TrustTypes.BUILDER)) { GDPermissionManager.getInstance().processEventLog(event, location, targetClaim, Flags.BLOCK_BREAK.getPermission(), source, blockState, user, TrustTypes.BUILDER.getName().toLowerCase(), Tristate.TRUE); continue; } if (isFireSource) { if (GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, Flags.BLOCK_SPREAD, source, blockState, user) == Tristate.FALSE) { event.setCancelled(true); GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); return; } } else if (isLiquidSource) { if (GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, Flags.LIQUID_FLOW, source, blockState, user) == Tristate.FALSE) { event.setCancelled(true); lastBlockPreCancelled = true; GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); return; } continue; } else if (GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, Flags.BLOCK_BREAK, source, blockState, user) == Tristate.FALSE) { event.setCancelled(true); lastBlockPreCancelled = true; GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); return; } } } GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); } // Handle fluids flowing into claims @Listener(order = Order.FIRST, beforeModifications = true) public void onBlockNotify(NotifyNeighborBlockEvent event) { LocatableBlock locatableBlock = event.getCause().first(LocatableBlock.class).orElse(null); TileEntity tileEntity = event.getCause().first(TileEntity.class).orElse(null); Location sourceLocation = locatableBlock != null ? locatableBlock.getLocation() : tileEntity != null ? tileEntity.getLocation() : null; GDClaim sourceClaim = null; GDPlayerData playerData = null; if (sourceLocation != null) { if (GriefDefenderPlugin.isSourceIdBlacklisted("block-notify", event.getSource(), sourceLocation.getExtent().getProperties())) { return; } } final User user = CauseContextHelper.getEventUser(event); if (user == null) { return; } if (sourceLocation == null) { Player player = event.getCause().first(Player.class).orElse(null); if (player == null) { return; } sourceLocation = player.getLocation(); playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); sourceClaim = this.dataStore.getClaimAtPlayer(playerData, player.getLocation()); } else { playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(sourceLocation.getExtent(), user.getUniqueId()); sourceClaim = this.dataStore.getClaimAt(sourceLocation); } if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(sourceLocation.getExtent().getUniqueId())) { return; } GDTimings.BLOCK_NOTIFY_EVENT.startTimingIfSync(); Iterator iterator = event.getNeighbors().keySet().iterator(); GDClaim targetClaim = null; while (iterator.hasNext()) { Direction direction = iterator.next(); Location location = sourceLocation.getBlockRelative(direction); Vector3i pos = location.getBlockPosition(); targetClaim = this.dataStore.getClaimAt(location, targetClaim); if (sourceClaim.isWilderness() && targetClaim.isWilderness()) { if (playerData != null) { playerData.eventResultCache = new EventResultCache(targetClaim, "block-notify", Tristate.TRUE); } continue; } else if (!sourceClaim.isWilderness() && targetClaim.isWilderness()) { if (playerData != null) { playerData.eventResultCache = new EventResultCache(targetClaim, "block-notify", Tristate.TRUE); } continue; } else if (sourceClaim.getUniqueId().equals(targetClaim.getUniqueId())) { if (playerData != null) { playerData.eventResultCache = new EventResultCache(targetClaim, "block-notify", Tristate.TRUE); } continue; } else { if (playerData.eventResultCache != null && playerData.eventResultCache.checkEventResultCache(targetClaim, "block-notify") == Tristate.TRUE) { continue; } // Needed to handle levers notifying doors to open etc. if (targetClaim.isUserTrusted(user, TrustTypes.ACCESSOR)) { if (playerData != null) { playerData.eventResultCache = new EventResultCache(targetClaim, "block-notify", Tristate.TRUE, TrustTypes.ACCESSOR.getName().toLowerCase()); } continue; } } // no claim crossing unless trusted iterator.remove(); } GDTimings.BLOCK_NOTIFY_EVENT.stopTimingIfSync(); } @Listener(order = Order.FIRST, beforeModifications = true) public void onBlockCollide(CollideBlockEvent event, @Root Entity source) { if (event instanceof CollideBlockEvent.Impact) { return; } // ignore falling blocks if (!GDFlags.COLLIDE_BLOCK || source instanceof FallingBlock) { return; } final GDBlockType gdBlock = BlockTypeRegistryModule.getInstance().getById(event.getTargetBlock().getType().getId()).orElse(null); if (gdBlock != null && !gdBlock.isCollidable() && !(source instanceof ItemFrame)) { return; } if (event.getTargetBlock().getType() == BlockTypes.PORTAL) { // ignore as we handle this with entity-teleport-from return; } if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.COLLIDE_BLOCK.getName(), source.getType().getId(), source.getWorld().getProperties())) { return; } if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.COLLIDE_BLOCK.getName(), event.getTargetBlock(), source.getWorld().getProperties())) { return; } final User user = CauseContextHelper.getEventUser(event); if (user == null) { return; } GDTimings.BLOCK_COLLIDE_EVENT.startTimingIfSync(); final BlockType blockType = event.getTargetBlock().getType(); if (blockType.equals(BlockTypes.AIR) || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(event.getTargetLocation().getExtent().getUniqueId())) { GDTimings.BLOCK_COLLIDE_EVENT.stopTimingIfSync(); return; } if (source instanceof Item && (blockType != BlockTypes.PORTAL && !NMSUtil.getInstance().isBlockPressurePlate(blockType))) { GDTimings.BLOCK_COLLIDE_EVENT.stopTimingIfSync(); return; } Vector3i collidePos = event.getTargetLocation().getBlockPosition(); short shortPos = BlockUtil.getInstance().blockPosToShort(collidePos); int entityId = NMSUtil.getInstance().getEntityMinecraftId(source); BlockPosCache entityBlockCache = BlockUtil.ENTITY_BLOCK_CACHE.get(entityId); if (entityBlockCache == null) { entityBlockCache = new BlockPosCache(shortPos); BlockUtil.ENTITY_BLOCK_CACHE.put(entityId, entityBlockCache); } else { Tristate result = entityBlockCache.getCacheResult(shortPos); if (result != Tristate.UNDEFINED) { if (result == Tristate.FALSE) { event.setCancelled(true); } GDTimings.BLOCK_COLLIDE_EVENT.stopTimingIfSync(); return; } } GDPlayerData playerData = null; GDClaim targetClaim = null; if (user instanceof Player) { playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(event.getTargetLocation().getExtent(), user.getUniqueId()); targetClaim = this.dataStore.getClaimAtPlayer(playerData, event.getTargetLocation()); } else { targetClaim = this.dataStore.getClaimAt(event.getTargetLocation()); } Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, event.getTargetLocation(), targetClaim, Flags.COLLIDE_BLOCK, source, event.getTargetBlock(), user, TrustTypes.ACCESSOR, true); if (result != Tristate.UNDEFINED) { if (result == Tristate.TRUE) { entityBlockCache.setLastResult(Tristate.TRUE); GDTimings.BLOCK_COLLIDE_EVENT.stopTimingIfSync(); return; } entityBlockCache.setLastResult(Tristate.FALSE); event.setCancelled(true); GDTimings.BLOCK_COLLIDE_EVENT.stopTimingIfSync(); return; } if (GDFlags.PORTAL_USE && event.getTargetBlock().getType() == BlockTypes.PORTAL) { if (GDPermissionManager.getInstance().getFinalPermission(event, event.getTargetLocation(), targetClaim, Flags.PORTAL_USE, source, event.getTargetBlock(), user, TrustTypes.ACCESSOR, true) == Tristate.TRUE) { entityBlockCache.setLastResult(Tristate.TRUE); GDTimings.BLOCK_COLLIDE_EVENT.stopTimingIfSync(); return; } if (event.getCause().root() instanceof Player){ if (event.getTargetLocation().getExtent().getProperties().getTotalTime() % 20 == 0L) { // log once a second to avoid spam // Disable message temporarily //GriefDefender.sendMessage((Player) user, TextMode.Err, Messages.NoPortalFromProtectedClaim, claim.getOwnerName()); /*final Text message = GriefDefenderPlugin.getInstance().messageData.permissionProtectedPortal .apply(ImmutableMap.of( "owner", targetClaim.getOwnerName())).build();*/ event.setCancelled(true); entityBlockCache.setLastResult(Tristate.FALSE); GDTimings.BLOCK_COLLIDE_EVENT.stopTimingIfSync(); return; } } } entityBlockCache.setLastResult(Tristate.TRUE); GDTimings.BLOCK_COLLIDE_EVENT.stopTimingIfSync(); } @Listener(order = Order.FIRST, beforeModifications = true) public void onProjectileImpactBlock(CollideBlockEvent.Impact event) { if (!GDFlags.PROJECTILE_IMPACT_BLOCK || !(event.getSource() instanceof Entity)) { return; } final Entity source = (Entity) event.getSource(); if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.PROJECTILE_IMPACT_BLOCK.getName(), source.getType().getId(), source.getWorld().getProperties())) { return; } final User user = CauseContextHelper.getEventUser(event); if (user == null) { return; } if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(event.getImpactPoint().getExtent().getUniqueId())) { return; } GDTimings.PROJECTILE_IMPACT_BLOCK_EVENT.startTimingIfSync(); Location impactPoint = event.getImpactPoint(); GDClaim targetClaim = null; GDPlayerData playerData = null; if (user instanceof Player) { playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(event.getTargetLocation().getExtent(), user.getUniqueId()); targetClaim = this.dataStore.getClaimAtPlayer(playerData, impactPoint); } else { targetClaim = this.dataStore.getClaimAt(impactPoint); } Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, impactPoint, targetClaim, Flags.PROJECTILE_IMPACT_BLOCK, source, event.getTargetBlock(), user, TrustTypes.ACCESSOR, true); if (result == Tristate.FALSE) { event.setCancelled(true); GDTimings.PROJECTILE_IMPACT_BLOCK_EVENT.stopTimingIfSync(); return; } GDTimings.PROJECTILE_IMPACT_BLOCK_EVENT.stopTimingIfSync(); } @Listener(order = Order.FIRST, beforeModifications = true) public void onExplosionPre(ExplosionEvent.Pre event) { final World world = event.getExplosion().getWorld(); if (!GDFlags.EXPLOSION_BLOCK || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { return; } Object source = event.getSource(); final Explosion explosion = event.getExplosion(); if (explosion.getSourceExplosive().isPresent()) { source = explosion.getSourceExplosive().get(); } else { Entity exploder = event.getCause().first(Entity.class).orElse(null); if (exploder != null) { source = exploder; } } if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.EXPLOSION_BLOCK.getName(), source, event.getExplosion().getWorld().getProperties())) { return; } GDTimings.EXPLOSION_PRE_EVENT.startTimingIfSync(); final User user = CauseContextHelper.getEventUser(event); final Location location = event.getExplosion().getLocation(); final GDClaim targetClaim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(location); // If affected claim does not inherit parent, skip logic if (!targetClaim.isWilderness() && targetClaim.getParent().isPresent() && !targetClaim.getInternalClaimData().doesInheritParent()) { GDTimings.EXPLOSION_PRE_EVENT.stopTimingIfSync(); return; } final GDClaim radiusClaim = NMSUtil.getInstance().createClaimFromCenter(location, event.getExplosion().getRadius()); final GDClaimManager claimManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(location.getExtent().getUniqueId()); final Set surroundingClaims = claimManager.findOverlappingClaims(radiusClaim); if (surroundingClaims.size() == 0) { return; } for (Claim claim : surroundingClaims) { // Use any location for permission check Location targetLocation = new Location<>(location.getExtent(), claim.getLesserBoundaryCorner()); Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, claim, Flags.EXPLOSION_BLOCK, source, targetLocation, user, true); if (result == Tristate.FALSE) { event.setCancelled(true); break; } } GDTimings.EXPLOSION_PRE_EVENT.stopTimingIfSync(); } @Listener(order = Order.FIRST, beforeModifications = true) public void onExplosionDetonate(ExplosionEvent.Detonate event) { final World world = event.getExplosion().getWorld(); if (!GDFlags.EXPLOSION_BLOCK || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { return; } Object source = event.getSource(); if (source instanceof Explosion) { final Explosion explosion = (Explosion) source; if (explosion.getSourceExplosive().isPresent()) { source = explosion.getSourceExplosive().get(); } else { Entity exploder = event.getCause().first(Entity.class).orElse(null); if (exploder != null) { source = exploder; } } } if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.EXPLOSION_BLOCK.getName(), source, event.getExplosion().getWorld().getProperties())) { return; } GDTimings.EXPLOSION_EVENT.startTimingIfSync(); final User user = CauseContextHelper.getEventUser(event); GDClaim targetClaim = null; final List> filteredLocations = new ArrayList<>(); final String sourceId = GDPermissionManager.getInstance().getPermissionIdentifier(source); final int surfaceBlockLevel = GriefDefenderPlugin.getActiveConfig(world.getUniqueId()).getConfig().claim.explosionSurfaceBlockLevel; boolean denySurfaceExplosion = GriefDefenderPlugin.getActiveConfig(world.getUniqueId()).getConfig().claim.explosionBlockSurfaceBlacklist.contains(sourceId); if (!denySurfaceExplosion) { denySurfaceExplosion = GriefDefenderPlugin.getActiveConfig(world.getUniqueId()).getConfig().claim.explosionBlockSurfaceBlacklist.contains("any"); } for (Location location : event.getAffectedLocations()) { if (location.getBlockType().equals(BlockTypes.AIR)) { continue; } targetClaim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(location, targetClaim); if (denySurfaceExplosion && world.getDimension().getType() != DimensionTypes.NETHER && location.getBlockY() >= surfaceBlockLevel) { filteredLocations.add(location); GDPermissionManager.getInstance().processEventLog(event, location, targetClaim, Flags.EXPLOSION_BLOCK.getPermission(), source, location.getBlock(), user, "explosion-surface", Tristate.FALSE); continue; } Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, Flags.EXPLOSION_BLOCK, source, location.getBlock(), user, true); if (result == Tristate.FALSE) { filteredLocations.add(location); } } // Workaround for SpongeForge bug if (event.isCancelled()) { event.getAffectedLocations().clear(); } else if (!filteredLocations.isEmpty()) { event.getAffectedLocations().removeAll(filteredLocations); } GDTimings.EXPLOSION_EVENT.stopTimingIfSync(); } @Listener(order = Order.FIRST, beforeModifications = true) public void onBlockBreak(ChangeBlockEvent.Break event) { if (!GDFlags.BLOCK_BREAK || event instanceof ExplosionEvent) { return; } if (lastBlockPreTick == Sponge.getServer().getRunningTimeTicks()) { event.setCancelled(lastBlockPreCancelled); return; } Object source = event.getSource(); // Handled in Explosion listeners if (source instanceof Explosion) { return; } // Pistons are handled in onBlockPre if (source == BlockTypes.PISTON || source instanceof Piston) { return; } final World world = event.getTransactions().get(0).getFinal().getLocation().get().getExtent(); if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { return; } if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.BLOCK_BREAK.getName(), source, world.getProperties())) { return; } final Player player = source instanceof Player ? (Player) source : null; final User user = player != null ? player : CauseContextHelper.getEventUser(event); // ignore falling blocks when there is no user // avoids dupes with falling blocks such as Dragon Egg if (user == null && source instanceof FallingBlock) { return; } GDClaim sourceClaim = null; LocatableBlock locatable = null; if (source instanceof LocatableBlock) { locatable = (LocatableBlock) source; sourceClaim = this.dataStore.getClaimAt(locatable.getLocation()); } else { sourceClaim = this.getSourceClaim(event.getCause()); } if (sourceClaim == null) { return; } GDTimings.BLOCK_BREAK_EVENT.startTimingIfSync(); List> transactions = event.getTransactions(); GDClaim targetClaim = null; for (Transaction transaction : transactions) { if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.BLOCK_BREAK.getName(), transaction.getOriginal(), world.getProperties())) { continue; } Location location = transaction.getOriginal().getLocation().orElse(null); targetClaim = this.dataStore.getClaimAt(location, targetClaim); if (location == null || transaction.getOriginal().getState().getType() == BlockTypes.AIR) { continue; } // check overrides final Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, Flags.BLOCK_BREAK, source, transaction.getOriginal(), user, TrustTypes.BUILDER, true); if (result != Tristate.TRUE) { if (player != null) { final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_BUILD, ImmutableMap.of("player", targetClaim.getOwnerName())); GriefDefenderPlugin.sendClaimDenyMessage(targetClaim, player, message); } event.setCancelled(true); GDTimings.BLOCK_BREAK_EVENT.stopTimingIfSync(); return; } } GDTimings.BLOCK_BREAK_EVENT.stopTimingIfSync(); } @Listener(order = Order.FIRST, beforeModifications = true) public void onBlockPlace(ChangeBlockEvent.Place event) { final Object source = event.getSource(); // Pistons are handled in onBlockPre if (source instanceof Piston) { return; } final World world = event.getTransactions().get(0).getFinal().getLocation().get().getExtent(); if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { return; } if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.BLOCK_PLACE.getName(), event.getSource(), world.getProperties())) { return; } ItemStackSnapshot itemSnapshot = event.getContext().get(EventContextKeys.USED_ITEM).orElse(null); if (itemSnapshot != null) { if (itemSnapshot.getType().equals(ItemTypes.BUCKET)) { // Sponge bug - empty buckets should never fire a block place return; } } GDTimings.BLOCK_PLACE_EVENT.startTimingIfSync(); GDClaim sourceClaim = null; LocatableBlock locatable = null; final User user = CauseContextHelper.getEventUser(event); // handle ice form/melt if (!(event.getCause().root() instanceof Entity) && event.getTransactions().size() == 1) { final BlockSnapshot sourceBlock = event.getTransactions().get(0).getOriginal(); final BlockSnapshot targetBlock = event.getTransactions().get(0).getFinal(); if (NMSUtil.getInstance().isBlockWater(sourceBlock.getState().getType()) && NMSUtil.getInstance().isBlockIce(targetBlock.getState().getType())) { final Location loc = targetBlock.getLocation().get(); final GDClaim claim = this.dataStore.getClaimAt(loc); final Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, loc, claim, Flags.BLOCK_MODIFY, sourceBlock, targetBlock, user, true); if (result == Tristate.FALSE) { event.setCancelled(true); } return; } else if (NMSUtil.getInstance().isBlockIce(sourceBlock.getState().getType()) && NMSUtil.getInstance().isBlockWater(targetBlock.getState().getType())) { final Location loc = targetBlock.getLocation().get(); final GDClaim claim = this.dataStore.getClaimAt(loc); final Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, loc, claim, Flags.BLOCK_MODIFY, sourceBlock, targetBlock, user, true); if (result == Tristate.FALSE) { event.setCancelled(true); } return; } } if (source instanceof LocatableBlock) { locatable = (LocatableBlock) source; if (user != null && user instanceof Player) { final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(locatable.getWorld(), user.getUniqueId()); sourceClaim = this.dataStore.getClaimAtPlayer(playerData, locatable.getLocation()); } else { sourceClaim = this.dataStore.getClaimAt(locatable.getLocation()); } } else { sourceClaim = this.getSourceClaim(event.getCause()); } if (sourceClaim == null && user == null) { GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); return; } Player player = user != null && user instanceof Player ? (Player) user : null; GDPlayerData playerData = null; if (user != null) { playerData = this.dataStore.getOrCreatePlayerData(world, user.getUniqueId()); } GriefDefenderConfig activeConfig = GriefDefenderPlugin.getActiveConfig(world.getProperties()); if (sourceClaim != null && !(source instanceof User) && playerData != null && playerData.eventResultCache != null && playerData.eventResultCache.checkEventResultCache(sourceClaim, Flags.BLOCK_PLACE.getName()) == Tristate.TRUE) { GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); return; } GDClaim targetClaim = null; for (Transaction transaction : event.getTransactions()) { final BlockSnapshot block = transaction.getFinal(); if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.BLOCK_PLACE.getName(), block, world.getProperties())) { continue; } Location location = block.getLocation().orElse(null); if (location == null) { continue; } targetClaim = this.dataStore.getClaimAt(location, targetClaim); if (!checkSurroundings(event, location, player, playerData, targetClaim)) { event.setCancelled(true); GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); return; } if (GDFlags.BLOCK_PLACE) { // Allow blocks to grow within claims if (user == null && sourceClaim != null && sourceClaim.getUniqueId().equals(targetClaim.getUniqueId())) { GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); return; } // check overrides final Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, Flags.BLOCK_PLACE, source, block, user, TrustTypes.BUILDER, true); if (result != Tristate.TRUE) { // TODO - make sure this doesn't spam /*if (source instanceof Player) { final Text message = GriefDefenderPlugin.getInstance().messageData.permissionBuild .apply(ImmutableMap.of( "player", Text.of(targetClaim.getOwnerName()) )).build(); GriefDefenderPlugin.sendClaimDenyMessage(targetClaim, (Player) source, message); }*/ event.setCancelled(true); GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); return; } } if (!(source instanceof Player)) { continue; } if (targetClaim.isWilderness() && activeConfig.getConfig().claim.autoChestClaimBlockRadius > -1) { TileEntity tileEntity = block.getLocation().get().getTileEntity().orElse(null); if (tileEntity == null || !(tileEntity instanceof Chest)) { GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); continue; } final int minClaimLevel = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), user, Options.MIN_LEVEL).intValue(); final int maxClaimLevel = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), user, Options.MAX_LEVEL).intValue(); if (block.getPosition().getY() < minClaimLevel || block.getPosition().getY() > maxClaimLevel) { final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_CHEST_OUTSIDE_LEVEL, ImmutableMap.of( "min-claim-level", minClaimLevel, "max-claim-level", maxClaimLevel)); GriefDefenderPlugin.sendMessage(player, message); GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); continue; } int radius = activeConfig.getConfig().claim.autoChestClaimBlockRadius; if (playerData.getInternalClaims().size() == 0) { if (activeConfig.getConfig().claim.autoChestClaimBlockRadius == 0) { GDCauseStackManager.getInstance().pushCause(player); final ClaimResult result = GriefDefender.getRegistry().createBuilder(Claim.Builder.class) .bounds(block.getPosition(), block.getPosition()) .cuboid(false) .owner(player.getUniqueId()) .sizeRestrictions(false) .type(ClaimTypes.BASIC) .world(block.getLocation().get().getExtent().getUniqueId()) .build(); GDCauseStackManager.getInstance().popCause(); if (result.successful()) { final Claim claim = result.getClaim().get(); final GDClaimManager claimManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(world.getUniqueId()); claimManager.addClaim(claim, true); GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_CHEST_CONFIRMATION); GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); continue; } } else { Vector3i lesserBoundary = new Vector3i( block.getPosition().getX() - radius, minClaimLevel, block.getPosition().getZ() - radius); Vector3i greaterBoundary = new Vector3i( block.getPosition().getX() + radius, maxClaimLevel, block.getPosition().getZ() + radius); while (radius >= 0) { GDCauseStackManager.getInstance().pushCause(player); ClaimResult result = GriefDefender.getRegistry().createBuilder(Claim.Builder.class) .bounds(lesserBoundary, greaterBoundary) .cuboid(false) .owner(player.getUniqueId()) .sizeRestrictions(false) .type(ClaimTypes.BASIC) .world(block.getLocation().get().getExtent().getUniqueId()) .build(); GDCauseStackManager.getInstance().popCause(); if (!result.successful()) { radius--; } else { GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_AUTOMATIC_NOTIFICATION); GDClaim newClaim = this.dataStore.getClaimAt(block.getLocation().get()); GDClaimVisual visualization = new GDClaimVisual(newClaim, ClaimVisualTypes.BASIC); visualization.createClaimBlockVisuals(block.getPosition().getY(), player.getLocation(), playerData); visualization.apply(player); GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); continue; } } } } if (targetClaim.isWilderness() && player.hasPermission(GDPermissions.CLAIM_SHOW_TUTORIAL)) { GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.TUTORIAL_CLAIM_BASIC)); } } } GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); } @Listener(order = Order.FIRST, beforeModifications = true) public void onSignChanged(ChangeSignEvent event) { final User user = CauseContextHelper.getEventUser(event); if (user == null) { return; } if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(event.getTargetTile().getLocation().getExtent().getUniqueId())) { return; } GDTimings.SIGN_CHANGE_EVENT.startTimingIfSync(); Location location = event.getTargetTile().getLocation(); // Prevent users exploiting signs GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(location); if (GDPermissionManager.getInstance().getFinalPermission(event, location, claim, Flags.INTERACT_BLOCK_SECONDARY, user, location.getBlock(), user, TrustTypes.ACCESSOR, true) == Tristate.FALSE) { if (user instanceof Player) { event.setCancelled(true); final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_ACCESS, ImmutableMap.of("player", claim.getOwnerName())); GriefDefenderPlugin.sendClaimDenyMessage(claim, (Player) user, message); return; } } GDTimings.SIGN_CHANGE_EVENT.stopTimingIfSync(); } @Listener(order = Order.FIRST) public void onSignChangeEvent(ChangeSignEvent event) { if (GriefDefenderPlugin.getInstance().getEconomyService() == null) { return; } final GriefDefenderConfig activeConfig = GriefDefenderPlugin.getActiveConfig(event.getTargetTile().getWorld().getUniqueId()); if (!activeConfig.getConfig().economy.isRentSignEnabled() && !activeConfig.getConfig().economy.isSellSignEnabled()) { return; } final User eventUser = CauseContextHelper.getEventUser(event); if (eventUser == null) { return; } final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(eventUser); if (user == null) { return; } final Player player = user.getOnlinePlayer(); if (player == null) { return; } final Sign sign = event.getTargetTile(); final GDClaim claim = this.dataStore.getClaimAt(sign.getLocation()); if (claim.isWilderness()) { return; } final List lines = event.getText().asList(); final String header = lines.get(0).toPlain(); if (header == null || (!header.equalsIgnoreCase("gd") && !header.equalsIgnoreCase("griefdefender"))) { return; } final String line1 = lines.get(1).toPlain(); final String line2 = lines.get(2).toPlain(); final String line3 = lines.get(3).toPlain(); if (line1.equalsIgnoreCase("sell") && activeConfig.getConfig().economy.isSellSignEnabled()) { if (!player.hasPermission(GDPermissions.USER_SELL_SIGN)) { return; } // check price Double price = null; try { price = Double.valueOf(line2); } catch (NumberFormatException e) { return; } SignUtil.setClaimForSale(claim, user.getOnlinePlayer(), sign, price); } else if (line1.equalsIgnoreCase("rent") && activeConfig.getConfig().economy.isRentSignEnabled() && activeConfig.getConfig().economy.rentSystem) { if (!player.hasPermission(GDPermissions.USER_RENT_SIGN)) { return; } Double rate = null; try { rate = Double.valueOf(line2.substring(0, line2.length() - 1)); } catch (NumberFormatException e) { return; } int rentMin = 0; int rentMax = 0; if (line3 != null) { rentMin = SignUtil.getRentMinTime(line3); rentMax = SignUtil.getRentMaxTime(line3); } String rentType = line2; final PaymentType paymentType = SignUtil.getPaymentType(rentType); if (paymentType == PaymentType.UNDEFINED) { // invalid return; } SignUtil.setClaimForRent(claim, player, sign, rate, rentMin, rentMax, paymentType); } } public GDClaim getSourceClaim(Cause cause) { BlockSnapshot blockSource = cause.first(BlockSnapshot.class).orElse(null); LocatableBlock locatableBlock = null; TileEntity tileEntitySource = null; Entity entitySource = null; if (blockSource == null) { locatableBlock = cause.first(LocatableBlock.class).orElse(null); if (locatableBlock == null) { entitySource = cause.first(Entity.class).orElse(null); } if (locatableBlock == null && entitySource == null) { tileEntitySource = cause.first(TileEntity.class).orElse(null); } } GDClaim sourceClaim = null; if (blockSource != null) { sourceClaim = this.dataStore.getClaimAt(blockSource.getLocation().get()); } else if (locatableBlock != null) { sourceClaim = this.dataStore.getClaimAt(locatableBlock.getLocation()); } else if (tileEntitySource != null) { sourceClaim = this.dataStore.getClaimAt(tileEntitySource.getLocation()); } else if (entitySource != null) { Entity entity = entitySource; if (entity instanceof Player) { Player player = (Player) entity; GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); sourceClaim = this.dataStore.getClaimAtPlayer(playerData, player.getLocation()); } else { sourceClaim = this.dataStore.getClaimAt(entity.getLocation()); } } return sourceClaim; } // TODO: Add configuration for distance between claims private boolean checkSurroundings(org.spongepowered.api.event.Event event, Location location, Player player, GDPlayerData playerData, GDClaim targetClaim) { if (playerData == null) { return true; } // Don't allow players to break blocks next to land they do not own if (!playerData.canIgnoreClaim(targetClaim)) { // check surrounding blocks for access for (Direction direction : BlockUtil.CARDINAL_SET) { Location loc = location.getBlockRelative(direction); if (!(loc.getTileEntity().isPresent())) { continue; } final GDClaim claim = this.dataStore.getClaimAt(loc, targetClaim); if (!claim.isWilderness() && !targetClaim.equals(claim)) { Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, loc, claim, Flags.BLOCK_BREAK, player, loc.getBlock(), player, TrustTypes.BUILDER, true); if (result != Tristate.TRUE) { final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_BUILD_NEAR_CLAIM, ImmutableMap.of( "player", claim.getOwnerName())); GriefDefenderPlugin.sendClaimDenyMessage(claim, player, message); return false; } } } } return true; } }