Add new, efficient options for handling pistons (#933)

This commit is contained in:
Adam 2020-09-14 19:45:08 -04:00 committed by GitHub
parent a3b8b6f2f1
commit 581e8881e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 174 additions and 162 deletions

View File

@ -31,6 +31,7 @@ import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.Hopper;
import org.bukkit.block.PistonMoveReaction;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Dispenser;
import org.bukkit.entity.Fireball;
@ -49,6 +50,7 @@ import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.block.BlockIgniteEvent;
import org.bukkit.event.block.BlockIgniteEvent.IgniteCause;
import org.bukkit.event.block.BlockMultiPlaceEvent;
import org.bukkit.event.block.BlockPistonEvent;
import org.bukkit.event.block.BlockPistonExtendEvent;
import org.bukkit.event.block.BlockPistonRetractEvent;
import org.bukkit.event.block.BlockPlaceEvent;
@ -65,6 +67,7 @@ import org.bukkit.projectiles.ProjectileSource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.UUID;
@ -427,7 +430,7 @@ public class BlockEventHandler implements Listener
}
//warn players about disabled pistons outside of land claims
if (GriefPrevention.instance.config_pistonsInClaimsOnly &&
if (GriefPrevention.instance.config_pistonMovement == PistonMode.CLAIMS_ONLY &&
(block.getType() == Material.PISTON || block.getType() == Material.STICKY_PISTON) &&
claim == null)
{
@ -463,33 +466,47 @@ public class BlockEventHandler implements Listener
return false;
}
//blocks "pushing" other players' blocks around (pistons)
// Prevent pistons pushing blocks into or out of claims.
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onBlockPistonExtend(BlockPistonExtendEvent event)
{
//return if piston checks are not enabled
if (!GriefPrevention.instance.config_checkPistonMovement) return;
onPistonEvent(event, event.getBlocks());
}
//pushing down is ALWAYS safe
if (event.getDirection() == BlockFace.DOWN) return;
// Prevent pistons pulling blocks into or out of claims.
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onBlockPistonRetract(BlockPistonRetractEvent event)
{
onPistonEvent(event, event.getBlocks());
}
//don't track in worlds where claims are not enabled
// Handle piston push and pulls.
private void onPistonEvent(BlockPistonEvent event, List<Block> blocks)
{
PistonMode pistonMode = GriefPrevention.instance.config_pistonMovement;
// Return if piston movements are ignored.
if (pistonMode == PistonMode.IGNORED) return;
// Don't check in worlds where claims are not enabled.
if (!GriefPrevention.instance.claimsEnabledForWorld(event.getBlock().getWorld())) return;
BlockFace direction = event.getDirection();
Block pistonBlock = event.getBlock();
List<Block> blocks = event.getBlocks();
Claim pistonClaim = this.dataStore.getClaimAt(pistonBlock.getLocation(), false, null);
//if no blocks moving, then only check to make sure we're not pushing into a claim from outside
//this avoids pistons breaking non-solids just inside a claim, like torches, doors, and touchplates
if (blocks.size() == 0)
// A claim is required, but the piston is not inside a claim.
if (pistonClaim == null && pistonMode == PistonMode.CLAIMS_ONLY)
{
Block invadedBlock = pistonBlock.getRelative(event.getDirection());
event.setCancelled(true);
return;
}
//pushing "air" is harmless
if (invadedBlock.getType() == Material.AIR) return;
if (this.dataStore.getClaimAt(pistonBlock.getLocation(), false, null) == null &&
this.dataStore.getClaimAt(invadedBlock.getLocation(), false, null) != null)
// If no blocks are moving, quickly check if another claim's boundaries are violated.
if (blocks.isEmpty())
{
Block invadedBlock = pistonBlock.getRelative(direction);
Claim invadedClaim = this.dataStore.getClaimAt(invadedBlock.getLocation(), false, pistonClaim);
if (invadedClaim != null && (pistonClaim == null || !pistonClaim.ownerID.equals(invadedClaim.ownerID)))
{
event.setCancelled(true);
}
@ -497,167 +514,138 @@ public class BlockEventHandler implements Listener
return;
}
//who owns the piston, if anyone?
String pistonClaimOwnerName = "_";
Claim claim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null);
if (claim != null) pistonClaimOwnerName = claim.getOwnerName();
int minX, maxX, minY, maxY, minZ, maxZ;
minX = maxX = pistonBlock.getX();
minY = maxY = pistonBlock.getY();
minZ = maxZ = pistonBlock.getZ();
//if pistons are limited to same-claim block movement
if (GriefPrevention.instance.config_pistonsInClaimsOnly)
// Find min and max values for faster claim lookups and bounding box-based fast mode.
for (Block block : blocks)
{
//if piston is not in a land claim, cancel event
if (claim == null)
{
event.setCancelled(true);
return;
}
for (Block pushedBlock : event.getBlocks())
{
//if pushing blocks located outside the land claim it lives in, cancel the event
if (!claim.contains(pushedBlock.getLocation(), false, false))
{
event.setCancelled(true);
return;
}
//if pushing a block inside the claim out of the claim, cancel the event
//reason: could push into another land claim, don't want to spend CPU checking for that
//reason: push ice out, place torch, get water outside the claim
if (!claim.contains(pushedBlock.getRelative(event.getDirection()).getLocation(), false, false))
{
event.setCancelled(true);
return;
}
}
minX = Math.min(minX, block.getX());
minY = Math.min(minY, block.getY());
minZ = Math.min(minZ, block.getZ());
maxX = Math.max(maxX, block.getX());
maxY = Math.max(maxY, block.getY());
maxZ = Math.max(maxZ, block.getZ());
}
//otherwise, consider ownership of piston and EACH pushed block
// Add direction to include invaded zone.
if (direction.getModX() > 0)
maxX += direction.getModX();
else
minX += direction.getModX();
if (direction.getModY() > 0)
maxY += direction.getModY();
if (direction.getModZ() > 0)
maxZ += direction.getModZ();
else
minZ += direction.getModZ();
/*
* 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)
{
//which blocks are being pushed?
Claim cachedClaim = claim;
for (int i = 0; i < blocks.size(); i++)
{
//if ANY of the pushed blocks are owned by someone other than the piston owner, cancel the event
Block block = blocks.get(i);
claim = this.dataStore.getClaimAt(block.getLocation(), false, cachedClaim);
if (claim != null)
{
cachedClaim = claim;
if (!claim.getOwnerName().equals(pistonClaimOwnerName))
{
event.setCancelled(true);
pistonBlock.getWorld().createExplosion(pistonBlock.getLocation(), 0);
pistonBlock.getWorld().dropItem(pistonBlock.getLocation(), new ItemStack(pistonBlock.getType()));
pistonBlock.setType(Material.AIR);
return;
}
}
}
Location minLoc = pistonClaim.getLesserBoundaryCorner();
Location maxLoc = pistonClaim.getGreaterBoundaryCorner();
//if any of the blocks are being pushed into a claim from outside, cancel the event
for (int i = 0; i < blocks.size(); i++)
{
Block block = blocks.get(i);
Claim originalClaim = this.dataStore.getClaimAt(block.getLocation(), false, cachedClaim);
String originalOwnerName = "";
if (originalClaim != null)
{
cachedClaim = originalClaim;
originalOwnerName = originalClaim.getOwnerName();
}
if (minY < minLoc.getY() || minX < minLoc.getBlockX() || maxX > maxLoc.getBlockX() || minZ < minLoc.getBlockZ() || maxZ > maxLoc.getBlockZ())
event.setCancelled(true);
Claim newClaim = this.dataStore.getClaimAt(block.getRelative(event.getDirection()).getLocation(), false, cachedClaim);
String newOwnerName = "";
if (newClaim != null)
{
newOwnerName = newClaim.getOwnerName();
}
//if pushing this block will change ownership, cancel the event and take away the piston (for performance reasons)
if (!newOwnerName.equals(originalOwnerName) && !newOwnerName.isEmpty())
{
event.setCancelled(true);
pistonBlock.getWorld().createExplosion(pistonBlock.getLocation(), 0);
pistonBlock.getWorld().dropItem(pistonBlock.getLocation(), new ItemStack(pistonBlock.getType()));
pistonBlock.setType(Material.AIR);
return;
}
}
return;
}
}
//blocks theft by pulling blocks out of a claim (again pistons)
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onBlockPistonRetract(BlockPistonRetractEvent event)
{
//return if piston checks are not enabled
if (!GriefPrevention.instance.config_checkPistonMovement) return;
// Pushing down or pulling up is safe if all blocks are in line with the piston.
if (minX == maxX && minZ == maxZ && direction == (event instanceof BlockPistonExtendEvent ? BlockFace.DOWN : BlockFace.UP)) return;
//pulling up is always safe
if (event.getDirection() == BlockFace.UP) return;
try
// Fast mode: Use the intersection of a cuboid containing all blocks instead of individual locations.
if (pistonMode == PistonMode.EVERYWHERE_SIMPLE)
{
//don't track in worlds where claims are not enabled
if (!GriefPrevention.instance.claimsEnabledForWorld(event.getBlock().getWorld())) return;
ArrayList<Claim> intersectable = new ArrayList<>();
int chunkXMax = maxX >> 4;
int chunkZMax = maxZ >> 4;
//if pistons limited to only pulling blocks which are in the same claim the piston is in
if (GriefPrevention.instance.config_pistonsInClaimsOnly)
for (int chunkX = minX >> 4; chunkX <= chunkXMax; ++chunkX)
{
//if piston not in a land claim, cancel event
Claim pistonClaim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null);
if (pistonClaim == null && !event.getBlocks().isEmpty())
for (int chunkZ = minZ >> 4; chunkZ <= chunkZMax; ++chunkZ)
{
ArrayList<Claim> chunkClaims = dataStore.chunksToClaimsMap.get(DataStore.getChunkHash(chunkX, chunkZ));
if (chunkClaims != null)
intersectable.addAll(chunkClaims);
}
}
for (Claim claim : intersectable)
{
if (claim == pistonClaim) continue;
Location minLoc = claim.getLesserBoundaryCorner();
Location maxLoc = claim.getGreaterBoundaryCorner();
// Ensure claim intersects with bounding box.
if (maxY < minLoc.getBlockY() || minX > maxLoc.getBlockX() || maxX < minLoc.getBlockX() || minZ > maxLoc.getBlockZ() || maxZ < minLoc.getBlockZ())
continue;
// If owners are different, cancel.
if (pistonClaim == null || !pistonClaim.ownerID.equals(claim.ownerID))
{
event.setCancelled(true);
return;
}
for (Block movedBlock : event.getBlocks())
{
//if pulled block isn't in the same land claim, cancel the event
if (!pistonClaim.contains(movedBlock.getLocation(), false, false))
{
event.setCancelled(true);
return;
}
}
}
//otherwise, consider ownership of both piston and block
return;
}
// Precise mode: Each block must be considered individually.
Claim lastClaim = pistonClaim;
HashSet<Block> checkBlocks = new HashSet<>(blocks);
// Add all blocks that will be occupied after the shift.
for (Block block : blocks)
if (block.getPistonMoveReaction() != PistonMoveReaction.BREAK)
checkBlocks.add(block.getRelative(direction));
for (Block block : checkBlocks)
{
// Reimplement DataStore#getClaimAt to ignore subclaims to maximize performance.
Location location = block.getLocation();
Claim claim = null;
if (lastClaim != null && lastClaim.inDataStore && lastClaim.contains(location, false, true))
claim = lastClaim;
else
{
//who owns the piston, if anyone?
String pistonOwnerName = "_";
Block block = event.getBlock();
Location pistonLocation = block.getLocation();
Claim pistonClaim = this.dataStore.getClaimAt(pistonLocation, false, null);
if (pistonClaim != null) pistonOwnerName = pistonClaim.getOwnerName();
String movingBlockOwnerName = "_";
for (Block movedBlock : event.getBlocks())
ArrayList<Claim> chunkClaims = dataStore.chunksToClaimsMap.get(DataStore.getChunkHash(location));
if (chunkClaims != null)
{
//who owns the moving block, if anyone?
Claim movingBlockClaim = this.dataStore.getClaimAt(movedBlock.getLocation(), false, pistonClaim);
if (movingBlockClaim != null) movingBlockOwnerName = movingBlockClaim.getOwnerName();
//if there are owners for the blocks, they must be the same player
//otherwise cancel the event
if (!pistonOwnerName.equals(movingBlockOwnerName))
for (Claim chunkClaim : chunkClaims)
{
event.setCancelled(true);
block.getWorld().createExplosion(block.getLocation(), 0);
block.getWorld().dropItem(block.getLocation(), new ItemStack(Material.STICKY_PISTON));
block.setType(Material.AIR);
return;
if (chunkClaim.contains(location, false, true))
{
claim = chunkClaim;
break;
}
}
}
}
}
catch (NoSuchMethodError exception)
{
GriefPrevention.AddLogEntry("Your server is running an outdated version of 1.8 which has a griefing vulnerability. Update your server (reruns buildtools.jar to get an updated server JAR file) to ensure players can't steal claimed blocks using pistons.");
if (claim == null) continue;
lastClaim = claim;
// If pushing this block will change ownership, cancel the event and take away the piston (for performance reasons).
if (pistonClaim == null || !pistonClaim.ownerID.equals(claim.ownerID))
{
event.setCancelled(true);
pistonBlock.getWorld().createExplosion(pistonBlock.getLocation(), 0);
pistonBlock.getWorld().dropItem(pistonBlock.getLocation(), new ItemStack(event.isSticky() ? Material.STICKY_PISTON : Material.PISTON));
pistonBlock.setType(Material.AIR);
return;
}
}
}

View File

@ -205,8 +205,7 @@ public class GriefPrevention extends JavaPlugin
public HashMap<String, Integer> config_seaLevelOverride; //override for sea level, because bukkit doesn't report the right value for all situations
public boolean config_limitTreeGrowth; //whether trees should be prevented from growing into a claim from outside
public boolean config_checkPistonMovement; //whether to check piston movement
public boolean config_pistonsInClaimsOnly; //whether pistons are limited to only move blocks located within the piston's land claim
public PistonMode config_pistonMovement; //Setting for piston check options
public boolean config_advanced_fixNegativeClaimblockAmounts; //whether to attempt to fix negative claim block amounts (some addons cause/assume players can go into negative amounts)
public int config_advanced_claim_expiration_check_rate; //How often GP should check for expired claims, amount in seconds
@ -625,12 +624,11 @@ public class GriefPrevention extends JavaPlugin
this.config_blockSurfaceOtherExplosions = config.getBoolean("GriefPrevention.BlockSurfaceOtherExplosions", true);
this.config_blockSkyTrees = config.getBoolean("GriefPrevention.LimitSkyTrees", true);
this.config_limitTreeGrowth = config.getBoolean("GriefPrevention.LimitTreeGrowth", false);
this.config_checkPistonMovement = config.getBoolean("GriefPrevention.CheckPistonMovement", true);
this.config_pistonsInClaimsOnly = config.getBoolean("GriefPrevention.LimitPistonsToLandClaims", true);
if (!this.config_checkPistonMovement && this.config_pistonsInClaimsOnly) {
AddLogEntry("Error: You have enabled LimitPistonsToLandClaims, but CheckPistonMovement is off!");
this.config_pistonsInClaimsOnly = false;
}
this.config_pistonMovement = PistonMode.of(config.getString("GriefPrevention.PistonMovement", "CLAIMS_ONLY"));
if (config.isBoolean("GriefPrevention.LimitPistonsToLandClaims") && !config.isBoolean("GriefPrevention.LimitPistonsToLandClaims"))
this.config_pistonMovement = PistonMode.EVERYWHERE_SIMPLE;
if (config.isBoolean("GriefPrevention.CheckPistonMovement") && !config.getBoolean("GriefPrevention.CheckPistonMovement"))
this.config_pistonMovement = PistonMode.IGNORED;
this.config_fireSpreads = config.getBoolean("GriefPrevention.FireSpreads", false);
this.config_fireDestroys = config.getBoolean("GriefPrevention.FireDestroys", false);
@ -897,8 +895,9 @@ public class GriefPrevention extends JavaPlugin
outConfig.set("GriefPrevention.BlockSurfaceOtherExplosions", this.config_blockSurfaceOtherExplosions);
outConfig.set("GriefPrevention.LimitSkyTrees", this.config_blockSkyTrees);
outConfig.set("GriefPrevention.LimitTreeGrowth", this.config_limitTreeGrowth);
outConfig.set("GriefPrevention.CheckPistonMovement", this.config_checkPistonMovement);
outConfig.set("GriefPrevention.LimitPistonsToLandClaims", this.config_pistonsInClaimsOnly);
outConfig.set("GriefPrevention.PistonMovement", this.config_pistonMovement.name());
outConfig.set("GriefPrevention.CheckPistonMovement", null);
outConfig.set("GriefPrevention.LimitPistonsToLandClaims", null);
outConfig.set("GriefPrevention.FireSpreads", this.config_fireSpreads);
outConfig.set("GriefPrevention.FireDestroys", this.config_fireDestroys);

View File

@ -0,0 +1,25 @@
package me.ryanhamshire.GriefPrevention;
public enum PistonMode {
EVERYWHERE,
EVERYWHERE_SIMPLE,
CLAIMS_ONLY,
IGNORED;
public static PistonMode of(String value)
{
if (value == null) {
return CLAIMS_ONLY;
}
try
{
return valueOf(value.toUpperCase());
}
catch (IllegalArgumentException e)
{
return CLAIMS_ONLY;
}
}
}

View File

@ -93,7 +93,7 @@ public class MetricsHandler
addSimplePie("block_sky_trees", plugin.config_blockSkyTrees);
addSimplePie("limit_tree_growth", plugin.config_limitTreeGrowth);
addSimplePie("pistons_only_work_in_claims", plugin.config_pistonsInClaimsOnly);
addSimplePie("pistons_only_work_in_claims", plugin.config_pistonMovement.name().toLowerCase().replace('_', ' '));
addSimplePie("creatures_trample_crops", plugin.config_creaturesTrampleCrops);
addSimplePie("claim_tool", plugin.config_claims_modificationTool.name());