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