From 68682cec142ab40d9571049378f36ead9c512793 Mon Sep 17 00:00:00 2001 From: William Blake Galbreath Date: Sat, 1 Feb 2020 03:34:10 -0600 Subject: [PATCH] More GUI work --- patches/server/0086-Make-the-GUI-better.patch | 1233 ++++++++++------- .../0097-UPnP-Port-Forwarding-Service.patch | 46 +- patches/server/0098-Add-tick-times-API.patch | 28 +- 3 files changed, 770 insertions(+), 537 deletions(-) diff --git a/patches/server/0086-Make-the-GUI-better.patch b/patches/server/0086-Make-the-GUI-better.patch index cb67e1965..8b9ee76f8 100644 --- a/patches/server/0086-Make-the-GUI-better.patch +++ b/patches/server/0086-Make-the-GUI-better.patch @@ -1,4 +1,4 @@ -From 1ef1f86e4ffacc1e104cb34f404d9caff58d51cd Mon Sep 17 00:00:00 2001 +From 95f806d67a4295c6baa09d577b61c0b03d088a8f 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 @@ -6,25 +6,37 @@ 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 | 80 ++++++++ - .../net/pl3x/purpur/gui/ConsolePanel.java | 128 +++++++++++++ - .../java/net/pl3x/purpur/gui/GUIColor.java | 58 ++++++ - .../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 ++++ - .../purpur/util/HighlightErrorConverter.java | 86 +++++++++ + .../java/net/pl3x/purpur/gui/ServerGUI.java | 123 ++++++++++++++++ + .../purpur/gui/console/JColorTextPane.java | 81 ++++++++++ + .../purpur/gui/console/JConsolePanel.java | 128 ++++++++++++++++ + .../gui/info/DetailsListSelectionModel.java | 21 +++ + .../net/pl3x/purpur/gui/info/JInfoPanel.java | 42 ++++++ + .../net/pl3x/purpur/gui/info/RAMDetails.java | 54 +++++++ + .../purpur/gui/info/graph/GraphColor.java | 44 ++++++ + .../pl3x/purpur/gui/info/graph/GraphData.java | 47 ++++++ + .../pl3x/purpur/gui/info/graph/RAMGraph.java | 138 ++++++++++++++++++ + .../purpur/gui/playerlist/JPlayerList.java | 57 ++++++++ + .../playerlist/PlayerListCellRenderer.java | 22 +++ + .../gui/playerlist/PlayerListModel.java | 47 ++++++ + .../playerlist/PlayerListMouseAdapter.java | 32 ++++ + .../net/pl3x/purpur/gui/util/GUIColor.java | 54 +++++++ + .../purpur/util/HighlightErrorConverter.java | 86 +++++++++++ src/main/resources/log4j2.xml | 13 +- - 12 files changed, 789 insertions(+), 6 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/GUIColor.java - create mode 100644 src/main/java/net/pl3x/purpur/gui/PlayerListComponent.java + 18 files changed, 992 insertions(+), 6 deletions(-) create mode 100644 src/main/java/net/pl3x/purpur/gui/ServerGUI.java + create mode 100644 src/main/java/net/pl3x/purpur/gui/console/JColorTextPane.java + create mode 100644 src/main/java/net/pl3x/purpur/gui/console/JConsolePanel.java + create mode 100644 src/main/java/net/pl3x/purpur/gui/info/DetailsListSelectionModel.java + create mode 100644 src/main/java/net/pl3x/purpur/gui/info/JInfoPanel.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 + create mode 100644 src/main/java/net/pl3x/purpur/gui/info/graph/GraphColor.java + create mode 100644 src/main/java/net/pl3x/purpur/gui/info/graph/GraphData.java + create mode 100644 src/main/java/net/pl3x/purpur/gui/info/graph/RAMGraph.java + create mode 100644 src/main/java/net/pl3x/purpur/gui/playerlist/JPlayerList.java + create mode 100644 src/main/java/net/pl3x/purpur/gui/playerlist/PlayerListCellRenderer.java + create mode 100644 src/main/java/net/pl3x/purpur/gui/playerlist/PlayerListModel.java + create mode 100644 src/main/java/net/pl3x/purpur/gui/playerlist/PlayerListMouseAdapter.java + create mode 100644 src/main/java/net/pl3x/purpur/gui/util/GUIColor.java create mode 100644 src/main/java/net/pl3x/purpur/util/HighlightErrorConverter.java diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java @@ -79,17 +91,147 @@ index 9d5ef40a03..105ac8a040 100644 public void b(Runnable runnable) { this.tickables.add(runnable); } -diff --git a/src/main/java/net/pl3x/purpur/gui/ColorPane.java b/src/main/java/net/pl3x/purpur/gui/ColorPane.java +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 0000000000..e819ecc1b0 +index 0000000000..973b5efef5 --- /dev/null -+++ b/src/main/java/net/pl3x/purpur/gui/ColorPane.java -@@ -0,0 +1,80 @@ ++++ b/src/main/java/net/pl3x/purpur/gui/ServerGUI.java +@@ -0,0 +1,123 @@ +package net.pl3x.purpur.gui; + ++import com.google.common.collect.Lists; ++import net.minecraft.server.DedicatedServer; ++import net.pl3x.purpur.gui.console.JConsolePanel; ++import net.pl3x.purpur.gui.info.JInfoPanel; ++import net.pl3x.purpur.gui.playerlist.JPlayerList; ++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 JConsolePanel 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 JConsolePanel(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()); ++ ++ JInfoPanel serverInfo = new JInfoPanel(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 JPlayerList(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/console/JColorTextPane.java b/src/main/java/net/pl3x/purpur/gui/console/JColorTextPane.java +new file mode 100644 +index 0000000000..55feec811f +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/gui/console/JColorTextPane.java +@@ -0,0 +1,81 @@ ++package net.pl3x.purpur.gui.console; ++ +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; ++import net.pl3x.purpur.gui.util.GUIColor; + +import javax.swing.JTextPane; +import javax.swing.Timer; @@ -101,7 +243,7 @@ index 0000000000..e819ecc1b0 +import java.util.HashSet; +import java.util.Set; + -+public class ColorPane extends JTextPane { ++public class JColorTextPane extends JTextPane { + private static final GUIColor DEFAULT_COLOR = GUIColor.BLACK; + + public void append(String str) { @@ -148,7 +290,7 @@ index 0000000000..e819ecc1b0 + timer.start(); + } + -+ private class Blink { ++ public class Blink { + private final int start, length; + private final AttributeSet attr1; + private final AttributeSet attr2; @@ -165,13 +307,13 @@ index 0000000000..e819ecc1b0 + } + } +} -diff --git a/src/main/java/net/pl3x/purpur/gui/ConsolePanel.java b/src/main/java/net/pl3x/purpur/gui/ConsolePanel.java +diff --git a/src/main/java/net/pl3x/purpur/gui/console/JConsolePanel.java b/src/main/java/net/pl3x/purpur/gui/console/JConsolePanel.java new file mode 100644 -index 0000000000..29d237d9e7 +index 0000000000..fb017a0708 --- /dev/null -+++ b/src/main/java/net/pl3x/purpur/gui/ConsolePanel.java ++++ b/src/main/java/net/pl3x/purpur/gui/console/JConsolePanel.java @@ -0,0 +1,128 @@ -+package net.pl3x.purpur.gui; ++package net.pl3x.purpur.gui.console; + +import com.mojang.util.QueueLogAppender; +import net.minecraft.server.DedicatedServer; @@ -194,7 +336,7 @@ index 0000000000..29d237d9e7 +import java.awt.event.ActionEvent; +import java.util.LinkedList; + -+public class ConsolePanel extends JPanel { ++public class JConsolePanel extends JPanel { + private static final Font MONOSPACED = new Font("Monospaced", Font.PLAIN, 12); + private static final Logger LOGGER = LogManager.getLogger(); + @@ -204,10 +346,10 @@ index 0000000000..29d237d9e7 + + private Thread logAppenderThread; + -+ ConsolePanel(DedicatedServer server) { ++ public JConsolePanel(DedicatedServer server) { + super(new BorderLayout()); + -+ ColorPane console = new ColorPane(); ++ JColorTextPane console = new JColorTextPane(); + console.setEditable(false); + console.setFont(MONOSPACED); + @@ -270,7 +412,7 @@ index 0000000000..29d237d9e7 + logAppenderThread.start(); + } + -+ private void print(ColorPane console, JScrollPane jscrollpane, String str) { ++ private void print(JColorTextPane console, JScrollPane jscrollpane, String str) { + if (!SwingUtilities.isEventDispatchThread()) { + SwingUtilities.invokeLater(() -> print(console, jscrollpane, str)); + } else { @@ -299,13 +441,577 @@ index 0000000000..29d237d9e7 + } + } +} -diff --git a/src/main/java/net/pl3x/purpur/gui/GUIColor.java b/src/main/java/net/pl3x/purpur/gui/GUIColor.java +diff --git a/src/main/java/net/pl3x/purpur/gui/info/DetailsListSelectionModel.java b/src/main/java/net/pl3x/purpur/gui/info/DetailsListSelectionModel.java new file mode 100644 -index 0000000000..2a280ae48a +index 0000000000..4e0b80fddb --- /dev/null -+++ b/src/main/java/net/pl3x/purpur/gui/GUIColor.java -@@ -0,0 +1,58 @@ -+package net.pl3x.purpur.gui; ++++ b/src/main/java/net/pl3x/purpur/gui/info/DetailsListSelectionModel.java +@@ -0,0 +1,21 @@ ++package net.pl3x.purpur.gui.info; ++ ++import javax.swing.DefaultListSelectionModel; ++ ++public class DetailsListSelectionModel extends DefaultListSelectionModel { ++ @Override ++ public void setAnchorSelectionIndex(final int anchorIndex) { ++ } ++ ++ @Override ++ public void setLeadAnchorNotificationEnabled(final boolean flag) { ++ } ++ ++ @Override ++ public void setLeadSelectionIndex(final int leadIndex) { ++ } ++ ++ @Override ++ public void setSelectionInterval(final int index0, final int index1) { ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/gui/info/JInfoPanel.java b/src/main/java/net/pl3x/purpur/gui/info/JInfoPanel.java +new file mode 100644 +index 0000000000..a4983863cb +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/gui/info/JInfoPanel.java +@@ -0,0 +1,42 @@ ++package net.pl3x.purpur.gui.info; ++ ++import net.minecraft.server.MinecraftServer; ++import net.pl3x.purpur.gui.info.graph.RAMGraph; ++ ++import javax.swing.JPanel; ++import javax.swing.Timer; ++import java.awt.BorderLayout; ++import java.awt.Dimension; ++ ++public class JInfoPanel extends JPanel { ++ private final Timer timer; ++ private final RAMGraph ramGraph; ++ ++ public JInfoPanel(MinecraftServer server) { ++ super(new BorderLayout()); ++ ++ setOpaque(false); ++ ++ ramGraph = new RAMGraph(); ++ RAMDetails ramDetails = new RAMDetails(); ++ ++ add(ramGraph, "North"); ++ add(ramDetails, "Center"); ++ ++ timer = new Timer(500, (event) -> { ++ ramGraph.update(); ++ ramDetails.update(); ++ }); ++ timer.start(); ++ } ++ ++ @Override ++ public Dimension getPreferredSize() { ++ return new Dimension(350, 200); ++ } ++ ++ public void stop() { ++ timer.stop(); ++ ramGraph.stop(); ++ } ++} +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 0000000000..00f79e4336 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/gui/info/RAMDetails.java +@@ -0,0 +1,54 @@ ++package net.pl3x.purpur.gui.info; ++ ++import net.minecraft.server.SystemUtils; ++import net.pl3x.purpur.gui.info.graph.GraphData; ++import net.pl3x.purpur.gui.info.graph.RAMGraph; ++import org.bukkit.Bukkit; ++ ++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))); ++ ++ public RAMDetails() { ++ setBorder(new EmptyBorder(0, 10, 0, 0)); ++ setFixedCellHeight(20); ++ setOpaque(false); ++ ++ DefaultListCellRenderer renderer = new DefaultListCellRenderer(); ++ renderer.setOpaque(false); ++ setCellRenderer(renderer); ++ ++ setSelectionModel(new DetailsListSelectionModel()); ++ } ++ ++ @Override ++ public Dimension getPreferredSize() { ++ return new Dimension(350, 100); ++ } ++ ++ public void update() { ++ GraphData data = RAMGraph.DATA.peekLast(); ++ Vector vector = new Vector<>(); ++ vector.add("Memory use: " + (data.getUsedMem() / 1024L / 1024L) + " mb (" + (data.getFree() * 100L / data.getMax()) + "% free)"); ++ vector.add("Heap: " + (data.getTotal() / 1024L / 1024L) + " / " + (data.getMax() / 1024L / 1024L) + " mb"); ++ vector.add("Avg tick: " + DECIMAL_FORMAT.format(Bukkit.getAverageTickTime()) + " 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/graph/GraphColor.java b/src/main/java/net/pl3x/purpur/gui/info/graph/GraphColor.java +new file mode 100644 +index 0000000000..2fdb9bdd0d +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/gui/info/graph/GraphColor.java +@@ -0,0 +1,44 @@ ++package net.pl3x.purpur.gui.info.graph; ++ ++import java.awt.Color; ++ ++public class GraphColor { ++ private static final Color[] colorLine = new Color[101]; ++ private static final Color[] colorFill = new Color[101]; ++ ++ static { ++ for (int i = 0; i < 101; i++) { ++ Color color = createColor(i); ++ colorLine[i] = new Color(color.getRed() / 2, color.getGreen() / 2, color.getBlue() / 2, 255); ++ colorFill[i] = new Color(colorLine[i].getRed(), colorLine[i].getGreen(), colorLine[i].getBlue(), 125); ++ } ++ } ++ ++ public static Color getLineColor(int percent) { ++ return colorLine[percent]; ++ } ++ ++ public static Color getFillColor(int percent) { ++ return colorFill[percent]; ++ } ++ ++ private static Color createColor(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); ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/gui/info/graph/GraphData.java b/src/main/java/net/pl3x/purpur/gui/info/graph/GraphData.java +new file mode 100644 +index 0000000000..85babad9cd +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/gui/info/graph/GraphData.java +@@ -0,0 +1,47 @@ ++package net.pl3x.purpur.gui.info.graph; ++ ++import java.awt.Color; ++ ++public class GraphData { ++ private long total; ++ private long free; ++ private long max; ++ private long usedMem; ++ private int usedPercent; ++ ++ public GraphData(long total, long free, long max) { ++ this.total = total; ++ this.free = free; ++ this.max = max; ++ this.usedMem = total - free; ++ this.usedPercent = usedMem == 0 ? 0 : (int) (usedMem * 100L / max); ++ } ++ ++ public long getTotal() { ++ return total; ++ } ++ ++ public long getFree() { ++ return free; ++ } ++ ++ public long getMax() { ++ return max; ++ } ++ ++ public long getUsedMem() { ++ return usedMem; ++ } ++ ++ public int getUsedPercent() { ++ return usedPercent; ++ } ++ ++ public Color getFillColor() { ++ return GraphColor.getFillColor(usedPercent); ++ } ++ ++ public Color getLineColor() { ++ return GraphColor.getLineColor(usedPercent); ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/gui/info/graph/RAMGraph.java b/src/main/java/net/pl3x/purpur/gui/info/graph/RAMGraph.java +new file mode 100644 +index 0000000000..f2f3dd2c16 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/gui/info/graph/RAMGraph.java +@@ -0,0 +1,138 @@ ++package net.pl3x.purpur.gui.info.graph; ++ ++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 { ++ public static final LinkedList DATA = new LinkedList() { ++ @Override ++ public boolean add(GraphData data) { ++ if (size() >= 348) { ++ remove(); ++ } ++ return super.add(data); ++ } ++ }; ++ ++ static { ++ GraphData empty = new GraphData(0, 0, 0); ++ for (int i = 0; i < 350; i++) { ++ DATA.add(empty); ++ } ++ } ++ ++ private final Timer timer; ++ private final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss"); ++ ++ private int currentTick; ++ ++ public RAMGraph() { ++ ToolTipManager.sharedInstance().setInitialDelay(0); ++ ++ addMouseListener(new MouseAdapter() { ++ final int defaultDismissTimeout = ToolTipManager.sharedInstance().getDismissDelay(); ++ final int dismissDelayMinutes = (int) TimeUnit.MINUTES.toMillis(10); ++ ++ @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); ++ } ++ ++ public void update() { ++ Runtime jvm = Runtime.getRuntime(); ++ DATA.add(new GraphData(jvm.totalMemory(), jvm.freeMemory(), jvm.maxMemory())); ++ ++ 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 (GraphData data : DATA) { ++ i++; ++ if ((i + currentTick) % 120 == 0) { ++ graphics.setColor(new Color(0x888888)); ++ graphics.drawLine(i, 1, i, 99); ++ } ++ int used = data.getUsedPercent(); ++ if (used > 0) { ++ Color color = data.getLineColor(); ++ graphics.setColor(data.getFillColor()); ++ 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) { ++ GraphData data = DATA.get(m.x); ++ int used = data.getUsedPercent(); ++ 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(data.getLineColor()); ++ graphics.fillOval(m.x - 2, 100 - used - 2, 5, 5); ++ setToolTipText(String.format("Used: %s mb (%s%%)
%s", ++ Math.round(data.getUsedMem() / 1024F / 1024F), ++ used, getTime(m.x))); ++ } ++ } ++ ++ public String getTime(int halfSeconds) { ++ int millis = (348 - halfSeconds) / 2 * 1000; ++ return TIME_FORMAT.format(new Date((System.currentTimeMillis() - millis))); ++ } ++ ++ public void stop() { ++ timer.stop(); ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/gui/playerlist/JPlayerList.java b/src/main/java/net/pl3x/purpur/gui/playerlist/JPlayerList.java +new file mode 100644 +index 0000000000..17e75a2070 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/gui/playerlist/JPlayerList.java +@@ -0,0 +1,57 @@ ++package net.pl3x.purpur.gui.playerlist; ++ ++import net.minecraft.server.EntityPlayer; ++import net.minecraft.server.MinecraftServer; ++ ++import javax.swing.JList; ++import javax.swing.JMenuItem; ++import javax.swing.JPopupMenu; ++import javax.swing.ListSelectionModel; ++import java.awt.Dimension; ++ ++public class JPlayerList extends JList { ++ private final MinecraftServer server; ++ private final PlayerListModel model; ++ private final JPopupMenu popupMenu; ++ private int currentSelection = -1; ++ private int tickCount; ++ ++ public JPlayerList(MinecraftServer server) { ++ this.server = server; ++ server.addTickable(this::tick); ++ ++ setModel(model = new PlayerListModel(this)); ++ setSelectionMode(ListSelectionModel.SINGLE_SELECTION); ++ addListSelectionListener(event -> currentSelection = event.getFirstIndex()); ++ addMouseListener(new PlayerListMouseAdapter(this)); ++ setCellRenderer(new PlayerListCellRenderer()); ++ ++ popupMenu = new JPopupMenu(); ++ popupMenu.add(new JMenuItem("Details")); ++ popupMenu.add(new JPopupMenu.Separator()); ++ popupMenu.add(new JMenuItem("Kick")); ++ popupMenu.add(new JMenuItem("Ban")); ++ popupMenu.add(new JPopupMenu.Separator()); ++ popupMenu.add(new JMenuItem("Op")); ++ } ++ ++ @Override ++ public Dimension getPreferredSize() { ++ Dimension superPref = super.getPreferredSize(); ++ return new Dimension(330, superPref.height); ++ } ++ ++ public void tick() { ++ if (tickCount++ % 20 == 0) { ++ model.update(server.getPlayerList().getPlayers()); ++ } ++ } ++ ++ public int getCurrentSelection() { ++ return currentSelection; ++ } ++ ++ public JPopupMenu getPopupMenu() { ++ return popupMenu; ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/gui/playerlist/PlayerListCellRenderer.java b/src/main/java/net/pl3x/purpur/gui/playerlist/PlayerListCellRenderer.java +new file mode 100644 +index 0000000000..2f02e9adc7 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/gui/playerlist/PlayerListCellRenderer.java +@@ -0,0 +1,22 @@ ++package net.pl3x.purpur.gui.playerlist; ++ ++import com.mojang.authlib.GameProfile; ++import net.minecraft.server.EntityPlayer; ++ ++import javax.swing.DefaultListCellRenderer; ++import javax.swing.JList; ++import java.awt.Component; ++ ++public class PlayerListCellRenderer extends DefaultListCellRenderer { ++ @Override ++ public Component getListCellRendererComponent(JList list, Object player, int index, boolean isSelected, boolean cellHasFocus) { ++ super.getListCellRendererComponent(list, player, index, isSelected, cellHasFocus); ++ if (player instanceof EntityPlayer) { ++ GameProfile profile = ((EntityPlayer) player).getProfile(); ++ setText(profile.getName()); ++ setToolTipText(String.format("%s
%s", ++ profile.getName(), profile.getId())); ++ } ++ return this; ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/gui/playerlist/PlayerListModel.java b/src/main/java/net/pl3x/purpur/gui/playerlist/PlayerListModel.java +new file mode 100644 +index 0000000000..edbf454163 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/gui/playerlist/PlayerListModel.java +@@ -0,0 +1,47 @@ ++package net.pl3x.purpur.gui.playerlist; ++ ++import net.minecraft.server.EntityPlayer; ++ ++import javax.swing.DefaultListModel; ++import java.util.Collection; ++import java.util.HashSet; ++import java.util.Set; ++ ++public class PlayerListModel extends DefaultListModel { ++ private final Set datas = new HashSet<>(); ++ private final JPlayerList parent; ++ ++ public PlayerListModel(JPlayerList parent) { ++ this.parent = parent; ++ } ++ ++ public void update(Collection players) { ++ boolean hadFocus = parent.hasFocus(); ++ ++ players.forEach(this::add); ++ ++ Set result = new HashSet<>(datas); ++ result.removeIf(players::contains); ++ result.forEach(this::remove); ++ ++ if (parent.getCurrentSelection() >= 0) { ++ parent.setSelectedIndex(parent.getCurrentSelection()); ++ } ++ ++ if (hadFocus) { ++ parent.grabFocus(); ++ } ++ } ++ ++ public void add(EntityPlayer player) { ++ if (datas.add(player)) { ++ addElement(player); ++ } ++ } ++ ++ public void remove(EntityPlayer player) { ++ if (datas.remove(player)) { ++ removeElement(player); ++ } ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/gui/playerlist/PlayerListMouseAdapter.java b/src/main/java/net/pl3x/purpur/gui/playerlist/PlayerListMouseAdapter.java +new file mode 100644 +index 0000000000..13a5eb3ad3 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/gui/playerlist/PlayerListMouseAdapter.java +@@ -0,0 +1,32 @@ ++package net.pl3x.purpur.gui.playerlist; ++ ++import javax.swing.SwingUtilities; ++import java.awt.event.MouseAdapter; ++import java.awt.event.MouseEvent; ++ ++public class PlayerListMouseAdapter extends MouseAdapter { ++ private final JPlayerList parent; ++ ++ public PlayerListMouseAdapter(JPlayerList parent) { ++ this.parent = parent; ++ } ++ ++ @Override ++ public void mousePressed(MouseEvent event) { ++ if (!SwingUtilities.isRightMouseButton(event)) { ++ return; ++ } ++ ++ parent.setSelectedIndex(parent.locationToIndex(event.getPoint())); ++ ++ if (parent.isSelectionEmpty()) { ++ return; ++ } ++ ++ if (parent.locationToIndex(event.getPoint()) != parent.getSelectedIndex()) { ++ return; ++ } ++ ++ parent.getPopupMenu().show(parent, event.getX(), event.getY()); ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/gui/util/GUIColor.java b/src/main/java/net/pl3x/purpur/gui/util/GUIColor.java +new file mode 100644 +index 0000000000..973c8ddf93 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/gui/util/GUIColor.java +@@ -0,0 +1,54 @@ ++package net.pl3x.purpur.gui.util; + +import net.md_5.bungee.api.ChatColor; + @@ -345,10 +1051,6 @@ index 0000000000..2a280ae48a + return color; + } + -+ public ChatColor getChatColor() { -+ return chat; -+ } -+ + public String getCode() { + return chat.toString(); + } @@ -363,457 +1065,6 @@ index 0000000000..2a280ae48a + } + } +} -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 0000000000..c97a6f7dc3 ---- /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 0000000000..a779fea689 ---- /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 0000000000..b1ea91b49b ---- /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 0000000000..7623088bae ---- /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 0000000000..c4519794c9 ---- /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/java/net/pl3x/purpur/util/HighlightErrorConverter.java b/src/main/java/net/pl3x/purpur/util/HighlightErrorConverter.java new file mode 100644 index 0000000000..4b340b88a2 diff --git a/patches/server/0097-UPnP-Port-Forwarding-Service.patch b/patches/server/0097-UPnP-Port-Forwarding-Service.patch index 5143b8607..1212e4aad 100644 --- a/patches/server/0097-UPnP-Port-Forwarding-Service.patch +++ b/patches/server/0097-UPnP-Port-Forwarding-Service.patch @@ -1,16 +1,16 @@ -From 05e998cef7295ffb4bdfbd3d237d586b17b6d187 Mon Sep 17 00:00:00 2001 +From 253b800c81e54ac67f69583a557f52d7dc390b3e 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 +++++++++++ + 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(+) + .../java/net/pl3x/purpur/PurpurConfig.java | 5 ++ + .../net/pl3x/purpur/gui/info/JInfoPanel.java | 3 ++ + .../pl3x/purpur/gui/info/UPnPComponent.java | 47 +++++++++++++++++++ + 6 files changed, 101 insertions(+) create mode 100644 src/main/java/net/pl3x/purpur/gui/info/UPnPComponent.java diff --git a/pom.xml b/pom.xml @@ -122,23 +122,15 @@ index 544c68b0d8..917f6503d2 100644 public static double laggingThreshold = 19.0D; private static void tickLoopSettings() { laggingThreshold = getDouble("settings.lagging-threshold", laggingThreshold); -diff --git a/src/main/java/net/pl3x/purpur/gui/info/ServerInfoPanel.java b/src/main/java/net/pl3x/purpur/gui/info/ServerInfoPanel.java -index c4519794c9..2d9d1859d9 100644 ---- a/src/main/java/net/pl3x/purpur/gui/info/ServerInfoPanel.java -+++ b/src/main/java/net/pl3x/purpur/gui/info/ServerInfoPanel.java -@@ -11,6 +11,7 @@ public class ServerInfoPanel extends JPanel { - private final Timer timer; - private final RAMGraph ramGraph; - private final RAMDetails ramDetails; -+ private final UPnPComponent upnpComponent; - - public ServerInfoPanel(MinecraftServer server) { - super(new BorderLayout()); -@@ -19,13 +20,16 @@ public class ServerInfoPanel extends JPanel { +diff --git a/src/main/java/net/pl3x/purpur/gui/info/JInfoPanel.java b/src/main/java/net/pl3x/purpur/gui/info/JInfoPanel.java +index a4983863cb..82fbb61659 100644 +--- a/src/main/java/net/pl3x/purpur/gui/info/JInfoPanel.java ++++ b/src/main/java/net/pl3x/purpur/gui/info/JInfoPanel.java +@@ -19,13 +19,16 @@ public class JInfoPanel extends JPanel { ramGraph = new RAMGraph(); - ramDetails = new RAMDetails(server); -+ upnpComponent = new UPnPComponent(server); + RAMDetails ramDetails = new RAMDetails(); ++ UPnPComponent upnpComponent = new UPnPComponent(server); add(ramGraph, "North"); add(ramDetails, "Center"); @@ -146,17 +138,17 @@ index c4519794c9..2d9d1859d9 100644 timer = new Timer(500, (event) -> { ramGraph.update(); - ramDetails.update(ramGraph); + ramDetails.update(); + 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 0000000000..af6a7e18f3 +index 0000000000..b0465d3608 --- /dev/null +++ b/src/main/java/net/pl3x/purpur/gui/info/UPnPComponent.java -@@ -0,0 +1,45 @@ +@@ -0,0 +1,47 @@ +package net.pl3x.purpur.gui.info; + +import net.minecraft.server.MinecraftServer; @@ -171,11 +163,13 @@ index 0000000000..af6a7e18f3 +public class UPnPComponent extends JTextPane { + private final MinecraftServer server; + -+ UPnPComponent(MinecraftServer server) { ++ public UPnPComponent(MinecraftServer server) { + this.server = server; + setBorder(new EmptyBorder(0, 30, 0, 10)); ++ setEditable(false); + setText("UPnP Status"); + setOpaque(false); ++ setHighlighter(null); + } + + @Override diff --git a/patches/server/0098-Add-tick-times-API.patch b/patches/server/0098-Add-tick-times-API.patch index caa3cbc90..f7f25b329 100644 --- a/patches/server/0098-Add-tick-times-API.patch +++ b/patches/server/0098-Add-tick-times-API.patch @@ -1,41 +1,29 @@ -From fe123a5e92ad8528ba7093f834591f403525f6e9 Mon Sep 17 00:00:00 2001 +From 13adbf70a09ba38f64b77b40cb5db808987850c7 Mon Sep 17 00:00:00 2001 From: William Blake Galbreath Date: Thu, 30 Jan 2020 00:41:24 -0600 Subject: [PATCH] Add tick times API --- - .../net/pl3x/purpur/gui/info/RAMDetails.java | 13 +++---------- + .../net/pl3x/purpur/gui/info/RAMDetails.java | 10 +--------- .../org/bukkit/craftbukkit/CraftServer.java | 15 +++++++++++++++ .../org/spigotmc/TicksPerSecondCommand.java | 18 +++++++++++++++++- - 3 files changed, 35 insertions(+), 11 deletions(-) + 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/main/java/net/pl3x/purpur/gui/info/RAMDetails.java b/src/main/java/net/pl3x/purpur/gui/info/RAMDetails.java -index b1ea91b49b..2981afaaaa 100644 +index 00f79e4336..9fe2550a4c 100644 --- a/src/main/java/net/pl3x/purpur/gui/info/RAMDetails.java +++ b/src/main/java/net/pl3x/purpur/gui/info/RAMDetails.java -@@ -2,6 +2,7 @@ package net.pl3x.purpur.gui.info; - - import net.minecraft.server.MinecraftServer; - import net.minecraft.server.SystemUtils; -+import org.bukkit.Bukkit; - - import javax.swing.DefaultListCellRenderer; - import javax.swing.JList; -@@ -13,7 +14,7 @@ import java.util.Locale; +@@ -15,7 +15,7 @@ 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) + public static final DecimalFormat DECIMAL_FORMAT = SystemUtils.a(new DecimalFormat("########0.000"), (format) -> format.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT))); - private final MinecraftServer server; -@@ -38,15 +39,7 @@ public class RAMDetails extends JList { - 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"); -+ vector.add("Avg tick: " + DECIMAL_FORMAT.format(Bukkit.getAverageTickTime()) + " ms"); + public RAMDetails() { +@@ -43,12 +43,4 @@ public class RAMDetails extends JList { + vector.add("Avg tick: " + DECIMAL_FORMAT.format(Bukkit.getAverageTickTime()) + " ms"); setListData(vector); } -