Skip to content

Commit

Permalink
Fix mob removal on folia (#7262)
Browse files Browse the repository at this point in the history
  • Loading branch information
Warriorrrr authored Feb 19, 2024
1 parent e06c22d commit 12ba989
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@
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;
import org.bukkit.block.Sign;
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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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")
Expand Down Expand Up @@ -189,4 +202,21 @@ private Consumer<Event> dragonFireballHitEventListener() {
((Cancellable) event).setCancelled(true);
};
}

private Consumer<EntityEvent> 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()));
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ public class MobRemovalTimerTask extends TownyTimerTask {
public static List<Class<?>> classesOfWildernessMobsToRemove = new ArrayList<>();
public static List<Class<?>> classesOfTownMobsToRemove = new ArrayList<>();
private static final Set<String> 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");
Expand All @@ -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) {
Expand Down Expand Up @@ -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<LivingEntity> 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;

Expand All @@ -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: ");
Expand Down

0 comments on commit 12ba989

Please # to comment.