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.getTargetBlock(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 (!location.isPlotArea()) { 170 return; 171 } 172 //If entities are spawning... the chunk should be loaded? 173 Entity[] entities = event.getSpawnLocation().getChunk().getEntities(); 174 if (entities.length > Settings.Chunk_Processor.MAX_ENTITIES) { 175 event.setShouldAbortSpawn(true); 176 event.setCancelled(true); 177 return; 178 } 179 CreatureSpawnEvent.SpawnReason reason = event.getReason(); 180 switch (reason.toString()) { 181 case "DISPENSE_EGG", "EGG", "OCELOT_BABY", "SPAWNER_EGG" -> { 182 if (!area.isSpawnEggs()) { 183 event.setShouldAbortSpawn(true); 184 event.setCancelled(true); 185 return; 186 } 187 } 188 case "REINFORCEMENTS", "NATURAL", "MOUNT", "PATROL", "RAID", "SHEARED", "SILVERFISH_BLOCK", "ENDER_PEARL", "TRAP", "VILLAGE_DEFENSE", "VILLAGE_INVASION", "BEEHIVE", "CHUNK_GEN" -> { 189 if (!area.isMobSpawning()) { 190 event.setShouldAbortSpawn(true); 191 event.setCancelled(true); 192 return; 193 } 194 } 195 case "BREEDING" -> { 196 if (!area.isSpawnBreeding()) { 197 event.setShouldAbortSpawn(true); 198 event.setCancelled(true); 199 return; 200 } 201 } 202 case "BUILD_IRONGOLEM", "BUILD_SNOWMAN", "BUILD_WITHER", "CUSTOM" -> { 203 if (!area.isSpawnCustom() && event.getType() != EntityType.ARMOR_STAND) { 204 event.setShouldAbortSpawn(true); 205 event.setCancelled(true); 206 return; 207 } 208 } 209 case "SPAWNER" -> { 210 if (!area.isMobSpawnerSpawning()) { 211 event.setShouldAbortSpawn(true); 212 event.setCancelled(true); 213 return; 214 } 215 } 216 } 217 Plot plot = location.getOwnedPlotAbs(); 218 if (plot == null) { 219 EntityType type = event.getType(); 220 // PreCreatureSpawnEvent **should** not be called for DROPPED_ITEM, just for the sake of consistency 221 if (type == EntityType.DROPPED_ITEM) { 222 if (Settings.Enabled_Components.KILL_ROAD_ITEMS) { 223 event.setCancelled(true); 224 } 225 return; 226 } 227 if (!area.isMobSpawning()) { 228 if (type == EntityType.PLAYER) { 229 return; 230 } 231 if (type.isAlive()) { 232 event.setShouldAbortSpawn(true); 233 event.setCancelled(true); 234 } 235 } 236 if (!area.isMiscSpawnUnowned() && !type.isAlive()) { 237 event.setShouldAbortSpawn(true); 238 event.setCancelled(true); 239 } 240 return; 241 } 242 if (Settings.Done.RESTRICT_BUILDING && DoneFlag.isDone(plot)) { 243 event.setShouldAbortSpawn(true); 244 event.setCancelled(true); 245 } 246 } 247 248 @EventHandler 249 public void onPlayerNaturallySpawnCreaturesEvent(PlayerNaturallySpawnCreaturesEvent event) { 250 if (Settings.Paper_Components.CANCEL_CHUNK_SPAWN) { 251 Location location = BukkitUtil.adapt(event.getPlayer().getLocation()); 252 PlotArea area = location.getPlotArea(); 253 if (area != null && !area.isMobSpawning()) { 254 event.setCancelled(true); 255 } 256 } 257 } 258 259 @EventHandler 260 public void onPreSpawnerSpawnEvent(PreSpawnerSpawnEvent event) { 261 if (Settings.Paper_Components.SPAWNER_SPAWN) { 262 Location location = BukkitUtil.adapt(event.getSpawnerLocation()); 263 PlotArea area = location.getPlotArea(); 264 if (area != null && !area.isMobSpawnerSpawning()) { 265 event.setCancelled(true); 266 event.setShouldAbortSpawn(true); 267 } 268 } 269 } 270 271 @EventHandler(priority = EventPriority.HIGHEST) 272 public void onBlockPlace(BlockPlaceEvent event) { 273 if (!Settings.Paper_Components.TILE_ENTITY_CHECK || !Settings.Enabled_Components.CHUNK_PROCESSOR) { 274 return; 275 } 276 if (!(event.getBlock().getState(false) instanceof TileState)) { 277 return; 278 } 279 final Location location = BukkitUtil.adapt(event.getBlock().getLocation()); 280 final PlotArea plotArea = location.getPlotArea(); 281 if (plotArea == null) { 282 return; 283 } 284 final int tileEntityCount = event.getBlock().getChunk().getTileEntities(false).length; 285 if (tileEntityCount >= Settings.Chunk_Processor.MAX_TILES) { 286 final PlotPlayer<?> plotPlayer = BukkitUtil.adapt(event.getPlayer()); 287 plotPlayer.sendMessage( 288 TranslatableCaption.of("errors.tile_entity_cap_reached"), 289 TagResolver.resolver("amount", Tag.inserting(Component.text(Settings.Chunk_Processor.MAX_TILES))) 290 ); 291 event.setCancelled(true); 292 event.setBuild(false); 293 } 294 } 295 296 /** 297 * Unsure if this will be any performance improvement over the spigot version, 298 * but here it is anyway :) 299 * 300 * @param event Paper's PlayerLaunchProjectileEvent 301 */ 302 @EventHandler 303 public void onProjectileLaunch(PlayerLaunchProjectileEvent event) { 304 if (!Settings.Paper_Components.PLAYER_PROJECTILE) { 305 return; 306 } 307 Projectile entity = event.getProjectile(); 308 ProjectileSource shooter = entity.getShooter(); 309 if (!(shooter instanceof Player)) { 310 return; 311 } 312 Location location = BukkitUtil.adapt(entity.getLocation()); 313 PlotArea area = location.getPlotArea(); 314 if (area == null) { 315 return; 316 } 317 PlotPlayer<Player> pp = BukkitUtil.adapt((Player) shooter); 318 Plot plot = location.getOwnedPlot(); 319 320 if (plot == null) { 321 if (!PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, ProjectilesFlag.class, true) && !pp.hasPermission( 322 Permission.PERMISSION_ADMIN_PROJECTILE_ROAD 323 )) { 324 pp.sendMessage( 325 TranslatableCaption.of("permission.no_permission_event"), 326 TagResolver.resolver( 327 "node", 328 Tag.inserting(Permission.PERMISSION_ADMIN_PROJECTILE_ROAD) 329 ) 330 ); 331 entity.remove(); 332 event.setCancelled(true); 333 } 334 } else if (!plot.hasOwner()) { 335 if (!pp.hasPermission(Permission.PERMISSION_ADMIN_PROJECTILE_UNOWNED)) { 336 pp.sendMessage( 337 TranslatableCaption.of("permission.no_permission_event"), 338 TagResolver.resolver( 339 "node", 340 Tag.inserting(Permission.PERMISSION_ADMIN_PROJECTILE_UNOWNED) 341 ) 342 ); 343 entity.remove(); 344 event.setCancelled(true); 345 } 346 } else if (!plot.isAdded(pp.getUUID())) { 347 if (!plot.getFlag(ProjectilesFlag.class)) { 348 if (!pp.hasPermission(Permission.PERMISSION_ADMIN_PROJECTILE_OTHER)) { 349 pp.sendMessage( 350 TranslatableCaption.of("permission.no_permission_event"), 351 TagResolver.resolver( 352 "node", 353 Tag.inserting(Permission.PERMISSION_ADMIN_PROJECTILE_OTHER) 354 ) 355 ); 356 entity.remove(); 357 event.setCancelled(true); 358 } 359 } 360 } 361 } 362 363 @EventHandler 364 public void onAsyncTabCompletion(final AsyncTabCompleteEvent event) { 365 if (!Settings.Paper_Components.ASYNC_TAB_COMPLETION) { 366 return; 367 } 368 String buffer = event.getBuffer(); 369 if (!(event.getSender() instanceof Player)) { 370 return; 371 } 372 if ((!event.isCommand() && !buffer.startsWith("/")) || buffer.indexOf(' ') == -1) { 373 return; 374 } 375 if (buffer.startsWith("/")) { 376 buffer = buffer.substring(1); 377 } 378 final String[] unprocessedArgs = buffer.split(Pattern.quote(" ")); 379 if (unprocessedArgs.length == 1) { 380 return; // We don't do anything in this case 381 } else if (!Settings.Enabled_Components.TAB_COMPLETED_ALIASES 382 .contains(unprocessedArgs[0].toLowerCase(Locale.ENGLISH))) { 383 return; 384 } 385 final String[] args = new String[unprocessedArgs.length - 1]; 386 System.arraycopy(unprocessedArgs, 1, args, 0, args.length); 387 try { 388 final PlotPlayer<?> player = BukkitUtil.adapt((Player) event.getSender()); 389 final Collection<Command> objects = MainCommand.getInstance().tab(player, args, buffer.endsWith(" ")); 390 if (objects == null) { 391 return; 392 } 393 final List<String> result = new ArrayList<>(); 394 for (final com.plotsquared.core.command.Command o : objects) { 395 result.add(o.toString()); 396 } 397 event.setCompletions(result); 398 event.setHandled(true); 399 } catch (final Exception ignored) { 400 } 401 } 402 403 @EventHandler(ignoreCancelled = true) 404 public void onBeaconEffect(final BeaconEffectEvent event) { 405 Block block = event.getBlock(); 406 Location beaconLocation = BukkitUtil.adapt(block.getLocation()); 407 Plot beaconPlot = beaconLocation.getPlot(); 408 409 PlotArea area = beaconLocation.getPlotArea(); 410 if (area == null) { 411 return; 412 } 413 414 Player player = event.getPlayer(); 415 Location playerLocation = BukkitUtil.adapt(player.getLocation()); 416 417 PlotPlayer<Player> plotPlayer = BukkitUtil.adapt(player); 418 Plot playerStandingPlot = playerLocation.getPlot(); 419 if (playerStandingPlot == null) { 420 FlagContainer container = area.getRoadFlagContainer(); 421 if (!getBooleanFlagValue(container, BeaconEffectsFlag.class, true) || 422 (beaconPlot != null && Settings.Enabled_Components.DISABLE_BEACON_EFFECT_OVERFLOW)) { 423 event.setCancelled(true); 424 } 425 return; 426 } 427 428 FlagContainer container = playerStandingPlot.getFlagContainer(); 429 boolean plotBeaconEffects = getBooleanFlagValue(container, BeaconEffectsFlag.class, true); 430 if (playerStandingPlot.equals(beaconPlot)) { 431 if (!plotBeaconEffects) { 432 event.setCancelled(true); 433 } 434 return; 435 } 436 437 if (!plotBeaconEffects || Settings.Enabled_Components.DISABLE_BEACON_EFFECT_OVERFLOW) { 438 event.setCancelled(true); 439 } 440 } 441 442 private boolean getBooleanFlagValue( 443 @NonNull FlagContainer container, 444 @NonNull Class<? extends BooleanFlag<?>> flagClass, 445 boolean defaultValue 446 ) { 447 BooleanFlag<?> flag = container.getFlag(flagClass); 448 return flag == null ? defaultValue : flag.getValue(); 449 } 450 451}