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