From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: William Blake Galbreath Date: Sat, 12 Dec 2020 21:19:05 -0600 Subject: [PATCH] Purpur: Implement TPSBar Original license: MIT Original project: https://github.com/PurpurMC/Purpur diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java index 39844531b03eb8a6c70700b4ecbf0ff1a557424d..632ae75cb3bbc7a3955872d14ad0fbc2459f32e8 100644 --- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java +++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java @@ -35,6 +35,7 @@ public abstract class CommandNode implements Comparable> { private final boolean forks; private Command command; public LiteralCommandNode clientNode = null; // Paper + private String permission = null; public String getPermission() { return permission; } public void setPermission(String permission) { this.permission = permission; } // Purpur // CraftBukkit start public void removeCommand(String name) { this.children.remove(name); diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java index 7b6b51392b123d34382233adcf4c3d4867bdaa32..ccc78857b51e25640ec1e4dcfe4c76a06d1bbff4 100644 --- a/src/main/java/net/minecraft/commands/CommandSourceStack.java +++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java @@ -317,6 +317,30 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy } } + // Purpur start + public void sendSuccess(@Nullable String message) { + sendSuccess(message, false); + } + + public void sendSuccess(@Nullable String message, boolean broadcastToOps) { + if (message == null) { + return; + } + sendSuccess(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message), broadcastToOps); + } + + public void sendSuccess(@Nullable net.kyori.adventure.text.Component message) { + sendSuccess(message, false); + } + + public void sendSuccess(@Nullable net.kyori.adventure.text.Component message, boolean broadcastToOps) { + if (message == null) { + return; + } + sendSuccess(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), broadcastToOps); + } + // Purpur end + public void sendSuccess(Component message, boolean broadcastToOps) { if (this.source.acceptsSuccess() && !this.silent) { this.source.sendSystemMessage(message); diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java index 1a28f9b348a24448bd4a327e1bf0dfab4dc301f5..265c2f9b6b5301f52ef8cf544bbeb8f4a9242737 100644 --- a/src/main/java/net/minecraft/commands/Commands.java +++ b/src/main/java/net/minecraft/commands/Commands.java @@ -221,6 +221,7 @@ public class Commands { SetPlayerIdleTimeoutCommand.register(this.dispatcher); StopCommand.register(this.dispatcher); WhitelistCommand.register(this.dispatcher); + org.dreeam.leaf.commands.TPSBarCommand.register(this.dispatcher); // Purpur } if (environment.includeIntegrated) { diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 3b73a250d738d75ce2bc2078b380938bc64186ee..ef9511d74a185c800e106c1192bbb8162f037326 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -1130,6 +1130,7 @@ public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop this.safeShutdown(waitForShutdown, false); } public void safeShutdown(boolean waitForShutdown, boolean isRestarting) { + org.dreeam.leaf.tasks.BossBarTask.stopAll(); // Purpur this.isRestarting = isRestarting; this.hasLoggedStop = true; // Paper if (isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java index b104a38d2c879b649a3862876c389564c69e83b4..001738c2553cb73a9ed647302a99b462a609e14a 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java @@ -51,6 +51,7 @@ import org.galemc.gale.command.GaleCommands; import org.galemc.gale.configuration.GaleGlobalConfiguration; import org.galemc.gale.executor.thread.OriginalServerThread; import org.galemc.gale.util.CPUCoresEstimation; +import org.dreeam.leaf.tasks.BossBarTask; import org.slf4j.Logger; // CraftBukkit start @@ -366,6 +367,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface DedicatedServer.LOGGER.info("JMX monitoring enabled"); } + BossBarTask.startAll(); // Purpur return true; } } diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java index aee10fe735b4ccbafbd92908890989b6b8b3685d..74556d5a894a83ca2053c6dbd8117b53b80c28ed 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -275,6 +275,7 @@ public class ServerPlayer extends Player { public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - there are a lot of changes to do if we change all methods leading to the event + private boolean tpsBar = false; // Purpur public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile) { super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); @@ -513,6 +514,7 @@ public class ServerPlayer extends Player { } } + if (nbt.contains("Purpur.TPSBar")) { this.tpsBar = nbt.getBoolean("Purpur.TPSBar"); } // Purpur } @Override @@ -579,6 +581,7 @@ public class ServerPlayer extends Player { } this.getBukkitEntity().setExtraData(nbt); // CraftBukkit + nbt.putBoolean("Purpur.TPSBar", this.tpsBar); // Purpur } // CraftBukkit start - World fallback code, either respawn location or global spawn @@ -2543,4 +2546,14 @@ public class ServerPlayer extends Player { return (CraftPlayer) super.getBukkitEntity(); } // CraftBukkit end + + // Purpur start + public boolean tpsBar() { + return this.tpsBar; + } + + public void tpsBar(boolean tpsBar) { + this.tpsBar = tpsBar; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java index 996f4debc4e5fd2aa6097a0d9769481b9bad70b9..cb5ff86443cd1c599f3167b050cf15ec348eacca 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -106,6 +106,7 @@ import net.minecraft.world.scores.Scoreboard; // Paper import net.minecraft.world.scores.Team; import org.galemc.gale.configuration.GaleGlobalConfiguration; import org.galemc.gale.executor.queue.ScheduledServerThreadTaskQueues; +import org.dreeam.leaf.tasks.BossBarTask; import org.slf4j.Logger; // CraftBukkit start @@ -528,6 +529,7 @@ public abstract class PlayerList { scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam); } // Paper end + BossBarTask.addToAll(player); // Purpur // CraftBukkit - Moved from above, added world if (GaleGlobalConfiguration.get().logToConsole.playerLoginLocations) { // Gale - JettPack - make logging login location configurable PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ()); @@ -643,6 +645,8 @@ public abstract class PlayerList { } public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) { // Paper end + BossBarTask.removeFromAll(entityplayer.getBukkitEntity()); // Purpur + ServerLevel worldserver = entityplayer.getLevel(); entityplayer.awardStat(Stats.LEAVE_GAME); diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java index 6035af2cf08353b3d3801220d8116d8611a0cd37..7774ab6a2e553a40def4bb4dceea9e5f58d31c1e 100644 --- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java +++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java @@ -94,6 +94,7 @@ public final class VanillaCommandWrapper extends BukkitCommand { } public static String getPermission(CommandNode vanillaCommand) { + if (vanillaCommand.getPermission() != null) return vanillaCommand.getPermission(); // Purpur // Paper start final String commandName; if (vanillaCommand.getRedirect() == null) { diff --git a/src/main/java/org/dreeam/leaf/LeafConfig.java b/src/main/java/org/dreeam/leaf/LeafConfig.java index cd8d422cb9a6ba38d0ff74e2028bcff76073e307..395327b9bd07a6860d6b41f76d865f63cd4619a7 100644 --- a/src/main/java/org/dreeam/leaf/LeafConfig.java +++ b/src/main/java/org/dreeam/leaf/LeafConfig.java @@ -1,9 +1,11 @@ package org.dreeam.leaf; import com.google.common.collect.ImmutableMap; +import net.kyori.adventure.bossbar.BossBar; import net.minecraft.server.MinecraftServer; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.MemoryConfiguration; +import org.dreeam.leaf.tasks.TPSBarTask; import org.jetbrains.annotations.Nullable; import org.simpleyaml.configuration.comments.CommentType; import org.simpleyaml.configuration.file.YamlFile; @@ -180,4 +182,30 @@ public class LeafConfig { "To enable this, players can use some packet modules with hack clients and the NoCom Exploit!!"); maxUseItemDistance = getDouble("max-UseItem-distance", maxUseItemDistance, "The max distance of UseItem for players"); } + + public static String commandTPSBarOutput = "Tpsbar toggled for "; + public static String commandTPSBarTitle = "TPS: MSPT: Ping: ms"; + public static BossBar.Overlay commandTPSBarProgressOverlay = BossBar.Overlay.NOTCHED_20; + public static TPSBarTask.FillMode commandTPSBarProgressFillMode = TPSBarTask.FillMode.MSPT; + public static BossBar.Color commandTPSBarProgressColorGood = BossBar.Color.GREEN; + public static BossBar.Color commandTPSBarProgressColorMedium = BossBar.Color.YELLOW; + public static BossBar.Color commandTPSBarProgressColorLow = BossBar.Color.RED; + public static String commandTPSBarTextColorGood = ""; + public static String commandTPSBarTextColorMedium = ""; + public static String commandTPSBarTextColorLow = ""; + public static int commandTPSBarTickInterval = 20; + + private static void commandSettings() { + commandTPSBarOutput = getString("command.messages.tpsbar-command-output", commandTPSBarOutput); + commandTPSBarTitle = getString("command.tpsbar.title", commandTPSBarTitle); + commandTPSBarProgressOverlay = BossBar.Overlay.valueOf(getString("command.tpsbar.overlay", commandTPSBarProgressOverlay.name())); + commandTPSBarProgressFillMode = TPSBarTask.FillMode.valueOf(getString("command.tpsbar.fill-mode", commandTPSBarProgressFillMode.name())); + commandTPSBarProgressColorGood = BossBar.Color.valueOf(getString("command.tpsbar.progress-color.good", commandTPSBarProgressColorGood.name())); + commandTPSBarProgressColorMedium = BossBar.Color.valueOf(getString("command.tpsbar.progress-color.medium", commandTPSBarProgressColorMedium.name())); + commandTPSBarProgressColorLow = BossBar.Color.valueOf(getString("command.tpsbar.progress-color.low", commandTPSBarProgressColorLow.name())); + commandTPSBarTextColorGood = getString("command.tpsbar.text-color.good", commandTPSBarTextColorGood); + commandTPSBarTextColorMedium = getString("command.tpsbar.text-color.medium", commandTPSBarTextColorMedium); + commandTPSBarTextColorLow = getString("command.tpsbar.text-color.low", commandTPSBarTextColorLow); + commandTPSBarTickInterval = getInt("command.tpsbar.tick-interval", commandTPSBarTickInterval); + } } diff --git a/src/main/java/org/dreeam/leaf/commands/TPSBarCommand.java b/src/main/java/org/dreeam/leaf/commands/TPSBarCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..186f29924edd411c6327fb4b53b069e85a4e0e91 --- /dev/null +++ b/src/main/java/org/dreeam/leaf/commands/TPSBarCommand.java @@ -0,0 +1,43 @@ +package org.dreeam.leaf.commands; + +import com.mojang.brigadier.CommandDispatcher; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.server.level.ServerPlayer; +import org.dreeam.leaf.LeafConfig; +import org.dreeam.leaf.tasks.TPSBarTask; + +import java.util.Collection; +import java.util.Collections; + +public class TPSBarCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("tpsbar") + .requires(listener -> listener.hasPermission(2)) + .executes(context -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) + .then(Commands.argument("targets", EntityArgument.players()) + .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) + ) + ).setPermission("bukkit.command.tpsbar"); + } + + private static int execute(CommandSourceStack sender, Collection targets) { + for (ServerPlayer player : targets) { + boolean result = TPSBarTask.instance().togglePlayer(player.getBukkitEntity()); + player.tpsBar(result); + + Component output = MiniMessage.miniMessage().deserialize(LeafConfig.commandTPSBarOutput, + Placeholder.component("onoff", Component.translatable(result ? "options.on" : "options.off") + .color(result ? NamedTextColor.GREEN : NamedTextColor.RED)), + Placeholder.parsed("target", player.getGameProfile().getName())); + + sender.sendSuccess(output, false); + } + return targets.size(); + } +} diff --git a/src/main/java/org/dreeam/leaf/tasks/BossBarTask.java b/src/main/java/org/dreeam/leaf/tasks/BossBarTask.java new file mode 100644 index 0000000000000000000000000000000000000000..ae629c689779339589de3ec28fb6a79bf54da583 --- /dev/null +++ b/src/main/java/org/dreeam/leaf/tasks/BossBarTask.java @@ -0,0 +1,109 @@ +package org.dreeam.leaf.tasks; + +import net.kyori.adventure.bossbar.BossBar; +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; + +public abstract class BossBarTask extends BukkitRunnable { + private final Map bossbars = new HashMap<>(); + private boolean started; + + abstract BossBar createBossBar(); + + abstract void updateBossBar(BossBar bossbar, Player player); + + @Override + public void run() { + Iterator> iter = bossbars.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + Player player = Bukkit.getPlayer(entry.getKey()); + if (player == null) { + iter.remove(); + continue; + } + updateBossBar(entry.getValue(), player); + } + } + + @Override + public void cancel() { + super.cancel(); + new HashSet<>(this.bossbars.keySet()).forEach(uuid -> { + Player player = Bukkit.getPlayer(uuid); + if (player != null) { + removePlayer(player); + } + }); + this.bossbars.clear(); + } + + public boolean removePlayer(Player player) { + BossBar bossbar = this.bossbars.remove(player.getUniqueId()); + if (bossbar != null) { + player.hideBossBar(bossbar); + return true; + } + return false; + } + + public void addPlayer(Player player) { + removePlayer(player); + BossBar bossbar = createBossBar(); + this.bossbars.put(player.getUniqueId(), bossbar); + this.updateBossBar(bossbar, player); + player.showBossBar(bossbar); + } + + public boolean hasPlayer(UUID uuid) { + return this.bossbars.containsKey(uuid); + } + + public boolean togglePlayer(Player player) { + if (removePlayer(player)) { + return false; + } + addPlayer(player); + return true; + } + + public void start() { + stop(); + this.runTaskTimerAsynchronously(new MinecraftInternalPlugin(), 1, 1); + started = true; + } + + public void stop() { + if (started) { + cancel(); + } + } + + public static void startAll() { + TPSBarTask.instance().start(); + } + + public static void stopAll() { + TPSBarTask.instance().stop(); + } + + public static void addToAll(ServerPlayer player) { + Player bukkit = player.getBukkitEntity(); + if (player.tpsBar()) { + TPSBarTask.instance().addPlayer(bukkit); + } + } + + public static void removeFromAll(Player player) { + TPSBarTask.instance().removePlayer(player); + } +} diff --git a/src/main/java/org/dreeam/leaf/tasks/TPSBarTask.java b/src/main/java/org/dreeam/leaf/tasks/TPSBarTask.java new file mode 100644 index 0000000000000000000000000000000000000000..9a184179d5968691a9c50be5536a7ce6b6c51dd0 --- /dev/null +++ b/src/main/java/org/dreeam/leaf/tasks/TPSBarTask.java @@ -0,0 +1,142 @@ +package org.dreeam.leaf.tasks; + +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import org.dreeam.leaf.LeafConfig; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +public class TPSBarTask extends BossBarTask { + private static TPSBarTask instance; + private double tps = 20.0D; + private double mspt = 0.0D; + private int tick = 0; + + public static TPSBarTask instance() { + if (instance == null) { + instance = new TPSBarTask(); + } + return instance; + } + + @Override + BossBar createBossBar() { + return BossBar.bossBar(Component.text(""), 0.0F, instance().getBossBarColor(), LeafConfig.commandTPSBarProgressOverlay); + } + + @Override + void updateBossBar(BossBar bossbar, Player player) { + bossbar.progress(getBossBarProgress()); + bossbar.color(getBossBarColor()); + bossbar.name(MiniMessage.miniMessage().deserialize(LeafConfig.commandTPSBarTitle, + Placeholder.component("tps", getTPSColor()), + Placeholder.component("mspt", getMSPTColor()), + Placeholder.component("ping", getPingColor(player.getPing())) + )); + } + + @Override + public void run() { + if (++tick < LeafConfig.commandTPSBarTickInterval) { + return; + } + tick = 0; + + this.tps = Math.max(Math.min(Bukkit.getTPS()[0], 20.0D), 0.0D); + this.mspt = Bukkit.getAverageTickTime(); + + super.run(); + } + + private float getBossBarProgress() { + if (LeafConfig.commandTPSBarProgressFillMode == FillMode.MSPT) { + return Math.max(Math.min((float) mspt / 50.0F, 1.0F), 0.0F); + } else { + return Math.max(Math.min((float) tps / 20.0F, 1.0F), 0.0F); + } + } + + private BossBar.Color getBossBarColor() { + if (isGood(LeafConfig.commandTPSBarProgressFillMode)) { + return LeafConfig.commandTPSBarProgressColorGood; + } else if (isMedium(LeafConfig.commandTPSBarProgressFillMode)) { + return LeafConfig.commandTPSBarProgressColorMedium; + } else { + return LeafConfig.commandTPSBarProgressColorLow; + } + } + + private boolean isGood(FillMode mode) { + return isGood(mode, 0); + } + + private boolean isGood(FillMode mode, int ping) { + if (mode == FillMode.MSPT) { + return mspt < 40; + } else if (mode == FillMode.TPS) { + return tps >= 19; + } else if (mode == FillMode.PING) { + return ping < 100; + } else { + return false; + } + } + + private boolean isMedium(FillMode mode) { + return isMedium(mode, 0); + } + + private boolean isMedium(FillMode mode, int ping) { + if (mode == FillMode.MSPT) { + return mspt < 50; + } else if (mode == FillMode.TPS) { + return tps >= 15; + } else if (mode == FillMode.PING) { + return ping < 200; + } else { + return false; + } + } + + private Component getTPSColor() { + String color; + if (isGood(FillMode.TPS)) { + color = LeafConfig.commandTPSBarTextColorGood; + } else if (isMedium(FillMode.TPS)) { + color = LeafConfig.commandTPSBarTextColorMedium; + } else { + color = LeafConfig.commandTPSBarTextColorLow; + } + return MiniMessage.miniMessage().deserialize(color, Placeholder.parsed("text", String.format("%.2f", tps))); + } + + private Component getMSPTColor() { + String color; + if (isGood(FillMode.MSPT)) { + color = LeafConfig.commandTPSBarTextColorGood; + } else if (isMedium(FillMode.MSPT)) { + color = LeafConfig.commandTPSBarTextColorMedium; + } else { + color = LeafConfig.commandTPSBarTextColorLow; + } + return MiniMessage.miniMessage().deserialize(color, Placeholder.parsed("text", String.format("%.2f", mspt))); + } + + private Component getPingColor(int ping) { + String color; + if (isGood(FillMode.PING, ping)) { + color = LeafConfig.commandTPSBarTextColorGood; + } else if (isMedium(FillMode.PING, ping)) { + color = LeafConfig.commandTPSBarTextColorMedium; + } else { + color = LeafConfig.commandTPSBarTextColorLow; + } + return MiniMessage.miniMessage().deserialize(color, Placeholder.parsed("text", String.format("%s", ping))); + } + + public enum FillMode { + TPS, MSPT, PING + } +}