1511 lines
67 KiB
Java
1511 lines
67 KiB
Java
/*
|
|
GriefPrevention Server Plugin for Minecraft
|
|
Copyright (C) 2012 Ryan Hamshire
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package me.ryanhamshire.GriefPrevention;
|
|
|
|
import 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.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.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<MetadataValue> 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<String> 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<Block> 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<Block> 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<PendingItemProtection> 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<ItemStack> drops = event.getDrops();
|
|
for (ItemStack stack : drops)
|
|
{
|
|
GriefPrevention.instance.pendingItemWatchList.add(
|
|
new PendingItemProtection(deathLocation, playerID, expirationTime, stack));
|
|
}
|
|
|
|
//allow the player to receive a message about how to unlock any drops
|
|
playerData.dropsAreUnlocked = false;
|
|
playerData.receivedDropUnlockAdvertisement = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
|
public void onItemMerge(ItemMergeEvent event)
|
|
{
|
|
Item item = event.getEntity();
|
|
List<MetadataValue> 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<MetadataValue> 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<String> 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<String> 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<String> 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<MetadataValue> 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<MetadataValue> 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<String> 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<String> 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<PotionEffect> 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<String> override = () -> instance.dataStore.getMessage(Messages.NoDamageClaimedEntity, claim.getOwnerName());
|
|
final Supplier<String> 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static final HashSet<PotionEffectType> 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
|
|
));
|
|
}
|