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.destroystokyo.paper.event.block.BeaconEffectEvent; 022import com.destroystokyo.paper.event.block.BlockDestroyEvent; 023import com.destroystokyo.paper.event.entity.EntityPathfindEvent; 024import com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent; 025import com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent; 026import com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent; 027import com.destroystokyo.paper.event.entity.SlimePathfindEvent; 028import com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent; 029import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent; 030import com.google.inject.Inject; 031import com.plotsquared.bukkit.util.BukkitUtil; 032import com.plotsquared.core.command.Command; 033import com.plotsquared.core.command.MainCommand; 034import com.plotsquared.core.configuration.Settings; 035import com.plotsquared.core.configuration.caption.TranslatableCaption; 036import com.plotsquared.core.location.Location; 037import com.plotsquared.core.permissions.Permission; 038import com.plotsquared.core.player.PlotPlayer; 039import com.plotsquared.core.plot.Plot; 040import com.plotsquared.core.plot.PlotArea; 041import com.plotsquared.core.plot.flag.FlagContainer; 042import com.plotsquared.core.plot.flag.implementations.BeaconEffectsFlag; 043import com.plotsquared.core.plot.flag.implementations.DoneFlag; 044import com.plotsquared.core.plot.flag.implementations.FishingFlag; 045import com.plotsquared.core.plot.flag.implementations.ProjectilesFlag; 046import com.plotsquared.core.plot.flag.implementations.TileDropFlag; 047import com.plotsquared.core.plot.flag.types.BooleanFlag; 048import com.plotsquared.core.plot.world.PlotAreaManager; 049import com.plotsquared.core.util.PlotFlagUtil; 050import net.kyori.adventure.text.Component; 051import net.kyori.adventure.text.minimessage.tag.Tag; 052import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; 053import org.bukkit.Chunk; 054import org.bukkit.block.Block; 055import org.bukkit.block.TileState; 056import org.bukkit.entity.Entity; 057import org.bukkit.entity.EntityType; 058import org.bukkit.entity.Player; 059import org.bukkit.entity.Projectile; 060import org.bukkit.entity.Slime; 061import org.bukkit.event.EventHandler; 062import org.bukkit.event.EventPriority; 063import org.bukkit.event.Listener; 064import org.bukkit.event.block.BlockPlaceEvent; 065import org.bukkit.event.entity.CreatureSpawnEvent; 066import org.bukkit.projectiles.ProjectileSource; 067import org.checkerframework.checker.nullness.qual.NonNull; 068 069import java.util.ArrayList; 070import java.util.Collection; 071import java.util.List; 072import java.util.Locale; 073import java.util.regex.Pattern; 074 075/** 076 * Events specific to Paper. Some toit nups here 077 */ 078@SuppressWarnings("unused") 079public class PaperListener implements Listener { 080 081 private final PlotAreaManager plotAreaManager; 082 private Chunk lastChunk; 083 084 @Inject 085 public PaperListener(final @NonNull PlotAreaManager plotAreaManager) { 086 this.plotAreaManager = plotAreaManager; 087 } 088 089 @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) 090 public void onBlockDestroy(final BlockDestroyEvent event) { 091 Location location = BukkitUtil.adapt(event.getBlock().getLocation()); 092 PlotArea area = location.getPlotArea(); 093 if (area == null) { 094 return; 095 } 096 Plot plot = area.getPlot(location); 097 if (plot != null) { 098 event.setWillDrop(plot.getFlag(TileDropFlag.class)); 099 } 100 } 101 102 @EventHandler 103 public void onEntityPathfind(EntityPathfindEvent event) { 104 if (!Settings.Paper_Components.ENTITY_PATHING) { 105 return; 106 } 107 Location toLoc = BukkitUtil.adapt(event.getLoc()); 108 Location fromLoc = BukkitUtil.adapt(event.getEntity().getLocation()); 109 PlotArea tarea = toLoc.getPlotArea(); 110 if (tarea == null) { 111 return; 112 } 113 PlotArea farea = fromLoc.getPlotArea(); 114 if (farea == null) { 115 return; 116 } 117 if (tarea != farea) { 118 event.setCancelled(true); 119 return; 120 } 121 Plot tplot = toLoc.getPlot(); 122 Plot fplot = fromLoc.getPlot(); 123 if (tplot == null ^ fplot == null) { 124 event.setCancelled(true); 125 return; 126 } 127 if (tplot == null || tplot.getId().hashCode() == fplot.getId().hashCode()) { 128 return; 129 } 130 if (fplot.isMerged() && fplot.getConnectedPlots().contains(fplot)) { 131 return; 132 } 133 event.setCancelled(true); 134 } 135 136 @EventHandler 137 public void onEntityPathfind(SlimePathfindEvent event) { 138 if (!Settings.Paper_Components.ENTITY_PATHING) { 139 return; 140 } 141 Slime slime = event.getEntity(); 142 143 Block b = slime.getTargetBlockExact(4); 144 if (b == null) { 145 return; 146 } 147 148 Location toLoc = BukkitUtil.adapt(b.getLocation()); 149 Location fromLoc = BukkitUtil.adapt(event.getEntity().getLocation()); 150 PlotArea tarea = toLoc.getPlotArea(); 151 if (tarea == null) { 152 return; 153 } 154 PlotArea farea = fromLoc.getPlotArea(); 155 if (farea == null) { 156 return; 157 } 158 159 if (tarea != farea) { 160 event.setCancelled(true); 161 return; 162 } 163 Plot tplot = toLoc.getPlot(); 164 Plot fplot = fromLoc.getPlot(); 165 if (tplot == null ^ fplot == null) { 166 event.setCancelled(true); 167 return; 168 } 169 if (tplot == null || tplot.getId().hashCode() == fplot.getId().hashCode()) { 170 return; 171 } 172 if (fplot.isMerged() && fplot.getConnectedPlots().contains(fplot)) { 173 return; 174 } 175 event.setCancelled(true); 176 } 177 178 @EventHandler 179 public void onPreCreatureSpawnEvent(PreCreatureSpawnEvent event) { 180 if (!Settings.Paper_Components.CREATURE_SPAWN) { 181 return; 182 } 183 Location location = BukkitUtil.adapt(event.getSpawnLocation()); 184 PlotArea area = location.getPlotArea(); 185 if (area == null) { 186 return; 187 } 188 // Armour-stands are handled elsewhere and should not be handled by area-wide entity-spawn options 189 if (event.getType() == EntityType.ARMOR_STAND) { 190 return; 191 } 192 // If entities are spawning... the chunk should be loaded? 193 Entity[] entities = event.getSpawnLocation().getChunk().getEntities(); 194 if (entities.length >= Settings.Chunk_Processor.MAX_ENTITIES) { 195 event.setShouldAbortSpawn(true); 196 event.setCancelled(true); 197 return; 198 } 199 CreatureSpawnEvent.SpawnReason reason = event.getReason(); 200 switch (reason.toString()) { 201 case "DISPENSE_EGG", "EGG", "OCELOT_BABY", "SPAWNER_EGG" -> { 202 if (!area.isSpawnEggs()) { 203 event.setShouldAbortSpawn(true); 204 event.setCancelled(true); 205 return; 206 } 207 } 208 case "REINFORCEMENTS", "NATURAL", "MOUNT", "PATROL", "RAID", "SHEARED", "SILVERFISH_BLOCK", "ENDER_PEARL", "TRAP", "VILLAGE_DEFENSE", "VILLAGE_INVASION", "BEEHIVE", "CHUNK_GEN" -> { 209 if (!area.isMobSpawning()) { 210 event.setShouldAbortSpawn(true); 211 event.setCancelled(true); 212 return; 213 } 214 } 215 case "BREEDING" -> { 216 if (!area.isSpawnBreeding()) { 217 event.setShouldAbortSpawn(true); 218 event.setCancelled(true); 219 return; 220 } 221 } 222 case "BUILD_IRONGOLEM", "BUILD_SNOWMAN", "BUILD_WITHER", "CUSTOM" -> { 223 if (!area.isSpawnCustom()) { 224 event.setShouldAbortSpawn(true); 225 event.setCancelled(true); 226 return; 227 } 228 } 229 case "SPAWNER" -> { 230 if (!area.isMobSpawnerSpawning()) { 231 event.setShouldAbortSpawn(true); 232 event.setCancelled(true); 233 return; 234 } 235 } 236 } 237 Plot plot = location.getOwnedPlotAbs(); 238 if (plot == null) { 239 EntityType type = event.getType(); 240 // PreCreatureSpawnEvent **should** not be called for DROPPED_ITEM, just for the sake of consistency 241 if (type == EntityType.DROPPED_ITEM) { 242 if (Settings.Enabled_Components.KILL_ROAD_ITEMS) { 243 event.setCancelled(true); 244 } 245 return; 246 } 247 if (!area.isMobSpawning()) { 248 if (type == EntityType.PLAYER) { 249 return; 250 } 251 if (type.isAlive()) { 252 event.setShouldAbortSpawn(true); 253 event.setCancelled(true); 254 } 255 } 256 if (!area.isMiscSpawnUnowned() && !type.isAlive()) { 257 event.setShouldAbortSpawn(true); 258 event.setCancelled(true); 259 } 260 return; 261 } 262 if (Settings.Done.RESTRICT_BUILDING && DoneFlag.isDone(plot)) { 263 event.setShouldAbortSpawn(true); 264 event.setCancelled(true); 265 } 266 } 267 268 @EventHandler 269 public void onPlayerNaturallySpawnCreaturesEvent(PlayerNaturallySpawnCreaturesEvent event) { 270 if (Settings.Paper_Components.CANCEL_CHUNK_SPAWN) { 271 Location location = BukkitUtil.adapt(event.getPlayer().getLocation()); 272 PlotArea area = location.getPlotArea(); 273 if (area != null && !area.isMobSpawning()) { 274 event.setCancelled(true); 275 } 276 } 277 } 278 279 @EventHandler 280 public void onPreSpawnerSpawnEvent(PreSpawnerSpawnEvent event) { 281 if (Settings.Paper_Components.SPAWNER_SPAWN) { 282 Location location = BukkitUtil.adapt(event.getSpawnerLocation()); 283 PlotArea area = location.getPlotArea(); 284 if (area != null && !area.isMobSpawnerSpawning()) { 285 event.setCancelled(true); 286 event.setShouldAbortSpawn(true); 287 } 288 } 289 } 290 291 @EventHandler(priority = EventPriority.HIGHEST) 292 public void onBlockPlace(BlockPlaceEvent event) { 293 if (!Settings.Paper_Components.TILE_ENTITY_CHECK || !Settings.Enabled_Components.CHUNK_PROCESSOR) { 294 return; 295 } 296 if (!(event.getBlock().getState(false) instanceof TileState)) { 297 return; 298 } 299 final Location location = BukkitUtil.adapt(event.getBlock().getLocation()); 300 final PlotArea plotArea = location.getPlotArea(); 301 if (plotArea == null) { 302 return; 303 } 304 final int tileEntityCount = event.getBlock().getChunk().getTileEntities(false).length; 305 if (tileEntityCount >= Settings.Chunk_Processor.MAX_TILES) { 306 final PlotPlayer<?> plotPlayer = BukkitUtil.adapt(event.getPlayer()); 307 plotPlayer.sendMessage( 308 TranslatableCaption.of("errors.tile_entity_cap_reached"), 309 TagResolver.resolver("amount", Tag.inserting(Component.text(Settings.Chunk_Processor.MAX_TILES))) 310 ); 311 event.setCancelled(true); 312 event.setBuild(false); 313 } 314 } 315 316 /** 317 * Unsure if this will be any performance improvement over the spigot version, 318 * but here it is anyway :) 319 * 320 * @param event Paper's PlayerLaunchProjectileEvent 321 */ 322 @EventHandler 323 public void onProjectileLaunch(PlayerLaunchProjectileEvent event) { 324 if (!Settings.Paper_Components.PLAYER_PROJECTILE) { 325 return; 326 } 327 Projectile entity = event.getProjectile(); 328 ProjectileSource shooter = entity.getShooter(); 329 if (!(shooter instanceof Player)) { 330 return; 331 } 332 Location location = BukkitUtil.adapt(entity.getLocation()); 333 PlotArea area = location.getPlotArea(); 334 if (area == null) { 335 return; 336 } 337 PlotPlayer<Player> pp = BukkitUtil.adapt((Player) shooter); 338 Plot plot = location.getOwnedPlot(); 339 340 if (plot == null) { 341 if (!PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, ProjectilesFlag.class, true) && !pp.hasPermission( 342 Permission.PERMISSION_ADMIN_PROJECTILE_ROAD 343 )) { 344 pp.sendMessage( 345 TranslatableCaption.of("permission.no_permission_event"), 346 TagResolver.resolver( 347 "node", 348 Tag.inserting(Permission.PERMISSION_ADMIN_PROJECTILE_ROAD) 349 ) 350 ); 351 entity.remove(); 352 event.setCancelled(true); 353 } 354 } else if (!plot.hasOwner()) { 355 if (!pp.hasPermission(Permission.PERMISSION_ADMIN_PROJECTILE_UNOWNED)) { 356 pp.sendMessage( 357 TranslatableCaption.of("permission.no_permission_event"), 358 TagResolver.resolver( 359 "node", 360 Tag.inserting(Permission.PERMISSION_ADMIN_PROJECTILE_UNOWNED) 361 ) 362 ); 363 entity.remove(); 364 event.setCancelled(true); 365 } 366 } else if (!plot.isAdded(pp.getUUID())) { 367 if (entity.getType().equals(EntityType.FISHING_HOOK)) { 368 if (plot.getFlag(FishingFlag.class)) { 369 return; 370 } 371 } 372 if (!plot.getFlag(ProjectilesFlag.class)) { 373 if (!pp.hasPermission(Permission.PERMISSION_ADMIN_PROJECTILE_OTHER)) { 374 pp.sendMessage( 375 TranslatableCaption.of("permission.no_permission_event"), 376 TagResolver.resolver( 377 "node", 378 Tag.inserting(Permission.PERMISSION_ADMIN_PROJECTILE_OTHER) 379 ) 380 ); 381 entity.remove(); 382 event.setCancelled(true); 383 } 384 } 385 } 386 } 387 388 @EventHandler 389 public void onAsyncTabCompletion(final AsyncTabCompleteEvent event) { 390 if (!Settings.Paper_Components.ASYNC_TAB_COMPLETION) { 391 return; 392 } 393 String buffer = event.getBuffer(); 394 if (!(event.getSender() instanceof Player)) { 395 return; 396 } 397 if ((!event.isCommand() && !buffer.startsWith("/")) || buffer.indexOf(' ') == -1) { 398 return; 399 } 400 if (buffer.startsWith("/")) { 401 buffer = buffer.substring(1); 402 } 403 final String[] unprocessedArgs = buffer.split(Pattern.quote(" ")); 404 if (unprocessedArgs.length == 1) { 405 return; // We don't do anything in this case 406 } else if (!Settings.Enabled_Components.TAB_COMPLETED_ALIASES 407 .contains(unprocessedArgs[0].toLowerCase(Locale.ENGLISH))) { 408 return; 409 } 410 final String[] args = new String[unprocessedArgs.length - 1]; 411 System.arraycopy(unprocessedArgs, 1, args, 0, args.length); 412 try { 413 final PlotPlayer<?> player = BukkitUtil.adapt((Player) event.getSender()); 414 final Collection<Command> objects = MainCommand.getInstance().tab(player, args, buffer.endsWith(" ")); 415 if (objects == null) { 416 return; 417 } 418 final List<String> result = new ArrayList<>(); 419 for (final com.plotsquared.core.command.Command o : objects) { 420 result.add(o.toString()); 421 } 422 event.setCompletions(result); 423 event.setHandled(true); 424 } catch (final Exception ignored) { 425 } 426 } 427 428 @EventHandler(ignoreCancelled = true) 429 public void onBeaconEffect(final BeaconEffectEvent event) { 430 Block block = event.getBlock(); 431 Location beaconLocation = BukkitUtil.adapt(block.getLocation()); 432 Plot beaconPlot = beaconLocation.getPlot(); 433 434 PlotArea area = beaconLocation.getPlotArea(); 435 if (area == null) { 436 return; 437 } 438 439 Player player = event.getPlayer(); 440 Location playerLocation = BukkitUtil.adapt(player.getLocation()); 441 442 PlotPlayer<Player> plotPlayer = BukkitUtil.adapt(player); 443 Plot playerStandingPlot = playerLocation.getPlot(); 444 if (playerStandingPlot == null) { 445 FlagContainer container = area.getRoadFlagContainer(); 446 if (!getBooleanFlagValue(container, BeaconEffectsFlag.class, true) || 447 (beaconPlot != null && Settings.Enabled_Components.DISABLE_BEACON_EFFECT_OVERFLOW)) { 448 event.setCancelled(true); 449 } 450 return; 451 } 452 453 FlagContainer container = playerStandingPlot.getFlagContainer(); 454 boolean plotBeaconEffects = getBooleanFlagValue(container, BeaconEffectsFlag.class, true); 455 if (playerStandingPlot.equals(beaconPlot)) { 456 if (!plotBeaconEffects) { 457 event.setCancelled(true); 458 } 459 return; 460 } 461 462 if (!plotBeaconEffects || Settings.Enabled_Components.DISABLE_BEACON_EFFECT_OVERFLOW) { 463 event.setCancelled(true); 464 } 465 } 466 467 private boolean getBooleanFlagValue( 468 @NonNull FlagContainer container, 469 @NonNull Class<? extends BooleanFlag<?>> flagClass, 470 boolean defaultValue 471 ) { 472 BooleanFlag<?> flag = container.getFlag(flagClass); 473 return flag == null ? defaultValue : flag.getValue(); 474 } 475 476}