diff --git a/src/main/java/com/legacyminecraft/poseidon/Poseidon.java b/src/main/java/com/legacyminecraft/poseidon/Poseidon.java new file mode 100644 index 00000000..2b116dd6 --- /dev/null +++ b/src/main/java/com/legacyminecraft/poseidon/Poseidon.java @@ -0,0 +1,21 @@ +package com.legacyminecraft.poseidon; + +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.CraftServer; + +import java.util.LinkedList; + +public class Poseidon { + + /** + * Returns a list of the server's TPS (Ticks Per Second) records for performance monitoring. + * The list contains Double values indicating the TPS at each second, ordered from most recent to oldest. + * + * @return LinkedList of TPS records. + */ + public static LinkedList getTpsRecords() { + return ((CraftServer) Bukkit.getServer()).getServer().getTpsRecords(); + } + + +} diff --git a/src/main/java/com/legacyminecraft/poseidon/PoseidonConfig.java b/src/main/java/com/legacyminecraft/poseidon/PoseidonConfig.java index 3dd9c613..2d9b0930 100644 --- a/src/main/java/com/legacyminecraft/poseidon/PoseidonConfig.java +++ b/src/main/java/com/legacyminecraft/poseidon/PoseidonConfig.java @@ -104,6 +104,14 @@ private void write() { generateConfigOption("world.settings.speed-hack-check.teleport", true); generateConfigOption("world.settings.speed-hack-check.distance", 100.0D); generateConfigOption("world.settings.speed-hack-check.info", "This setting allows you to configure the automatic speedhack detection."); + //Mob Spawner Area Limit (8 chunks) + generateConfigOption("world.settings.mob-spawner-area-limit.enable", true); + generateConfigOption("world.settings.mob-spawner-area-limit.limit", 150); + generateConfigOption("world.settings.mob-spawner-area-limit.chunk-radius", 8); + generateConfigOption("world.settings.mob-spawner-area-limit.info", + "This setting controls the maximum number of entities of a mob spawner type that can exist within the defined chunk radius around a mob spawner. If the number of entities exceeds this limit, the spawner will stop spawning additional entities of that type. This is useful to stop the extreme lag that can be caused by mob spawners."); + + //generateConfigOption("world-settings.eject-from-vehicle-on-teleport.enabled", true); //generateConfigOption("world-settings.eject-from-vehicle-on-teleport.info", "Eject the player from a boat or minecart before teleporting them preventing cross world coordinate exploits."); @@ -139,6 +147,11 @@ private void write() { generateConfigOption("message.player.join", "\u00A7e%player% joined the game."); generateConfigOption("message.player.leave", "\u00A7e%player% left the game."); + //Optional Poseidon Commands + generateConfigOption("command.info", "This section allows you to enable or disable optional Poseidon commands. This is useful if you have a plugin that conflicts with a Poseidon command."); + generateConfigOption("command.tps.info", "Enables the /tps command to show the server's TPS for various intervals."); + generateConfigOption("command.tps.enabled", true); + //Tree Leave Destroy Blacklist if (Boolean.valueOf(String.valueOf(getConfigOption("world.settings.block-tree-growth.enabled", true)))) { if (String.valueOf(this.getConfigOption("world.settings.block-tree-growth.list", "")).trim().isEmpty()) { diff --git a/src/main/java/com/legacyminecraft/poseidon/commands/TPSCommand.java b/src/main/java/com/legacyminecraft/poseidon/commands/TPSCommand.java new file mode 100644 index 00000000..a085f06f --- /dev/null +++ b/src/main/java/com/legacyminecraft/poseidon/commands/TPSCommand.java @@ -0,0 +1,75 @@ +package com.legacyminecraft.poseidon.commands; + +import com.legacyminecraft.poseidon.Poseidon; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.defaults.VanillaCommand; + +import java.util.LinkedList; +import java.util.LinkedHashMap; +import java.util.Map; + +public class TPSCommand extends Command { + + private final LinkedHashMap intervals = new LinkedHashMap<>(); + + public TPSCommand(String name) { + super(name); + this.description = "Shows the server's TPS for various intervals"; + this.usageMessage = "/tps"; + this.setPermission("poseidon.command.tps"); + + // Define the intervals for TPS calculation + intervals.put("5s", 5); + intervals.put("30s", 30); + intervals.put("1m", 60); + intervals.put("5m", 300); + intervals.put("10m", 600); + intervals.put("15m", 900); + } + + @Override + public boolean execute(CommandSender sender, String currentAlias, String[] args) { + if (!testPermission(sender)) return true; + + LinkedList tpsRecords = Poseidon.getTpsRecords(); + StringBuilder message = new StringBuilder("§bServer TPS: "); + + // Calculate and format TPS for each interval dynamically + for (Map.Entry entry : intervals.entrySet()) { + double averageTps = calculateAverage(tpsRecords, entry.getValue()); + message.append(formatTps(averageTps)).append(" (").append(entry.getKey()).append("), "); + } + + // Remove the trailing comma and space + if (message.length() > 0) { + message.setLength(message.length() - 2); + } + + sender.sendMessage(message.toString()); + return true; + } + + private double calculateAverage(LinkedList records, int seconds) { + int size = Math.min(records.size(), seconds); + if (size == 0) return 20.0; + + double total = 0; + for (int i = 0; i < size; i++) { + total += records.get(i); + } + return total / size; + } + + private String formatTps(double tps) { + String colorCode; + if (tps >= 19) { + colorCode = "§a"; + } else if (tps >= 15) { + colorCode = "§e"; + } else { + colorCode = "§c"; + } + return colorCode + String.format("%.2f", tps); + } +} diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 871d6cd8..dff247ce 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -467,6 +467,16 @@ public void run() { } } + //Project Poseidon Start - Tick Update + private final LinkedList tpsRecords = new LinkedList<>(); + private long lastTick = System.currentTimeMillis(); + private int tickCount = 0; + + public LinkedList getTpsRecords() { + return tpsRecords; + } + //Project Poseidon End - Tick Update + private void h() { ArrayList arraylist = new ArrayList(); Iterator iterator = trackerList.keySet().iterator(); @@ -494,6 +504,26 @@ private void h() { ((CraftScheduler) this.server.getScheduler()).mainThreadHeartbeat(this.ticks); // CraftBukkit + //Project Poseidon Start - Tick Update + long currentTime = System.currentTimeMillis(); + tickCount++; + + //Check if a second has passed + if (currentTime - lastTick >= 1000) { + double tps = tickCount / ((currentTime - lastTick) / 1000.0); + tpsRecords.addFirst(tps); + if(tpsRecords.size() > 900) { //Don't keep more than 15 minutes of data + tpsRecords.removeLast(); + } + + tickCount = 0; + lastTick = currentTime; + } + + //Project Poseidon End - Tick Update + + + for (j = 0; j < this.worlds.size(); ++j) { // CraftBukkit // if (j == 0 || this.propertyManager.getBoolean("allow-nether", true)) { // CraftBukkit WorldServer worldserver = this.worlds.get(j); // CraftBukkit diff --git a/src/main/java/net/minecraft/server/TileEntityMobSpawner.java b/src/main/java/net/minecraft/server/TileEntityMobSpawner.java index 2668427d..349b220d 100644 --- a/src/main/java/net/minecraft/server/TileEntityMobSpawner.java +++ b/src/main/java/net/minecraft/server/TileEntityMobSpawner.java @@ -1,7 +1,10 @@ package net.minecraft.server; +import com.legacyminecraft.poseidon.PoseidonConfig; import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; +import java.util.List; + public class TileEntityMobSpawner extends TileEntity { public int spawnDelay = -1; @@ -9,6 +12,10 @@ public class TileEntityMobSpawner extends TileEntity { public double b; public double c = 0.0D; + private static boolean poseidonAreaLimit = PoseidonConfig.getInstance().getConfigBoolean("world.settings.mob-spawner-area-limit.enable"); + private static int poseidonAreaLimitRadius = PoseidonConfig.getInstance().getConfigInteger("world.settings.mob-spawner-area-limit.limit"); + private static int poseidonChunkRadius = PoseidonConfig.getInstance().getConfigInteger("world.settings.mob-spawner-area-limit.chunk-radius"); + public TileEntityMobSpawner() { this.spawnDelay = 20; } @@ -61,6 +68,8 @@ public void g_() { } // CraftBukkit end + + // Check mob cap within the spawning radius int j = this.world.a(entityliving.getClass(), AxisAlignedBB.b((double) this.x, (double) this.y, (double) this.z, (double) (this.x + 1), (double) (this.y + 1), (double) (this.z + 1)).b(8.0D, 4.0D, 8.0D)).size(); if (j >= 6) { @@ -68,6 +77,18 @@ public void g_() { return; } + //Poseidon Start - Ensure the mob cound of the specific type of mob is under the defined limit within the area + if(poseidonAreaLimit) { + double chunkSize = 16.0D; + AxisAlignedBB searchArea = AxisAlignedBB.b(this.x - poseidonChunkRadius * chunkSize, 0.0D, this.z - poseidonChunkRadius * chunkSize, this.x + poseidonChunkRadius * chunkSize, 128, this.z + poseidonChunkRadius * chunkSize); + List existingEntities = this.world.a(entityliving.getClass(), searchArea); + if (existingEntities.size() >= poseidonAreaLimitRadius) { + this.c(); + return; + } + } + //Poseidon End + if (entityliving != null) { double d3 = (double) this.x + (this.world.random.nextDouble() - this.world.random.nextDouble()) * 4.0D; double d4 = (double) (this.y + this.world.random.nextInt(3) - 1); diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java index cca4d71a..3736fe30 100644 --- a/src/main/java/org/bukkit/command/SimpleCommandMap.java +++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java @@ -1,5 +1,7 @@ package org.bukkit.command; +import com.legacyminecraft.poseidon.PoseidonConfig; +import com.legacyminecraft.poseidon.commands.TPSCommand; import org.bukkit.Server; import org.bukkit.command.defaults.*; @@ -43,10 +45,14 @@ public SimpleCommandMap(final Server server) { } private void setDefaultCommands(final Server server) { - register("poseidon", new PoseidonCommand("poseidon")); register("bukkit", new VersionCommand("version")); register("bukkit", new ReloadCommand("reload")); register("bukkit", new PluginsCommand("plugins")); + + //Poseidon Command + register("poseidon", new PoseidonCommand("poseidon")); + if (PoseidonConfig.getInstance().getConfigBoolean("command.tps.enabled")) + register("poseidon", new TPSCommand("tps")); } /**