diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/TownyTimerHandler.java b/Towny/src/main/java/com/palmergames/bukkit/towny/TownyTimerHandler.java index 741f0afa08..2d726f5344 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/TownyTimerHandler.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/TownyTimerHandler.java @@ -59,7 +59,7 @@ public static void toggleTownyRepeatingTimer(boolean on) { public static void toggleMobRemoval(boolean on) { - if (on && !isMobRemovalRunning()) { + if (on && !isMobRemovalRunning() && !plugin.isFolia()) { mobRemoveTask = plugin.getScheduler().runRepeating(new MobRemovalTimerTask(plugin), 1, TimeTools.convertToTicks(TownySettings.getMobRemovalSpeed())); } else if (!on && isMobRemovalRunning()) { mobRemoveTask.cancel(); diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyPaperEvents.java b/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyPaperEvents.java index 4ca91597a9..d1b1d728d6 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyPaperEvents.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyPaperEvents.java @@ -3,9 +3,14 @@ import com.palmergames.bukkit.towny.Towny; import com.palmergames.bukkit.towny.TownyAPI; import com.palmergames.bukkit.towny.TownyMessaging; +import com.palmergames.bukkit.towny.TownySettings; import com.palmergames.bukkit.towny.event.executors.TownyActionEventExecutor; +import com.palmergames.bukkit.towny.hooks.PluginIntegrations; +import com.palmergames.bukkit.towny.object.TownyWorld; +import com.palmergames.bukkit.towny.tasks.MobRemovalTimerTask; import com.palmergames.bukkit.towny.utils.BorderUtil; import com.palmergames.util.JavaUtil; +import com.palmergames.util.TimeTools; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.block.Block; @@ -13,6 +18,7 @@ import org.bukkit.entity.AreaEffectCloud; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; import org.bukkit.event.Cancellable; @@ -22,6 +28,7 @@ import org.bukkit.event.block.BlockEvent; import org.bukkit.event.block.TNTPrimeEvent; import org.bukkit.event.entity.EntityChangeBlockEvent; +import org.bukkit.event.entity.EntityEvent; import org.bukkit.event.player.PlayerEvent; import org.bukkit.projectiles.BlockProjectileSource; import org.jetbrains.annotations.ApiStatus; @@ -52,6 +59,8 @@ public class TownyPaperEvents implements Listener { public static final MethodHandle DRAGON_FIREBALL_GET_EFFECT_CLOUD = JavaUtil.getMethodHandle(DRAGON_FIREBALL_HIT_EVENT, "getAreaEffectCloud"); + public static final String ADD_TO_WORLD_EVENT = "com.destroystokyo.paper.event.entity.EntityAddToWorldEvent"; + public TownyPaperEvents(Towny plugin) { this.plugin = plugin; } @@ -78,6 +87,10 @@ else if (GET_PRIMER_ENTITY != null) { registerEvent(DRAGON_FIREBALL_HIT_EVENT, this::dragonFireballHitEventListener, EventPriority.LOW, true); TownyMessaging.sendDebugMsg("Using " + DRAGON_FIREBALL_GET_EFFECT_CLOUD + " listener."); } + + if (this.plugin.isFolia()) { + registerEvent(ADD_TO_WORLD_EVENT, this::entityAddToWorldListener, EventPriority.MONITOR, false /* n/a */); + } } @SuppressWarnings("unchecked") @@ -189,4 +202,21 @@ private Consumer dragonFireballHitEventListener() { ((Cancellable) event).setCancelled(true); }; } + + private Consumer entityAddToWorldListener() { + return event -> { + if (!(event.getEntity() instanceof LivingEntity entity)) + return; + + if (entity instanceof Player || PluginIntegrations.getInstance().isNPC(entity)) + return; + + plugin.getScheduler().runRepeating(entity, () -> { + final TownyWorld world = TownyAPI.getInstance().getTownyWorld(entity.getWorld()); + + if (MobRemovalTimerTask.isRemovingEntities(world)) + MobRemovalTimerTask.checkEntity(plugin, world, entity); + }, 1L, TimeTools.convertToTicks(TownySettings.getMobRemovalSpeed())); + }; + } } diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/tasks/MobRemovalTimerTask.java b/Towny/src/main/java/com/palmergames/bukkit/towny/tasks/MobRemovalTimerTask.java index 57a9c779f9..c68700370f 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/tasks/MobRemovalTimerTask.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/tasks/MobRemovalTimerTask.java @@ -37,7 +37,12 @@ public class MobRemovalTimerTask extends TownyTimerTask { public static List> classesOfWildernessMobsToRemove = new ArrayList<>(); public static List> classesOfTownMobsToRemove = new ArrayList<>(); private static final Set ignoredSpawnReasons = new HashSet<>(); - private boolean isRemovingKillerBunny; + private static boolean isRemovingKillerBunny; + + static { + populateFields(); + TownySettings.addReloadListener(NamespacedKey.fromString("towny:mob-removal-task"), config -> populateFields()); + } // https://jd.papermc.io/paper/1.20/org/bukkit/entity/Entity.html#getEntitySpawnReason() private static final MethodHandle GET_SPAWN_REASON = JavaUtil.getMethodHandle(Entity.class, "getEntitySpawnReason"); @@ -46,7 +51,6 @@ public MobRemovalTimerTask(Towny plugin) { super(plugin); populateFields(); - TownySettings.addReloadListener(NamespacedKey.fromString("towny:mob-removal-task"), config -> this.populateFields()); } public static boolean isRemovingWorldEntity(LivingEntity livingEntity) { @@ -86,79 +90,98 @@ public void run() { for (final World world : Bukkit.getWorlds()) { // Filter worlds not using towny. final TownyWorld townyWorld = TownyAPI.getInstance().getTownyWorld(world); - if (townyWorld == null || !townyWorld.isUsingTowny()) - continue; - - // Filter worlds that will always pass all checks in a world, regardless of possible conditions. - if (townyWorld.isForceTownMobs() && townyWorld.hasWorldMobs()) + if (!isRemovingEntities(townyWorld)) continue; - final List entities = world.getLivingEntities(); - if (entities.isEmpty()) - continue; - - for (final LivingEntity entity : entities) { - // Check if entity is a player or Citizens NPC - if (entity instanceof Player || PluginIntegrations.getInstance().isNPC(entity)) - continue; - - // Handles entities Globally. - if (!townyWorld.hasWorldMobs() && isRemovingWorldEntity(entity)) { - removeEntity(entity); - continue; - } - - final Runnable runnable = () -> { - final Location livingEntityLoc = entity.getLocation(); - final TownBlock townBlock = TownyAPI.getInstance().getTownBlock(livingEntityLoc); - - // Handles entities in the wilderness. - if (townBlock == null) { - if (townyWorld.hasWildernessMobs() || !isRemovingWildernessEntity(entity)) - return; - } else { - // The entity is inside of a town. - - // Check if mobs are always allowed inside towns in this world, if the townblock allows it, or if the town has mobs forced on. - if (townyWorld.isForceTownMobs() || townBlock.getPermissions().mobs || townBlock.getTownOrNull().isAdminEnabledMobs()) - return; - - // Check that Towny is removing this type of entity inside towns. - if (!isRemovingTownEntity(entity)) - return; - } - - // Check if this is an EliteMob before we do any skipping-removal-of-named-mobs. - if (PluginIntegrations.getInstance().checkHostileEliteMobs(entity)) { - removeEntity(entity); - return; - } - - // Special check if it's a rabbit, for the Killer Bunny variant. - if (entity instanceof Rabbit rabbit && isRemovingKillerBunny && rabbit.getRabbitType() == Rabbit.Type.THE_KILLER_BUNNY) { - removeEntity(entity); - return; - } - - if (TownySettings.isSkippingRemovalOfNamedMobs() && entity.getCustomName() != null) - return; - - // Don't remove if the entity's spawn reason is considered ignored by the config - if (isSpawnReasonIgnored(entity)) - return; - - removeEntity(entity); - }; - - if (plugin.isFolia()) - plugin.getScheduler().run(entity, runnable); - else - runnable.run(); + for (final LivingEntity entity : world.getLivingEntities()) { + checkEntity(plugin, townyWorld, entity); } } } + + /** + * @param world The world to check + * @return Whether entities have a chance of being removed in this world + */ + public static boolean isRemovingEntities(final @Nullable TownyWorld world) { + if (world == null || !world.isUsingTowny()) + return false; + + // Filter worlds that will always pass all checks in a world, regardless of possible conditions. + if (world.isForceTownMobs() && world.hasWorldMobs()) + return false; + + return true; + } + + /** + * Checks and removes entities if necessary. Can be called from any thread. + * @param plugin Towny's plugin instance + * @param townyWorld The world the entity is in + * @param ent The entity to check + */ + public static void checkEntity(final @NotNull Towny plugin, final @NotNull TownyWorld townyWorld, final @NotNull Entity ent) { + if (!(ent instanceof LivingEntity entity)) + return; + + if (entity instanceof Player || PluginIntegrations.getInstance().isNPC(entity)) + return; + + // Handles entities Globally. + if (!townyWorld.hasWorldMobs() && isRemovingWorldEntity(entity)) { + removeEntity(plugin, entity); + return; + } + + final Runnable runnable = () -> { + final Location livingEntityLoc = entity.getLocation(); + final TownBlock townBlock = TownyAPI.getInstance().getTownBlock(livingEntityLoc); + + // Handles entities in the wilderness. + if (townBlock == null) { + if (townyWorld.hasWildernessMobs() || !isRemovingWildernessEntity(entity)) + return; + } else { + // The entity is inside of a town. + + // Check if mobs are always allowed inside towns in this world, if the townblock allows it, or if the town has mobs forced on. + if (townyWorld.isForceTownMobs() || townBlock.getPermissions().mobs || townBlock.getTownOrNull().isAdminEnabledMobs()) + return; + + // Check that Towny is removing this type of entity inside towns. + if (!isRemovingTownEntity(entity)) + return; + } + + // Check if this is an EliteMob before we do any skipping-removal-of-named-mobs. + if (PluginIntegrations.getInstance().checkHostileEliteMobs(entity)) { + removeEntity(plugin, entity); + return; + } + + // Special check if it's a rabbit, for the Killer Bunny variant. + if (entity instanceof Rabbit rabbit && isRemovingKillerBunny && rabbit.getRabbitType() == Rabbit.Type.THE_KILLER_BUNNY) { + removeEntity(plugin, entity); + return; + } + + if (TownySettings.isSkippingRemovalOfNamedMobs() && entity.getCustomName() != null) + return; + + // Don't remove if the entity's spawn reason is considered ignored by the config + if (isSpawnReasonIgnored(entity)) + return; + + removeEntity(plugin, entity); + }; + + if (plugin.getScheduler().isEntityThread(entity)) + runnable.run(); + else + plugin.getScheduler().run(entity, runnable); + } - private void removeEntity(@NotNull Entity entity) { + private static void removeEntity(final @NotNull Towny plugin, final @NotNull Entity entity) { if (MobRemovalEvent.getHandlerList().getRegisteredListeners().length > 0 && BukkitTools.isEventCancelled(new MobRemovalEvent(entity))) return; @@ -168,7 +191,7 @@ private void removeEntity(@NotNull Entity entity) { entity.remove(); } - private void populateFields() { + private static void populateFields() { classesOfWorldMobsToRemove = EntityTypeUtil.parseLivingEntityClassNames(TownySettings.getWorldMobRemovalEntities(), "WorldMob: "); classesOfWildernessMobsToRemove = EntityTypeUtil.parseLivingEntityClassNames(TownySettings.getWildernessMobRemovalEntities(),"WildernessMob: "); classesOfTownMobsToRemove = EntityTypeUtil.parseLivingEntityClassNames(TownySettings.getTownMobRemovalEntities(), "TownMob: ");