diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/EntityEventHandler.java b/src/main/java/me/ryanhamshire/GriefPrevention/EntityEventHandler.java
index 63749e6..b1e33b4 100644
--- a/src/main/java/me/ryanhamshire/GriefPrevention/EntityEventHandler.java
+++ b/src/main/java/me/ryanhamshire/GriefPrevention/EntityEventHandler.java
@@ -1,1575 +1,1584 @@
-/*
- 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 .
- */
-
-package me.ryanhamshire.GriefPrevention;
-
-import de.Keyle.MyPet.api.entity.MyPetBukkitEntity;
-import me.ryanhamshire.GriefPrevention.events.PreventPvPEvent;
-import me.ryanhamshire.GriefPrevention.events.ProtectDeathDropsEvent;
-import org.bukkit.Bukkit;
-import org.bukkit.Location;
-import org.bukkit.Material;
-import org.bukkit.NamespacedKey;
-import org.bukkit.OfflinePlayer;
-import org.bukkit.World;
-import org.bukkit.World.Environment;
-import org.bukkit.block.Block;
-import org.bukkit.entity.Animals;
-import org.bukkit.entity.Creature;
-import org.bukkit.entity.Donkey;
-import org.bukkit.entity.Entity;
-import org.bukkit.entity.EntityType;
-import org.bukkit.entity.Explosive;
-import org.bukkit.entity.FallingBlock;
-import org.bukkit.entity.Firework;
-import org.bukkit.entity.Horse;
-import org.bukkit.entity.Item;
-import org.bukkit.entity.LightningStrike;
-import org.bukkit.entity.LivingEntity;
-import org.bukkit.entity.Llama;
-import org.bukkit.entity.Mob;
-import org.bukkit.entity.Monster;
-import org.bukkit.entity.Mule;
-import org.bukkit.entity.Panda;
-import org.bukkit.entity.Player;
-import org.bukkit.entity.Projectile;
-import org.bukkit.entity.Rabbit;
-import org.bukkit.entity.Slime;
-import org.bukkit.entity.Tameable;
-import org.bukkit.entity.ThrownPotion;
-import org.bukkit.entity.Vehicle;
-import org.bukkit.entity.WaterMob;
-import org.bukkit.entity.Wolf;
-import org.bukkit.entity.Zombie;
-import org.bukkit.entity.minecart.ExplosiveMinecart;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.EventPriority;
-import org.bukkit.event.Listener;
-import org.bukkit.event.block.BlockExplodeEvent;
-import org.bukkit.event.block.EntityBlockFormEvent;
-import org.bukkit.event.entity.AreaEffectCloudApplyEvent;
-import org.bukkit.event.entity.CreatureSpawnEvent;
-import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
-import org.bukkit.event.entity.EntityBreakDoorEvent;
-import org.bukkit.event.entity.EntityChangeBlockEvent;
-import org.bukkit.event.entity.EntityCombustByEntityEvent;
-import org.bukkit.event.entity.EntityDamageByEntityEvent;
-import org.bukkit.event.entity.EntityDamageEvent;
-import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
-import org.bukkit.event.entity.EntityDeathEvent;
-import org.bukkit.event.entity.EntityExplodeEvent;
-import org.bukkit.event.entity.EntityInteractEvent;
-import org.bukkit.event.entity.EntityPortalEnterEvent;
-import org.bukkit.event.entity.EntityPortalExitEvent;
-import org.bukkit.event.entity.EntityShootBowEvent;
-import org.bukkit.event.entity.EntityTargetEvent;
-import org.bukkit.event.entity.ExpBottleEvent;
-import org.bukkit.event.entity.ItemMergeEvent;
-import org.bukkit.event.entity.ItemSpawnEvent;
-import org.bukkit.event.entity.LingeringPotionSplashEvent;
-import org.bukkit.event.entity.PotionSplashEvent;
-import org.bukkit.event.hanging.HangingBreakByEntityEvent;
-import org.bukkit.event.hanging.HangingBreakEvent;
-import org.bukkit.event.hanging.HangingBreakEvent.RemoveCause;
-import org.bukkit.event.hanging.HangingPlaceEvent;
-import org.bukkit.event.vehicle.VehicleDamageEvent;
-import org.bukkit.inventory.ItemStack;
-import org.bukkit.metadata.FixedMetadataValue;
-import org.bukkit.metadata.MetadataValue;
-import org.bukkit.persistence.PersistentDataType;
-import org.bukkit.potion.PotionEffect;
-import org.bukkit.potion.PotionEffectType;
-import org.bukkit.projectiles.BlockProjectileSource;
-import org.bukkit.projectiles.ProjectileSource;
-import org.bukkit.util.Vector;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.UUID;
-import java.util.function.Supplier;
-
-//handles events related to entities
-public class EntityEventHandler implements Listener
-{
- //convenience reference for the singleton datastore
- private final DataStore dataStore;
- private final GriefPrevention instance;
- private final NamespacedKey luredByPlayer;
-
- public EntityEventHandler(DataStore dataStore, GriefPrevention plugin)
- {
- this.dataStore = dataStore;
- instance = plugin;
- luredByPlayer = new NamespacedKey(plugin, "lured_by_player");
- }
-
- @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onEntityFormBlock(EntityBlockFormEvent event)
- {
- Entity entity = event.getEntity();
- if (entity.getType() == EntityType.PLAYER)
- {
- Player player = (Player) event.getEntity();
- String noBuildReason = GriefPrevention.instance.allowBuild(player, event.getBlock().getLocation(), event.getNewState().getType());
- if (noBuildReason != null)
- {
- event.setCancelled(true);
- }
- }
- }
-
- @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onEntityChangeBLock(EntityChangeBlockEvent event)
- {
- if (!GriefPrevention.instance.config_endermenMoveBlocks && event.getEntityType() == EntityType.ENDERMAN)
- {
- event.setCancelled(true);
- }
- else if (!GriefPrevention.instance.config_silverfishBreakBlocks && event.getEntityType() == EntityType.SILVERFISH)
- {
- event.setCancelled(true);
- }
- else if (!GriefPrevention.instance.config_rabbitsEatCrops && event.getEntityType() == EntityType.RABBIT)
- {
- event.setCancelled(true);
- }
- else if (!GriefPrevention.instance.config_claims_ravagersBreakBlocks && event.getEntityType() == EntityType.RAVAGER)
- {
- event.setCancelled(true);
- }
- // All other handling depends on claims being enabled.
- else if (GriefPrevention.instance.config_claims_worldModes.get(event.getBlock().getWorld()) == ClaimsMode.Disabled)
- {
- return;
- }
-
- // Handle projectiles changing blocks: TNT ignition, tridents knocking down pointed dripstone, etc.
- if (event.getEntity() instanceof Projectile)
- {
- handleProjectileChangeBlock(event, (Projectile) event.getEntity());
- }
-
- else if (event.getEntityType() == EntityType.WITHER)
- {
- Claim claim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null);
- if (claim == null || !claim.areExplosivesAllowed || !GriefPrevention.instance.config_blockClaimExplosions)
- {
- event.setCancelled(true);
- }
- }
-
- //don't allow crops to be trampled, except by a player with build permission
- else if (event.getTo() == Material.DIRT && event.getBlock().getType() == Material.FARMLAND)
- {
- if (event.getEntityType() != EntityType.PLAYER)
- {
- event.setCancelled(true);
- }
- else
- {
- Player player = (Player) event.getEntity();
- Block block = event.getBlock();
- if (GriefPrevention.instance.allowBreak(player, block, block.getLocation()) != null)
- {
- event.setCancelled(true);
- }
- }
- }
-
- // Prevent breaking lily pads via collision with a boat.
- else if (event.getEntity() instanceof Vehicle && !event.getEntity().getPassengers().isEmpty())
- {
- Entity driver = event.getEntity().getPassengers().get(0);
- if (driver instanceof Player)
- {
- Block block = event.getBlock();
- if (GriefPrevention.instance.allowBreak((Player) driver, block, block.getLocation()) != null)
- {
- event.setCancelled(true);
- }
- }
- }
-
- //sand cannon fix - when the falling block doesn't fall straight down, take additional anti-grief steps
- else if (event.getEntityType() == EntityType.FALLING_BLOCK)
- {
- FallingBlock entity = (FallingBlock) event.getEntity();
- Block block = event.getBlock();
-
- //if changing a block TO air, this is when the falling block formed. note its original location
- if (event.getTo() == Material.AIR)
- {
- entity.setMetadata("GP_FALLINGBLOCK", new FixedMetadataValue(GriefPrevention.instance, block.getLocation()));
- }
- //otherwise, the falling block is forming a block. compare new location to original source
- else
- {
- List values = entity.getMetadata("GP_FALLINGBLOCK");
- //if we're not sure where this entity came from (maybe another plugin didn't follow the standard?), allow the block to form
- //Or if entity fell through an end portal, allow it to form, as the event is erroneously fired twice in this scenario.
- if (values.size() < 1) return;
-
- Location originalLocation = (Location) (values.get(0).value());
- Location newLocation = block.getLocation();
-
- //if did not fall straight down
- if (originalLocation.getBlockX() != newLocation.getBlockX() || originalLocation.getBlockZ() != newLocation.getBlockZ())
- {
-
- //in other worlds, if landing in land claim, only allow if source was also in the land claim
- Claim claim = this.dataStore.getClaimAt(newLocation, false, null);
- if (claim != null && !claim.contains(originalLocation, false, false))
- {
- //when not allowed, drop as item instead of forming a block
- event.setCancelled(true);
-
- // Just in case, skip already dead entities.
- if (entity.isDead())
- {
- return;
- }
-
- // Remove entity so it doesn't continuously spawn drops.
- entity.remove();
-
- ItemStack itemStack = new ItemStack(entity.getBlockData().getMaterial(), 1);
- block.getWorld().dropItemNaturally(entity.getLocation(), itemStack);
- }
- }
- }
- }
- }
-
- private void handleProjectileChangeBlock(EntityChangeBlockEvent event, Projectile projectile)
- {
- Block block = event.getBlock();
- Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, null);
-
- // Wilderness rules
- if (claim == null)
- {
- // TNT change means ignition. If no claim is present, use global fire rules.
- if (block.getType() == Material.TNT)
- {
- if (!GriefPrevention.instance.config_fireDestroys || !GriefPrevention.instance.config_fireSpreads)
- event.setCancelled(true);
- return;
- }
-
- // Unclaimed area is fair game.
- return;
- }
-
- ProjectileSource shooter = projectile.getShooter();
-
- if (shooter instanceof Player)
- {
- Supplier denial = claim.checkPermission((Player) shooter, ClaimPermission.Build, event);
-
- // If the player cannot place the material being broken, disallow.
- if (denial != null)
- {
- // Unlike entities where arrows rebound and may cause multiple alerts,
- // projectiles lodged in blocks do not continuously re-trigger events.
- GriefPrevention.sendMessage((Player) shooter, TextMode.Err, denial.get());
- event.setCancelled(true);
- }
-
- return;
- }
-
- // Allow change if projectile was shot by a dispenser in the same claim.
- if (isBlockSourceInClaim(shooter, claim))
- return;
-
- // Prevent change in all other cases.
- event.setCancelled(true);
- }
-
- private boolean isBlockSourceInClaim(ProjectileSource projectileSource, Claim claim)
- {
- return projectileSource instanceof BlockProjectileSource &&
- GriefPrevention.instance.dataStore.getClaimAt(((BlockProjectileSource) projectileSource).getBlock().getLocation(), false, claim) == claim;
- }
-
- //Used by "sand cannon" fix to ignore fallingblocks that fell through End Portals
- //This is largely due to a CB issue with the above event
- @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onFallingBlockEnterPortal(EntityPortalEnterEvent event)
- {
- if (event.getEntityType() != EntityType.FALLING_BLOCK)
- return;
- event.getEntity().removeMetadata("GP_FALLINGBLOCK", instance);
- }
-
- //Don't let people drop in TNT through end portals
- //Necessarily this shouldn't be an issue anyways since the platform is obsidian...
- @EventHandler(ignoreCancelled = true)
- void onTNTExitPortal(EntityPortalExitEvent event)
- {
- if (event.getEntityType() != EntityType.PRIMED_TNT)
- return;
- if (event.getTo().getWorld().getEnvironment() != Environment.THE_END)
- return;
- event.getEntity().remove();
- }
-
- //don't allow zombies to break down doors
- @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onZombieBreakDoor(EntityBreakDoorEvent event)
- {
- if (!GriefPrevention.instance.config_zombiesBreakDoors) event.setCancelled(true);
- }
-
- //don't allow entities to trample crops
- @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onEntityInteract(EntityInteractEvent event)
- {
- Material material = event.getBlock().getType();
- if (material == Material.FARMLAND)
- {
- if (!GriefPrevention.instance.config_creaturesTrampleCrops)
- {
- event.setCancelled(true);
- }
- else
- {
- Entity rider = event.getEntity().getPassenger();
- if (rider != null && rider.getType() == EntityType.PLAYER)
- {
- event.setCancelled(true);
- }
- }
- }
- }
-
- //when an entity explodes...
- @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onEntityExplode(EntityExplodeEvent explodeEvent)
- {
- this.handleExplosion(explodeEvent.getLocation(), explodeEvent.getEntity(), explodeEvent.blockList());
- }
-
- //when a block explodes...
- @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onBlockExplode(BlockExplodeEvent explodeEvent)
- {
- this.handleExplosion(explodeEvent.getBlock().getLocation(), null, explodeEvent.blockList());
- }
-
-
- void handleExplosion(Location location, Entity entity, List blocks)
- {
- //only applies to claims-enabled worlds
- World world = location.getWorld();
-
- if (!GriefPrevention.instance.claimsEnabledForWorld(world)) return;
-
- //FEATURE: explosions don't destroy surface blocks by default
- boolean isCreeper = (entity != null && entity.getType() == EntityType.CREEPER);
-
- boolean applySurfaceRules = world.getEnvironment() == Environment.NORMAL && ((isCreeper && GriefPrevention.instance.config_blockSurfaceCreeperExplosions) || (!isCreeper && GriefPrevention.instance.config_blockSurfaceOtherExplosions));
-
- //make a list of blocks which were allowed to explode
- List explodedBlocks = new ArrayList<>();
- Claim cachedClaim = null;
- for (Block block : blocks)
- {
- //always ignore air blocks
- if (block.getType() == Material.AIR) continue;
-
- //is it in a land claim?
- Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, cachedClaim);
- if (claim != null)
- {
- cachedClaim = claim;
- }
-
- //if yes, apply claim exemptions if they should apply
- if (claim != null && (claim.areExplosivesAllowed || !GriefPrevention.instance.config_blockClaimExplosions))
- {
- explodedBlocks.add(block);
- continue;
- }
-
- //if no, then also consider surface rules
- if (claim == null)
- {
- if (!applySurfaceRules || block.getLocation().getBlockY() < GriefPrevention.instance.getSeaLevel(world) - 7)
- {
- explodedBlocks.add(block);
- }
- }
- }
-
- //clear original damage list and replace with allowed damage list
- blocks.clear();
- blocks.addAll(explodedBlocks);
- }
-
- //when an item spawns...
- @EventHandler(priority = EventPriority.LOWEST)
- public void onItemSpawn(ItemSpawnEvent event)
- {
- //if item is on watch list, apply protection
- ArrayList watchList = GriefPrevention.instance.pendingItemWatchList;
- Item newItem = event.getEntity();
- Long now = null;
- for (int i = 0; i < watchList.size(); i++)
- {
- PendingItemProtection pendingProtection = watchList.get(i);
- //ignore and remove any expired pending protections
- if (now == null) now = System.currentTimeMillis();
- if (pendingProtection.expirationTimestamp < now)
- {
- watchList.remove(i--);
- continue;
- }
- //skip if item stack doesn't match
- if (pendingProtection.itemStack.getAmount() != newItem.getItemStack().getAmount() ||
- pendingProtection.itemStack.getType() != newItem.getItemStack().getType())
- {
- continue;
- }
-
- //skip if new item location isn't near the expected spawn area
- Location spawn = event.getLocation();
- Location expected = pendingProtection.location;
- if (!spawn.getWorld().equals(expected.getWorld()) ||
- spawn.getX() < expected.getX() - 5 ||
- spawn.getX() > expected.getX() + 5 ||
- spawn.getZ() < expected.getZ() - 5 ||
- spawn.getZ() > expected.getZ() + 5 ||
- spawn.getY() < expected.getY() - 15 ||
- spawn.getY() > expected.getY() + 3)
- {
- continue;
- }
-
- //otherwise, mark item with protection information
- newItem.setMetadata("GP_ITEMOWNER", new FixedMetadataValue(GriefPrevention.instance, pendingProtection.owner));
-
- //and remove pending protection data
- watchList.remove(i);
- break;
- }
- }
-
- //when an entity dies...
- @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
- public void onEntityDeath(EntityDeathEvent event)
- {
- LivingEntity entity = event.getEntity();
-
- //don't do the rest in worlds where claims are not enabled
- if (!GriefPrevention.instance.claimsEnabledForWorld(entity.getWorld())) return;
-
- //FEATURE: when a player is involved in a siege (attacker or defender role)
- //his death will end the siege
-
- if (entity.getType() != EntityType.PLAYER) return; //only tracking players
-
- Player player = (Player) entity;
- PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId());
-
- //FEATURE: lock dropped items to player who dropped them
-
- World world = entity.getWorld();
-
- //decide whether or not to apply this feature to this situation (depends on the world where it happens)
- boolean isPvPWorld = GriefPrevention.instance.pvpRulesApply(world);
- if ((isPvPWorld && GriefPrevention.instance.config_lockDeathDropsInPvpWorlds) ||
- (!isPvPWorld && GriefPrevention.instance.config_lockDeathDropsInNonPvpWorlds))
- {
- Claim claim = this.dataStore.getClaimAt(player.getLocation(), false, playerData.lastClaim);
- ProtectDeathDropsEvent protectionEvent = new ProtectDeathDropsEvent(claim);
- Bukkit.getPluginManager().callEvent(protectionEvent);
- if (!protectionEvent.isCancelled())
- {
- //remember information about these drops so that they can be marked when they spawn as items
- long expirationTime = System.currentTimeMillis() + 3000; //now + 3 seconds
- Location deathLocation = player.getLocation();
- UUID playerID = player.getUniqueId();
- List drops = event.getDrops();
- for (ItemStack stack : drops)
- {
- GriefPrevention.instance.pendingItemWatchList.add(
- new PendingItemProtection(deathLocation, playerID, expirationTime, stack));
- }
-
- }
- }
- }
-
- @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onItemMerge(ItemMergeEvent event)
- {
- Item item = event.getEntity();
- List data = item.getMetadata("GP_ITEMOWNER");
- event.setCancelled(data != null && data.size() > 0);
- }
-
- //when an entity picks up an item
- @EventHandler(priority = EventPriority.LOWEST)
- public void onEntityPickup(EntityChangeBlockEvent event)
- {
- //FEATURE: endermen don't steal claimed blocks
-
- //if its an enderman
- if (event.getEntity().getType() == EntityType.ENDERMAN)
- {
- //and the block is claimed
- if (this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null) != null)
- {
- //he doesn't get to steal it
- event.setCancelled(true);
- }
- }
- }
-
- //when a painting is broken
- @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onHangingBreak(HangingBreakEvent event)
- {
- //don't track in worlds where claims are not enabled
- if (!GriefPrevention.instance.claimsEnabledForWorld(event.getEntity().getWorld())) return;
-
- //Ignore cases where itemframes should break due to no supporting blocks
- if (event.getCause() == RemoveCause.PHYSICS) return;
-
- //FEATURE: claimed paintings are protected from breakage
-
- //explosions don't destroy hangings
- if (event.getCause() == RemoveCause.EXPLOSION)
- {
- event.setCancelled(true);
- return;
- }
-
- //only allow players to break paintings, not anything else (like water and explosions)
- if (!(event instanceof HangingBreakByEntityEvent))
- {
- event.setCancelled(true);
- return;
- }
-
- HangingBreakByEntityEvent entityEvent = (HangingBreakByEntityEvent) event;
-
- //who is removing it?
- Entity remover = entityEvent.getRemover();
-
- //again, making sure the breaker is a player
- if (remover.getType() != EntityType.PLAYER)
- {
- event.setCancelled(true);
- return;
- }
-
- //if the player doesn't have build permission, don't allow the breakage
- Player playerRemover = (Player) entityEvent.getRemover();
- String noBuildReason = GriefPrevention.instance.allowBuild(playerRemover, event.getEntity().getLocation(), Material.AIR);
- if (noBuildReason != null)
- {
- event.setCancelled(true);
- GriefPrevention.sendMessage(playerRemover, TextMode.Err, noBuildReason);
- }
- }
-
- //when a painting is placed...
- @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onPaintingPlace(HangingPlaceEvent event)
- {
- //don't track in worlds where claims are not enabled
- if (!GriefPrevention.instance.claimsEnabledForWorld(event.getBlock().getWorld())) return;
-
- //FEATURE: similar to above, placing a painting requires build permission in the claim
-
- //if the player doesn't have permission, don't allow the placement
- String noBuildReason = GriefPrevention.instance.allowBuild(event.getPlayer(), event.getEntity().getLocation(), Material.PAINTING);
- if (noBuildReason != null)
- {
- event.setCancelled(true);
- GriefPrevention.sendMessage(event.getPlayer(), TextMode.Err, noBuildReason);
- return;
- }
-
- }
-
- private boolean isMonster(Entity entity)
- {
- if (entity instanceof Monster) return true;
-
- EntityType type = entity.getType();
- if (type == EntityType.GHAST || type == EntityType.MAGMA_CUBE || type == EntityType.SHULKER)
- return true;
-
- if (type == EntityType.SLIME)
- return ((Slime) entity).getSize() > 0;
-
- if (type == EntityType.RABBIT)
- return ((Rabbit) entity).getRabbitType() == Rabbit.Type.THE_KILLER_BUNNY;
-
- if (type == EntityType.PANDA)
- return ((Panda) entity).getMainGene() == Panda.Gene.AGGRESSIVE;
-
- if ((type == EntityType.HOGLIN || type == EntityType.POLAR_BEAR) && entity instanceof Mob)
- return !entity.getPersistentDataContainer().has(luredByPlayer, PersistentDataType.BYTE) && ((Mob) entity).getTarget() != null;
-
- return false;
- }
-
- // Tag passive animals that can become aggressive so we can tell whether or not they are hostile later
- @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
- public void onEntityTarget(EntityTargetEvent event)
- {
- if (!GriefPrevention.instance.claimsEnabledForWorld(event.getEntity().getWorld())) return;
-
- EntityType entityType = event.getEntityType();
- if (entityType != EntityType.HOGLIN && entityType != EntityType.POLAR_BEAR)
- return;
-
- if (event.getReason() == EntityTargetEvent.TargetReason.TEMPT)
- event.getEntity().getPersistentDataContainer().set(luredByPlayer, PersistentDataType.BYTE, (byte) 1);
- else
- event.getEntity().getPersistentDataContainer().remove(luredByPlayer);
-
- }
-
- //when an entity is damaged
- @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onEntityDamage(EntityDamageEvent event)
- {
- this.handleEntityDamageEvent(event, true);
- }
-
- //when an entity is set on fire
- @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onEntityCombustByEntity(EntityCombustByEntityEvent event)
- {
- //handle it just like we would an entity damge by entity event, except don't send player messages to avoid double messages
- //in cases like attacking with a flame sword or flame arrow, which would ALSO trigger the direct damage event handler
-
- EntityDamageByEntityEvent eventWrapper = new EntityDamageByEntityEvent(event.getCombuster(), event.getEntity(), DamageCause.FIRE_TICK, event.getDuration());
- this.handleEntityDamageEvent(eventWrapper, false);
- event.setCancelled(eventWrapper.isCancelled());
- }
-
- private void handleEntityDamageEvent(EntityDamageEvent event, boolean sendErrorMessagesToPlayers)
- {
- //monsters are never protected
- if (isMonster(event.getEntity())) return;
-
- //horse protections can be disabled
- if (event.getEntity() instanceof Horse && !GriefPrevention.instance.config_claims_protectHorses) return;
- if (event.getEntity() instanceof Donkey && !GriefPrevention.instance.config_claims_protectDonkeys) return;
- if (event.getEntity() instanceof Mule && !GriefPrevention.instance.config_claims_protectDonkeys) return;
- if (event.getEntity() instanceof Llama && !GriefPrevention.instance.config_claims_protectLlamas) return;
- //protected death loot can't be destroyed, only picked up or despawned due to expiration
- if (event.getEntityType() == EntityType.DROPPED_ITEM)
- {
- if (event.getEntity().hasMetadata("GP_ITEMOWNER"))
- {
- event.setCancelled(true);
- }
- }
-
- //protect pets from environmental damage types which could be easily caused by griefers
- if (event.getEntity() instanceof Tameable && !GriefPrevention.instance.pvpRulesApply(event.getEntity().getWorld()))
- {
- Tameable tameable = (Tameable) event.getEntity();
- if (tameable.isTamed())
- {
- DamageCause cause = event.getCause();
- if (cause != null && (
- cause == DamageCause.BLOCK_EXPLOSION ||
- cause == DamageCause.ENTITY_EXPLOSION ||
- cause == DamageCause.FALLING_BLOCK ||
- cause == DamageCause.FIRE ||
- cause == DamageCause.FIRE_TICK ||
- cause == DamageCause.LAVA ||
- cause == DamageCause.SUFFOCATION ||
- cause == DamageCause.CONTACT ||
- cause == DamageCause.DROWNING))
- {
- event.setCancelled(true);
- return;
- }
- }
- }
-
- if (handleBlockExplosionDamage(event)) return;
-
- //the rest is only interested in entities damaging entities (ignoring environmental damage)
- if (!(event instanceof EntityDamageByEntityEvent)) return;
-
- EntityDamageByEntityEvent subEvent = (EntityDamageByEntityEvent) event;
-
- if (subEvent.getDamager() instanceof LightningStrike && subEvent.getDamager().hasMetadata("GP_TRIDENT"))
- {
- event.setCancelled(true);
- return;
- }
- //determine which player is attacking, if any
- Player attacker = null;
- Projectile arrow = null;
- Firework firework = null;
- Entity damageSource = subEvent.getDamager();
-
- if (damageSource != null)
- {
- if (damageSource.getType() == EntityType.PLAYER)
- {
- attacker = (Player) damageSource;
- }
- else if (damageSource instanceof Projectile)
- {
- arrow = (Projectile) damageSource;
- if (arrow.getShooter() instanceof Player)
- {
- attacker = (Player) arrow.getShooter();
- }
- }
- else if (subEvent.getDamager() instanceof Firework)
- {
- damageSource = subEvent.getDamager();
- if (damageSource.hasMetadata("GP_FIREWORK"))
- {
- List data = damageSource.getMetadata("GP_FIREWORK");
- if (data != null && data.size() > 0)
- {
- firework = (Firework) damageSource;
- attacker = (Player) data.get(0).value();
- }
- }
- }
-
- //protect players from lingering potion damage when protected from pvp
- if (damageSource.getType() == EntityType.AREA_EFFECT_CLOUD && event.getEntityType() == EntityType.PLAYER && GriefPrevention.instance.pvpRulesApply(event.getEntity().getWorld()))
- {
- Player damaged = (Player) event.getEntity();
- PlayerData damagedData = GriefPrevention.instance.dataStore.getPlayerData(damaged.getUniqueId());
-
- //case 1: recently spawned
- if (GriefPrevention.instance.config_pvp_protectFreshSpawns && damagedData.pvpImmune)
- {
- event.setCancelled(true);
- return;
- }
-
- //case 2: in a pvp safe zone
- else
- {
- Claim damagedClaim = GriefPrevention.instance.dataStore.getClaimAt(damaged.getLocation(), false, damagedData.lastClaim);
- if (damagedClaim != null)
- {
- damagedData.lastClaim = damagedClaim;
- if (GriefPrevention.instance.claimIsPvPSafeZone(damagedClaim))
- {
- PreventPvPEvent pvpEvent = new PreventPvPEvent(damagedClaim, attacker, damaged);
- Bukkit.getPluginManager().callEvent(pvpEvent);
- if (!pvpEvent.isCancelled())
- {
- event.setCancelled(true);
- }
- return;
- }
- }
- }
- }
- }
-
- //if the attacker is a firework from a crossbow by a player and defender is a player (nonpvp)
- if (firework != null && event.getEntityType() == EntityType.PLAYER && !GriefPrevention.instance.pvpRulesApply(attacker.getWorld()))
- {
- Player defender = (Player) (event.getEntity());
- if (attacker != defender)
- {
- event.setCancelled(true);
- return;
- }
- }
-
-
- //if the attacker is a player and defender is a player (pvp combat)
- if (attacker != null && event.getEntityType() == EntityType.PLAYER && GriefPrevention.instance.pvpRulesApply(attacker.getWorld()))
- {
- //FEATURE: prevent pvp in the first minute after spawn, and prevent pvp when one or both players have no inventory
-
- Player defender = (Player) (event.getEntity());
-
- if (attacker != defender)
- {
- PlayerData defenderData = this.dataStore.getPlayerData(((Player) event.getEntity()).getUniqueId());
- PlayerData attackerData = this.dataStore.getPlayerData(attacker.getUniqueId());
-
- //otherwise if protecting spawning players
- if (GriefPrevention.instance.config_pvp_protectFreshSpawns)
- {
- if (defenderData.pvpImmune)
- {
- event.setCancelled(true);
- if (sendErrorMessagesToPlayers)
- GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.ThatPlayerPvPImmune);
- return;
- }
-
- if (attackerData.pvpImmune)
- {
- event.setCancelled(true);
- if (sendErrorMessagesToPlayers)
- GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.CantFightWhileImmune);
- return;
- }
- }
-
- //FEATURE: prevent players from engaging in PvP combat inside land claims (when it's disabled)
- if (GriefPrevention.instance.config_pvp_noCombatInPlayerLandClaims || GriefPrevention.instance.config_pvp_noCombatInAdminLandClaims)
- {
- Claim attackerClaim = this.dataStore.getClaimAt(attacker.getLocation(), false, attackerData.lastClaim);
- if (!attackerData.ignoreClaims)
- {
- if (attackerClaim != null && //ignore claims mode allows for pvp inside land claims
- !attackerData.inPvpCombat() &&
- GriefPrevention.instance.claimIsPvPSafeZone(attackerClaim))
- {
- attackerData.lastClaim = attackerClaim;
- PreventPvPEvent pvpEvent = new PreventPvPEvent(attackerClaim, attacker, defender);
- Bukkit.getPluginManager().callEvent(pvpEvent);
- if (!pvpEvent.isCancelled())
- {
- event.setCancelled(true);
- if (sendErrorMessagesToPlayers)
- GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.CantFightWhileImmune);
- }
- return;
- }
-
- Claim defenderClaim = this.dataStore.getClaimAt(defender.getLocation(), false, defenderData.lastClaim);
- if (defenderClaim != null &&
- !defenderData.inPvpCombat() &&
- GriefPrevention.instance.claimIsPvPSafeZone(defenderClaim))
- {
- defenderData.lastClaim = defenderClaim;
- PreventPvPEvent pvpEvent = new PreventPvPEvent(defenderClaim, attacker, defender);
- Bukkit.getPluginManager().callEvent(pvpEvent);
- if (!pvpEvent.isCancelled())
- {
- event.setCancelled(true);
- if (sendErrorMessagesToPlayers)
- GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.PlayerInPvPSafeZone);
- }
- return;
- }
- }
- }
- }
- }
-
- if (event instanceof EntityDamageByEntityEvent)
- {
- //don't track in worlds where claims are not enabled
- if (!GriefPrevention.instance.claimsEnabledForWorld(event.getEntity().getWorld())) return;
-
- //protect players from being attacked by other players' pets when protected from pvp
- if (event.getEntityType() == EntityType.PLAYER)
- {
- Player defender = (Player) event.getEntity();
-
- //if attacker is a pet
- Entity damager = subEvent.getDamager();
- if (damager != null && damager instanceof Tameable)
- {
- Tameable pet = (Tameable) damager;
- if (pet.isTamed() && pet.getOwner() != null)
- {
- //if defender is NOT in pvp combat and not immune to pvp right now due to recent respawn
- PlayerData defenderData = GriefPrevention.instance.dataStore.getPlayerData(event.getEntity().getUniqueId());
- if (!defenderData.pvpImmune && !defenderData.inPvpCombat())
- {
- //if defender is not in a protected area
- Claim defenderClaim = this.dataStore.getClaimAt(defender.getLocation(), false, defenderData.lastClaim);
- if (defenderClaim != null &&
- !defenderData.inPvpCombat() &&
- GriefPrevention.instance.claimIsPvPSafeZone(defenderClaim))
- {
- defenderData.lastClaim = defenderClaim;
- PreventPvPEvent pvpEvent = new PreventPvPEvent(defenderClaim, attacker, defender);
- Bukkit.getPluginManager().callEvent(pvpEvent);
-
- //if other plugins aren't making an exception to the rule
- if (!pvpEvent.isCancelled())
- {
- event.setCancelled(true);
- if (damager instanceof Creature) ((Creature) damager).setTarget(null);
- }
- return;
- }
- }
- }
- }
- }
-
- //if the damaged entity is a claimed item frame or armor stand, the damager needs to be a player with build trust in the claim
- if (subEvent.getEntityType() == EntityType.ITEM_FRAME
- || subEvent.getEntityType() == EntityType.GLOW_ITEM_FRAME
- || subEvent.getEntityType() == EntityType.ARMOR_STAND
- || subEvent.getEntityType() == EntityType.VILLAGER
- || subEvent.getEntityType() == EntityType.ENDER_CRYSTAL)
- {
- //allow for disabling villager protections in the config
- if (subEvent.getEntityType() == EntityType.VILLAGER && !GriefPrevention.instance.config_claims_protectCreatures)
- return;
-
- //don't protect polar bears, they may be aggressive
- if (subEvent.getEntityType() == EntityType.POLAR_BEAR) return;
-
- //decide whether it's claimed
- Claim cachedClaim = null;
- PlayerData playerData = null;
- if (attacker != null)
- {
- playerData = this.dataStore.getPlayerData(attacker.getUniqueId());
- cachedClaim = playerData.lastClaim;
- }
-
- Claim claim = this.dataStore.getClaimAt(event.getEntity().getLocation(), false, cachedClaim);
-
- //if it's claimed
- if (claim != null)
- {
- //if attacker isn't a player, cancel
- if (attacker == null)
- {
- //exception case
- if (event.getEntityType() == EntityType.VILLAGER && damageSource != null && damageSource instanceof Zombie)
- {
- return;
- }
-
- event.setCancelled(true);
- return;
- }
-
- //otherwise player must have container trust in the claim
- Supplier failureReason = claim.checkPermission(attacker, ClaimPermission.Build, event);
- if (failureReason != null)
- {
- event.setCancelled(true);
- if (sendErrorMessagesToPlayers)
- GriefPrevention.sendMessage(attacker, TextMode.Err, failureReason.get());
- return;
- }
- }
- }
-
- //if the entity is an non-monster creature (remember monsters disqualified above), or a vehicle
- if (((subEvent.getEntity() instanceof Creature || subEvent.getEntity() instanceof WaterMob) && GriefPrevention.instance.config_claims_protectCreatures))
- {
- //if entity is tameable and has an owner, apply special rules
- if (subEvent.getEntity() instanceof Tameable)
- {
- Tameable tameable = (Tameable) subEvent.getEntity();
- if (tameable.isTamed() && tameable.getOwner() != null)
- {
- //limit attacks by players to owners and admins in ignore claims mode
- if (attacker != null)
- {
- UUID ownerID = tameable.getOwner().getUniqueId();
-
- //if the player interacting is the owner, always allow
- if (attacker.getUniqueId().equals(ownerID)) return;
-
- //allow for admin override
- PlayerData attackerData = this.dataStore.getPlayerData(attacker.getUniqueId());
- if (attackerData.ignoreClaims) return;
-
- //otherwise disallow in non-pvp worlds (and also pvp worlds if configured to do so)
- if (!GriefPrevention.instance.pvpRulesApply(subEvent.getEntity().getLocation().getWorld()) || (GriefPrevention.instance.config_pvp_protectPets && subEvent.getEntityType() != EntityType.WOLF))
- {
- OfflinePlayer owner = GriefPrevention.instance.getServer().getOfflinePlayer(ownerID);
- String ownerName = owner.getName();
- if (ownerName == null) ownerName = "someone";
- String message = GriefPrevention.instance.dataStore.getMessage(Messages.NoDamageClaimedEntity, ownerName);
- if (attacker.hasPermission("griefprevention.ignoreclaims"))
- message += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement);
- if (sendErrorMessagesToPlayers)
- GriefPrevention.sendMessage(attacker, TextMode.Err, message);
- PreventPvPEvent pvpEvent = new PreventPvPEvent(new Claim(subEvent.getEntity().getLocation(), subEvent.getEntity().getLocation(), null, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), null), attacker, tameable);
- Bukkit.getPluginManager().callEvent(pvpEvent);
- if (!pvpEvent.isCancelled())
- {
- event.setCancelled(true);
- }
- return;
- }
- //and disallow if attacker is pvp immune
- else if (attackerData.pvpImmune)
- {
- event.setCancelled(true);
- if (sendErrorMessagesToPlayers)
- GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.CantFightWhileImmune);
- return;
- }
- // disallow players attacking tamed wolves (dogs) unless under attack by said wolf
- else if (tameable.getType() == EntityType.WOLF)
- {
- if (!tameable.getOwner().equals(attacker))
- {
- if (((Wolf) tameable).getTarget() != null)
- {
- if (((Wolf) tameable).getTarget() == attacker) return;
- }
- event.setCancelled(true);
- String ownerName = GriefPrevention.instance.getServer().getOfflinePlayer(ownerID).getName();
- String message = GriefPrevention.instance.dataStore.getMessage(Messages.NoDamageClaimedEntity, ownerName);
- if (attacker.hasPermission("griefprevention.ignoreclaims"))
- message += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement);
- if (sendErrorMessagesToPlayers)
- GriefPrevention.sendMessage(attacker, TextMode.Err, message);
- return;
- }
- }
- }
- }
- }
-
- Claim cachedClaim = null;
- PlayerData playerData = null;
-
- //if not a player or an explosive, allow
- //RoboMWM: Or a lingering potion, or a witch
- if (attacker == null
- && damageSource != null
- && damageSource.getType() != EntityType.CREEPER
- && damageSource.getType() != EntityType.WITHER
- && damageSource.getType() != EntityType.ENDER_CRYSTAL
- && damageSource.getType() != EntityType.AREA_EFFECT_CLOUD
- && damageSource.getType() != EntityType.WITCH
- && !(damageSource instanceof Projectile)
- && !(damageSource instanceof Explosive)
- && !(damageSource instanceof ExplosiveMinecart))
- {
- return;
- }
-
- if (attacker != null)
- {
- playerData = this.dataStore.getPlayerData(attacker.getUniqueId());
- cachedClaim = playerData.lastClaim;
- }
-
- Claim claim = this.dataStore.getClaimAt(event.getEntity().getLocation(), false, cachedClaim);
-
- //if it's claimed
- if (claim != null)
- {
- //if damaged by anything other than a player (exception villagers injured by zombies in admin claims), cancel the event
- //why exception? so admins can set up a village which can't be CHANGED by players, but must be "protected" by players.
- //TODO: Discuss if this should only apply to admin claims...?
- if (attacker == null)
- {
- //exception case
- if (event.getEntityType() == EntityType.VILLAGER && damageSource != null && (damageSource.getType() == EntityType.ZOMBIE || damageSource.getType() == EntityType.VINDICATOR || damageSource.getType() == EntityType.EVOKER || damageSource.getType() == EntityType.EVOKER_FANGS || damageSource.getType() == EntityType.VEX))
- {
- return;
- }
-
- //all other cases
- else
- {
- event.setCancelled(true);
- if (damageSource instanceof Projectile)
- {
- damageSource.remove();
- }
- }
- }
-
- //otherwise the player damaging the entity must have permission, unless it's a dog in a pvp world
- else if (!(event.getEntity().getWorld().getPVP() && event.getEntity().getType() == EntityType.WOLF))
- {
- Supplier override = null;
- if (sendErrorMessagesToPlayers)
- {
- final Player finalAttacker = attacker;
- override = () ->
- {
- String message = GriefPrevention.instance.dataStore.getMessage(Messages.NoDamageClaimedEntity, claim.getOwnerName());
- if (finalAttacker.hasPermission("griefprevention.ignoreclaims"))
- message += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement);
- return message;
- };
- }
- Supplier noContainersReason = claim.checkPermission(attacker, ClaimPermission.Inventory, event, override);
- if (noContainersReason != null)
- {
- event.setCancelled(true);
-
- //kill the arrow to avoid infinite bounce between crowded together animals //RoboMWM: except for tridents
- if (arrow != null && arrow.getType() != EntityType.TRIDENT) arrow.remove();
- if (damageSource != null && damageSource.getType() == EntityType.FIREWORK && event.getEntity().getType() != EntityType.PLAYER)
- return;
-
- try {
- if (sendErrorMessagesToPlayers && !(event.getEntity() instanceof MyPetBukkitEntity)) // don't send the error message if it's a mypet
- {
- GriefPrevention.sendMessage(attacker, TextMode.Err, noContainersReason.get());
- }
- } catch (NoClassDefFoundError ex) {
- if (sendErrorMessagesToPlayers) // don't send the error message if it's a mypet
- {
- GriefPrevention.sendMessage(attacker, TextMode.Err, noContainersReason.get());
- }
- }
- event.setCancelled(true);
- }
-
- //cache claim for later
- if (playerData != null)
- {
- playerData.lastClaim = claim;
- }
- }
- }
- }
- }
- }
-
- /**
- * Handles entity damage caused by block explosions.
- *
- * @param event the EntityDamageEvent
- * @return true if the damage has been handled
- */
- private boolean handleBlockExplosionDamage(EntityDamageEvent event)
- {
- if (event.getCause() != DamageCause.BLOCK_EXPLOSION) return false;
-
- Entity entity = event.getEntity();
-
- // Skip players - does allow players to use block explosions to bypass PVP protections,
- // but also doesn't disable self-damage.
- if (entity instanceof Player) return false;
-
- Claim claim = GriefPrevention.instance.dataStore.getClaimAt(entity.getLocation(), false, null);
-
- // Only block explosion damage inside claims.
- if (claim == null) return false;
-
- event.setCancelled(true);
- return true;
- }
-
- @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onCrossbowFireWork(EntityShootBowEvent shootEvent)
- {
- if (shootEvent.getEntity() instanceof Player && shootEvent.getProjectile() instanceof Firework)
- {
- shootEvent.getProjectile().setMetadata("GP_FIREWORK", new FixedMetadataValue(GriefPrevention.instance, shootEvent.getEntity()));
- }
- }
-
- //when an entity is damaged
- @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
- public void onEntityDamageMonitor(EntityDamageEvent event)
- {
- //FEATURE: prevent players who very recently participated in pvp combat from hiding inventory to protect it from looting
- //FEATURE: prevent players who are in pvp combat from logging out to avoid being defeated
-
- if (event.getEntity().getType() != EntityType.PLAYER) return;
-
- Player defender = (Player) event.getEntity();
-
- //only interested in entities damaging entities (ignoring environmental damage)
- if (!(event instanceof EntityDamageByEntityEvent)) return;
-
- //Ignore "damage" from snowballs, eggs, etc. from triggering the PvP timer
- if (event.getDamage() == 0) return;
-
- EntityDamageByEntityEvent subEvent = (EntityDamageByEntityEvent) event;
-
- //if not in a pvp rules world, do nothing
- if (!GriefPrevention.instance.pvpRulesApply(defender.getWorld())) return;
-
- //determine which player is attacking, if any
- Player attacker = null;
- Projectile arrow = null;
- Entity damageSource = subEvent.getDamager();
-
- if (damageSource != null)
- {
- if (damageSource.getType() == EntityType.PLAYER)
- {
- attacker = (Player) damageSource;
- }
- else if (damageSource instanceof Projectile)
- {
- arrow = (Projectile) damageSource;
- if (arrow.getShooter() instanceof Player)
- {
- attacker = (Player) arrow.getShooter();
- }
- }
- else if (damageSource instanceof Firework)
- {
- if (damageSource.hasMetadata("GP_FIREWORK"))
- {
- List data = damageSource.getMetadata("GP_FIREWORK");
- if (data != null && data.size() > 0)
- {
- attacker = (Player) data.get(0).value();
- }
- }
- }
- }
-
- //if attacker not a player, do nothing
- if (attacker == null) return;
-
- PlayerData defenderData = this.dataStore.getPlayerData(defender.getUniqueId());
- PlayerData attackerData = this.dataStore.getPlayerData(attacker.getUniqueId());
-
- if (attacker != defender)
- {
- long now = Calendar.getInstance().getTimeInMillis();
- defenderData.lastPvpTimestamp = now;
- defenderData.lastPvpPlayer = attacker.getName();
- attackerData.lastPvpTimestamp = now;
- attackerData.lastPvpPlayer = defender.getName();
- }
- }
-
- //when a vehicle is damaged
- @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onVehicleDamage(VehicleDamageEvent event)
- {
- //all of this is anti theft code
- if (!GriefPrevention.instance.config_claims_preventTheft) return;
-
- //input validation
- if (event.getVehicle() == null) return;
-
- //don't track in worlds where claims are not enabled
- if (!GriefPrevention.instance.claimsEnabledForWorld(event.getVehicle().getWorld())) return;
-
- //determine which player is attacking, if any
- Player attacker = null;
- Entity damageSource = event.getAttacker();
- EntityType damageSourceType = null;
-
- //if damage source is null or a creeper, don't allow the damage when the vehicle is in a land claim
- if (damageSource != null)
- {
- damageSourceType = damageSource.getType();
-
- if (damageSource.getType() == EntityType.PLAYER)
- {
- attacker = (Player) damageSource;
- }
- else if (damageSource instanceof Projectile)
- {
- Projectile arrow = (Projectile) damageSource;
- if (arrow.getShooter() instanceof Player)
- {
- attacker = (Player) arrow.getShooter();
- }
- }
- else if (damageSource instanceof Firework)
- {
- if (damageSource.hasMetadata("GP_FIREWORK"))
- {
- List data = damageSource.getMetadata("GP_FIREWORK");
- if (data != null && data.size() > 0)
- {
- attacker = (Player) data.get(0).value();
- }
- }
- }
- }
-
- //if not a player and not an explosion, always allow
- if (attacker == null && damageSourceType != EntityType.CREEPER && damageSourceType != EntityType.WITHER && damageSourceType != EntityType.PRIMED_TNT)
- {
- return;
- }
-
- //NOTE: vehicles can be pushed around.
- //so unless precautions are taken by the owner, a resourceful thief might find ways to steal anyway
- Claim cachedClaim = null;
- PlayerData playerData = null;
-
- if (attacker != null)
- {
- playerData = this.dataStore.getPlayerData(attacker.getUniqueId());
- cachedClaim = playerData.lastClaim;
- }
-
- Claim claim = this.dataStore.getClaimAt(event.getVehicle().getLocation(), false, cachedClaim);
-
- //if it's claimed
- if (claim != null)
- {
- //if damaged by anything other than a player, cancel the event
- if (attacker == null)
- {
- event.setCancelled(true);
- }
-
- //otherwise the player damaging the entity must have permission
- else
- {
- final Player finalAttacker = attacker;
- Supplier override = () ->
- {
- String message = GriefPrevention.instance.dataStore.getMessage(Messages.NoDamageClaimedEntity, claim.getOwnerName());
- if (finalAttacker.hasPermission("griefprevention.ignoreclaims"))
- message += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement);
- return message;
- };
- Supplier noContainersReason = claim.checkPermission(attacker, ClaimPermission.Inventory, event, override);
- if (noContainersReason != null)
- {
- event.setCancelled(true);
- GriefPrevention.sendMessage(attacker, TextMode.Err, noContainersReason.get());
- }
-
- //cache claim for later
- if (playerData != null)
- {
- playerData.lastClaim = claim;
- }
- }
- }
- }
-
- //when a splash potion effects one or more entities...
- @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onPotionSplash(PotionSplashEvent event)
- {
- ThrownPotion potion = event.getPotion();
-
- ProjectileSource projectileSource = potion.getShooter();
- // Ignore potions with no source.
- if (projectileSource == null) return;
- Player thrower = null;
- if ((projectileSource instanceof Player))
- thrower = (Player) projectileSource;
- boolean messagedPlayer = false;
-
- Collection effects = potion.getEffects();
- for (PotionEffect effect : effects)
- {
- PotionEffectType effectType = effect.getType();
-
- // Restrict some potions on claimed villagers and animals.
- // Griefers could use potions to kill entities or steal them over fences.
- if (PotionEffectType.HARM.equals(effectType)
- || PotionEffectType.POISON.equals(effectType)
- || PotionEffectType.JUMP.equals(effectType)
- || PotionEffectType.WITHER.equals(effectType))
- {
- Claim cachedClaim = null;
- for (LivingEntity affected : event.getAffectedEntities())
- {
- // Always impact the thrower.
- if (affected == thrower) continue;
-
- if (affected.getType() == EntityType.VILLAGER || affected instanceof Animals)
- {
- Claim claim = this.dataStore.getClaimAt(affected.getLocation(), false, cachedClaim);
- if (claim != null)
- {
- cachedClaim = claim;
-
- if (thrower == null)
- {
- // Non-player source: Witches, dispensers, etc.
- if (!isBlockSourceInClaim(projectileSource, claim))
- {
- // If the source is not a block in the same claim as the affected entity, disallow.
- event.setIntensity(affected, 0);
- }
- }
- else
- {
- // Source is a player. Determine if they have permission to access entities in the claim.
- Supplier override = () -> instance.dataStore.getMessage(Messages.NoDamageClaimedEntity, claim.getOwnerName());
- final Supplier noContainersReason = claim.checkPermission(thrower, ClaimPermission.Inventory, event, override);
- if (noContainersReason != null)
- {
- event.setIntensity(affected, 0);
- if (!messagedPlayer)
- {
- GriefPrevention.sendMessage(thrower, TextMode.Err, noContainersReason.get());
- messagedPlayer = true;
- }
- }
- }
- }
- }
- }
- }
-
- //Otherwise, ignore potions not thrown by players
- if (thrower == null) return;
-
- //otherwise, no restrictions for positive effects
- if (positiveEffects.contains(effectType)) continue;
-
- for (LivingEntity effected : event.getAffectedEntities())
- {
- //always impact the thrower
- if (effected == thrower) continue;
-
- //always impact non players
- if (effected.getType() != EntityType.PLAYER) continue;
-
- //otherwise if in no-pvp zone, stop effect
- //FEATURE: prevent players from engaging in PvP combat inside land claims (when it's disabled)
- else if (GriefPrevention.instance.config_pvp_noCombatInPlayerLandClaims || GriefPrevention.instance.config_pvp_noCombatInAdminLandClaims)
- {
- Player effectedPlayer = (Player) effected;
- PlayerData defenderData = this.dataStore.getPlayerData(effectedPlayer.getUniqueId());
- PlayerData attackerData = this.dataStore.getPlayerData(thrower.getUniqueId());
- Claim attackerClaim = this.dataStore.getClaimAt(thrower.getLocation(), false, attackerData.lastClaim);
- if (attackerClaim != null && GriefPrevention.instance.claimIsPvPSafeZone(attackerClaim))
- {
- attackerData.lastClaim = attackerClaim;
- PreventPvPEvent pvpEvent = new PreventPvPEvent(attackerClaim, thrower, effectedPlayer);
- Bukkit.getPluginManager().callEvent(pvpEvent);
- if (!pvpEvent.isCancelled())
- {
- event.setIntensity(effected, 0);
- if (!messagedPlayer)
- {
- GriefPrevention.sendMessage(thrower, TextMode.Err, Messages.CantFightWhileImmune);
- messagedPlayer = true;
- }
- }
- continue;
- }
-
- Claim defenderClaim = this.dataStore.getClaimAt(effectedPlayer.getLocation(), false, defenderData.lastClaim);
- if (defenderClaim != null && GriefPrevention.instance.claimIsPvPSafeZone(defenderClaim))
- {
- defenderData.lastClaim = defenderClaim;
- PreventPvPEvent pvpEvent = new PreventPvPEvent(defenderClaim, thrower, effectedPlayer);
- Bukkit.getPluginManager().callEvent(pvpEvent);
- if (!pvpEvent.isCancelled())
- {
- event.setIntensity(effected, 0);
- if (!messagedPlayer)
- {
- GriefPrevention.sendMessage(thrower, TextMode.Err, Messages.PlayerInPvPSafeZone);
- messagedPlayer = true;
- }
- }
- }
- }
- }
- }
- }
- @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onAreaEffectCloudApplyEvent(AreaEffectCloudApplyEvent event)
- {
- ProjectileSource projectileSource = event.getEntity().getSource();
- if (projectileSource == null) return;
- Player thrower = null;
- if ((projectileSource instanceof Player))
- thrower = (Player) projectileSource;
- boolean messagedPlayer = false;
-
- Collection effects = event.getEntity().getCustomEffects();
- for (PotionEffect effect : effects)
- {
- PotionEffectType effectType = effect.getType();
-
- // Restrict some potions on claimed villagers and animals.
- // Griefers could use potions to kill entities or steal them over fences.
- if (PotionEffectType.HARM.equals(effectType)
- || PotionEffectType.POISON.equals(effectType)
- || PotionEffectType.JUMP.equals(effectType)
- || PotionEffectType.WITHER.equals(effectType))
- {
- Claim cachedClaim = null;
- for (LivingEntity affected : event.getAffectedEntities())
- {
- // Always impact the thrower.
- if (affected == thrower) continue;
-
- if (affected.getType() == EntityType.VILLAGER || affected instanceof Animals)
- {
- Claim claim = this.dataStore.getClaimAt(affected.getLocation(), false, cachedClaim);
- if (claim != null)
- {
- cachedClaim = claim;
-
- if (thrower == null)
- {
- // Non-player source: Witches, dispensers, etc.
- if (!isBlockSourceInClaim(projectileSource, claim))
- {
- // If the source is not a block in the same claim as the affected entity, disallow.
- affected.removePotionEffect(effectType);
- }
- }
- else
- {
- // Source is a player. Determine if they have permission to access entities in the claim.
- Supplier override = () -> instance.dataStore.getMessage(Messages.NoDamageClaimedEntity, claim.getOwnerName());
- final Supplier noContainersReason = claim.checkPermission(thrower, ClaimPermission.Inventory, event, override);
- if (noContainersReason != null)
- {
- affected.removePotionEffect(effectType);
- if (!messagedPlayer)
- {
- GriefPrevention.sendMessage(thrower, TextMode.Err, noContainersReason.get());
- messagedPlayer = true;
- }
- }
- }
- }
- }
- }
- }
- }
-
- }
-
- public static final HashSet positiveEffects = new HashSet<>(Arrays.asList
- (
- PotionEffectType.ABSORPTION,
- PotionEffectType.DAMAGE_RESISTANCE,
- PotionEffectType.FAST_DIGGING,
- PotionEffectType.FIRE_RESISTANCE,
- PotionEffectType.HEAL,
- PotionEffectType.HEALTH_BOOST,
- PotionEffectType.INCREASE_DAMAGE,
- PotionEffectType.INVISIBILITY,
- PotionEffectType.JUMP,
- PotionEffectType.NIGHT_VISION,
- PotionEffectType.REGENERATION,
- PotionEffectType.SATURATION,
- PotionEffectType.SPEED,
- PotionEffectType.WATER_BREATHING
- ));
-}
+/*
+ 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 .
+ */
+
+package me.ryanhamshire.GriefPrevention;
+
+import de.Keyle.MyPet.api.entity.MyPetBukkitEntity;
+import me.ryanhamshire.GriefPrevention.events.PreventPvPEvent;
+import me.ryanhamshire.GriefPrevention.events.ProtectDeathDropsEvent;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.World;
+import org.bukkit.World.Environment;
+import org.bukkit.block.Block;
+import org.bukkit.entity.Animals;
+import org.bukkit.entity.Creature;
+import org.bukkit.entity.Donkey;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Explosive;
+import org.bukkit.entity.FallingBlock;
+import org.bukkit.entity.Firework;
+import org.bukkit.entity.Horse;
+import org.bukkit.entity.Item;
+import org.bukkit.entity.LightningStrike;
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.entity.Llama;
+import org.bukkit.entity.Mob;
+import org.bukkit.entity.Monster;
+import org.bukkit.entity.Mule;
+import org.bukkit.entity.Panda;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Projectile;
+import org.bukkit.entity.Rabbit;
+import org.bukkit.entity.Slime;
+import org.bukkit.entity.Snowball;
+import org.bukkit.entity.Tameable;
+import org.bukkit.entity.ThrownPotion;
+import org.bukkit.entity.Vehicle;
+import org.bukkit.entity.WaterMob;
+import org.bukkit.entity.Wolf;
+import org.bukkit.entity.Zombie;
+import org.bukkit.entity.minecart.ExplosiveMinecart;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.BlockExplodeEvent;
+import org.bukkit.event.block.EntityBlockFormEvent;
+import org.bukkit.event.entity.AreaEffectCloudApplyEvent;
+import org.bukkit.event.entity.CreatureSpawnEvent;
+import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
+import org.bukkit.event.entity.EntityBreakDoorEvent;
+import org.bukkit.event.entity.EntityChangeBlockEvent;
+import org.bukkit.event.entity.EntityCombustByEntityEvent;
+import org.bukkit.event.entity.EntityDamageByEntityEvent;
+import org.bukkit.event.entity.EntityDamageEvent;
+import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
+import org.bukkit.event.entity.EntityDeathEvent;
+import org.bukkit.event.entity.EntityExplodeEvent;
+import org.bukkit.event.entity.EntityInteractEvent;
+import org.bukkit.event.entity.EntityPortalEnterEvent;
+import org.bukkit.event.entity.EntityPortalExitEvent;
+import org.bukkit.event.entity.EntityShootBowEvent;
+import org.bukkit.event.entity.EntityTargetEvent;
+import org.bukkit.event.entity.ExpBottleEvent;
+import org.bukkit.event.entity.ItemMergeEvent;
+import org.bukkit.event.entity.ItemSpawnEvent;
+import org.bukkit.event.entity.LingeringPotionSplashEvent;
+import org.bukkit.event.entity.PotionSplashEvent;
+import org.bukkit.event.hanging.HangingBreakByEntityEvent;
+import org.bukkit.event.hanging.HangingBreakEvent;
+import org.bukkit.event.hanging.HangingBreakEvent.RemoveCause;
+import org.bukkit.event.hanging.HangingPlaceEvent;
+import org.bukkit.event.vehicle.VehicleDamageEvent;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.metadata.FixedMetadataValue;
+import org.bukkit.metadata.MetadataValue;
+import org.bukkit.persistence.PersistentDataType;
+import org.bukkit.potion.PotionEffect;
+import org.bukkit.potion.PotionEffectType;
+import org.bukkit.projectiles.BlockProjectileSource;
+import org.bukkit.projectiles.ProjectileSource;
+import org.bukkit.util.Vector;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.UUID;
+import java.util.function.Supplier;
+
+//handles events related to entities
+public class EntityEventHandler implements Listener
+{
+ //convenience reference for the singleton datastore
+ private final DataStore dataStore;
+ private final GriefPrevention instance;
+ private final NamespacedKey luredByPlayer;
+
+ public EntityEventHandler(DataStore dataStore, GriefPrevention plugin)
+ {
+ this.dataStore = dataStore;
+ instance = plugin;
+ luredByPlayer = new NamespacedKey(plugin, "lured_by_player");
+ }
+
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onEntityFormBlock(EntityBlockFormEvent event)
+ {
+ Entity entity = event.getEntity();
+ if (entity.getType() == EntityType.PLAYER)
+ {
+ Player player = (Player) event.getEntity();
+ String noBuildReason = GriefPrevention.instance.allowBuild(player, event.getBlock().getLocation(), event.getNewState().getType());
+ if (noBuildReason != null)
+ {
+ event.setCancelled(true);
+ }
+ }
+ }
+
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onEntityChangeBLock(EntityChangeBlockEvent event)
+ {
+ if (!GriefPrevention.instance.config_endermenMoveBlocks && event.getEntityType() == EntityType.ENDERMAN)
+ {
+ event.setCancelled(true);
+ }
+ else if (!GriefPrevention.instance.config_silverfishBreakBlocks && event.getEntityType() == EntityType.SILVERFISH)
+ {
+ event.setCancelled(true);
+ }
+ else if (!GriefPrevention.instance.config_rabbitsEatCrops && event.getEntityType() == EntityType.RABBIT)
+ {
+ event.setCancelled(true);
+ }
+ else if (!GriefPrevention.instance.config_claims_ravagersBreakBlocks && event.getEntityType() == EntityType.RAVAGER)
+ {
+ event.setCancelled(true);
+ }
+ // All other handling depends on claims being enabled.
+ else if (GriefPrevention.instance.config_claims_worldModes.get(event.getBlock().getWorld()) == ClaimsMode.Disabled)
+ {
+ return;
+ }
+
+ // Handle projectiles changing blocks: TNT ignition, tridents knocking down pointed dripstone, etc.
+ if (event.getEntity() instanceof Projectile)
+ {
+ handleProjectileChangeBlock(event, (Projectile) event.getEntity());
+ }
+
+ else if (event.getEntityType() == EntityType.WITHER)
+ {
+ Claim claim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null);
+ if (claim == null || !claim.areExplosivesAllowed || !GriefPrevention.instance.config_blockClaimExplosions)
+ {
+ event.setCancelled(true);
+ }
+ }
+
+ //don't allow crops to be trampled, except by a player with build permission
+ else if (event.getTo() == Material.DIRT && event.getBlock().getType() == Material.FARMLAND)
+ {
+ if (event.getEntityType() != EntityType.PLAYER)
+ {
+ event.setCancelled(true);
+ }
+ else
+ {
+ Player player = (Player) event.getEntity();
+ Block block = event.getBlock();
+ if (GriefPrevention.instance.allowBreak(player, block, block.getLocation()) != null)
+ {
+ event.setCancelled(true);
+ }
+ }
+ }
+
+ // Prevent breaking lily pads via collision with a boat.
+ else if (event.getEntity() instanceof Vehicle && !event.getEntity().getPassengers().isEmpty())
+ {
+ Entity driver = event.getEntity().getPassengers().get(0);
+ if (driver instanceof Player)
+ {
+ Block block = event.getBlock();
+ if (GriefPrevention.instance.allowBreak((Player) driver, block, block.getLocation()) != null)
+ {
+ event.setCancelled(true);
+ }
+ }
+ }
+
+ //sand cannon fix - when the falling block doesn't fall straight down, take additional anti-grief steps
+ else if (event.getEntityType() == EntityType.FALLING_BLOCK)
+ {
+ FallingBlock entity = (FallingBlock) event.getEntity();
+ Block block = event.getBlock();
+
+ //if changing a block TO air, this is when the falling block formed. note its original location
+ if (event.getTo() == Material.AIR)
+ {
+ entity.setMetadata("GP_FALLINGBLOCK", new FixedMetadataValue(GriefPrevention.instance, block.getLocation()));
+ }
+ //otherwise, the falling block is forming a block. compare new location to original source
+ else
+ {
+ List values = entity.getMetadata("GP_FALLINGBLOCK");
+ //if we're not sure where this entity came from (maybe another plugin didn't follow the standard?), allow the block to form
+ //Or if entity fell through an end portal, allow it to form, as the event is erroneously fired twice in this scenario.
+ if (values.size() < 1) return;
+
+ Location originalLocation = (Location) (values.get(0).value());
+ Location newLocation = block.getLocation();
+
+ //if did not fall straight down
+ if (originalLocation.getBlockX() != newLocation.getBlockX() || originalLocation.getBlockZ() != newLocation.getBlockZ())
+ {
+
+ //in other worlds, if landing in land claim, only allow if source was also in the land claim
+ Claim claim = this.dataStore.getClaimAt(newLocation, false, null);
+ if (claim != null && !claim.contains(originalLocation, false, false))
+ {
+ //when not allowed, drop as item instead of forming a block
+ event.setCancelled(true);
+
+ // Just in case, skip already dead entities.
+ if (entity.isDead())
+ {
+ return;
+ }
+
+ // Remove entity so it doesn't continuously spawn drops.
+ entity.remove();
+
+ ItemStack itemStack = new ItemStack(entity.getBlockData().getMaterial(), 1);
+ block.getWorld().dropItemNaturally(entity.getLocation(), itemStack);
+ }
+ }
+ }
+ }
+ }
+
+ private void handleProjectileChangeBlock(EntityChangeBlockEvent event, Projectile projectile)
+ {
+ Block block = event.getBlock();
+ Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, null);
+ Bukkit.getLogger().info(event + "/n" + projectile);
+ // Wilderness rules
+ if (claim == null)
+ {
+ // TNT change means ignition. If no claim is present, use global fire rules.
+ if (block.getType() == Material.TNT)
+ {
+ if (!GriefPrevention.instance.config_fireDestroys || !GriefPrevention.instance.config_fireSpreads)
+ event.setCancelled(true);
+ return;
+ }
+
+ // Unclaimed area is fair game.
+ return;
+ }
+
+ ProjectileSource shooter = projectile.getShooter();
+
+ if (shooter instanceof Player)
+ {
+ Supplier denial;
+ if ((block.getType() == Material.BIG_DRIPLEAF || block.getType() == Material.SMALL_DRIPLEAF) && projectile instanceof Snowball)
+ {
+ denial = claim.checkPermission((Player) shooter, ClaimPermission.Access, event);
+ }
+ else
+ {
+ denial = claim.checkPermission((Player) shooter, ClaimPermission.Build, event);
+ }
+
+ // If the player cannot place the material being broken, disallow.
+ if (denial != null)
+ {
+ // Unlike entities where arrows rebound and may cause multiple alerts,
+ // projectiles lodged in blocks do not continuously re-trigger events.
+ GriefPrevention.sendMessage((Player) shooter, TextMode.Err, denial.get());
+ event.setCancelled(true);
+ }
+
+ return;
+ }
+
+ // Allow change if projectile was shot by a dispenser in the same claim.
+ if (isBlockSourceInClaim(shooter, claim))
+ return;
+
+ // Prevent change in all other cases.
+ event.setCancelled(true);
+ }
+
+ private boolean isBlockSourceInClaim(ProjectileSource projectileSource, Claim claim)
+ {
+ return projectileSource instanceof BlockProjectileSource &&
+ GriefPrevention.instance.dataStore.getClaimAt(((BlockProjectileSource) projectileSource).getBlock().getLocation(), false, claim) == claim;
+ }
+
+ //Used by "sand cannon" fix to ignore fallingblocks that fell through End Portals
+ //This is largely due to a CB issue with the above event
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onFallingBlockEnterPortal(EntityPortalEnterEvent event)
+ {
+ if (event.getEntityType() != EntityType.FALLING_BLOCK)
+ return;
+ event.getEntity().removeMetadata("GP_FALLINGBLOCK", instance);
+ }
+
+ //Don't let people drop in TNT through end portals
+ //Necessarily this shouldn't be an issue anyways since the platform is obsidian...
+ @EventHandler(ignoreCancelled = true)
+ void onTNTExitPortal(EntityPortalExitEvent event)
+ {
+ if (event.getEntityType() != EntityType.PRIMED_TNT)
+ return;
+ if (event.getTo().getWorld().getEnvironment() != Environment.THE_END)
+ return;
+ event.getEntity().remove();
+ }
+
+ //don't allow zombies to break down doors
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onZombieBreakDoor(EntityBreakDoorEvent event)
+ {
+ if (!GriefPrevention.instance.config_zombiesBreakDoors) event.setCancelled(true);
+ }
+
+ //don't allow entities to trample crops
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onEntityInteract(EntityInteractEvent event)
+ {
+ Material material = event.getBlock().getType();
+ if (material == Material.FARMLAND)
+ {
+ if (!GriefPrevention.instance.config_creaturesTrampleCrops)
+ {
+ event.setCancelled(true);
+ }
+ else
+ {
+ Entity rider = event.getEntity().getPassenger();
+ if (rider != null && rider.getType() == EntityType.PLAYER)
+ {
+ event.setCancelled(true);
+ }
+ }
+ }
+ }
+
+ //when an entity explodes...
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onEntityExplode(EntityExplodeEvent explodeEvent)
+ {
+ this.handleExplosion(explodeEvent.getLocation(), explodeEvent.getEntity(), explodeEvent.blockList());
+ }
+
+ //when a block explodes...
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onBlockExplode(BlockExplodeEvent explodeEvent)
+ {
+ this.handleExplosion(explodeEvent.getBlock().getLocation(), null, explodeEvent.blockList());
+ }
+
+
+ void handleExplosion(Location location, Entity entity, List blocks)
+ {
+ //only applies to claims-enabled worlds
+ World world = location.getWorld();
+
+ if (!GriefPrevention.instance.claimsEnabledForWorld(world)) return;
+
+ //FEATURE: explosions don't destroy surface blocks by default
+ boolean isCreeper = (entity != null && entity.getType() == EntityType.CREEPER);
+
+ boolean applySurfaceRules = world.getEnvironment() == Environment.NORMAL && ((isCreeper && GriefPrevention.instance.config_blockSurfaceCreeperExplosions) || (!isCreeper && GriefPrevention.instance.config_blockSurfaceOtherExplosions));
+
+ //make a list of blocks which were allowed to explode
+ List explodedBlocks = new ArrayList<>();
+ Claim cachedClaim = null;
+ for (Block block : blocks)
+ {
+ //always ignore air blocks
+ if (block.getType() == Material.AIR) continue;
+
+ //is it in a land claim?
+ Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, cachedClaim);
+ if (claim != null)
+ {
+ cachedClaim = claim;
+ }
+
+ //if yes, apply claim exemptions if they should apply
+ if (claim != null && (claim.areExplosivesAllowed || !GriefPrevention.instance.config_blockClaimExplosions))
+ {
+ explodedBlocks.add(block);
+ continue;
+ }
+
+ //if no, then also consider surface rules
+ if (claim == null)
+ {
+ if (!applySurfaceRules || block.getLocation().getBlockY() < GriefPrevention.instance.getSeaLevel(world) - 7)
+ {
+ explodedBlocks.add(block);
+ }
+ }
+ }
+
+ //clear original damage list and replace with allowed damage list
+ blocks.clear();
+ blocks.addAll(explodedBlocks);
+ }
+
+ //when an item spawns...
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void onItemSpawn(ItemSpawnEvent event)
+ {
+ //if item is on watch list, apply protection
+ ArrayList watchList = GriefPrevention.instance.pendingItemWatchList;
+ Item newItem = event.getEntity();
+ Long now = null;
+ for (int i = 0; i < watchList.size(); i++)
+ {
+ PendingItemProtection pendingProtection = watchList.get(i);
+ //ignore and remove any expired pending protections
+ if (now == null) now = System.currentTimeMillis();
+ if (pendingProtection.expirationTimestamp < now)
+ {
+ watchList.remove(i--);
+ continue;
+ }
+ //skip if item stack doesn't match
+ if (pendingProtection.itemStack.getAmount() != newItem.getItemStack().getAmount() ||
+ pendingProtection.itemStack.getType() != newItem.getItemStack().getType())
+ {
+ continue;
+ }
+
+ //skip if new item location isn't near the expected spawn area
+ Location spawn = event.getLocation();
+ Location expected = pendingProtection.location;
+ if (!spawn.getWorld().equals(expected.getWorld()) ||
+ spawn.getX() < expected.getX() - 5 ||
+ spawn.getX() > expected.getX() + 5 ||
+ spawn.getZ() < expected.getZ() - 5 ||
+ spawn.getZ() > expected.getZ() + 5 ||
+ spawn.getY() < expected.getY() - 15 ||
+ spawn.getY() > expected.getY() + 3)
+ {
+ continue;
+ }
+
+ //otherwise, mark item with protection information
+ newItem.setMetadata("GP_ITEMOWNER", new FixedMetadataValue(GriefPrevention.instance, pendingProtection.owner));
+
+ //and remove pending protection data
+ watchList.remove(i);
+ break;
+ }
+ }
+
+ //when an entity dies...
+ @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
+ public void onEntityDeath(EntityDeathEvent event)
+ {
+ LivingEntity entity = event.getEntity();
+
+ //don't do the rest in worlds where claims are not enabled
+ if (!GriefPrevention.instance.claimsEnabledForWorld(entity.getWorld())) return;
+
+ //FEATURE: when a player is involved in a siege (attacker or defender role)
+ //his death will end the siege
+
+ if (entity.getType() != EntityType.PLAYER) return; //only tracking players
+
+ Player player = (Player) entity;
+ PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId());
+
+ //FEATURE: lock dropped items to player who dropped them
+
+ World world = entity.getWorld();
+
+ //decide whether or not to apply this feature to this situation (depends on the world where it happens)
+ boolean isPvPWorld = GriefPrevention.instance.pvpRulesApply(world);
+ if ((isPvPWorld && GriefPrevention.instance.config_lockDeathDropsInPvpWorlds) ||
+ (!isPvPWorld && GriefPrevention.instance.config_lockDeathDropsInNonPvpWorlds))
+ {
+ Claim claim = this.dataStore.getClaimAt(player.getLocation(), false, playerData.lastClaim);
+ ProtectDeathDropsEvent protectionEvent = new ProtectDeathDropsEvent(claim);
+ Bukkit.getPluginManager().callEvent(protectionEvent);
+ if (!protectionEvent.isCancelled())
+ {
+ //remember information about these drops so that they can be marked when they spawn as items
+ long expirationTime = System.currentTimeMillis() + 3000; //now + 3 seconds
+ Location deathLocation = player.getLocation();
+ UUID playerID = player.getUniqueId();
+ List drops = event.getDrops();
+ for (ItemStack stack : drops)
+ {
+ GriefPrevention.instance.pendingItemWatchList.add(
+ new PendingItemProtection(deathLocation, playerID, expirationTime, stack));
+ }
+
+ }
+ }
+ }
+
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onItemMerge(ItemMergeEvent event)
+ {
+ Item item = event.getEntity();
+ List data = item.getMetadata("GP_ITEMOWNER");
+ event.setCancelled(data != null && data.size() > 0);
+ }
+
+ //when an entity picks up an item
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void onEntityPickup(EntityChangeBlockEvent event)
+ {
+ //FEATURE: endermen don't steal claimed blocks
+
+ //if its an enderman
+ if (event.getEntity().getType() == EntityType.ENDERMAN)
+ {
+ //and the block is claimed
+ if (this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null) != null)
+ {
+ //he doesn't get to steal it
+ event.setCancelled(true);
+ }
+ }
+ }
+
+ //when a painting is broken
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onHangingBreak(HangingBreakEvent event)
+ {
+ //don't track in worlds where claims are not enabled
+ if (!GriefPrevention.instance.claimsEnabledForWorld(event.getEntity().getWorld())) return;
+
+ //Ignore cases where itemframes should break due to no supporting blocks
+ if (event.getCause() == RemoveCause.PHYSICS) return;
+
+ //FEATURE: claimed paintings are protected from breakage
+
+ //explosions don't destroy hangings
+ if (event.getCause() == RemoveCause.EXPLOSION)
+ {
+ event.setCancelled(true);
+ return;
+ }
+
+ //only allow players to break paintings, not anything else (like water and explosions)
+ if (!(event instanceof HangingBreakByEntityEvent))
+ {
+ event.setCancelled(true);
+ return;
+ }
+
+ HangingBreakByEntityEvent entityEvent = (HangingBreakByEntityEvent) event;
+
+ //who is removing it?
+ Entity remover = entityEvent.getRemover();
+
+ //again, making sure the breaker is a player
+ if (remover.getType() != EntityType.PLAYER)
+ {
+ event.setCancelled(true);
+ return;
+ }
+
+ //if the player doesn't have build permission, don't allow the breakage
+ Player playerRemover = (Player) entityEvent.getRemover();
+ String noBuildReason = GriefPrevention.instance.allowBuild(playerRemover, event.getEntity().getLocation(), Material.AIR);
+ if (noBuildReason != null)
+ {
+ event.setCancelled(true);
+ GriefPrevention.sendMessage(playerRemover, TextMode.Err, noBuildReason);
+ }
+ }
+
+ //when a painting is placed...
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onPaintingPlace(HangingPlaceEvent event)
+ {
+ //don't track in worlds where claims are not enabled
+ if (!GriefPrevention.instance.claimsEnabledForWorld(event.getBlock().getWorld())) return;
+
+ //FEATURE: similar to above, placing a painting requires build permission in the claim
+
+ //if the player doesn't have permission, don't allow the placement
+ String noBuildReason = GriefPrevention.instance.allowBuild(event.getPlayer(), event.getEntity().getLocation(), Material.PAINTING);
+ if (noBuildReason != null)
+ {
+ event.setCancelled(true);
+ GriefPrevention.sendMessage(event.getPlayer(), TextMode.Err, noBuildReason);
+ return;
+ }
+
+ }
+
+ private boolean isMonster(Entity entity)
+ {
+ if (entity instanceof Monster) return true;
+
+ EntityType type = entity.getType();
+ if (type == EntityType.GHAST || type == EntityType.MAGMA_CUBE || type == EntityType.SHULKER)
+ return true;
+
+ if (type == EntityType.SLIME)
+ return ((Slime) entity).getSize() > 0;
+
+ if (type == EntityType.RABBIT)
+ return ((Rabbit) entity).getRabbitType() == Rabbit.Type.THE_KILLER_BUNNY;
+
+ if (type == EntityType.PANDA)
+ return ((Panda) entity).getMainGene() == Panda.Gene.AGGRESSIVE;
+
+ if ((type == EntityType.HOGLIN || type == EntityType.POLAR_BEAR) && entity instanceof Mob)
+ return !entity.getPersistentDataContainer().has(luredByPlayer, PersistentDataType.BYTE) && ((Mob) entity).getTarget() != null;
+
+ return false;
+ }
+
+ // Tag passive animals that can become aggressive so we can tell whether or not they are hostile later
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
+ public void onEntityTarget(EntityTargetEvent event)
+ {
+ if (!GriefPrevention.instance.claimsEnabledForWorld(event.getEntity().getWorld())) return;
+
+ EntityType entityType = event.getEntityType();
+ if (entityType != EntityType.HOGLIN && entityType != EntityType.POLAR_BEAR)
+ return;
+
+ if (event.getReason() == EntityTargetEvent.TargetReason.TEMPT)
+ event.getEntity().getPersistentDataContainer().set(luredByPlayer, PersistentDataType.BYTE, (byte) 1);
+ else
+ event.getEntity().getPersistentDataContainer().remove(luredByPlayer);
+
+ }
+
+ //when an entity is damaged
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onEntityDamage(EntityDamageEvent event)
+ {
+ this.handleEntityDamageEvent(event, true);
+ }
+
+ //when an entity is set on fire
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onEntityCombustByEntity(EntityCombustByEntityEvent event)
+ {
+ //handle it just like we would an entity damge by entity event, except don't send player messages to avoid double messages
+ //in cases like attacking with a flame sword or flame arrow, which would ALSO trigger the direct damage event handler
+
+ EntityDamageByEntityEvent eventWrapper = new EntityDamageByEntityEvent(event.getCombuster(), event.getEntity(), DamageCause.FIRE_TICK, event.getDuration());
+ this.handleEntityDamageEvent(eventWrapper, false);
+ event.setCancelled(eventWrapper.isCancelled());
+ }
+
+ private void handleEntityDamageEvent(EntityDamageEvent event, boolean sendErrorMessagesToPlayers)
+ {
+ //monsters are never protected
+ if (isMonster(event.getEntity())) return;
+
+ //horse protections can be disabled
+ if (event.getEntity() instanceof Horse && !GriefPrevention.instance.config_claims_protectHorses) return;
+ if (event.getEntity() instanceof Donkey && !GriefPrevention.instance.config_claims_protectDonkeys) return;
+ if (event.getEntity() instanceof Mule && !GriefPrevention.instance.config_claims_protectDonkeys) return;
+ if (event.getEntity() instanceof Llama && !GriefPrevention.instance.config_claims_protectLlamas) return;
+ //protected death loot can't be destroyed, only picked up or despawned due to expiration
+ if (event.getEntityType() == EntityType.DROPPED_ITEM)
+ {
+ if (event.getEntity().hasMetadata("GP_ITEMOWNER"))
+ {
+ event.setCancelled(true);
+ }
+ }
+
+ //protect pets from environmental damage types which could be easily caused by griefers
+ if (event.getEntity() instanceof Tameable && !GriefPrevention.instance.pvpRulesApply(event.getEntity().getWorld()))
+ {
+ Tameable tameable = (Tameable) event.getEntity();
+ if (tameable.isTamed())
+ {
+ DamageCause cause = event.getCause();
+ if (cause != null && (
+ cause == DamageCause.BLOCK_EXPLOSION ||
+ cause == DamageCause.ENTITY_EXPLOSION ||
+ cause == DamageCause.FALLING_BLOCK ||
+ cause == DamageCause.FIRE ||
+ cause == DamageCause.FIRE_TICK ||
+ cause == DamageCause.LAVA ||
+ cause == DamageCause.SUFFOCATION ||
+ cause == DamageCause.CONTACT ||
+ cause == DamageCause.DROWNING))
+ {
+ event.setCancelled(true);
+ return;
+ }
+ }
+ }
+
+ if (handleBlockExplosionDamage(event)) return;
+
+ //the rest is only interested in entities damaging entities (ignoring environmental damage)
+ if (!(event instanceof EntityDamageByEntityEvent)) return;
+
+ EntityDamageByEntityEvent subEvent = (EntityDamageByEntityEvent) event;
+
+ if (subEvent.getDamager() instanceof LightningStrike && subEvent.getDamager().hasMetadata("GP_TRIDENT"))
+ {
+ event.setCancelled(true);
+ return;
+ }
+ //determine which player is attacking, if any
+ Player attacker = null;
+ Projectile arrow = null;
+ Firework firework = null;
+ Entity damageSource = subEvent.getDamager();
+
+ if (damageSource != null)
+ {
+ if (damageSource.getType() == EntityType.PLAYER)
+ {
+ attacker = (Player) damageSource;
+ }
+ else if (damageSource instanceof Projectile)
+ {
+ arrow = (Projectile) damageSource;
+ if (arrow.getShooter() instanceof Player)
+ {
+ attacker = (Player) arrow.getShooter();
+ }
+ }
+ else if (subEvent.getDamager() instanceof Firework)
+ {
+ damageSource = subEvent.getDamager();
+ if (damageSource.hasMetadata("GP_FIREWORK"))
+ {
+ List data = damageSource.getMetadata("GP_FIREWORK");
+ if (data != null && data.size() > 0)
+ {
+ firework = (Firework) damageSource;
+ attacker = (Player) data.get(0).value();
+ }
+ }
+ }
+
+ //protect players from lingering potion damage when protected from pvp
+ if (damageSource.getType() == EntityType.AREA_EFFECT_CLOUD && event.getEntityType() == EntityType.PLAYER && GriefPrevention.instance.pvpRulesApply(event.getEntity().getWorld()))
+ {
+ Player damaged = (Player) event.getEntity();
+ PlayerData damagedData = GriefPrevention.instance.dataStore.getPlayerData(damaged.getUniqueId());
+
+ //case 1: recently spawned
+ if (GriefPrevention.instance.config_pvp_protectFreshSpawns && damagedData.pvpImmune)
+ {
+ event.setCancelled(true);
+ return;
+ }
+
+ //case 2: in a pvp safe zone
+ else
+ {
+ Claim damagedClaim = GriefPrevention.instance.dataStore.getClaimAt(damaged.getLocation(), false, damagedData.lastClaim);
+ if (damagedClaim != null)
+ {
+ damagedData.lastClaim = damagedClaim;
+ if (GriefPrevention.instance.claimIsPvPSafeZone(damagedClaim))
+ {
+ PreventPvPEvent pvpEvent = new PreventPvPEvent(damagedClaim, attacker, damaged);
+ Bukkit.getPluginManager().callEvent(pvpEvent);
+ if (!pvpEvent.isCancelled())
+ {
+ event.setCancelled(true);
+ }
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ //if the attacker is a firework from a crossbow by a player and defender is a player (nonpvp)
+ if (firework != null && event.getEntityType() == EntityType.PLAYER && !GriefPrevention.instance.pvpRulesApply(attacker.getWorld()))
+ {
+ Player defender = (Player) (event.getEntity());
+ if (attacker != defender)
+ {
+ event.setCancelled(true);
+ return;
+ }
+ }
+
+
+ //if the attacker is a player and defender is a player (pvp combat)
+ if (attacker != null && event.getEntityType() == EntityType.PLAYER && GriefPrevention.instance.pvpRulesApply(attacker.getWorld()))
+ {
+ //FEATURE: prevent pvp in the first minute after spawn, and prevent pvp when one or both players have no inventory
+
+ Player defender = (Player) (event.getEntity());
+
+ if (attacker != defender)
+ {
+ PlayerData defenderData = this.dataStore.getPlayerData(((Player) event.getEntity()).getUniqueId());
+ PlayerData attackerData = this.dataStore.getPlayerData(attacker.getUniqueId());
+
+ //otherwise if protecting spawning players
+ if (GriefPrevention.instance.config_pvp_protectFreshSpawns)
+ {
+ if (defenderData.pvpImmune)
+ {
+ event.setCancelled(true);
+ if (sendErrorMessagesToPlayers)
+ GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.ThatPlayerPvPImmune);
+ return;
+ }
+
+ if (attackerData.pvpImmune)
+ {
+ event.setCancelled(true);
+ if (sendErrorMessagesToPlayers)
+ GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.CantFightWhileImmune);
+ return;
+ }
+ }
+
+ //FEATURE: prevent players from engaging in PvP combat inside land claims (when it's disabled)
+ if (GriefPrevention.instance.config_pvp_noCombatInPlayerLandClaims || GriefPrevention.instance.config_pvp_noCombatInAdminLandClaims)
+ {
+ Claim attackerClaim = this.dataStore.getClaimAt(attacker.getLocation(), false, attackerData.lastClaim);
+ if (!attackerData.ignoreClaims)
+ {
+ if (attackerClaim != null && //ignore claims mode allows for pvp inside land claims
+ !attackerData.inPvpCombat() &&
+ GriefPrevention.instance.claimIsPvPSafeZone(attackerClaim))
+ {
+ attackerData.lastClaim = attackerClaim;
+ PreventPvPEvent pvpEvent = new PreventPvPEvent(attackerClaim, attacker, defender);
+ Bukkit.getPluginManager().callEvent(pvpEvent);
+ if (!pvpEvent.isCancelled())
+ {
+ event.setCancelled(true);
+ if (sendErrorMessagesToPlayers)
+ GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.CantFightWhileImmune);
+ }
+ return;
+ }
+
+ Claim defenderClaim = this.dataStore.getClaimAt(defender.getLocation(), false, defenderData.lastClaim);
+ if (defenderClaim != null &&
+ !defenderData.inPvpCombat() &&
+ GriefPrevention.instance.claimIsPvPSafeZone(defenderClaim))
+ {
+ defenderData.lastClaim = defenderClaim;
+ PreventPvPEvent pvpEvent = new PreventPvPEvent(defenderClaim, attacker, defender);
+ Bukkit.getPluginManager().callEvent(pvpEvent);
+ if (!pvpEvent.isCancelled())
+ {
+ event.setCancelled(true);
+ if (sendErrorMessagesToPlayers)
+ GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.PlayerInPvPSafeZone);
+ }
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ if (event instanceof EntityDamageByEntityEvent)
+ {
+ //don't track in worlds where claims are not enabled
+ if (!GriefPrevention.instance.claimsEnabledForWorld(event.getEntity().getWorld())) return;
+
+ //protect players from being attacked by other players' pets when protected from pvp
+ if (event.getEntityType() == EntityType.PLAYER)
+ {
+ Player defender = (Player) event.getEntity();
+
+ //if attacker is a pet
+ Entity damager = subEvent.getDamager();
+ if (damager != null && damager instanceof Tameable)
+ {
+ Tameable pet = (Tameable) damager;
+ if (pet.isTamed() && pet.getOwner() != null)
+ {
+ //if defender is NOT in pvp combat and not immune to pvp right now due to recent respawn
+ PlayerData defenderData = GriefPrevention.instance.dataStore.getPlayerData(event.getEntity().getUniqueId());
+ if (!defenderData.pvpImmune && !defenderData.inPvpCombat())
+ {
+ //if defender is not in a protected area
+ Claim defenderClaim = this.dataStore.getClaimAt(defender.getLocation(), false, defenderData.lastClaim);
+ if (defenderClaim != null &&
+ !defenderData.inPvpCombat() &&
+ GriefPrevention.instance.claimIsPvPSafeZone(defenderClaim))
+ {
+ defenderData.lastClaim = defenderClaim;
+ PreventPvPEvent pvpEvent = new PreventPvPEvent(defenderClaim, attacker, defender);
+ Bukkit.getPluginManager().callEvent(pvpEvent);
+
+ //if other plugins aren't making an exception to the rule
+ if (!pvpEvent.isCancelled())
+ {
+ event.setCancelled(true);
+ if (damager instanceof Creature) ((Creature) damager).setTarget(null);
+ }
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ //if the damaged entity is a claimed item frame or armor stand, the damager needs to be a player with build trust in the claim
+ if (subEvent.getEntityType() == EntityType.ITEM_FRAME
+ || subEvent.getEntityType() == EntityType.GLOW_ITEM_FRAME
+ || subEvent.getEntityType() == EntityType.ARMOR_STAND
+ || subEvent.getEntityType() == EntityType.VILLAGER
+ || subEvent.getEntityType() == EntityType.ENDER_CRYSTAL)
+ {
+ //allow for disabling villager protections in the config
+ if (subEvent.getEntityType() == EntityType.VILLAGER && !GriefPrevention.instance.config_claims_protectCreatures)
+ return;
+
+ //don't protect polar bears, they may be aggressive
+ if (subEvent.getEntityType() == EntityType.POLAR_BEAR) return;
+
+ //decide whether it's claimed
+ Claim cachedClaim = null;
+ PlayerData playerData = null;
+ if (attacker != null)
+ {
+ playerData = this.dataStore.getPlayerData(attacker.getUniqueId());
+ cachedClaim = playerData.lastClaim;
+ }
+
+ Claim claim = this.dataStore.getClaimAt(event.getEntity().getLocation(), false, cachedClaim);
+
+ //if it's claimed
+ if (claim != null)
+ {
+ //if attacker isn't a player, cancel
+ if (attacker == null)
+ {
+ //exception case
+ if (event.getEntityType() == EntityType.VILLAGER && damageSource != null && damageSource instanceof Zombie)
+ {
+ return;
+ }
+
+ event.setCancelled(true);
+ return;
+ }
+
+ //otherwise player must have container trust in the claim
+ Supplier failureReason = claim.checkPermission(attacker, ClaimPermission.Build, event);
+ if (failureReason != null)
+ {
+ event.setCancelled(true);
+ if (sendErrorMessagesToPlayers)
+ GriefPrevention.sendMessage(attacker, TextMode.Err, failureReason.get());
+ return;
+ }
+ }
+ }
+
+ //if the entity is an non-monster creature (remember monsters disqualified above), or a vehicle
+ if (((subEvent.getEntity() instanceof Creature || subEvent.getEntity() instanceof WaterMob) && GriefPrevention.instance.config_claims_protectCreatures))
+ {
+ //if entity is tameable and has an owner, apply special rules
+ if (subEvent.getEntity() instanceof Tameable)
+ {
+ Tameable tameable = (Tameable) subEvent.getEntity();
+ if (tameable.isTamed() && tameable.getOwner() != null)
+ {
+ //limit attacks by players to owners and admins in ignore claims mode
+ if (attacker != null)
+ {
+ UUID ownerID = tameable.getOwner().getUniqueId();
+
+ //if the player interacting is the owner, always allow
+ if (attacker.getUniqueId().equals(ownerID)) return;
+
+ //allow for admin override
+ PlayerData attackerData = this.dataStore.getPlayerData(attacker.getUniqueId());
+ if (attackerData.ignoreClaims) return;
+
+ //otherwise disallow in non-pvp worlds (and also pvp worlds if configured to do so)
+ if (!GriefPrevention.instance.pvpRulesApply(subEvent.getEntity().getLocation().getWorld()) || (GriefPrevention.instance.config_pvp_protectPets && subEvent.getEntityType() != EntityType.WOLF))
+ {
+ OfflinePlayer owner = GriefPrevention.instance.getServer().getOfflinePlayer(ownerID);
+ String ownerName = owner.getName();
+ if (ownerName == null) ownerName = "someone";
+ String message = GriefPrevention.instance.dataStore.getMessage(Messages.NoDamageClaimedEntity, ownerName);
+ if (attacker.hasPermission("griefprevention.ignoreclaims"))
+ message += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement);
+ if (sendErrorMessagesToPlayers)
+ GriefPrevention.sendMessage(attacker, TextMode.Err, message);
+ PreventPvPEvent pvpEvent = new PreventPvPEvent(new Claim(subEvent.getEntity().getLocation(), subEvent.getEntity().getLocation(), null, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), null), attacker, tameable);
+ Bukkit.getPluginManager().callEvent(pvpEvent);
+ if (!pvpEvent.isCancelled())
+ {
+ event.setCancelled(true);
+ }
+ return;
+ }
+ //and disallow if attacker is pvp immune
+ else if (attackerData.pvpImmune)
+ {
+ event.setCancelled(true);
+ if (sendErrorMessagesToPlayers)
+ GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.CantFightWhileImmune);
+ return;
+ }
+ // disallow players attacking tamed wolves (dogs) unless under attack by said wolf
+ else if (tameable.getType() == EntityType.WOLF)
+ {
+ if (!tameable.getOwner().equals(attacker))
+ {
+ if (((Wolf) tameable).getTarget() != null)
+ {
+ if (((Wolf) tameable).getTarget() == attacker) return;
+ }
+ event.setCancelled(true);
+ String ownerName = GriefPrevention.instance.getServer().getOfflinePlayer(ownerID).getName();
+ String message = GriefPrevention.instance.dataStore.getMessage(Messages.NoDamageClaimedEntity, ownerName);
+ if (attacker.hasPermission("griefprevention.ignoreclaims"))
+ message += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement);
+ if (sendErrorMessagesToPlayers)
+ GriefPrevention.sendMessage(attacker, TextMode.Err, message);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ Claim cachedClaim = null;
+ PlayerData playerData = null;
+
+ //if not a player or an explosive, allow
+ //RoboMWM: Or a lingering potion, or a witch
+ if (attacker == null
+ && damageSource != null
+ && damageSource.getType() != EntityType.CREEPER
+ && damageSource.getType() != EntityType.WITHER
+ && damageSource.getType() != EntityType.ENDER_CRYSTAL
+ && damageSource.getType() != EntityType.AREA_EFFECT_CLOUD
+ && damageSource.getType() != EntityType.WITCH
+ && !(damageSource instanceof Projectile)
+ && !(damageSource instanceof Explosive)
+ && !(damageSource instanceof ExplosiveMinecart))
+ {
+ return;
+ }
+
+ if (attacker != null)
+ {
+ playerData = this.dataStore.getPlayerData(attacker.getUniqueId());
+ cachedClaim = playerData.lastClaim;
+ }
+
+ Claim claim = this.dataStore.getClaimAt(event.getEntity().getLocation(), false, cachedClaim);
+
+ //if it's claimed
+ if (claim != null)
+ {
+ //if damaged by anything other than a player (exception villagers injured by zombies in admin claims), cancel the event
+ //why exception? so admins can set up a village which can't be CHANGED by players, but must be "protected" by players.
+ //TODO: Discuss if this should only apply to admin claims...?
+ if (attacker == null)
+ {
+ //exception case
+ if (event.getEntityType() == EntityType.VILLAGER && damageSource != null && (damageSource.getType() == EntityType.ZOMBIE || damageSource.getType() == EntityType.VINDICATOR || damageSource.getType() == EntityType.EVOKER || damageSource.getType() == EntityType.EVOKER_FANGS || damageSource.getType() == EntityType.VEX))
+ {
+ return;
+ }
+
+ //all other cases
+ else
+ {
+ event.setCancelled(true);
+ if (damageSource instanceof Projectile)
+ {
+ damageSource.remove();
+ }
+ }
+ }
+
+ //otherwise the player damaging the entity must have permission, unless it's a dog in a pvp world
+ else if (!(event.getEntity().getWorld().getPVP() && event.getEntity().getType() == EntityType.WOLF))
+ {
+ Supplier override = null;
+ if (sendErrorMessagesToPlayers)
+ {
+ final Player finalAttacker = attacker;
+ override = () ->
+ {
+ String message = GriefPrevention.instance.dataStore.getMessage(Messages.NoDamageClaimedEntity, claim.getOwnerName());
+ if (finalAttacker.hasPermission("griefprevention.ignoreclaims"))
+ message += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement);
+ return message;
+ };
+ }
+ Supplier noContainersReason = claim.checkPermission(attacker, ClaimPermission.Inventory, event, override);
+ if (noContainersReason != null)
+ {
+ event.setCancelled(true);
+
+ //kill the arrow to avoid infinite bounce between crowded together animals //RoboMWM: except for tridents
+ if (arrow != null && arrow.getType() != EntityType.TRIDENT) arrow.remove();
+ if (damageSource != null && damageSource.getType() == EntityType.FIREWORK && event.getEntity().getType() != EntityType.PLAYER)
+ return;
+
+ try {
+ if (sendErrorMessagesToPlayers && !(event.getEntity() instanceof MyPetBukkitEntity)) // don't send the error message if it's a mypet
+ {
+ GriefPrevention.sendMessage(attacker, TextMode.Err, noContainersReason.get());
+ }
+ } catch (NoClassDefFoundError ex) {
+ if (sendErrorMessagesToPlayers) // don't send the error message if it's a mypet
+ {
+ GriefPrevention.sendMessage(attacker, TextMode.Err, noContainersReason.get());
+ }
+ }
+ event.setCancelled(true);
+ }
+
+ //cache claim for later
+ if (playerData != null)
+ {
+ playerData.lastClaim = claim;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles entity damage caused by block explosions.
+ *
+ * @param event the EntityDamageEvent
+ * @return true if the damage has been handled
+ */
+ private boolean handleBlockExplosionDamage(EntityDamageEvent event)
+ {
+ if (event.getCause() != DamageCause.BLOCK_EXPLOSION) return false;
+
+ Entity entity = event.getEntity();
+
+ // Skip players - does allow players to use block explosions to bypass PVP protections,
+ // but also doesn't disable self-damage.
+ if (entity instanceof Player) return false;
+
+ Claim claim = GriefPrevention.instance.dataStore.getClaimAt(entity.getLocation(), false, null);
+
+ // Only block explosion damage inside claims.
+ if (claim == null) return false;
+
+ event.setCancelled(true);
+ return true;
+ }
+
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onCrossbowFireWork(EntityShootBowEvent shootEvent)
+ {
+ if (shootEvent.getEntity() instanceof Player && shootEvent.getProjectile() instanceof Firework)
+ {
+ shootEvent.getProjectile().setMetadata("GP_FIREWORK", new FixedMetadataValue(GriefPrevention.instance, shootEvent.getEntity()));
+ }
+ }
+
+ //when an entity is damaged
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
+ public void onEntityDamageMonitor(EntityDamageEvent event)
+ {
+ //FEATURE: prevent players who very recently participated in pvp combat from hiding inventory to protect it from looting
+ //FEATURE: prevent players who are in pvp combat from logging out to avoid being defeated
+
+ if (event.getEntity().getType() != EntityType.PLAYER) return;
+
+ Player defender = (Player) event.getEntity();
+
+ //only interested in entities damaging entities (ignoring environmental damage)
+ if (!(event instanceof EntityDamageByEntityEvent)) return;
+
+ //Ignore "damage" from snowballs, eggs, etc. from triggering the PvP timer
+ if (event.getDamage() == 0) return;
+
+ EntityDamageByEntityEvent subEvent = (EntityDamageByEntityEvent) event;
+
+ //if not in a pvp rules world, do nothing
+ if (!GriefPrevention.instance.pvpRulesApply(defender.getWorld())) return;
+
+ //determine which player is attacking, if any
+ Player attacker = null;
+ Projectile arrow = null;
+ Entity damageSource = subEvent.getDamager();
+
+ if (damageSource != null)
+ {
+ if (damageSource.getType() == EntityType.PLAYER)
+ {
+ attacker = (Player) damageSource;
+ }
+ else if (damageSource instanceof Projectile)
+ {
+ arrow = (Projectile) damageSource;
+ if (arrow.getShooter() instanceof Player)
+ {
+ attacker = (Player) arrow.getShooter();
+ }
+ }
+ else if (damageSource instanceof Firework)
+ {
+ if (damageSource.hasMetadata("GP_FIREWORK"))
+ {
+ List data = damageSource.getMetadata("GP_FIREWORK");
+ if (data != null && data.size() > 0)
+ {
+ attacker = (Player) data.get(0).value();
+ }
+ }
+ }
+ }
+
+ //if attacker not a player, do nothing
+ if (attacker == null) return;
+
+ PlayerData defenderData = this.dataStore.getPlayerData(defender.getUniqueId());
+ PlayerData attackerData = this.dataStore.getPlayerData(attacker.getUniqueId());
+
+ if (attacker != defender)
+ {
+ long now = Calendar.getInstance().getTimeInMillis();
+ defenderData.lastPvpTimestamp = now;
+ defenderData.lastPvpPlayer = attacker.getName();
+ attackerData.lastPvpTimestamp = now;
+ attackerData.lastPvpPlayer = defender.getName();
+ }
+ }
+
+ //when a vehicle is damaged
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onVehicleDamage(VehicleDamageEvent event)
+ {
+ //all of this is anti theft code
+ if (!GriefPrevention.instance.config_claims_preventTheft) return;
+
+ //input validation
+ if (event.getVehicle() == null) return;
+
+ //don't track in worlds where claims are not enabled
+ if (!GriefPrevention.instance.claimsEnabledForWorld(event.getVehicle().getWorld())) return;
+
+ //determine which player is attacking, if any
+ Player attacker = null;
+ Entity damageSource = event.getAttacker();
+ EntityType damageSourceType = null;
+
+ //if damage source is null or a creeper, don't allow the damage when the vehicle is in a land claim
+ if (damageSource != null)
+ {
+ damageSourceType = damageSource.getType();
+
+ if (damageSource.getType() == EntityType.PLAYER)
+ {
+ attacker = (Player) damageSource;
+ }
+ else if (damageSource instanceof Projectile)
+ {
+ Projectile arrow = (Projectile) damageSource;
+ if (arrow.getShooter() instanceof Player)
+ {
+ attacker = (Player) arrow.getShooter();
+ }
+ }
+ else if (damageSource instanceof Firework)
+ {
+ if (damageSource.hasMetadata("GP_FIREWORK"))
+ {
+ List data = damageSource.getMetadata("GP_FIREWORK");
+ if (data != null && data.size() > 0)
+ {
+ attacker = (Player) data.get(0).value();
+ }
+ }
+ }
+ }
+
+ //if not a player and not an explosion, always allow
+ if (attacker == null && damageSourceType != EntityType.CREEPER && damageSourceType != EntityType.WITHER && damageSourceType != EntityType.PRIMED_TNT)
+ {
+ return;
+ }
+
+ //NOTE: vehicles can be pushed around.
+ //so unless precautions are taken by the owner, a resourceful thief might find ways to steal anyway
+ Claim cachedClaim = null;
+ PlayerData playerData = null;
+
+ if (attacker != null)
+ {
+ playerData = this.dataStore.getPlayerData(attacker.getUniqueId());
+ cachedClaim = playerData.lastClaim;
+ }
+
+ Claim claim = this.dataStore.getClaimAt(event.getVehicle().getLocation(), false, cachedClaim);
+
+ //if it's claimed
+ if (claim != null)
+ {
+ //if damaged by anything other than a player, cancel the event
+ if (attacker == null)
+ {
+ event.setCancelled(true);
+ }
+
+ //otherwise the player damaging the entity must have permission
+ else
+ {
+ final Player finalAttacker = attacker;
+ Supplier override = () ->
+ {
+ String message = GriefPrevention.instance.dataStore.getMessage(Messages.NoDamageClaimedEntity, claim.getOwnerName());
+ if (finalAttacker.hasPermission("griefprevention.ignoreclaims"))
+ message += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement);
+ return message;
+ };
+ Supplier noContainersReason = claim.checkPermission(attacker, ClaimPermission.Inventory, event, override);
+ if (noContainersReason != null)
+ {
+ event.setCancelled(true);
+ GriefPrevention.sendMessage(attacker, TextMode.Err, noContainersReason.get());
+ }
+
+ //cache claim for later
+ if (playerData != null)
+ {
+ playerData.lastClaim = claim;
+ }
+ }
+ }
+ }
+
+ //when a splash potion effects one or more entities...
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onPotionSplash(PotionSplashEvent event)
+ {
+ ThrownPotion potion = event.getPotion();
+
+ ProjectileSource projectileSource = potion.getShooter();
+ // Ignore potions with no source.
+ if (projectileSource == null) return;
+ Player thrower = null;
+ if ((projectileSource instanceof Player))
+ thrower = (Player) projectileSource;
+ boolean messagedPlayer = false;
+
+ Collection effects = potion.getEffects();
+ for (PotionEffect effect : effects)
+ {
+ PotionEffectType effectType = effect.getType();
+
+ // Restrict some potions on claimed villagers and animals.
+ // Griefers could use potions to kill entities or steal them over fences.
+ if (PotionEffectType.HARM.equals(effectType)
+ || PotionEffectType.POISON.equals(effectType)
+ || PotionEffectType.JUMP.equals(effectType)
+ || PotionEffectType.WITHER.equals(effectType))
+ {
+ Claim cachedClaim = null;
+ for (LivingEntity affected : event.getAffectedEntities())
+ {
+ // Always impact the thrower.
+ if (affected == thrower) continue;
+
+ if (affected.getType() == EntityType.VILLAGER || affected instanceof Animals)
+ {
+ Claim claim = this.dataStore.getClaimAt(affected.getLocation(), false, cachedClaim);
+ if (claim != null)
+ {
+ cachedClaim = claim;
+
+ if (thrower == null)
+ {
+ // Non-player source: Witches, dispensers, etc.
+ if (!isBlockSourceInClaim(projectileSource, claim))
+ {
+ // If the source is not a block in the same claim as the affected entity, disallow.
+ event.setIntensity(affected, 0);
+ }
+ }
+ else
+ {
+ // Source is a player. Determine if they have permission to access entities in the claim.
+ Supplier override = () -> instance.dataStore.getMessage(Messages.NoDamageClaimedEntity, claim.getOwnerName());
+ final Supplier noContainersReason = claim.checkPermission(thrower, ClaimPermission.Inventory, event, override);
+ if (noContainersReason != null)
+ {
+ event.setIntensity(affected, 0);
+ if (!messagedPlayer)
+ {
+ GriefPrevention.sendMessage(thrower, TextMode.Err, noContainersReason.get());
+ messagedPlayer = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //Otherwise, ignore potions not thrown by players
+ if (thrower == null) return;
+
+ //otherwise, no restrictions for positive effects
+ if (positiveEffects.contains(effectType)) continue;
+
+ for (LivingEntity effected : event.getAffectedEntities())
+ {
+ //always impact the thrower
+ if (effected == thrower) continue;
+
+ //always impact non players
+ if (effected.getType() != EntityType.PLAYER) continue;
+
+ //otherwise if in no-pvp zone, stop effect
+ //FEATURE: prevent players from engaging in PvP combat inside land claims (when it's disabled)
+ else if (GriefPrevention.instance.config_pvp_noCombatInPlayerLandClaims || GriefPrevention.instance.config_pvp_noCombatInAdminLandClaims)
+ {
+ Player effectedPlayer = (Player) effected;
+ PlayerData defenderData = this.dataStore.getPlayerData(effectedPlayer.getUniqueId());
+ PlayerData attackerData = this.dataStore.getPlayerData(thrower.getUniqueId());
+ Claim attackerClaim = this.dataStore.getClaimAt(thrower.getLocation(), false, attackerData.lastClaim);
+ if (attackerClaim != null && GriefPrevention.instance.claimIsPvPSafeZone(attackerClaim))
+ {
+ attackerData.lastClaim = attackerClaim;
+ PreventPvPEvent pvpEvent = new PreventPvPEvent(attackerClaim, thrower, effectedPlayer);
+ Bukkit.getPluginManager().callEvent(pvpEvent);
+ if (!pvpEvent.isCancelled())
+ {
+ event.setIntensity(effected, 0);
+ if (!messagedPlayer)
+ {
+ GriefPrevention.sendMessage(thrower, TextMode.Err, Messages.CantFightWhileImmune);
+ messagedPlayer = true;
+ }
+ }
+ continue;
+ }
+
+ Claim defenderClaim = this.dataStore.getClaimAt(effectedPlayer.getLocation(), false, defenderData.lastClaim);
+ if (defenderClaim != null && GriefPrevention.instance.claimIsPvPSafeZone(defenderClaim))
+ {
+ defenderData.lastClaim = defenderClaim;
+ PreventPvPEvent pvpEvent = new PreventPvPEvent(defenderClaim, thrower, effectedPlayer);
+ Bukkit.getPluginManager().callEvent(pvpEvent);
+ if (!pvpEvent.isCancelled())
+ {
+ event.setIntensity(effected, 0);
+ if (!messagedPlayer)
+ {
+ GriefPrevention.sendMessage(thrower, TextMode.Err, Messages.PlayerInPvPSafeZone);
+ messagedPlayer = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onAreaEffectCloudApplyEvent(AreaEffectCloudApplyEvent event)
+ {
+ ProjectileSource projectileSource = event.getEntity().getSource();
+ if (projectileSource == null) return;
+ Player thrower = null;
+ if ((projectileSource instanceof Player))
+ thrower = (Player) projectileSource;
+ boolean messagedPlayer = false;
+
+ Collection effects = event.getEntity().getCustomEffects();
+ for (PotionEffect effect : effects)
+ {
+ PotionEffectType effectType = effect.getType();
+
+ // Restrict some potions on claimed villagers and animals.
+ // Griefers could use potions to kill entities or steal them over fences.
+ if (PotionEffectType.HARM.equals(effectType)
+ || PotionEffectType.POISON.equals(effectType)
+ || PotionEffectType.JUMP.equals(effectType)
+ || PotionEffectType.WITHER.equals(effectType))
+ {
+ Claim cachedClaim = null;
+ for (LivingEntity affected : event.getAffectedEntities())
+ {
+ // Always impact the thrower.
+ if (affected == thrower) continue;
+
+ if (affected.getType() == EntityType.VILLAGER || affected instanceof Animals)
+ {
+ Claim claim = this.dataStore.getClaimAt(affected.getLocation(), false, cachedClaim);
+ if (claim != null)
+ {
+ cachedClaim = claim;
+
+ if (thrower == null)
+ {
+ // Non-player source: Witches, dispensers, etc.
+ if (!isBlockSourceInClaim(projectileSource, claim))
+ {
+ // If the source is not a block in the same claim as the affected entity, disallow.
+ affected.removePotionEffect(effectType);
+ }
+ }
+ else
+ {
+ // Source is a player. Determine if they have permission to access entities in the claim.
+ Supplier override = () -> instance.dataStore.getMessage(Messages.NoDamageClaimedEntity, claim.getOwnerName());
+ final Supplier noContainersReason = claim.checkPermission(thrower, ClaimPermission.Inventory, event, override);
+ if (noContainersReason != null)
+ {
+ affected.removePotionEffect(effectType);
+ if (!messagedPlayer)
+ {
+ GriefPrevention.sendMessage(thrower, TextMode.Err, noContainersReason.get());
+ messagedPlayer = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ public static final HashSet positiveEffects = new HashSet<>(Arrays.asList
+ (
+ PotionEffectType.ABSORPTION,
+ PotionEffectType.DAMAGE_RESISTANCE,
+ PotionEffectType.FAST_DIGGING,
+ PotionEffectType.FIRE_RESISTANCE,
+ PotionEffectType.HEAL,
+ PotionEffectType.HEALTH_BOOST,
+ PotionEffectType.INCREASE_DAMAGE,
+ PotionEffectType.INVISIBILITY,
+ PotionEffectType.JUMP,
+ PotionEffectType.NIGHT_VISION,
+ PotionEffectType.REGENERATION,
+ PotionEffectType.SATURATION,
+ PotionEffectType.SPEED,
+ PotionEffectType.WATER_BREATHING
+ ));
+}