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