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", "EGG", "OCELOT_BABY", "SPAWNER_EGG" -> { 149 if (!area.isSpawnEggs()) { 150 event.setCancelled(true); 151 return; 152 } 153 } 154 case "REINFORCEMENTS", "NATURAL", "MOUNT", "PATROL", "RAID", "SHEARED", "SILVERFISH_BLOCK", "ENDER_PEARL", 155 "TRAP", "VILLAGE_DEFENSE", "VILLAGE_INVASION", "BEEHIVE", "CHUNK_GEN", "NETHER_PORTAL", 156 "DUPLICATION", "FROZEN", "SPELL" -> { 157 if (!area.isMobSpawning()) { 158 event.setCancelled(true); 159 return; 160 } 161 } 162 case "BREEDING" -> { 163 if (!area.isSpawnBreeding()) { 164 event.setCancelled(true); 165 return; 166 } 167 } 168 case "BUILD_IRONGOLEM", "BUILD_SNOWMAN", "BUILD_WITHER", "CUSTOM" -> { 169 if (!area.isSpawnCustom() && entity.getType() != EntityType.ARMOR_STAND) { 170 event.setCancelled(true); 171 return; 172 } 173 } 174 case "SPAWNER" -> { 175 if (!area.isMobSpawnerSpawning()) { 176 event.setCancelled(true); 177 return; 178 } 179 } 180 } 181 Plot plot = area.getOwnedPlotAbs(location); 182 if (plot == null) { 183 if (!area.isMobSpawning()) { 184 event.setCancelled(true); 185 } 186 return; 187 } 188 if (BukkitEntityUtil.checkEntity(entity, plot.getBasePlot(false))) { 189 event.setCancelled(true); 190 } 191 } 192 193 @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) 194 public void onEntityFall(EntityChangeBlockEvent event) { 195 if (event.getEntityType() != EntityType.FALLING_BLOCK) { 196 return; 197 } 198 Block block = event.getBlock(); 199 World world = block.getWorld(); 200 String worldName = world.getName(); 201 if (!this.plotAreaManager.hasPlotArea(worldName)) { 202 return; 203 } 204 Location location = BukkitUtil.adapt(block.getLocation()); 205 PlotArea area = location.getPlotArea(); 206 if (area == null) { 207 return; 208 } 209 Plot plot = area.getOwnedPlotAbs(location); 210 if (plot == null || plot.getFlag(DisablePhysicsFlag.class)) { 211 event.setCancelled(true); 212 if (plot != null) { 213 if (block.getType().hasGravity()) { 214 BlockEventListener.sendBlockChange(block.getLocation(), block.getBlockData()); 215 } 216 plot.debug("Falling block event was cancelled because disable-physics = true"); 217 } 218 return; 219 } 220 if (event.getTo().hasGravity()) { 221 Entity entity = event.getEntity(); 222 List<MetadataValue> meta = entity.getMetadata("plot"); 223 if (meta.isEmpty()) { 224 return; 225 } 226 Plot origin = (Plot) meta.get(0).value(); 227 if (origin != null && !origin.equals(plot)) { 228 event.setCancelled(true); 229 entity.remove(); 230 } 231 } else if (event.getTo() == Material.AIR) { 232 event.getEntity().setMetadata("plot", new FixedMetadataValue((Plugin) PlotSquared.platform(), plot)); 233 } 234 } 235 236 @EventHandler(priority = EventPriority.HIGH) 237 public void onDamage(EntityDamageEvent event) { 238 if (event.getEntityType() != EntityType.PLAYER) { 239 return; 240 } 241 Location location = BukkitUtil.adapt(event.getEntity().getLocation()); 242 PlotArea area = location.getPlotArea(); 243 if (area == null) { 244 return; 245 } 246 Plot plot = location.getOwnedPlot(); 247 if (plot == null) { 248 if (PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, InvincibleFlag.class, true)) { 249 event.setCancelled(true); 250 } 251 return; 252 } 253 if (plot.getFlag(InvincibleFlag.class)) { 254 plot.debug(event.getEntity().getName() + " could not take damage because invincible = true"); 255 event.setCancelled(true); 256 } 257 } 258 259 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 260 public void onBigBoom(EntityExplodeEvent event) { 261 Location location = BukkitUtil.adapt(event.getLocation()); 262 PlotArea area = location.getPlotArea(); 263 boolean plotArea = location.isPlotArea(); 264 if (!plotArea) { 265 if (!this.plotAreaManager.hasPlotArea(location.getWorldName())) { 266 return; 267 } 268 return; 269 } 270 Plot plot = area.getOwnedPlot(location); 271 if (plot != null) { 272 if (plot.getFlag(ExplosionFlag.class)) { 273 List<MetadataValue> meta = event.getEntity().getMetadata("plot"); 274 Plot origin; 275 if (meta.isEmpty()) { 276 origin = plot; 277 } else { 278 origin = (Plot) meta.get(0).value(); 279 } 280 if (this.lastRadius != 0) { 281 List<Entity> nearby = event.getEntity().getNearbyEntities(this.lastRadius, this.lastRadius, this.lastRadius); 282 for (Entity near : nearby) { 283 if (near instanceof TNTPrimed || near.getType().equals(EntityType.MINECART_TNT)) { 284 if (!near.hasMetadata("plot")) { 285 near.setMetadata("plot", new FixedMetadataValue((Plugin) PlotSquared.platform(), plot)); 286 } 287 } 288 } 289 this.lastRadius = 0; 290 } 291 Iterator<Block> iterator = event.blockList().iterator(); 292 while (iterator.hasNext()) { 293 Block block = iterator.next(); 294 location = BukkitUtil.adapt(block.getLocation()); 295 if (!area.contains(location.getX(), location.getZ()) || !origin.equals(area.getOwnedPlot(location))) { 296 iterator.remove(); 297 } 298 } 299 return; 300 } else { 301 plot.debug("Explosion was cancelled because explosion = false"); 302 } 303 } 304 event.setCancelled(true); 305 //Spawn Explosion Particles when enabled in settings 306 if (Settings.General.ALWAYS_SHOW_EXPLOSIONS) { 307 event.getLocation().getWorld().spawnParticle(Particle.EXPLOSION_HUGE, event.getLocation(), 0); 308 } 309 } 310 311 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 312 public void onPeskyMobsChangeTheWorldLikeWTFEvent(EntityChangeBlockEvent event) { 313 Entity e = event.getEntity(); 314 Material type = event.getBlock().getType(); 315 Location location = BukkitUtil.adapt(event.getBlock().getLocation()); 316 PlotArea area = location.getPlotArea(); 317 if (area == null) { 318 return; 319 } 320 if (e instanceof FallingBlock) { 321 // allow falling blocks converting to blocks and vice versa 322 return; 323 } else if (e instanceof Boat) { 324 // allow boats destroying lily pads 325 if (type == Material.LILY_PAD) { 326 return; 327 } 328 } else if (e instanceof Player player) { 329 BukkitPlayer pp = BukkitUtil.adapt(player); 330 if (type.toString().equals("POWDER_SNOW")) { 331 // Burning player evaporating powder snow. Use same checks as 332 // trampling farmland 333 BlockType blockType = BukkitAdapter.asBlockType(type); 334 if (!this.eventDispatcher.checkPlayerBlockEvent(pp, 335 PlayerBlockEventType.TRIGGER_PHYSICAL, location, blockType, true 336 )) { 337 event.setCancelled(true); 338 } 339 return; 340 } else { 341 // already handled by other flags (mainly the 'use' flag): 342 // - player tilting big dripleaf by standing on it 343 // - player picking glow berries from cave vine 344 // - player trampling farmland 345 // - player standing on or clicking redstone ore 346 return; 347 } 348 } else if (e instanceof Projectile entity) { 349 // Exact same as the ProjectileHitEvent listener, except that we let 350 // the entity-change-block determine what to do with shooters that 351 // aren't players and aren't blocks 352 Plot plot = area.getPlot(location); 353 ProjectileSource shooter = entity.getShooter(); 354 if (shooter instanceof Player) { 355 PlotPlayer<?> pp = BukkitUtil.adapt((Player) shooter); 356 if (plot == null) { 357 if (!pp.hasPermission(Permission.PERMISSION_ADMIN_PROJECTILE_UNOWNED)) { 358 entity.remove(); 359 event.setCancelled(true); 360 } 361 return; 362 } 363 if (plot.isAdded(pp.getUUID()) || pp.hasPermission(Permission.PERMISSION_ADMIN_PROJECTILE_OTHER)) { 364 return; 365 } 366 entity.remove(); 367 event.setCancelled(true); 368 return; 369 } 370 if (!(shooter instanceof Entity) && shooter != null) { 371 if (plot == null) { 372 entity.remove(); 373 event.setCancelled(true); 374 return; 375 } 376 Location sLoc = 377 BukkitUtil.adapt(((BlockProjectileSource) shooter).getBlock().getLocation()); 378 if (!area.contains(sLoc.getX(), sLoc.getZ())) { 379 entity.remove(); 380 event.setCancelled(true); 381 return; 382 } 383 Plot sPlot = area.getOwnedPlotAbs(sLoc); 384 if (sPlot == null || !PlotHandler.sameOwners(plot, sPlot)) { 385 entity.remove(); 386 event.setCancelled(true); 387 } 388 return; 389 } 390 // fall back to entity-change-block flag 391 } 392 393 Plot plot = area.getOwnedPlot(location); 394 if (plot != null && !plot.getFlag(EntityChangeBlockFlag.class)) { 395 plot.debug(e.getType() + " could not change block because entity-change-block = false"); 396 event.setCancelled(true); 397 } 398 } 399 400 @EventHandler 401 public void onPrime(ExplosionPrimeEvent event) { 402 this.lastRadius = event.getRadius() + 1; 403 } 404 405 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 406 public void onVehicleCreate(VehicleCreateEvent event) { 407 Vehicle entity = event.getVehicle(); 408 Location location = BukkitUtil.adapt(entity.getLocation()); 409 PlotArea area = location.getPlotArea(); 410 if (area == null) { 411 return; 412 } 413 Plot plot = area.getOwnedPlotAbs(location); 414 if (plot == null || BukkitEntityUtil.checkEntity(entity, plot)) { 415 entity.remove(); 416 return; 417 } 418 if (Settings.Enabled_Components.KILL_ROAD_VEHICLES) { 419 entity.setMetadata("plot", new FixedMetadataValue((Plugin) PlotSquared.platform(), plot)); 420 } 421 } 422 423}