001/* 002 * PlotSquared, a land and world management plugin for Minecraft. 003 * Copyright (C) IntellectualSites <https://intellectualsites.com> 004 * Copyright (C) IntellectualSites team and contributors 005 * 006 * This program is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with this program. If not, see <https://www.gnu.org/licenses/>. 018 */ 019package com.plotsquared.bukkit.listener; 020 021import com.google.inject.Inject; 022import com.plotsquared.bukkit.player.BukkitPlayer; 023import com.plotsquared.bukkit.util.BukkitEntityUtil; 024import com.plotsquared.bukkit.util.BukkitUtil; 025import com.plotsquared.core.PlotSquared; 026import com.plotsquared.core.configuration.Settings; 027import com.plotsquared.core.listener.PlayerBlockEventType; 028import com.plotsquared.core.location.Location; 029import com.plotsquared.core.permissions.Permission; 030import com.plotsquared.core.player.PlotPlayer; 031import com.plotsquared.core.plot.Plot; 032import com.plotsquared.core.plot.PlotArea; 033import com.plotsquared.core.plot.PlotHandler; 034import com.plotsquared.core.plot.flag.implementations.DisablePhysicsFlag; 035import com.plotsquared.core.plot.flag.implementations.EntityChangeBlockFlag; 036import com.plotsquared.core.plot.flag.implementations.ExplosionFlag; 037import com.plotsquared.core.plot.flag.implementations.InvincibleFlag; 038import com.plotsquared.core.plot.world.PlotAreaManager; 039import com.plotsquared.core.util.EventDispatcher; 040import com.plotsquared.core.util.PlotFlagUtil; 041import com.sk89q.worldedit.bukkit.BukkitAdapter; 042import com.sk89q.worldedit.world.block.BlockType; 043import org.bukkit.Material; 044import org.bukkit.Particle; 045import org.bukkit.World; 046import org.bukkit.block.Block; 047import org.bukkit.entity.Ageable; 048import org.bukkit.entity.Boat; 049import org.bukkit.entity.Entity; 050import org.bukkit.entity.EntityType; 051import org.bukkit.entity.FallingBlock; 052import org.bukkit.entity.Player; 053import org.bukkit.entity.Projectile; 054import org.bukkit.entity.TNTPrimed; 055import org.bukkit.entity.Vehicle; 056import org.bukkit.event.EventHandler; 057import org.bukkit.event.EventPriority; 058import org.bukkit.event.Listener; 059import org.bukkit.event.entity.CreatureSpawnEvent; 060import org.bukkit.event.entity.EntityChangeBlockEvent; 061import org.bukkit.event.entity.EntityCombustByEntityEvent; 062import org.bukkit.event.entity.EntityDamageByEntityEvent; 063import org.bukkit.event.entity.EntityDamageEvent; 064import org.bukkit.event.entity.EntityExplodeEvent; 065import org.bukkit.event.entity.ExplosionPrimeEvent; 066import org.bukkit.event.vehicle.VehicleCreateEvent; 067import org.bukkit.metadata.FixedMetadataValue; 068import org.bukkit.metadata.MetadataValue; 069import org.bukkit.plugin.Plugin; 070import org.bukkit.projectiles.BlockProjectileSource; 071import org.bukkit.projectiles.ProjectileSource; 072import org.checkerframework.checker.nullness.qual.NonNull; 073 074import java.util.Iterator; 075import java.util.List; 076 077@SuppressWarnings("unused") 078public class EntityEventListener implements Listener { 079 080 private final PlotAreaManager plotAreaManager; 081 private final EventDispatcher eventDispatcher; 082 private float lastRadius; 083 084 @Inject 085 public EntityEventListener( 086 final @NonNull PlotAreaManager plotAreaManager, 087 final @NonNull EventDispatcher eventDispatcher 088 ) { 089 this.plotAreaManager = plotAreaManager; 090 this.eventDispatcher = eventDispatcher; 091 } 092 093 @EventHandler(priority = EventPriority.HIGHEST) 094 public void onEntityCombustByEntity(EntityCombustByEntityEvent event) { 095 EntityDamageByEntityEvent eventChange = 096 new EntityDamageByEntityEvent( 097 event.getCombuster(), 098 event.getEntity(), 099 EntityDamageEvent.DamageCause.FIRE_TICK, 100 event.getDuration() 101 ); 102 onEntityDamageByEntityEvent(eventChange); 103 if (eventChange.isCancelled()) { 104 event.setCancelled(true); 105 } 106 } 107 108 @EventHandler(priority = EventPriority.HIGHEST) 109 public void onEntityDamageByEntityEvent(EntityDamageByEntityEvent event) { 110 Entity damager = event.getDamager(); 111 Location location = BukkitUtil.adapt(damager.getLocation()); 112 if (!this.plotAreaManager.hasPlotArea(location.getWorldName())) { 113 return; 114 } 115 Entity victim = event.getEntity(); 116/* 117 if (victim.getType().equals(EntityType.ITEM_FRAME)) { 118 Plot plot = BukkitUtil.getLocation(victim).getPlot(); 119 if (plot != null && !plot.isAdded(damager.getUniqueId())) { 120 event.setCancelled(true); 121 return; 122 } 123 } 124*/ 125 if (!BukkitEntityUtil.entityDamage(damager, victim, event.getCause())) { 126 if (event.isCancelled()) { 127 if (victim instanceof Ageable ageable) { 128 if (ageable.getAge() == -24000) { 129 ageable.setAge(0); 130 ageable.setAdult(); 131 } 132 } 133 } 134 event.setCancelled(true); 135 } 136 } 137 138 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 139 public void creatureSpawnEvent(CreatureSpawnEvent event) { 140 Entity entity = event.getEntity(); 141 Location location = BukkitUtil.adapt(entity.getLocation()); 142 PlotArea area = location.getPlotArea(); 143 if (area == null) { 144 return; 145 } 146 CreatureSpawnEvent.SpawnReason reason = event.getSpawnReason(); 147 switch (reason.toString()) { 148 case "DISPENSE_EGG": 149 case "EGG": 150 case "OCELOT_BABY": 151 case "SPAWNER_EGG": 152 if (!area.isSpawnEggs()) { 153 event.setCancelled(true); 154 return; 155 } 156 break; 157 case "REINFORCEMENTS": 158 case "NATURAL": 159 case "MOUNT": 160 case "PATROL": 161 case "RAID": 162 case "SHEARED": 163 case "SILVERFISH_BLOCK": 164 case "ENDER_PEARL": 165 case "TRAP": 166 case "VILLAGE_DEFENSE": 167 case "VILLAGE_INVASION": 168 case "BEEHIVE": 169 case "CHUNK_GEN": 170 if (!area.isMobSpawning()) { 171 event.setCancelled(true); 172 return; 173 } 174 break; 175 case "BREEDING": 176 if (!area.isSpawnBreeding()) { 177 event.setCancelled(true); 178 return; 179 } 180 break; 181 case "BUILD_IRONGOLEM": 182 case "BUILD_SNOWMAN": 183 case "BUILD_WITHER": 184 case "CUSTOM": 185 if (!area.isSpawnCustom() && entity.getType() != EntityType.ARMOR_STAND) { 186 event.setCancelled(true); 187 return; 188 } 189 break; 190 case "SPAWNER": 191 if (!area.isMobSpawnerSpawning()) { 192 event.setCancelled(true); 193 return; 194 } 195 break; 196 } 197 Plot plot = area.getOwnedPlotAbs(location); 198 if (plot == null) { 199 if (!area.isMobSpawning()) { 200 event.setCancelled(true); 201 } 202 return; 203 } 204 if (BukkitEntityUtil.checkEntity(entity, plot.getBasePlot(false))) { 205 event.setCancelled(true); 206 } 207 } 208 209 @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) 210 public void onEntityFall(EntityChangeBlockEvent event) { 211 if (event.getEntityType() != EntityType.FALLING_BLOCK) { 212 return; 213 } 214 Block block = event.getBlock(); 215 World world = block.getWorld(); 216 String worldName = world.getName(); 217 if (!this.plotAreaManager.hasPlotArea(worldName)) { 218 return; 219 } 220 Location location = BukkitUtil.adapt(block.getLocation()); 221 PlotArea area = location.getPlotArea(); 222 if (area == null) { 223 return; 224 } 225 Plot plot = area.getOwnedPlotAbs(location); 226 if (plot == null || plot.getFlag(DisablePhysicsFlag.class)) { 227 event.setCancelled(true); 228 if (plot != null) { 229 if (block.getType().hasGravity()) { 230 BlockEventListener.sendBlockChange(block.getLocation(), block.getBlockData()); 231 } 232 plot.debug("Falling block event was cancelled because disable-physics = true"); 233 } 234 return; 235 } 236 if (event.getTo().hasGravity()) { 237 Entity entity = event.getEntity(); 238 List<MetadataValue> meta = entity.getMetadata("plot"); 239 if (meta.isEmpty()) { 240 return; 241 } 242 Plot origin = (Plot) meta.get(0).value(); 243 if (origin != null && !origin.equals(plot)) { 244 event.setCancelled(true); 245 entity.remove(); 246 } 247 } else if (event.getTo() == Material.AIR) { 248 event.getEntity().setMetadata("plot", new FixedMetadataValue((Plugin) PlotSquared.platform(), plot)); 249 } 250 } 251 252 @EventHandler(priority = EventPriority.HIGH) 253 public void onDamage(EntityDamageEvent event) { 254 if (event.getEntityType() != EntityType.PLAYER) { 255 return; 256 } 257 Location location = BukkitUtil.adapt(event.getEntity().getLocation()); 258 PlotArea area = location.getPlotArea(); 259 if (area == null) { 260 return; 261 } 262 Plot plot = location.getOwnedPlot(); 263 if (plot == null) { 264 if (PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, InvincibleFlag.class, true)) { 265 event.setCancelled(true); 266 } 267 return; 268 } 269 if (plot.getFlag(InvincibleFlag.class)) { 270 plot.debug(event.getEntity().getName() + " could not take damage because invincible = true"); 271 event.setCancelled(true); 272 } 273 } 274 275 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 276 public void onBigBoom(EntityExplodeEvent event) { 277 Location location = BukkitUtil.adapt(event.getLocation()); 278 PlotArea area = location.getPlotArea(); 279 boolean plotArea = location.isPlotArea(); 280 if (!plotArea) { 281 if (!this.plotAreaManager.hasPlotArea(location.getWorldName())) { 282 return; 283 } 284 return; 285 } 286 Plot plot = area.getOwnedPlot(location); 287 if (plot != null) { 288 if (plot.getFlag(ExplosionFlag.class)) { 289 List<MetadataValue> meta = event.getEntity().getMetadata("plot"); 290 Plot origin; 291 if (meta.isEmpty()) { 292 origin = plot; 293 } else { 294 origin = (Plot) meta.get(0).value(); 295 } 296 if (this.lastRadius != 0) { 297 List<Entity> nearby = event.getEntity().getNearbyEntities(this.lastRadius, this.lastRadius, this.lastRadius); 298 for (Entity near : nearby) { 299 if (near instanceof TNTPrimed || near.getType().equals(EntityType.MINECART_TNT)) { 300 if (!near.hasMetadata("plot")) { 301 near.setMetadata("plot", new FixedMetadataValue((Plugin) PlotSquared.platform(), plot)); 302 } 303 } 304 } 305 this.lastRadius = 0; 306 } 307 Iterator<Block> iterator = event.blockList().iterator(); 308 while (iterator.hasNext()) { 309 Block block = iterator.next(); 310 location = BukkitUtil.adapt(block.getLocation()); 311 if (!area.contains(location.getX(), location.getZ()) || !origin.equals(area.getOwnedPlot(location))) { 312 iterator.remove(); 313 } 314 } 315 return; 316 } else { 317 plot.debug("Explosion was cancelled because explosion = false"); 318 } 319 } 320 event.setCancelled(true); 321 //Spawn Explosion Particles when enabled in settings 322 if (Settings.General.ALWAYS_SHOW_EXPLOSIONS) { 323 event.getLocation().getWorld().spawnParticle(Particle.EXPLOSION_HUGE, event.getLocation(), 0); 324 } 325 } 326 327 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 328 public void onPeskyMobsChangeTheWorldLikeWTFEvent(EntityChangeBlockEvent event) { 329 Entity e = event.getEntity(); 330 Material type = event.getBlock().getType(); 331 Location location = BukkitUtil.adapt(event.getBlock().getLocation()); 332 PlotArea area = location.getPlotArea(); 333 if (area == null) { 334 return; 335 } 336 if (e instanceof FallingBlock) { 337 // allow falling blocks converting to blocks and vice versa 338 return; 339 } else if (e instanceof Boat) { 340 // allow boats destroying lily pads 341 if (type == Material.LILY_PAD) { 342 return; 343 } 344 } else if (e instanceof Player player) { 345 BukkitPlayer pp = BukkitUtil.adapt(player); 346 if (type.toString().equals("POWDER_SNOW")) { 347 // Burning player evaporating powder snow. Use same checks as 348 // trampling farmland 349 BlockType blockType = BukkitAdapter.asBlockType(type); 350 if (!this.eventDispatcher.checkPlayerBlockEvent(pp, 351 PlayerBlockEventType.TRIGGER_PHYSICAL, location, blockType, true 352 )) { 353 event.setCancelled(true); 354 } 355 return; 356 } else { 357 // already handled by other flags (mainly the 'use' flag): 358 // - player tilting big dripleaf by standing on it 359 // - player picking glow berries from cave vine 360 // - player trampling farmland 361 // - player standing on or clicking redstone ore 362 return; 363 } 364 } else if (e instanceof Projectile entity) { 365 // Exact same as the ProjectileHitEvent listener, except that we let 366 // the entity-change-block determine what to do with shooters that 367 // aren't players and aren't blocks 368 Plot plot = area.getPlot(location); 369 ProjectileSource shooter = entity.getShooter(); 370 if (shooter instanceof Player) { 371 PlotPlayer<?> pp = BukkitUtil.adapt((Player) shooter); 372 if (plot == null) { 373 if (!pp.hasPermission(Permission.PERMISSION_ADMIN_PROJECTILE_UNOWNED)) { 374 entity.remove(); 375 event.setCancelled(true); 376 } 377 return; 378 } 379 if (plot.isAdded(pp.getUUID()) || pp.hasPermission(Permission.PERMISSION_ADMIN_PROJECTILE_OTHER)) { 380 return; 381 } 382 entity.remove(); 383 event.setCancelled(true); 384 return; 385 } 386 if (!(shooter instanceof Entity) && shooter != null) { 387 if (plot == null) { 388 entity.remove(); 389 event.setCancelled(true); 390 return; 391 } 392 Location sLoc = 393 BukkitUtil.adapt(((BlockProjectileSource) shooter).getBlock().getLocation()); 394 if (!area.contains(sLoc.getX(), sLoc.getZ())) { 395 entity.remove(); 396 event.setCancelled(true); 397 return; 398 } 399 Plot sPlot = area.getOwnedPlotAbs(sLoc); 400 if (sPlot == null || !PlotHandler.sameOwners(plot, sPlot)) { 401 entity.remove(); 402 event.setCancelled(true); 403 } 404 return; 405 } 406 // fall back to entity-change-block flag 407 } 408 409 Plot plot = area.getOwnedPlot(location); 410 if (plot != null && !plot.getFlag(EntityChangeBlockFlag.class)) { 411 plot.debug(e.getType() + " could not change block because entity-change-block = false"); 412 event.setCancelled(true); 413 } 414 } 415 416 @EventHandler 417 public void onPrime(ExplosionPrimeEvent event) { 418 this.lastRadius = event.getRadius() + 1; 419 } 420 421 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 422 public void onVehicleCreate(VehicleCreateEvent event) { 423 Vehicle entity = event.getVehicle(); 424 Location location = BukkitUtil.adapt(entity.getLocation()); 425 PlotArea area = location.getPlotArea(); 426 if (area == null) { 427 return; 428 } 429 Plot plot = area.getOwnedPlotAbs(location); 430 if (plot == null || BukkitEntityUtil.checkEntity(entity, plot)) { 431 entity.remove(); 432 return; 433 } 434 if (Settings.Enabled_Components.KILL_ROAD_VEHICLES) { 435 entity.setMetadata("plot", new FixedMetadataValue((Plugin) PlotSquared.platform(), plot)); 436 } 437 } 438 439}