From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <blake.galbreath@gmail.com>
Date: Thu, 1 Jul 2021 15:48:02 -0500
Subject: [PATCH] Add uptime command


diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java
index 73953ea69776bfe1dcb1504cd14a0f003f1b5766..528fd87ae1c1c01e13885d88fc8056b0f932fb61 100644
--- a/src/main/java/net/minecraft/commands/Commands.java
+++ b/src/main/java/net/minecraft/commands/Commands.java
@@ -201,6 +201,7 @@ public class Commands {
             net.pl3x.purpur.command.CreditsCommand.register(this.dispatcher); // Purpur
             net.pl3x.purpur.command.DemoCommand.register(this.dispatcher); // Purpur
             net.pl3x.purpur.command.PingCommand.register(this.dispatcher); // Purpur
+            net.pl3x.purpur.command.UptimeCommand.register(this.dispatcher); // Purpur
             net.pl3x.purpur.command.TPSBarCommand.register(this.dispatcher); // Purpur
         }
 
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index a456c7f6eedb2cff1b58ec6f0ef330c74dfcdaed..8d9704ce916b13de96a6c95bfcb553ddd130b21c 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -294,6 +294,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
     //public ConsoleReader reader; // Paper
     public static int currentTick = 0; // Paper - Further improve tick loop
     public static long currentTickLong = 0L; // Tuinity
+    public static final long startTimeMillis = System.currentTimeMillis();
     public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
     public int autosavePeriod;
     public boolean serverAutoSave = false; // Paper
diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java
index f1db3948b7284da35b9905862229bafb87a863fb..2def6767b2ee97d9c662b93796ed97bcfd3bbebc 100644
--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java
+++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java
@@ -166,6 +166,7 @@ public class PurpurConfig {
     public static String demoCommandOutput = "<green>%s has been shown the demo screen";
     public static String pingCommandOutput = "<green>%s's ping is %sms";
     public static String dontRunWithScissors = "<red><italic>Don't run with scissors!";
+    public static String uptimeCommandOutput = "<green>Server uptime is <uptime>";
     public static String unverifiedUsername = "default";
     private static void messages() {
         cannotRideMob = getString("settings.messages.cannot-ride-mob", cannotRideMob);
@@ -177,6 +178,7 @@ public class PurpurConfig {
         demoCommandOutput = getString("settings.messages.demo-command-output", demoCommandOutput);
         pingCommandOutput = getString("settings.messages.ping-command-output", pingCommandOutput);
         dontRunWithScissors = getString("settings.messages.dont-run-with-scissors", dontRunWithScissors);
+        uptimeCommandOutput = getString("settings.messages.uptime-command-output", uptimeCommandOutput);
         unverifiedUsername = getString("settings.messages.unverified-username", unverifiedUsername);
     }
 
@@ -359,6 +361,15 @@ public class PurpurConfig {
     public static int commandTPSBarTickInterval = 20;
     public static boolean commandGamemodeRequiresPermission = false;
     public static boolean hideHiddenPlayersFromEntitySelector = false;
+    public static String uptimeFormat = "<days><hours><minutes><seconds>";
+    public static String uptimeDay = "%02d day, ";
+    public static String uptimeDays = "%02d days, ";
+    public static String uptimeHour = "%02d hour, ";
+    public static String uptimeHours = "%02d hours, ";
+    public static String uptimeMinute = "%02d minute, and ";
+    public static String uptimeMinutes = "%02d minutes, and ";
+    public static String uptimeSecond = "%02d second";
+    public static String uptimeSeconds = "%02d seconds";
     private static void commandSettings() {
         commandTPSBarTitle = getString("settings.command.tpsbar.title", commandTPSBarTitle);
         commandTPSBarProgressOverlay = BossBar.Overlay.valueOf(getString("settings.command.tpsbar.overlay", commandTPSBarProgressOverlay.name()));
@@ -372,6 +383,15 @@ public class PurpurConfig {
         commandTPSBarTickInterval = getInt("settings.command.tpsbar.tick-interval", commandTPSBarTickInterval);
         commandGamemodeRequiresPermission = getBoolean("settings.command.gamemode.requires-specific-permission", commandGamemodeRequiresPermission);
         hideHiddenPlayersFromEntitySelector = getBoolean("settings.command.hide-hidden-players-from-entity-selector", hideHiddenPlayersFromEntitySelector);
+        uptimeFormat = getString("settings.command.uptime.format", uptimeFormat);
+        uptimeDay = getString("settings.command.uptime.day", uptimeDay);
+        uptimeDays = getString("settings.command.uptime.days", uptimeDays);
+        uptimeHour = getString("settings.command.uptime.hour", uptimeHour);
+        uptimeHours = getString("settings.command.uptime.hours", uptimeHours);
+        uptimeMinute = getString("settings.command.uptime.minute", uptimeMinute);
+        uptimeMinutes = getString("settings.command.uptime.minutes", uptimeMinutes);
+        uptimeSecond = getString("settings.command.uptime.second", uptimeSecond);
+        uptimeSeconds = getString("settings.command.uptime.seconds", uptimeSeconds);
     }
 
     public static boolean barrelSixRows = false;
diff --git a/src/main/java/net/pl3x/purpur/command/UptimeCommand.java b/src/main/java/net/pl3x/purpur/command/UptimeCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..c63ad24ca4ea4c2bd6239c7fe9beb64e78332e77
--- /dev/null
+++ b/src/main/java/net/pl3x/purpur/command/UptimeCommand.java
@@ -0,0 +1,55 @@
+package net.pl3x.purpur.command;
+
+import com.mojang.brigadier.CommandDispatcher;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.kyori.adventure.text.minimessage.Template;
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.commands.Commands;
+import net.minecraft.server.MinecraftServer;
+import net.pl3x.purpur.PurpurConfig;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+
+public class UptimeCommand {
+    public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
+        dispatcher.register(Commands.literal("uptime")
+                .requires((listener) -> listener.hasPermission(2))
+                .executes((context) -> execute(context.getSource()))
+        ).setPermission("bukkit.command.uptime");
+    }
+
+    private static int execute(CommandSourceStack sender) {
+        Data data = new Data();
+
+        data.format = PurpurConfig.uptimeFormat;
+        data.hide = true;
+        data.millis = System.currentTimeMillis() - MinecraftServer.startTimeMillis;
+
+        process(data, "<days>", PurpurConfig.uptimeDay, PurpurConfig.uptimeDays, TimeUnit.DAYS, TimeUnit.MILLISECONDS::toDays);
+        process(data, "<hours>", PurpurConfig.uptimeHour, PurpurConfig.uptimeHours, TimeUnit.HOURS, TimeUnit.MILLISECONDS::toHours);
+        process(data, "<minutes>", PurpurConfig.uptimeMinute, PurpurConfig.uptimeMinutes, TimeUnit.MINUTES, TimeUnit.MILLISECONDS::toMinutes);
+        data.hide = false; // never hide seconds
+        process(data, "<seconds>", PurpurConfig.uptimeSecond, PurpurConfig.uptimeSeconds, TimeUnit.SECONDS, TimeUnit.MILLISECONDS::toSeconds);
+
+        Component output = MiniMessage.get().parse(PurpurConfig.uptimeCommandOutput, Template.of("uptime", data.format));
+        sender.sendSuccess(output, false);
+        return 1;
+    }
+
+    private static void process(Data data, String replace, String singular, String plural, TimeUnit unit, Function<Long, Long> func) {
+        if (data.format.contains(replace)) {
+            long val = func.apply(data.millis);
+            if (data.hide) data.hide = val == 0;
+            if (!data.hide) data.millis -= unit.toMillis(val);
+            data.format = data.format.replace(replace, data.hide ? "" : String.format(val == 1 ? singular : plural, val));
+        }
+    }
+
+    private static class Data {
+        String format;
+        boolean hide;
+        long millis;
+    }
+}
