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