2013-02-07 05:26:00 +00:00
/ *
GriefPrevention Server Plugin for Minecraft
Copyright ( C ) 2012 Ryan Hamshire
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
* /
package me.ryanhamshire.GriefPrevention ;
2014-12-17 05:00:42 +00:00
import java.util.ArrayList ;
2015-01-11 22:48:40 +00:00
import java.util.Arrays ;
2013-02-07 05:26:00 +00:00
import java.util.Calendar ;
2015-01-11 22:48:40 +00:00
import java.util.Collection ;
import java.util.HashSet ;
2013-02-07 05:26:00 +00:00
import java.util.List ;
2014-12-17 05:00:42 +00:00
import java.util.UUID ;
2013-02-07 05:26:00 +00:00
import org.bukkit.Location ;
import org.bukkit.Material ;
2014-12-17 05:00:42 +00:00
import org.bukkit.OfflinePlayer ;
2014-12-09 02:58:19 +00:00
import org.bukkit.World ;
2013-02-07 05:26:00 +00:00
import org.bukkit.World.Environment ;
import org.bukkit.block.Block ;
2015-01-27 00:40:08 +00:00
import org.bukkit.entity.Animals ;
2013-02-07 05:26:00 +00:00
import org.bukkit.entity.Creature ;
import org.bukkit.entity.Creeper ;
import org.bukkit.entity.Enderman ;
import org.bukkit.entity.Entity ;
import org.bukkit.entity.EntityType ;
2014-10-03 02:27:15 +00:00
import org.bukkit.entity.Explosive ;
2014-12-23 18:43:50 +00:00
import org.bukkit.entity.FallingBlock ;
2014-12-17 05:00:42 +00:00
import org.bukkit.entity.Item ;
2013-02-07 05:26:00 +00:00
import org.bukkit.entity.LivingEntity ;
import org.bukkit.entity.Monster ;
import org.bukkit.entity.Player ;
2014-11-16 23:38:30 +00:00
import org.bukkit.entity.Projectile ;
2014-12-17 05:00:42 +00:00
import org.bukkit.entity.Tameable ;
2015-01-11 22:48:40 +00:00
import org.bukkit.entity.ThrownPotion ;
2013-02-07 05:26:00 +00:00
import org.bukkit.entity.Villager ;
import org.bukkit.event.EventHandler ;
import org.bukkit.event.EventPriority ;
import org.bukkit.event.Listener ;
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.EntityDamageByEntityEvent ;
import org.bukkit.event.entity.EntityDamageEvent ;
2014-12-18 00:58:57 +00:00
import org.bukkit.event.entity.EntityDamageEvent.DamageCause ;
2013-02-07 05:26:00 +00:00
import org.bukkit.event.entity.EntityDeathEvent ;
import org.bukkit.event.entity.EntityExplodeEvent ;
import org.bukkit.event.entity.EntityInteractEvent ;
import org.bukkit.event.entity.ExpBottleEvent ;
import org.bukkit.event.entity.ItemSpawnEvent ;
2015-01-11 22:48:40 +00:00
import org.bukkit.event.entity.PotionSplashEvent ;
2013-02-07 05:26:00 +00:00
import org.bukkit.event.hanging.HangingBreakByEntityEvent ;
import org.bukkit.event.hanging.HangingBreakEvent ;
2014-09-07 20:28:57 +00:00
import org.bukkit.event.hanging.HangingBreakEvent.RemoveCause ;
2013-02-07 05:26:00 +00:00
import org.bukkit.event.hanging.HangingPlaceEvent ;
import org.bukkit.event.vehicle.VehicleDamageEvent ;
2014-12-09 02:58:19 +00:00
import org.bukkit.inventory.ItemStack ;
2014-12-17 05:00:42 +00:00
import org.bukkit.metadata.FixedMetadataValue ;
2014-12-23 18:43:50 +00:00
import org.bukkit.metadata.MetadataValue ;
2015-01-11 22:48:40 +00:00
import org.bukkit.potion.PotionEffect ;
import org.bukkit.potion.PotionEffectType ;
import org.bukkit.projectiles.ProjectileSource ;
2014-12-23 18:43:50 +00:00
import org.bukkit.util.Vector ;
2013-02-07 05:26:00 +00:00
//handles events related to entities
class EntityEventHandler implements Listener
{
//convenience reference for the singleton datastore
private DataStore dataStore ;
public EntityEventHandler ( DataStore dataStore )
{
this . dataStore = dataStore ;
}
//don't allow endermen to change blocks
@EventHandler ( ignoreCancelled = true , priority = EventPriority . LOWEST )
public void onEntityChangeBLock ( EntityChangeBlockEvent event )
{
2015-01-16 02:56:19 +00:00
if ( ! GriefPrevention . instance . config_endermenMoveBlocks & & event . getEntityType ( ) = = EntityType . ENDERMAN )
2013-02-07 05:26:00 +00:00
{
event . setCancelled ( true ) ;
}
else if ( ! GriefPrevention . instance . config_silverfishBreakBlocks & & event . getEntityType ( ) = = EntityType . SILVERFISH )
{
event . setCancelled ( true ) ;
}
//don't allow the wither to break blocks, when the wither is determined, too expensive to constantly check for claimed blocks
2014-10-19 20:19:53 +00:00
else if ( event . getEntityType ( ) = = EntityType . WITHER & & GriefPrevention . instance . config_claims_worldModes . get ( event . getBlock ( ) . getWorld ( ) ) ! = ClaimsMode . Disabled )
2013-02-07 05:26:00 +00:00
{
event . setCancelled ( true ) ;
}
2015-01-27 00:07:33 +00:00
2015-02-04 02:50:54 +00:00
//don't allow crops to be trampled
else if ( event . getTo ( ) = = Material . DIRT & & event . getBlock ( ) . getType ( ) = = Material . SOIL )
2015-01-27 00:07:33 +00:00
{
event . setCancelled ( true ) ;
}
2014-12-23 18:43:50 +00:00
//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
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 creative mode worlds, never form the block
if ( GriefPrevention . instance . config_claims_worldModes . get ( newLocation . getWorld ( ) ) = = ClaimsMode . Creative )
{
event . setCancelled ( true ) ;
return ;
}
//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 ) ;
ItemStack itemStack = new ItemStack ( entity . getMaterial ( ) , 1 , entity . getBlockData ( ) ) ;
Item item = block . getWorld ( ) . dropItem ( entity . getLocation ( ) , itemStack ) ;
item . setVelocity ( new Vector ( ) ) ;
}
}
}
}
2013-02-07 05:26:00 +00:00
}
//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 )
{
2015-01-16 02:56:19 +00:00
Material material = event . getBlock ( ) . getType ( ) ;
if ( material = = Material . SOIL )
{
if ( ! GriefPrevention . instance . config_creaturesTrampleCrops )
{
event . setCancelled ( true ) ;
}
else
{
Entity rider = event . getEntity ( ) . getPassenger ( ) ;
if ( rider ! = null & & rider . getType ( ) = = EntityType . PLAYER )
{
event . setCancelled ( true ) ;
}
}
2013-02-07 05:26:00 +00:00
}
}
//when an entity explodes...
@EventHandler ( ignoreCancelled = true , priority = EventPriority . LOWEST )
public void onEntityExplode ( EntityExplodeEvent explodeEvent )
{
List < Block > blocks = explodeEvent . blockList ( ) ;
Location location = explodeEvent . getLocation ( ) ;
//FEATURE: explosions don't destroy blocks when they explode near or above sea level in standard worlds
boolean isCreeper = ( explodeEvent . getEntity ( ) ! = null & & explodeEvent . getEntity ( ) instanceof Creeper ) ;
2014-12-13 19:56:37 +00:00
//exception for some land claims in survival worlds, see notes below
Claim originationClaim = null ;
if ( ! GriefPrevention . instance . creativeRulesApply ( location ) )
{
originationClaim = GriefPrevention . instance . dataStore . getClaimAt ( location , false , null ) ;
}
2014-10-19 20:19:53 +00:00
if ( location . getWorld ( ) . getEnvironment ( ) = = Environment . NORMAL & & GriefPrevention . instance . claimsEnabledForWorld ( location . getWorld ( ) ) & & ( ( isCreeper & & GriefPrevention . instance . config_blockSurfaceCreeperExplosions ) | | ( ! isCreeper & & GriefPrevention . instance . config_blockSurfaceOtherExplosions ) ) )
2013-02-07 05:26:00 +00:00
{
for ( int i = 0 ; i < blocks . size ( ) ; i + + )
{
Block block = blocks . get ( i ) ;
if ( GriefPrevention . instance . config_mods_explodableIds . Contains ( new MaterialInfo ( block . getTypeId ( ) , block . getData ( ) , null ) ) ) continue ;
2014-12-13 19:56:37 +00:00
//in survival worlds, if claim explosions are enabled for the source claim, allow non-creeper explosions to destroy blocks in and under that claim even above sea level.
if ( ! isCreeper & & originationClaim ! = null & & originationClaim . areExplosivesAllowed & & originationClaim . contains ( block . getLocation ( ) , true , false ) ) continue ;
2013-02-07 05:26:00 +00:00
if ( block . getLocation ( ) . getBlockY ( ) > GriefPrevention . instance . getSeaLevel ( location . getWorld ( ) ) - 7 )
{
blocks . remove ( i - - ) ;
}
}
}
//special rule for creative worlds: explosions don't destroy anything
if ( GriefPrevention . instance . creativeRulesApply ( explodeEvent . getLocation ( ) ) )
{
for ( int i = 0 ; i < blocks . size ( ) ; i + + )
{
Block block = blocks . get ( i ) ;
if ( GriefPrevention . instance . config_mods_explodableIds . Contains ( new MaterialInfo ( block . getTypeId ( ) , block . getData ( ) , null ) ) ) continue ;
blocks . remove ( i - - ) ;
}
}
//FEATURE: explosions don't damage claimed blocks
Claim claim = null ;
for ( int i = 0 ; i < blocks . size ( ) ; i + + ) //for each destroyed block
{
Block block = blocks . get ( i ) ;
if ( block . getType ( ) = = Material . AIR ) continue ; //if it's air, we don't care
if ( GriefPrevention . instance . config_mods_explodableIds . Contains ( new MaterialInfo ( block . getTypeId ( ) , block . getData ( ) , null ) ) ) continue ;
claim = this . dataStore . getClaimAt ( block . getLocation ( ) , false , claim ) ;
//if the block is claimed, remove it from the list of destroyed blocks
if ( claim ! = null & & ! claim . areExplosivesAllowed )
{
blocks . remove ( i - - ) ;
}
}
}
//when an item spawns...
@EventHandler ( priority = EventPriority . LOWEST )
public void onItemSpawn ( ItemSpawnEvent event )
{
//if in a creative world, cancel the event (don't drop items on the ground)
if ( GriefPrevention . instance . creativeRulesApply ( event . getLocation ( ) ) )
{
event . setCancelled ( true ) ;
}
2014-12-17 05:00:42 +00:00
//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 ;
}
2013-02-07 05:26:00 +00:00
}
//when an experience bottle explodes...
@EventHandler ( priority = EventPriority . LOWEST )
public void onExpBottle ( ExpBottleEvent event )
{
//if in a creative world, cancel the event (don't drop exp on the ground)
if ( GriefPrevention . instance . creativeRulesApply ( event . getEntity ( ) . getLocation ( ) ) )
{
event . setExperience ( 0 ) ;
}
}
//when a creature spawns...
@EventHandler ( priority = EventPriority . LOWEST )
public void onEntitySpawn ( CreatureSpawnEvent event )
{
//these rules apply only to creative worlds
2014-11-12 05:47:16 +00:00
if ( ! GriefPrevention . instance . creativeRulesApply ( event . getLocation ( ) ) ) return ;
2013-02-07 05:26:00 +00:00
//chicken eggs and breeding could potentially make a mess in the wilderness, once griefers get involved
SpawnReason reason = event . getSpawnReason ( ) ;
2014-12-05 00:23:06 +00:00
if ( reason ! = SpawnReason . SPAWNER_EGG & & reason ! = SpawnReason . BUILD_IRONGOLEM & & reason ! = SpawnReason . BUILD_SNOWMAN & & event . getEntityType ( ) ! = EntityType . ARMOR_STAND )
2013-02-07 05:26:00 +00:00
{
event . setCancelled ( true ) ;
return ;
}
//otherwise, just apply the limit on total entities per claim (and no spawning in the wilderness!)
Claim claim = this . dataStore . getClaimAt ( event . getLocation ( ) , false , null ) ;
if ( claim = = null | | claim . allowMoreEntities ( ) ! = null )
{
event . setCancelled ( true ) ;
return ;
}
}
//when an entity dies...
2014-12-17 05:00:42 +00:00
@EventHandler ( priority = EventPriority . HIGHEST , ignoreCancelled = true )
2013-02-07 05:26:00 +00:00
public void onEntityDeath ( EntityDeathEvent event )
{
LivingEntity entity = event . getEntity ( ) ;
2014-12-09 02:58:19 +00:00
//don't do the rest in worlds where claims are not enabled
2014-10-19 20:19:53 +00:00
if ( ! GriefPrevention . instance . claimsEnabledForWorld ( entity . getWorld ( ) ) ) return ;
2013-02-07 05:26:00 +00:00
//special rule for creative worlds: killed entities don't drop items or experience orbs
if ( GriefPrevention . instance . creativeRulesApply ( entity . getLocation ( ) ) )
{
event . setDroppedExp ( 0 ) ;
event . getDrops ( ) . clear ( ) ;
}
//FEATURE: when a player is involved in a siege (attacker or defender role)
//his death will end the siege
if ( ! ( entity instanceof Player ) ) return ; //only tracking players
Player player = ( Player ) entity ;
2014-09-22 20:46:13 +00:00
PlayerData playerData = this . dataStore . getPlayerData ( player . getUniqueId ( ) ) ;
2013-02-07 05:26:00 +00:00
//if involved in a siege
if ( playerData . siegeData ! = null )
{
//don't drop items as usual, they will be sent to the siege winner
event . getDrops ( ) . clear ( ) ;
//end it, with the dieing player being the loser
this . dataStore . endSiege ( playerData . siegeData , null , player . getName ( ) , true /*ended due to death*/ ) ;
}
2014-12-17 05:00:42 +00:00
//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 . config_pvp_enabledWorlds . contains ( world ) ;
if ( ( isPvPWorld & & GriefPrevention . instance . config_lockDeathDropsInPvpWorlds ) | |
( ! isPvPWorld & & GriefPrevention . instance . config_lockDeathDropsInNonPvpWorlds ) )
{
//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 ;
}
2013-02-07 05:26:00 +00:00
}
//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 ( ) instanceof 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 )
{
2014-10-19 20:19:53 +00:00
//don't track in worlds where claims are not enabled
if ( ! GriefPrevention . instance . claimsEnabledForWorld ( event . getEntity ( ) . getWorld ( ) ) ) return ;
//FEATURE: claimed paintings are protected from breakage
2013-02-07 05:26:00 +00:00
2014-09-07 20:28:57 +00:00
//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)
2013-02-07 05:26:00 +00:00
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 instanceof Player ) )
{
event . setCancelled ( true ) ;
return ;
}
//if the player doesn't have build permission, don't allow the breakage
Player playerRemover = ( Player ) entityEvent . getRemover ( ) ;
2014-10-24 02:57:25 +00:00
String noBuildReason = GriefPrevention . instance . allowBuild ( playerRemover , event . getEntity ( ) . getLocation ( ) , Material . AIR ) ;
2013-02-07 05:26:00 +00:00
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 )
{
2014-10-19 20:19:53 +00:00
//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
2013-02-07 05:26:00 +00:00
//if the player doesn't have permission, don't allow the placement
2014-10-24 02:57:25 +00:00
String noBuildReason = GriefPrevention . instance . allowBuild ( event . getPlayer ( ) , event . getEntity ( ) . getLocation ( ) , Material . PAINTING ) ;
2013-02-07 05:26:00 +00:00
if ( noBuildReason ! = null )
{
event . setCancelled ( true ) ;
GriefPrevention . sendMessage ( event . getPlayer ( ) , TextMode . Err , noBuildReason ) ;
return ;
}
//otherwise, apply entity-count limitations for creative worlds
else if ( GriefPrevention . instance . creativeRulesApply ( event . getEntity ( ) . getLocation ( ) ) )
{
2014-09-22 20:46:13 +00:00
PlayerData playerData = this . dataStore . getPlayerData ( event . getPlayer ( ) . getUniqueId ( ) ) ;
2013-02-07 05:26:00 +00:00
Claim claim = this . dataStore . getClaimAt ( event . getBlock ( ) . getLocation ( ) , false , playerData . lastClaim ) ;
if ( claim = = null ) return ;
String noEntitiesReason = claim . allowMoreEntities ( ) ;
if ( noEntitiesReason ! = null )
{
GriefPrevention . sendMessage ( event . getPlayer ( ) , TextMode . Err , noEntitiesReason ) ;
event . setCancelled ( true ) ;
return ;
}
}
}
//when an entity is damaged
2015-01-27 00:23:00 +00:00
@EventHandler ( ignoreCancelled = true , priority = EventPriority . LOWEST )
2013-02-07 05:26:00 +00:00
public void onEntityDamage ( EntityDamageEvent event )
{
//monsters are never protected
if ( event . getEntity ( ) instanceof Monster ) return ;
2014-12-18 05:19:47 +00:00
//protect pets from environmental damage types which could be easily caused by griefers
if ( event . getEntity ( ) instanceof Tameable & & ! GriefPrevention . instance . config_pvp_enabledWorlds . contains ( event . getEntity ( ) . getWorld ( ) ) )
{
Tameable tameable = ( Tameable ) event . getEntity ( ) ;
if ( tameable . isTamed ( ) )
{
DamageCause cause = event . getCause ( ) ;
if ( cause ! = null & & (
cause = = DamageCause . ENTITY_EXPLOSION | |
cause = = DamageCause . FALLING_BLOCK | |
cause = = DamageCause . FIRE | |
cause = = DamageCause . FIRE_TICK | |
cause = = DamageCause . LAVA | |
cause = = DamageCause . SUFFOCATION ) )
{
event . setCancelled ( true ) ;
return ;
}
}
}
//the rest is only interested in entities damaging entities (ignoring environmental damage)
if ( ! ( event instanceof EntityDamageByEntityEvent ) ) return ;
2013-02-07 05:26:00 +00:00
EntityDamageByEntityEvent subEvent = ( EntityDamageByEntityEvent ) event ;
//determine which player is attacking, if any
Player attacker = null ;
2014-11-16 23:38:30 +00:00
Projectile arrow = null ;
2013-02-07 05:26:00 +00:00
Entity damageSource = subEvent . getDamager ( ) ;
2014-12-02 22:58:04 +00:00
if ( damageSource ! = null )
2013-02-07 05:26:00 +00:00
{
2014-12-02 22:58:04 +00:00
if ( damageSource instanceof Player )
{
attacker = ( Player ) damageSource ;
}
else if ( damageSource instanceof Projectile )
{
arrow = ( Projectile ) damageSource ;
if ( arrow . getShooter ( ) instanceof Player )
{
attacker = ( Player ) arrow . getShooter ( ) ;
}
}
2013-02-07 05:26:00 +00:00
}
//if the attacker is a player and defender is a player (pvp combat)
if ( attacker ! = null & & event . getEntity ( ) instanceof Player & & GriefPrevention . instance . config_pvp_enabledWorlds . contains ( attacker . getWorld ( ) ) )
{
//FEATURE: prevent pvp in the first minute after spawn, and prevent pvp when one or both players have no inventory
//doesn't apply when the attacker has the no pvp immunity permission
//this rule is here to allow server owners to have a world with no spawn camp protection by assigning permissions based on the player's world
if ( attacker . hasPermission ( " griefprevention.nopvpimmunity " ) ) return ;
Player defender = ( Player ) ( event . getEntity ( ) ) ;
2015-02-07 02:45:00 +00:00
if ( attacker ! = defender )
2013-02-07 05:26:00 +00:00
{
2015-02-07 02:45:00 +00:00
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 ) ;
GriefPrevention . sendMessage ( attacker , TextMode . Err , Messages . ThatPlayerPvPImmune ) ;
return ;
}
if ( attackerData . pvpImmune )
{
event . setCancelled ( true ) ;
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 ( attackerClaim ! = null & &
( attackerClaim . isAdminClaim ( ) & & attackerClaim . parent = = null & & GriefPrevention . instance . config_pvp_noCombatInAdminLandClaims | |
attackerClaim . isAdminClaim ( ) & & attackerClaim . parent ! = null & & GriefPrevention . instance . config_pvp_noCombatInAdminSubdivisions | |
! attackerClaim . isAdminClaim ( ) & & GriefPrevention . instance . config_pvp_noCombatInPlayerLandClaims ) )
{
attackerData . lastClaim = attackerClaim ;
event . setCancelled ( true ) ;
GriefPrevention . sendMessage ( attacker , TextMode . Err , Messages . CantFightWhileImmune ) ;
return ;
}
Claim defenderClaim = this . dataStore . getClaimAt ( defender . getLocation ( ) , false , defenderData . lastClaim ) ;
if ( defenderClaim ! = null & &
( defenderClaim . isAdminClaim ( ) & & defenderClaim . parent = = null & & GriefPrevention . instance . config_pvp_noCombatInAdminLandClaims | |
defenderClaim . isAdminClaim ( ) & & defenderClaim . parent ! = null & & GriefPrevention . instance . config_pvp_noCombatInAdminSubdivisions | |
! defenderClaim . isAdminClaim ( ) & & GriefPrevention . instance . config_pvp_noCombatInPlayerLandClaims ) )
{
defenderData . lastClaim = defenderClaim ;
event . setCancelled ( true ) ;
GriefPrevention . sendMessage ( attacker , TextMode . Err , Messages . PlayerInPvPSafeZone ) ;
return ;
}
}
//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
long now = Calendar . getInstance ( ) . getTimeInMillis ( ) ;
defenderData . lastPvpTimestamp = now ;
defenderData . lastPvpPlayer = attacker . getName ( ) ;
attackerData . lastPvpTimestamp = now ;
attackerData . lastPvpPlayer = defender . getName ( ) ;
2013-02-07 05:26:00 +00:00
}
}
2014-09-07 20:28:57 +00:00
//FEATURE: protect claimed animals, boats, minecarts, and items inside item frames
2013-02-07 05:26:00 +00:00
//NOTE: animals can be lead with wheat, vehicles can be pushed around.
//so unless precautions are taken by the owner, a resourceful thief might find ways to steal anyway
//if theft protection is enabled
if ( event instanceof EntityDamageByEntityEvent )
{
2014-10-19 20:19:53 +00:00
//don't track in worlds where claims are not enabled
if ( ! GriefPrevention . instance . claimsEnabledForWorld ( event . getEntity ( ) . getWorld ( ) ) ) return ;
2014-12-18 00:58:57 +00:00
//if the damaged entity is a claimed item frame or armor stand, the damager needs to be a player with container trust in the claim
2015-01-15 01:28:09 +00:00
if ( subEvent . getEntityType ( ) = = EntityType . ITEM_FRAME
| | subEvent . getEntityType ( ) = = EntityType . ARMOR_STAND
| | subEvent . getEntityType ( ) = = EntityType . VILLAGER )
2014-09-07 20:28:57 +00:00
{
//decide whether it's claimed
Claim cachedClaim = null ;
PlayerData playerData = null ;
if ( attacker ! = null )
{
2014-09-22 20:46:13 +00:00
playerData = this . dataStore . getPlayerData ( attacker . getUniqueId ( ) ) ;
2014-09-07 20:28:57 +00:00
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 )
{
event . setCancelled ( true ) ;
return ;
}
//otherwise player must have container trust in the claim
2015-01-15 01:24:57 +00:00
String failureReason = claim . allowBuild ( attacker , Material . AIR ) ;
2014-09-07 20:28:57 +00:00
if ( failureReason ! = null )
{
event . setCancelled ( true ) ;
GriefPrevention . sendMessage ( attacker , TextMode . Err , failureReason ) ;
return ;
}
}
}
//if the entity is an non-monster creature (remember monsters disqualified above), or a vehicle
2013-02-07 05:26:00 +00:00
if ( ( subEvent . getEntity ( ) instanceof Creature & & GriefPrevention . instance . config_claims_protectCreatures ) )
{
2014-12-17 05:00:42 +00:00
//if entity is tameable and has an owner, apply special rules
2014-12-18 05:19:47 +00:00
if ( subEvent . getEntity ( ) instanceof Tameable & & ! GriefPrevention . instance . config_pvp_enabledWorlds . contains ( subEvent . getEntity ( ) . getWorld ( ) ) )
2014-12-17 05:00:42 +00:00
{
Tameable tameable = ( Tameable ) subEvent . getEntity ( ) ;
if ( tameable . isTamed ( ) & & tameable . getOwner ( ) ! = null )
{
2014-12-18 00:58:57 +00:00
//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
if ( ! GriefPrevention . instance . config_pvp_enabledWorlds . contains ( subEvent . getEntity ( ) . getLocation ( ) . getWorld ( ) ) )
{
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 ) ;
GriefPrevention . sendMessage ( attacker , TextMode . Err , message ) ;
event . setCancelled ( true ) ;
return ;
}
}
2014-12-17 05:00:42 +00:00
}
}
Claim cachedClaim = null ;
2013-02-07 05:26:00 +00:00
PlayerData playerData = null ;
2014-10-03 02:27:15 +00:00
//if not a player or an explosive, allow
2015-02-10 02:53:27 +00:00
if ( attacker = = null & & damageSource ! = null & & ! ( damageSource instanceof Projectile ) & & damageSource . getType ( ) ! = EntityType . CREEPER & & ! ( damageSource instanceof Explosive ) )
2014-10-03 02:27:15 +00:00
{
return ;
}
2013-02-07 05:26:00 +00:00
if ( attacker ! = null )
{
2014-09-22 20:46:13 +00:00
playerData = this . dataStore . getPlayerData ( attacker . getUniqueId ( ) ) ;
2013-02-07 05:26:00 +00:00
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.
if ( attacker = = null )
{
//exception case
2015-02-10 02:53:27 +00:00
if ( event . getEntity ( ) instanceof Villager & & damageSource ! = null & & damageSource instanceof Monster )
2013-02-07 05:26:00 +00:00
{
return ;
}
//all other cases
else
{
event . setCancelled ( true ) ;
2015-02-10 02:53:27 +00:00
if ( damageSource ! = null & & damageSource instanceof Projectile )
{
damageSource . remove ( ) ;
}
2013-02-07 05:26:00 +00:00
}
}
//otherwise the player damaging the entity must have permission
else
{
String noContainersReason = claim . allowContainers ( attacker ) ;
if ( noContainersReason ! = null )
{
event . setCancelled ( true ) ;
//kill the arrow to avoid infinite bounce between crowded together animals
if ( arrow ! = null ) arrow . remove ( ) ;
2014-10-19 20:26:57 +00:00
String message = GriefPrevention . instance . dataStore . getMessage ( Messages . NoDamageClaimedEntity , claim . getOwnerName ( ) ) ;
if ( attacker . hasPermission ( " griefprevention.ignoreclaims " ) )
message + = " " + GriefPrevention . instance . dataStore . getMessage ( Messages . IgnoreClaimsAdvertisement ) ;
GriefPrevention . sendMessage ( attacker , TextMode . Err , message ) ;
event . setCancelled ( true ) ;
2013-02-07 05:26:00 +00:00
}
//cache claim for later
if ( playerData ! = null )
{
playerData . lastClaim = claim ;
}
}
}
}
}
}
//when a vehicle is damaged
@EventHandler ( ignoreCancelled = true , priority = EventPriority . LOWEST )
public void onVehicleDamage ( VehicleDamageEvent event )
{
//all of this is anti theft code
2014-11-16 23:38:30 +00:00
if ( ! GriefPrevention . instance . config_claims_preventTheft ) return ;
2013-02-07 05:26:00 +00:00
2014-12-05 04:01:21 +00:00
//input validation
if ( event . getVehicle ( ) = = null ) return ;
2014-10-19 20:19:53 +00:00
//don't track in worlds where claims are not enabled
if ( ! GriefPrevention . instance . claimsEnabledForWorld ( event . getVehicle ( ) . getWorld ( ) ) ) return ;
2013-02-07 05:26:00 +00:00
//determine which player is attacking, if any
Player attacker = null ;
Entity damageSource = event . getAttacker ( ) ;
2015-01-08 04:08:04 +00:00
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 )
2013-02-07 05:26:00 +00:00
{
2015-01-08 04:08:04 +00:00
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 ( ) ;
}
}
2013-02-07 05:26:00 +00:00
}
2014-10-03 02:27:15 +00:00
//if not a player and not an explosion, always allow
2015-01-08 04:08:04 +00:00
if ( attacker = = null & & damageSourceType ! = EntityType . CREEPER & & damageSourceType ! = EntityType . PRIMED_TNT )
2014-10-03 02:27:15 +00:00
{
return ;
}
2013-02-07 05:26:00 +00:00
//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 ;
2014-10-03 02:27:15 +00:00
2013-02-07 05:26:00 +00:00
if ( attacker ! = null )
{
2014-09-22 20:46:13 +00:00
playerData = this . dataStore . getPlayerData ( attacker . getUniqueId ( ) ) ;
2013-02-07 05:26:00 +00:00
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
{
String noContainersReason = claim . allowContainers ( attacker ) ;
if ( noContainersReason ! = null )
{
event . setCancelled ( true ) ;
2014-10-19 20:26:57 +00:00
String message = GriefPrevention . instance . dataStore . getMessage ( Messages . NoDamageClaimedEntity , claim . getOwnerName ( ) ) ;
if ( attacker . hasPermission ( " griefprevention.ignoreclaims " ) )
message + = " " + GriefPrevention . instance . dataStore . getMessage ( Messages . IgnoreClaimsAdvertisement ) ;
GriefPrevention . sendMessage ( attacker , TextMode . Err , message ) ;
event . setCancelled ( true ) ;
2013-02-07 05:26:00 +00:00
}
//cache claim for later
if ( playerData ! = null )
{
playerData . lastClaim = claim ;
}
}
}
}
2015-01-11 22:48:40 +00:00
//when a splash potion effects one or more entities...
@EventHandler ( ignoreCancelled = true , priority = EventPriority . HIGHEST )
public void onPotionSplash ( PotionSplashEvent event )
{
ThrownPotion potion = event . getPotion ( ) ;
//ignore potions not thrown by players
ProjectileSource projectileSource = potion . getShooter ( ) ;
if ( projectileSource = = null | | ! ( projectileSource instanceof Player ) ) return ;
Player thrower = ( Player ) projectileSource ;
Collection < PotionEffect > effects = potion . getEffects ( ) ;
for ( PotionEffect effect : effects )
{
PotionEffectType effectType = effect . getType ( ) ;
2015-02-11 23:30:30 +00:00
//restrict some potions on claimed animals (griefers could use this to kill or steal animals over fences)
2015-02-12 03:01:41 +00:00
if ( effectType . getName ( ) . equals ( " JUMP " ) | | effectType . getName ( ) . equals ( " POISON " ) )
2015-01-27 00:40:08 +00:00
{
for ( LivingEntity effected : event . getAffectedEntities ( ) )
{
Claim cachedClaim = null ;
if ( effected instanceof Animals )
{
2015-02-12 03:01:41 +00:00
Claim claim = this . dataStore . getClaimAt ( effected . getLocation ( ) , false , cachedClaim ) ;
2015-01-27 00:40:08 +00:00
if ( claim ! = null )
{
cachedClaim = claim ;
if ( claim . allowContainers ( thrower ) ! = null )
{
event . setCancelled ( true ) ;
2015-02-12 03:01:41 +00:00
GriefPrevention . sendMessage ( thrower , TextMode . Err , Messages . NoDamageClaimedEntity , claim . getOwnerName ( ) ) ;
2015-01-27 00:40:08 +00:00
return ;
}
}
}
}
}
//otherwise, no restrictions for positive effects
if ( positiveEffects . contains ( effectType ) ) continue ;
2015-01-11 22:48:40 +00:00
for ( LivingEntity effected : event . getAffectedEntities ( ) )
{
//always impact the thrower
if ( effected = = thrower ) continue ;
//always impact non players
if ( ! ( effected instanceof 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 & &
( attackerClaim . isAdminClaim ( ) & & attackerClaim . parent = = null & & GriefPrevention . instance . config_pvp_noCombatInAdminLandClaims | |
attackerClaim . isAdminClaim ( ) & & attackerClaim . parent ! = null & & GriefPrevention . instance . config_pvp_noCombatInAdminSubdivisions | |
! attackerClaim . isAdminClaim ( ) & & GriefPrevention . instance . config_pvp_noCombatInPlayerLandClaims ) )
{
attackerData . lastClaim = attackerClaim ;
event . setIntensity ( effected , 0 ) ;
GriefPrevention . sendMessage ( thrower , TextMode . Err , Messages . CantFightWhileImmune ) ;
continue ;
}
Claim defenderClaim = this . dataStore . getClaimAt ( effectedPlayer . getLocation ( ) , false , defenderData . lastClaim ) ;
if ( defenderClaim ! = null & &
( defenderClaim . isAdminClaim ( ) & & defenderClaim . parent = = null & & GriefPrevention . instance . config_pvp_noCombatInAdminLandClaims | |
defenderClaim . isAdminClaim ( ) & & defenderClaim . parent ! = null & & GriefPrevention . instance . config_pvp_noCombatInAdminSubdivisions | |
! defenderClaim . isAdminClaim ( ) & & GriefPrevention . instance . config_pvp_noCombatInPlayerLandClaims ) )
{
defenderData . lastClaim = defenderClaim ;
event . setIntensity ( effected , 0 ) ;
GriefPrevention . sendMessage ( thrower , TextMode . Err , Messages . PlayerInPvPSafeZone ) ;
continue ;
}
}
}
}
}
2015-01-11 22:49:43 +00:00
private static final HashSet < PotionEffectType > positiveEffects = new HashSet < PotionEffectType > ( Arrays . asList
2015-01-11 22:48:40 +00:00
(
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
) ) ;
2013-02-07 05:26:00 +00:00
}