diff --git a/server/src/main/java/dev/plex/command/impl/MobLimitCMD.java b/server/src/main/java/dev/plex/command/impl/MobLimitCMD.java new file mode 100644 index 0000000..66bbfe6 --- /dev/null +++ b/server/src/main/java/dev/plex/command/impl/MobLimitCMD.java @@ -0,0 +1,96 @@ +package dev.plex.command.impl; + +import dev.plex.command.PlexCommand; +import dev.plex.command.annotation.CommandParameters; +import dev.plex.command.annotation.CommandPermissions; +import dev.plex.command.source.RequiredCommandSource; +import dev.plex.util.PlexUtils; +import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@CommandParameters(name = "moblimit", usage = "/ [on/off/setmax ]", aliases = "entitylimit", description = "Manages the mob limit per chunk.") +@CommandPermissions(permission = "plex.moblimit", source = RequiredCommandSource.ANY) +public class MobLimitCMD extends PlexCommand +{ + @Override + protected Component execute(@NotNull CommandSender sender, @Nullable Player playerSender, String[] args) + { + if (args.length == 0) + { + Chunk chunk = playerSender != null ? playerSender.getLocation().getChunk() : Bukkit.getWorlds().get(0).getChunkAt(0, 0); + + int currentLimit = plugin.config.getInt("entity_limit.max_mobs_per_chunk"); + int currentMobCount = (int) Arrays.stream(chunk.getEntities()) + .filter(entity -> entity instanceof LivingEntity && !(entity instanceof Player)) + .count(); + + String status = plugin.config.getBoolean("entity_limit.mob_limit_enabled") ? "Enabled" : "Disabled"; + return PlexUtils.messageComponent("mobLimitStatus", status, currentMobCount, currentLimit, chunk.getX(), chunk.getZ()); + } + + switch (args[0].toLowerCase()) + { + case "on": + plugin.config.set("entity_limit.mob_limit_enabled", true); + plugin.config.save(); + return PlexUtils.messageComponent("mobLimitToggle", "enabled"); + case "off": + plugin.config.set("entity_limit.mob_limit_enabled", false); + plugin.config.save(); + return PlexUtils.messageComponent("mobLimitToggle", "disabled"); + case "setmax": + try + { + if (args.length != 2) return usage(); + + int newLimit = Integer.parseInt(args[1]); + if (newLimit < 0) throw new NumberFormatException(); + + int limitCeiling = plugin.config.getInt("entity_limit.mob_limit_ceiling"); + if (newLimit > limitCeiling) + { + newLimit = limitCeiling; + sender.sendMessage(PlexUtils.messageComponent("mobLimitCeiling")); + } + + plugin.config.set("entity_limit.max_mobs_per_chunk", newLimit); + plugin.config.save(); + return PlexUtils.messageComponent("mobLimitSet", newLimit); + } + catch (NumberFormatException e) + { + return PlexUtils.messageComponent("unableToParseNumber", args[1]); + } + default: + return usage(); + } + } + + @Override + public @NotNull List smartTabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException + { + if (silentCheckPermission(sender, this.getPermission())) + { + if (args.length == 1) + { + return Arrays.asList("on", "off", "setmax"); + } + if (args.length == 2 && args[0].equals("setmax")) + { + return Collections.emptyList(); + } + return Collections.emptyList(); + } + return Collections.emptyList(); + } +} \ No newline at end of file diff --git a/server/src/main/java/dev/plex/listener/impl/MobListener.java b/server/src/main/java/dev/plex/listener/impl/MobListener.java index 3bde0f6..ec88c2d 100644 --- a/server/src/main/java/dev/plex/listener/impl/MobListener.java +++ b/server/src/main/java/dev/plex/listener/impl/MobListener.java @@ -3,6 +3,7 @@ package dev.plex.listener.impl; import dev.plex.listener.PlexListener; import dev.plex.util.BlockUtils; import dev.plex.util.PlexUtils; +import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; @@ -11,6 +12,7 @@ import org.bukkit.entity.Ageable; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; +import org.bukkit.entity.LivingEntity; import org.bukkit.event.Event; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -70,6 +72,17 @@ public class MobListener extends PlexListener Collection coll = location.getNearbyEntitiesByType(Player.class, 10); PlexUtils.disabledEffectMultiple(coll.toArray(new Player[coll.size()]), location); // dont let intellij auto correct toArray to an empty array (for efficiency) } + + if (plugin.config.getBoolean("entity_limit.mob_limit_enabled")) + { + Location location = event.getLocation(); + Chunk chunk = location.getChunk(); + + if (isEntityLimitReached(chunk, plugin.config.getInt("entity_limit.max_mobs_per_chunk"))) + { + event.setCancelled(true); + } + } } @EventHandler @@ -135,4 +148,11 @@ public class MobListener extends PlexListener } } } + + public static boolean isEntityLimitReached(Chunk chunk, int limit) + { + return Arrays.stream(chunk.getEntities()) + .filter(entity -> entity instanceof LivingEntity && !(entity instanceof Player)) + .count() >= limit; + } } diff --git a/server/src/main/java/dev/plex/util/PlexUtils.java b/server/src/main/java/dev/plex/util/PlexUtils.java index 1137201..22909ab 100644 --- a/server/src/main/java/dev/plex/util/PlexUtils.java +++ b/server/src/main/java/dev/plex/util/PlexUtils.java @@ -15,10 +15,12 @@ import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.Bukkit; +import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.Particle; import org.bukkit.command.Command; import org.bukkit.command.PluginCommandYamlParser; +import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; diff --git a/server/src/main/resources/config.yml b/server/src/main/resources/config.yml index 7d3c2f0..b30ddbf 100644 --- a/server/src/main/resources/config.yml +++ b/server/src/main/resources/config.yml @@ -156,6 +156,15 @@ blocked_entities: - "ENDER_DRAGON" - "MINECART_TNT" +# Limit entities per chunk +entity_limit: + # Is the mob limit enabled? + mob_limit_enabled: true + # The maximum number of mobs allowed in a chunk + max_mobs_per_chunk: 50 + # The available ceiling for the maximum number of mobs + mob_limit_ceiling: 500 + # See https://docs.plex.us.org/docs/customization/config#worlds for documentation # These gamerules apply to all worlds on the server global_gamerules: diff --git a/server/src/main/resources/messages.yml b/server/src/main/resources/messages.yml index 96576a9..fa5114f 100644 --- a/server/src/main/resources/messages.yml +++ b/server/src/main/resources/messages.yml @@ -175,6 +175,18 @@ notAValidMobButValidEntity: "That is a valid entity, but is not a valid mob # 1 - Number of mobs removed removedMobs: "{0} - Removed {1} mobs" autoWipeDisabled: "Item wiping is currently disabled in the config!" +# 0 - The boolean for whether the limit is enabled or disabled +mobLimitToggle: "The mob limit has been {0}" +# 0 - The amount that the mob limit has been set to +mobLimitSet: "The mob limit has been set to: {0}" +# 0 - The boolean for whether the limit is enabled or disabled +# 1 - The current amount of mobs in the world +# 2 - The current set mob limit +# 3 - Chunk x value +# 4 - Chunk z value +mobLimitStatus: "({0}) {1} / {2} per chunk (Chunk: {3}, {4})" +# 0 - The max set limit in config +mobLimitCeiling: "The limit you have entered is too high. Defaulting to the ceiling value from config" commandBlocked: "That command is blocked." # 0 - The command sender # 1 - The message being said