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