diff --git a/patches/server/0087-Fix-layout-and-add-colors-to-the-Server-GUI.patch b/patches/server/0087-Fix-layout-and-add-colors-to-the-Server-GUI.patch deleted file mode 100644 index 1ee137b37..000000000 --- a/patches/server/0087-Fix-layout-and-add-colors-to-the-Server-GUI.patch +++ /dev/null @@ -1,221 +0,0 @@ -From dc3dc7f0e53caf7b9e9936243c9f994b87b3d883 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Thu, 16 Jan 2020 14:59:16 -0600 -Subject: [PATCH] Fix layout and add colors to the Server GUI - ---- - .../java/net/minecraft/server/ServerGUI.java | 6 +- - .../java/net/pl3x/purpur/swing/ColorPane.java | 129 ++++++++++++++++++ - .../java/org/bukkit/craftbukkit/Main.java | 1 + - src/main/resources/log4j2.xml | 11 +- - 4 files changed, 144 insertions(+), 3 deletions(-) - create mode 100644 src/main/java/net/pl3x/purpur/swing/ColorPane.java - -diff --git a/src/main/java/net/minecraft/server/ServerGUI.java b/src/main/java/net/minecraft/server/ServerGUI.java -index 95561d9db..2ce99770b 100644 ---- a/src/main/java/net/minecraft/server/ServerGUI.java -+++ b/src/main/java/net/minecraft/server/ServerGUI.java -@@ -109,7 +109,7 @@ public class ServerGUI extends JComponent { - - private JComponent e() { - JPanel jpanel = new JPanel(new BorderLayout()); -- JTextArea jtextarea = new JTextArea(); -+ net.pl3x.purpur.swing.ColorPane jtextarea = new net.pl3x.purpur.swing.ColorPane(); // Purpur - JScrollPane jscrollpane = new JScrollPane(jtextarea, 22, 30); - - jtextarea.setEditable(false); -@@ -160,7 +160,7 @@ public class ServerGUI extends JComponent { - } - - private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})*)?[m|K]"); // CraftBukkit -- public void a(JTextArea jtextarea, JScrollPane jscrollpane, String s) { -+ public void a(net.pl3x.purpur.swing.ColorPane jtextarea, JScrollPane jscrollpane, String s) { // Purpur - if (!SwingUtilities.isEventDispatchThread()) { - SwingUtilities.invokeLater(() -> { - this.a(jtextarea, jscrollpane, s); -@@ -174,11 +174,13 @@ public class ServerGUI extends JComponent { - flag = (double) jscrollbar.getValue() + jscrollbar.getSize().getHeight() + (double) (ServerGUI.a.getSize() * 4) > (double) jscrollbar.getMaximum(); - } - -+ /* // Purpur - try { - document.insertString(document.getLength(), ANSI.matcher(s).replaceAll(""), (AttributeSet) null); // CraftBukkit - } catch (BadLocationException badlocationexception) { - ; - } -+ */ jtextarea.appendANSI(s); // Purpur - - if (flag) { - jscrollbar.setValue(Integer.MAX_VALUE); -diff --git a/src/main/java/net/pl3x/purpur/swing/ColorPane.java b/src/main/java/net/pl3x/purpur/swing/ColorPane.java -new file mode 100644 -index 000000000..6f702ad4d ---- /dev/null -+++ b/src/main/java/net/pl3x/purpur/swing/ColorPane.java -@@ -0,0 +1,129 @@ -+package net.pl3x.purpur.swing; -+ -+import org.apache.commons.lang.StringEscapeUtils; -+ -+import javax.swing.*; -+import javax.swing.text.AttributeSet; -+import javax.swing.text.BadLocationException; -+import javax.swing.text.SimpleAttributeSet; -+import javax.swing.text.StyleConstants; -+import javax.swing.text.StyleContext; -+import java.awt.*; -+ -+/* -+ * Class from: https://stackoverflow.com/a/6899478 -+ */ -+ -+public class ColorPane extends JTextPane { -+ static final Color BLACK = Color.BLACK; -+ static final Color RED = Color.RED; -+ static final Color BLUE = Color.BLUE; -+ static final Color MAGENTA = Color.MAGENTA; -+ static final Color GREEN = Color.GREEN; -+ static final Color YELLOW = Color.getHSBColor(0.15f, 1.0f, 1.0f); -+ static final Color CYAN = Color.CYAN; -+ static final Color WHITE = Color.LIGHT_GRAY; -+ -+ static Color colorCurrent = BLACK; -+ String remaining = ""; -+ -+ public void append(Color c, String s) { -+ StyleContext sc = StyleContext.getDefaultStyleContext(); -+ AttributeSet aset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, c); -+ if (c != BLACK) { -+ aset = sc.addAttribute(aset, StyleConstants.CharacterConstants.Bold, true); -+ } -+ int len = getDocument().getLength(); // same value as getText().length(); -+ -+ //setCaretPosition(len); // place caret at the end (with no selection) -+ //setCharacterAttributes(aset, false); -+ //replaceSelection(s); // there is no selection, so inserts at caret -+ -+ try { -+ getDocument().insertString(len, s, aset); -+ } catch (BadLocationException e) { -+ e.printStackTrace(); -+ } -+ } -+ -+ public void appendANSI(String s) { // convert ANSI color codes first -+ int aPos = 0; // current char position in addString -+ int aIndex = 0; // index of next Escape sequence -+ int mIndex = 0; // index of "m" terminating Escape sequence -+ String tmpString = ""; -+ boolean stillSearching = true; // true until no more Escape sequences -+ String addString = remaining + s; -+ remaining = ""; -+ colorCurrent = BLACK; // reset the colors -+ -+ if (addString.length() > 0) { -+ aIndex = addString.indexOf("\u001B"); // find first escape -+ if (aIndex == -1) { // no escape/color change in this string, so just send it with current color -+ append(colorCurrent, addString); -+ return; -+ } -+ // otherwise There is an escape character in the string, so we must process it -+ -+ if (aIndex > 0) { // Escape is not first char, so send text up to first escape -+ tmpString = addString.substring(0, aIndex); -+ append(colorCurrent, tmpString); -+ aPos = aIndex; -+ } -+ // aPos is now at the beginning of the first escape sequence -+ -+ stillSearching = true; -+ while (stillSearching) { -+ mIndex = addString.indexOf("m", aPos); // find the end of the escape sequence -+ if (mIndex < 0) { // the buffer ends halfway through the ansi string! -+ remaining = addString.substring(aPos, addString.length()); -+ stillSearching = false; -+ continue; -+ } else { -+ tmpString = addString.substring(aPos, mIndex + 1); -+ colorCurrent = getANSIColor(tmpString); -+ } -+ aPos = mIndex + 1; -+ // now we have the color, send text that is in that color (up to next escape) -+ -+ aIndex = addString.indexOf("\u001B", aPos); -+ -+ if (aIndex == -1) { // if that was the last sequence of the input, send remaining text -+ tmpString = addString.substring(aPos, addString.length()); -+ append(colorCurrent, tmpString); -+ stillSearching = false; -+ continue; // jump out of loop early, as the whole string has been sent now -+ } -+ -+ // there is another escape sequence, so send part of the string and prepare for the next -+ tmpString = addString.substring(aPos, aIndex); -+ aPos = aIndex; -+ append(colorCurrent, tmpString); -+ -+ } // while there's text in the input buffer -+ } -+ } -+ -+ public Color getANSIColor(String ANSIColor) { -+ if (ANSIColor.isEmpty()) { -+ return BLACK; -+ } else if (ANSIColor.contains("30")) { -+ return BLACK; -+ } else if (ANSIColor.contains("31")) { -+ return RED; -+ } else if (ANSIColor.contains("32")) { -+ return GREEN; -+ } else if (ANSIColor.contains("33")) { -+ return YELLOW; -+ } else if (ANSIColor.contains("34")) { -+ return BLUE; -+ } else if (ANSIColor.contains("35")) { -+ return MAGENTA; -+ } else if (ANSIColor.contains("36")) { -+ return CYAN; -+ } else if (ANSIColor.contains("37")) { -+ return WHITE; -+ } else { -+ return BLACK; -+ } -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index 7e8b6cab7..f30bcbd48 100644 ---- a/src/main/java/org/bukkit/craftbukkit/Main.java -+++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -144,6 +144,7 @@ public class Main { - .ofType(File.class) - .defaultsTo(new File("purpur.yml")) - .describedAs("Yml file"); -+ accepts("nogui", "Disables the graphical window"); - // Purpur end - - // Paper start -diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml -index 869bff4af..2a3e57c2f 100644 ---- a/src/main/resources/log4j2.xml -+++ b/src/main/resources/log4j2.xml -@@ -2,7 +2,16 @@ - - - -- -+ -+ -+ -+ -+ -+ -+ -+ -+ - - - --- -2.24.0 - diff --git a/patches/server/0087-Make-the-GUI-better.patch b/patches/server/0087-Make-the-GUI-better.patch new file mode 100644 index 000000000..6de696336 --- /dev/null +++ b/patches/server/0087-Make-the-GUI-better.patch @@ -0,0 +1,819 @@ +From ccac65fb797842f093e4f04c6750152a193afeaf Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 16 Jan 2020 14:59:16 -0600 +Subject: [PATCH] Make the GUI better + +--- + .../net/minecraft/server/DedicatedServer.java | 6 +- + .../net/minecraft/server/MinecraftServer.java | 3 +- + .../java/net/pl3x/purpur/gui/ColorPane.java | 126 ++++++++++++ + .../net/pl3x/purpur/gui/ConsolePanel.java | 128 +++++++++++++ + .../pl3x/purpur/gui/PlayerListComponent.java | 27 +++ + .../java/net/pl3x/purpur/gui/ServerGUI.java | 121 ++++++++++++ + .../net/pl3x/purpur/gui/info/RAMDetails.java | 52 +++++ + .../net/pl3x/purpur/gui/info/RAMGraph.java | 179 ++++++++++++++++++ + .../pl3x/purpur/gui/info/ServerInfoPanel.java | 42 ++++ + src/main/resources/log4j2.xml | 11 +- + 10 files changed, 690 insertions(+), 5 deletions(-) + create mode 100644 src/main/java/net/pl3x/purpur/gui/ColorPane.java + create mode 100644 src/main/java/net/pl3x/purpur/gui/ConsolePanel.java + create mode 100644 src/main/java/net/pl3x/purpur/gui/PlayerListComponent.java + create mode 100644 src/main/java/net/pl3x/purpur/gui/ServerGUI.java + create mode 100644 src/main/java/net/pl3x/purpur/gui/info/RAMDetails.java + create mode 100644 src/main/java/net/pl3x/purpur/gui/info/RAMGraph.java + create mode 100644 src/main/java/net/pl3x/purpur/gui/info/ServerInfoPanel.java + +diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java +index 8b5f4cab0..aec6040c8 100644 +--- a/src/main/java/net/minecraft/server/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/DedicatedServer.java +@@ -51,7 +51,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer + public DedicatedServerSettings propertyManager; + private EnumGamemode o; + @Nullable +- private ServerGUI p; ++ private net.pl3x.purpur.gui.ServerGUI p; // Purpur + + // CraftBukkit start - Signature changed + public DedicatedServer(joptsimple.OptionSet options, DedicatedServerSettings dedicatedserversettings, DataFixer datafixer, YggdrasilAuthenticationService yggdrasilauthenticationservice, MinecraftSessionService minecraftsessionservice, GameProfileRepository gameprofilerepository, UserCache usercache, WorldLoadListenerFactory worldloadlistenerfactory, String s) { +@@ -413,7 +413,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer + @Override + public void exit() { + if (this.p != null) { +- this.p.b(); ++ this.p.close(); // Purpur + } + + if (this.remoteControlListener != null) { +@@ -512,7 +512,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer + + public void bc() { + if (this.p == null) { +- this.p = ServerGUI.a(this); ++ this.p = net.pl3x.purpur.gui.ServerGUI.create(this); // Purpur + } + + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 9d5ef40a0..105ac8a04 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -105,7 +105,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 0) { ++ aIndex = addString.indexOf("\u001B"); // find first escape ++ if (aIndex == -1) { // no escape/color change in this string, so just send it with current color ++ append(colorCurrent, addString); ++ return; ++ } ++ // otherwise There is an escape character in the string, so we must process it ++ ++ if (aIndex > 0) { // Escape is not first char, so send text up to first escape ++ tmpString = addString.substring(0, aIndex); ++ append(colorCurrent, tmpString); ++ aPos = aIndex; ++ } ++ // aPos is now at the beginning of the first escape sequence ++ ++ stillSearching = true; ++ while (stillSearching) { ++ mIndex = addString.indexOf("m", aPos); // find the end of the escape sequence ++ if (mIndex < 0) { // the buffer ends halfway through the ansi string! ++ remaining = addString.substring(aPos); ++ stillSearching = false; ++ continue; ++ } else { ++ tmpString = addString.substring(aPos, mIndex + 1); ++ colorCurrent = getANSIColor(tmpString); ++ } ++ aPos = mIndex + 1; ++ // now we have the color, send text that is in that color (up to next escape) ++ ++ aIndex = addString.indexOf("\u001B", aPos); ++ ++ if (aIndex == -1) { // if that was the last sequence of the input, send remaining text ++ tmpString = addString.substring(aPos); ++ append(colorCurrent, tmpString); ++ stillSearching = false; ++ continue; // jump out of loop early, as the whole string has been sent now ++ } ++ ++ // there is another escape sequence, so send part of the string and prepare for the next ++ tmpString = addString.substring(aPos, aIndex); ++ aPos = aIndex; ++ append(colorCurrent, tmpString); ++ ++ } // while there's text in the input buffer ++ } ++ } ++ ++ private Color getANSIColor(String ANSIColor) { ++ if (ANSIColor.isEmpty()) { ++ return BLACK; ++ } else if (ANSIColor.contains("30")) { ++ return BLACK; ++ } else if (ANSIColor.contains("31")) { ++ return RED; ++ } else if (ANSIColor.contains("32")) { ++ return GREEN; ++ } else if (ANSIColor.contains("33")) { ++ return YELLOW; ++ } else if (ANSIColor.contains("34")) { ++ return BLUE; ++ } else if (ANSIColor.contains("35")) { ++ return MAGENTA; ++ } else if (ANSIColor.contains("36")) { ++ return CYAN; ++ } else if (ANSIColor.contains("37")) { ++ return WHITE; ++ } else { ++ return BLACK; ++ } ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/gui/ConsolePanel.java b/src/main/java/net/pl3x/purpur/gui/ConsolePanel.java +new file mode 100644 +index 000000000..a728f46e6 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/gui/ConsolePanel.java +@@ -0,0 +1,128 @@ ++package net.pl3x.purpur.gui; ++ ++import com.mojang.util.QueueLogAppender; ++import net.minecraft.server.DedicatedServer; ++import net.minecraft.server.DefaultUncaughtExceptionHandler; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++import javax.swing.AbstractAction; ++import javax.swing.JPanel; ++import javax.swing.JScrollBar; ++import javax.swing.JScrollPane; ++import javax.swing.JTextField; ++import javax.swing.KeyStroke; ++import javax.swing.ScrollPaneConstants; ++import javax.swing.SwingUtilities; ++import javax.swing.border.EtchedBorder; ++import javax.swing.border.TitledBorder; ++import java.awt.BorderLayout; ++import java.awt.Font; ++import java.awt.event.ActionEvent; ++import java.util.LinkedList; ++ ++public class ConsolePanel extends JPanel { ++ private static final Font MONOSPACED = new Font("Monospaced", Font.PLAIN, 12); ++ private static final Logger LOGGER = LogManager.getLogger(); ++ ++ private final CommandHistory history = new CommandHistory(); ++ private String currentCommand = ""; ++ private int historyIndex = 0; ++ ++ private Thread logAppenderThread; ++ ++ ConsolePanel(DedicatedServer server) { ++ super(new BorderLayout()); ++ ++ ColorPane console = new ColorPane(); ++ console.setEditable(false); ++ console.setFont(MONOSPACED); ++ ++ JTextField jtextfield = new JTextField(); ++ jtextfield.addActionListener((actionevent) -> { ++ String msg = jtextfield.getText().trim(); ++ if (!msg.isEmpty()) { ++ server.issueCommand(msg, server.getServerCommandListener()); ++ history.add(msg); ++ historyIndex = -1; ++ } ++ jtextfield.setText(""); ++ }); ++ jtextfield.getInputMap().put(KeyStroke.getKeyStroke("UP"), "up"); ++ jtextfield.getInputMap().put(KeyStroke.getKeyStroke("DOWN"), "down"); ++ jtextfield.getActionMap().put("up", new AbstractAction() { ++ @Override ++ public void actionPerformed(ActionEvent actionEvent) { ++ if (historyIndex < 0) { ++ currentCommand = jtextfield.getText(); ++ } ++ if (historyIndex < history.size() - 1) { ++ jtextfield.setText(history.get(++historyIndex)); ++ } ++ } ++ }); ++ jtextfield.getActionMap().put("down", new AbstractAction() { ++ @Override ++ public void actionPerformed(ActionEvent actionEvent) { ++ if (historyIndex >= 0) { ++ if (historyIndex == 0) { ++ --historyIndex; ++ jtextfield.setText(currentCommand); ++ } else { ++ --historyIndex; ++ jtextfield.setText(history.get(historyIndex)); ++ } ++ } ++ } ++ }); ++ ++ JScrollPane jscrollpane = new JScrollPane(console, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); ++ ++ add(jscrollpane, "Center"); ++ add(jtextfield, "South"); ++ setBorder(new TitledBorder(new EtchedBorder(), "Console")); ++ ++ logAppenderThread = new Thread(() -> { ++ String msg; ++ while ((msg = QueueLogAppender.getNextLogEvent("ServerGuiConsole")) != null) { ++ this.print(console, jscrollpane, msg); ++ } ++ ++ }); ++ logAppenderThread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)); ++ logAppenderThread.setDaemon(true); ++ } ++ ++ public void start() { ++ logAppenderThread.start(); ++ } ++ ++ private void print(ColorPane console, JScrollPane jscrollpane, String s) { ++ if (!SwingUtilities.isEventDispatchThread()) { ++ SwingUtilities.invokeLater(() -> print(console, jscrollpane, s)); ++ } else { ++ JScrollBar jscrollbar = jscrollpane.getVerticalScrollBar(); ++ boolean scrollToBottom = false; ++ ++ if (jscrollpane.getViewport().getView() == console) { ++ scrollToBottom = (double) jscrollbar.getValue() + jscrollbar.getSize().getHeight() + (double) (MONOSPACED.getSize() * 4) > (double) jscrollbar.getMaximum(); ++ } ++ ++ console.appendANSI(s); ++ ++ if (scrollToBottom) { ++ jscrollbar.setValue(Integer.MAX_VALUE); ++ } ++ } ++ } ++ ++ public static class CommandHistory extends LinkedList { ++ @Override ++ public boolean add(String command) { ++ if (size() > 1000) { ++ remove(); ++ } ++ return super.offerFirst(command); ++ } ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/gui/PlayerListComponent.java b/src/main/java/net/pl3x/purpur/gui/PlayerListComponent.java +new file mode 100644 +index 000000000..c97a6f7dc +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/gui/PlayerListComponent.java +@@ -0,0 +1,27 @@ ++package net.pl3x.purpur.gui; ++ ++import net.minecraft.server.EntityPlayer; ++import net.minecraft.server.MinecraftServer; ++ ++import javax.swing.JList; ++import java.util.Vector; ++ ++public class PlayerListComponent extends JList { ++ private final MinecraftServer server; ++ private int tickCount; ++ ++ PlayerListComponent(MinecraftServer server) { ++ this.server = server; ++ server.addTickable(this::tick); ++ } ++ ++ public void tick() { ++ if (tickCount++ % 20 == 0) { ++ Vector vector = new Vector<>(); ++ for (EntityPlayer player : server.getPlayerList().getPlayers()) { ++ vector.add(player.getProfile().getName()); ++ } ++ setListData(vector); ++ } ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/gui/ServerGUI.java b/src/main/java/net/pl3x/purpur/gui/ServerGUI.java +new file mode 100644 +index 000000000..a779fea68 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/gui/ServerGUI.java +@@ -0,0 +1,121 @@ ++package net.pl3x.purpur.gui; ++ ++import com.google.common.collect.Lists; ++import net.minecraft.server.DedicatedServer; ++import net.pl3x.purpur.gui.info.ServerInfoPanel; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++import javax.swing.JComponent; ++import javax.swing.JFrame; ++import javax.swing.JPanel; ++import javax.swing.JScrollPane; ++import javax.swing.ScrollPaneConstants; ++import javax.swing.UIManager; ++import javax.swing.WindowConstants; ++import javax.swing.border.EtchedBorder; ++import javax.swing.border.TitledBorder; ++import java.awt.BorderLayout; ++import java.awt.Dimension; ++import java.awt.event.WindowAdapter; ++import java.awt.event.WindowEvent; ++import java.util.Collection; ++import java.util.concurrent.atomic.AtomicBoolean; ++ ++public class ServerGUI extends JComponent { ++ private static final Logger LOGGER = LogManager.getLogger(); ++ ++ private final DedicatedServer server; ++ ++ private final Collection finalizers = Lists.newArrayList(); ++ private final AtomicBoolean isClosing = new AtomicBoolean(); ++ ++ private ConsolePanel consolePanel; ++ ++ public static ServerGUI create(final DedicatedServer dedicatedserver) { ++ try { ++ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); ++ } catch (Exception ignore) { ++ } ++ ++ JFrame window = new JFrame("Purpur Minecraft Server"); ++ ServerGUI serverGUI = new ServerGUI(dedicatedserver); ++ ++ window.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); ++ window.add(serverGUI); ++ window.pack(); ++ window.setLocationRelativeTo(null); ++ window.setVisible(true); ++ ++ window.addWindowListener(new WindowAdapter() { ++ @Override ++ public void windowClosing(WindowEvent windowevent) { ++ if (!serverGUI.isClosing.getAndSet(true)) { ++ window.setTitle("Purpur Minecraft Server - Shutting Down!"); ++ dedicatedserver.safeShutdown(true); ++ serverGUI.runFinalizers(); ++ } ++ ++ } ++ }); ++ ++ serverGUI.addFinalizer(window::dispose); ++ serverGUI.start(); ++ ++ return serverGUI; ++ } ++ ++ private ServerGUI(DedicatedServer dedicatedserver) { ++ this.server = dedicatedserver; ++ ++ setPreferredSize(new Dimension(854, 480)); ++ setLayout(new BorderLayout()); ++ ++ consolePanel = new ConsolePanel(server); ++ ++ try { ++ add(consolePanel, "Center"); ++ add(buildInfoPanel(), "West"); ++ } catch (Exception exception) { ++ LOGGER.error("Couldn't build server GUI", exception); ++ } ++ } ++ ++ private void addFinalizer(Runnable runnable) { ++ finalizers.add(runnable); ++ } ++ ++ private JComponent buildInfoPanel() { ++ JPanel jpanel = new JPanel(new BorderLayout()); ++ ++ ServerInfoPanel serverInfo = new ServerInfoPanel(server); ++ finalizers.add(serverInfo::stop); ++ ++ jpanel.add(serverInfo, "North"); ++ jpanel.add(buildPlayerPanel(), "Center"); ++ ++ jpanel.setBorder(new TitledBorder(new EtchedBorder(), "Stats")); ++ return jpanel; ++ } ++ ++ private JComponent buildPlayerPanel() { ++ JScrollPane jscrollpane = new JScrollPane(new PlayerListComponent(server), ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); ++ jscrollpane.setBorder(new TitledBorder(new EtchedBorder(), "Players")); ++ return jscrollpane; ++ } ++ ++ public void start() { ++ consolePanel.start(); ++ } ++ ++ public void close() { ++ if (!isClosing.getAndSet(true)) { ++ runFinalizers(); ++ } ++ ++ } ++ ++ private void runFinalizers() { ++ finalizers.forEach(Runnable::run); ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/gui/info/RAMDetails.java b/src/main/java/net/pl3x/purpur/gui/info/RAMDetails.java +new file mode 100644 +index 000000000..b1ea91b49 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/gui/info/RAMDetails.java +@@ -0,0 +1,52 @@ ++package net.pl3x.purpur.gui.info; ++ ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.SystemUtils; ++ ++import javax.swing.DefaultListCellRenderer; ++import javax.swing.JList; ++import javax.swing.border.EmptyBorder; ++import java.awt.Dimension; ++import java.text.DecimalFormat; ++import java.text.DecimalFormatSymbols; ++import java.util.Locale; ++import java.util.Vector; ++ ++public class RAMDetails extends JList { ++ private static final DecimalFormat DECIMAL_FORMAT = SystemUtils.a(new DecimalFormat("########0.000"), (format) ++ -> format.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT))); ++ private final MinecraftServer server; ++ ++ RAMDetails(MinecraftServer server) { ++ this.server = server; ++ ++ setBorder(new EmptyBorder(0, 10, 0, 0)); ++ setFixedCellHeight(20); ++ setOpaque(false); ++ ++ DefaultListCellRenderer renderer = new DefaultListCellRenderer(); ++ renderer.setOpaque(false); ++ setCellRenderer(renderer); ++ } ++ ++ @Override ++ public Dimension getPreferredSize() { ++ return new Dimension(350, 100); ++ } ++ ++ public void update(RAMGraph graph) { ++ Vector vector = new Vector<>(); ++ vector.add("Memory use: " + (graph.usedMem / 1024L / 1024L) + " mb (" + (graph.free * 100L / graph.max) + "% free)"); ++ vector.add("Heap: " + (graph.total / 1024L / 1024L) + " / " + (graph.max / 1024L / 1024L) + " mb"); ++ vector.add("Avg tick: " + DECIMAL_FORMAT.format(getAverage(server.getTickTimes()) * 1.0E-6D) + " ms"); ++ setListData(vector); ++ } ++ ++ private double getAverage(long[] values) { ++ long total = 0L; ++ for (long value : values) { ++ total += value; ++ } ++ return (double) total / (double) values.length; ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/gui/info/RAMGraph.java b/src/main/java/net/pl3x/purpur/gui/info/RAMGraph.java +new file mode 100644 +index 000000000..7623088ba +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/gui/info/RAMGraph.java +@@ -0,0 +1,179 @@ ++package net.pl3x.purpur.gui.info; ++ ++import javax.swing.JComponent; ++import javax.swing.SwingUtilities; ++import javax.swing.Timer; ++import javax.swing.ToolTipManager; ++import java.awt.Color; ++import java.awt.Dimension; ++import java.awt.Graphics; ++import java.awt.MouseInfo; ++import java.awt.Point; ++import java.awt.event.MouseAdapter; ++import java.awt.event.MouseEvent; ++import java.text.SimpleDateFormat; ++import java.util.Date; ++import java.util.LinkedList; ++import java.util.concurrent.TimeUnit; ++ ++public class RAMGraph extends JComponent { ++ private final Timer timer; ++ ++ private final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); ++ ++ private final Color[] colors = new Color[101]; ++ private final Color[] colorsAlpha = new Color[101]; ++ ++ private final RAMQueue usedMemQueue = new RAMQueue<>(); ++ private final RAMQueue usedPercentQueue = new RAMQueue<>(); ++ ++ private int currentTick; ++ ++ protected long total; ++ protected long free; ++ protected long max; ++ ++ long usedMem; ++ ++ RAMGraph() { ++ for (int i = 0; i < 350; i++) { ++ usedMemQueue.add(0L); ++ usedPercentQueue.add(0); ++ } ++ ++ for (int i = 0; i < 101; i++) { ++ Color color = getColor(i); ++ colors[i] = new Color(color.getRed() / 2, color.getGreen() / 2, color.getBlue() / 2, 255); ++ colorsAlpha[i] = new Color(colors[i].getRed(), colors[i].getGreen(), colors[i].getBlue(), 125); ++ } ++ ++ ToolTipManager.sharedInstance().setInitialDelay(0); ++ ++ addMouseListener(new MouseAdapter() { ++ final int defaultDismissTimeout = ToolTipManager.sharedInstance().getDismissDelay(); ++ final int dismissDelayMinutes = (int) TimeUnit.MINUTES.toMillis(10); // 10 minutes ++ ++ @Override ++ public void mouseEntered(MouseEvent me) { ++ ToolTipManager.sharedInstance().setDismissDelay(dismissDelayMinutes); ++ } ++ ++ @Override ++ public void mouseExited(MouseEvent me) { ++ ToolTipManager.sharedInstance().setDismissDelay(defaultDismissTimeout); ++ } ++ }); ++ ++ timer = new Timer(50, (event) -> repaint()); ++ timer.start(); ++ } ++ ++ @Override ++ public Dimension getPreferredSize() { ++ return new Dimension(350, 110); ++ } ++ ++ protected void update() { ++ total = Runtime.getRuntime().totalMemory(); ++ free = Runtime.getRuntime().freeMemory(); ++ max = Runtime.getRuntime().maxMemory(); ++ usedMem = total - free; ++ ++ usedMemQueue.add(usedMem); ++ usedPercentQueue.add((int) (usedMem * 100L / max)); ++ ++ Point scr = MouseInfo.getPointerInfo().getLocation(); ++ Point loc = new Point(scr); ++ SwingUtilities.convertPointFromScreen(loc, this); ++ if (this.contains(loc)) { ++ ToolTipManager.sharedInstance().mouseMoved( ++ new MouseEvent(this, -1, System.currentTimeMillis(), 0, loc.x, loc.y, ++ scr.x, scr.y, 0, false, 0)); ++ } ++ ++ currentTick++; ++ } ++ ++ @Override ++ public void paint(Graphics graphics) { ++ graphics.setColor(new Color(0xFFFFFFFF)); ++ graphics.fillRect(0, 0, 350, 100); ++ ++ graphics.setColor(new Color(0x888888)); ++ graphics.drawLine(1, 25, 348, 25); ++ graphics.drawLine(1, 50, 348, 50); ++ graphics.drawLine(1, 75, 348, 75); ++ ++ int i = 0; ++ for (Integer used : usedPercentQueue) { ++ i++; ++ if ((i + currentTick) % 120 == 0) { ++ graphics.setColor(new Color(0x888888)); ++ graphics.drawLine(i, 1, i, 99); ++ } ++ ++ if (used > 0) { ++ Color color = colors[used]; ++ graphics.setColor(colorsAlpha[used]); ++ graphics.fillRect(i, 100 - used, 1, used); ++ graphics.setColor(color); ++ graphics.fillRect(i, 100 - used, 1, 1); ++ } ++ } ++ ++ graphics.setColor(new Color(0xFF000000)); ++ graphics.drawRect(0, 0, 348, 100); ++ ++ Point m = getMousePosition(); ++ if (m != null && m.x > 0 && m.x < 348 && m.y > 0 && m.y < 100) { ++ Integer used = usedPercentQueue.get(m.x); ++ graphics.setColor(new Color(0x000000)); ++ graphics.drawLine(m.x, 1, m.x, 99); ++ graphics.drawOval(m.x - 2, 100 - used - 2, 5, 5); ++ graphics.setColor(colors[used]); ++ graphics.fillOval(m.x - 2, 100 - used - 2, 5, 5); ++ setToolTipText(String.format("Used: %s mb (%s%%)
%s", ++ Math.round(usedMemQueue.get(m.x) / 1024F / 1024F), ++ used, getTime(m.x))); ++ } ++ } ++ ++ protected void stop() { ++ timer.stop(); ++ } ++ ++ private String getTime(int halfSeconds) { ++ int millis = (348 - halfSeconds) / 2 * 1000; ++ return sdf.format(new Date((System.currentTimeMillis() - millis))); ++ } ++ ++ private Color getColor(int percent) { ++ if (percent <= 50) { ++ return new Color(0X00FF00); ++ } ++ ++ int value = 510 - (int) (Math.min(Math.max(0, ((percent - 50) / 50F)), 1) * 510); ++ ++ int red, green; ++ if (value < 255) { ++ red = 255; ++ green = (int) (Math.sqrt(value) * 16); ++ } else { ++ green = 255; ++ value = value - 255; ++ red = 255 - (value * value / 255); ++ } ++ ++ return new Color(red, green, 0); ++ } ++ ++ public static class RAMQueue extends LinkedList { ++ @Override ++ public boolean add(E e) { ++ if (size() >= 348) { ++ remove(); ++ } ++ return super.add(e); ++ } ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/gui/info/ServerInfoPanel.java b/src/main/java/net/pl3x/purpur/gui/info/ServerInfoPanel.java +new file mode 100644 +index 000000000..c4519794c +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/gui/info/ServerInfoPanel.java +@@ -0,0 +1,42 @@ ++package net.pl3x.purpur.gui.info; ++ ++import net.minecraft.server.MinecraftServer; ++ ++import javax.swing.JPanel; ++import javax.swing.Timer; ++import java.awt.BorderLayout; ++import java.awt.Dimension; ++ ++public class ServerInfoPanel extends JPanel { ++ private final Timer timer; ++ private final RAMGraph ramGraph; ++ private final RAMDetails ramDetails; ++ ++ public ServerInfoPanel(MinecraftServer server) { ++ super(new BorderLayout()); ++ ++ setOpaque(false); ++ ++ ramGraph = new RAMGraph(); ++ ramDetails = new RAMDetails(server); ++ ++ add(ramGraph, "North"); ++ add(ramDetails, "Center"); ++ ++ timer = new Timer(500, (event) -> { ++ ramGraph.update(); ++ ramDetails.update(ramGraph); ++ }); ++ timer.start(); ++ } ++ ++ @Override ++ public Dimension getPreferredSize() { ++ return new Dimension(350, 200); ++ } ++ ++ public void stop() { ++ timer.stop(); ++ ramGraph.stop(); ++ } ++} +diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml +index 869bff4af..2a3e57c2f 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -2,7 +2,16 @@ + + + +- ++ ++ ++ ++ ++ ++ ++ ++ ++ + + + +-- +2.24.0 + diff --git a/patches/server/0088-Disable-outdated-build-check.patch b/patches/server/0088-Disable-outdated-build-check.patch index fe7a99e1b..ca6b551a8 100644 --- a/patches/server/0088-Disable-outdated-build-check.patch +++ b/patches/server/0088-Disable-outdated-build-check.patch @@ -1,4 +1,4 @@ -From fd558eb5594f12ec9911f00566a37a5ffba31977 Mon Sep 17 00:00:00 2001 +From 575046416748239741024cd3f2ed453860aa17bb Mon Sep 17 00:00:00 2001 From: William Blake Galbreath Date: Sun, 15 Dec 2019 12:53:59 -0600 Subject: [PATCH] Disable outdated build check @@ -8,10 +8,10 @@ Subject: [PATCH] Disable outdated build check 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index f30bcbd48..1980256b7 100644 +index 7e8b6cab7..fd0748582 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -221,7 +221,7 @@ public class Main { +@@ -220,7 +220,7 @@ public class Main { System.setProperty(TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper } diff --git a/patches/server/0099-UPnP-Port-Forwarding-Service.patch b/patches/server/0099-UPnP-Port-Forwarding-Service.patch index 6a2a7fa33..99636c474 100644 --- a/patches/server/0099-UPnP-Port-Forwarding-Service.patch +++ b/patches/server/0099-UPnP-Port-Forwarding-Service.patch @@ -1,14 +1,17 @@ -From 063bb512eb620d23fc41913e95e6405a43dbf699 Mon Sep 17 00:00:00 2001 +From f57586d123fab52e1a14494b485936afc38994c6 Mon Sep 17 00:00:00 2001 From: William Blake Galbreath Date: Wed, 22 Jan 2020 20:13:40 -0600 Subject: [PATCH] UPnP Port Forwarding Service --- - pom.xml | 10 ++++++++ - .../net/minecraft/server/DedicatedServer.java | 25 +++++++++++++++++++ - .../net/minecraft/server/MinecraftServer.java | 11 ++++++++ - .../java/net/pl3x/purpur/PurpurConfig.java | 5 ++++ - 4 files changed, 51 insertions(+) + pom.xml | 10 +++++ + .../net/minecraft/server/DedicatedServer.java | 25 +++++++++++ + .../net/minecraft/server/MinecraftServer.java | 11 +++++ + .../java/net/pl3x/purpur/PurpurConfig.java | 5 +++ + .../pl3x/purpur/gui/info/ServerInfoPanel.java | 4 ++ + .../pl3x/purpur/gui/info/UPnPComponent.java | 45 +++++++++++++++++++ + 6 files changed, 100 insertions(+) + create mode 100644 src/main/java/net/pl3x/purpur/gui/info/UPnPComponent.java diff --git a/pom.xml b/pom.xml index 37ff489db..c886104c5 100644 @@ -39,7 +42,7 @@ index 37ff489db..c886104c5 100644 diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java -index 8b5f4cab0..3e31b2bb3 100644 +index aec6040c8..c04940d0a 100644 --- a/src/main/java/net/minecraft/server/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/DedicatedServer.java @@ -231,6 +231,31 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer @@ -75,14 +78,14 @@ index 8b5f4cab0..3e31b2bb3 100644 // this.a((PlayerList) (new DedicatedPlayerList(this))); // Spigot - moved up server.loadPlugins(); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 9d5ef40a0..01b5a6b3e 100644 +index 105ac8a04..d36bac5d7 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -181,6 +181,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant { + ramGraph.update(); + ramDetails.update(ramGraph); ++ upnpComponent.repaint(); + }); + timer.start(); + } +diff --git a/src/main/java/net/pl3x/purpur/gui/info/UPnPComponent.java b/src/main/java/net/pl3x/purpur/gui/info/UPnPComponent.java +new file mode 100644 +index 000000000..af6a7e18f +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/gui/info/UPnPComponent.java +@@ -0,0 +1,45 @@ ++package net.pl3x.purpur.gui.info; ++ ++import net.minecraft.server.MinecraftServer; ++import net.pl3x.purpur.PurpurConfig; ++ ++import javax.swing.JTextPane; ++import javax.swing.border.EmptyBorder; ++import java.awt.Color; ++import java.awt.Dimension; ++import java.awt.Graphics; ++ ++public class UPnPComponent extends JTextPane { ++ private final MinecraftServer server; ++ ++ UPnPComponent(MinecraftServer server) { ++ this.server = server; ++ setBorder(new EmptyBorder(0, 30, 0, 10)); ++ setText("UPnP Status"); ++ setOpaque(false); ++ } ++ ++ @Override ++ public Dimension getPreferredSize() { ++ return new Dimension(350, 20); ++ } ++ ++ @Override ++ public void paint(Graphics graphics) { ++ super.paint(graphics); ++ graphics.setColor(server.isUPnPEnabled() ? Color.GREEN : Color.RED); ++ graphics.fillOval(10, 0, 15, 15); ++ setToolTipText(getTooltip()); ++ } ++ ++ private String getTooltip() { ++ if (!PurpurConfig.useUPnP) { ++ return "UPnP Disabled"; ++ } ++ if (server.isUPnPEnabled()) { ++ return "UPnP Enabled"; ++ } else { ++ return "UPnP Unavailable"; ++ } ++ } ++} -- 2.24.0