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