mirror of
https://github.com/PurpurMC/Purpur.git
synced 2026-02-18 08:57:44 +01:00
Upstream has released updates that appears to apply and compile correctly Paper Changes: c8028d1c Fix data version check for ItemStack serialization (#3394) 9254a80a Fix race condition reintroduced in Prioritize class loader patch 6f196fe7 Add Raw Byte ItemStack Serialization df43f828 Allow server startup for those poor people running <1G Xmx 3c9b65ef Fix cases where no-tick < tick view distance 72f89a07 Workaround for Client Lag Spikes (MC-162253) 3f941c0c Add option for console having all permissions d2ae4658 Add permission for command blocks 9f8ae5cb Prioritise own classes where possible 74466412 Check portal restrictions when entering end gateways fc9cf84d Fix NPE when temp ip bans expire (#3373) 16bd420d Add missing mob goals for API (#3367) b5c4e2f6 Ensure no-tick view is not smaller than ticking VD 52564b1f Expand Pathfinding API with more options dde65481 Fix usage of vanilla goals 7797aebe Drop Leads from nether portals - Fixes #3226 511b6bc2 Reduce MutableInt and Vec3d allocations, use ArrayDeque 84673141 Optimize NibbleArray to use pooled buffers 897dd2c8 Foundational work for Future Memory usage improvements bb4002d8 Handle CraftPlayer#setSpectatorTarget better 4ae08959 Fix collision checks on spawning hanging entities and null on async chunk loads c2f8d1ef Protect Bedrock and End Portal/Frames from being destroyed 827cc632 Updated Upstream (Bukkit/CraftBukkit/Spigot) 92f680ed Fix Pathfinding and obscure glitchy buggy 0 tick farms 7a7c4292 Optimize Pathfinder - Remove Streams / Optimized collections fc917d16 Optimize Hoppers - Major performance improvement 14ad77c6 Fix PotionEffect API Ignoring Icon bug eb3ce8a2 Fix EntityRaider picking up items when they shouldn't be able 1ea9ada0 Add a TELEPORT ticket when changing dimensions 8e9459ea Fix missing flag pass for isUrgent 7befec44 Potential bed api (#3339) 27945a6b Optimize WorldBorder collision checks and air 55e17a85 Wait for Async Tasks during shutdown b5905256 Ensure Entity AABB's are never invalid a054aa6f Properly remove Entities from current chunk c894ddfd Fix teleporting onto a chunk line 57d6cc01 Send LOGIN protocol packets immediately - Fix disconnect during async prelogin cd93e54d Don't use our modified chunk checks for collision in world gen b4003ef1 Allow loading entities current chunk if needed to fix collision checks e5f64896 Add Urgent API for Async Chunks API and use it for Async Teleport ad8e59dc Ensure chunks loaded on respawn for suffocation check
1206 lines
43 KiB
Diff
1206 lines
43 KiB
Diff
From 045bfdd1709b11fa5befd268f29f18b5187e81eb Mon Sep 17 00:00:00 2001
|
|
From: William Blake Galbreath <Blake.Galbreath@GMail.com>
|
|
Date: Thu, 16 Jan 2020 14:59:16 -0600
|
|
Subject: [PATCH] Make the GUI better
|
|
|
|
---
|
|
.../net/minecraft/server/DedicatedServer.java | 9 +-
|
|
.../net/minecraft/server/MinecraftServer.java | 1 +
|
|
.../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 | 59 +++++++
|
|
.../purpur/gui/info/graph/GraphColor.java | 44 ++++++
|
|
.../pl3x/purpur/gui/info/graph/GraphData.java | 47 ++++++
|
|
.../pl3x/purpur/gui/info/graph/RAMGraph.java | 144 ++++++++++++++++++
|
|
.../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 +-
|
|
18 files changed, 1005 insertions(+), 5 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/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
|
|
index cf5abd393..73d2b32c5 100644
|
|
--- a/src/main/java/net/minecraft/server/DedicatedServer.java
|
|
+++ b/src/main/java/net/minecraft/server/DedicatedServer.java
|
|
@@ -8,6 +8,8 @@ import com.mojang.authlib.GameProfileRepository;
|
|
import com.mojang.authlib.minecraft.MinecraftSessionService;
|
|
import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
|
|
import com.mojang.datafixers.DataFixer;
|
|
+
|
|
+import java.awt.GraphicsEnvironment;
|
|
import java.io.BufferedReader;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
@@ -51,7 +53,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) {
|
|
@@ -87,6 +89,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer
|
|
return;
|
|
}
|
|
// Paper start - Use TerminalConsoleAppender
|
|
+ if (DedicatedServer.this.p == null || System.console() != null) // Purpur - has no GUI or has console (did not double click)
|
|
new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start();
|
|
/*
|
|
jline.console.ConsoleReader bufferedreader = reader;
|
|
@@ -420,7 +423,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) {
|
|
@@ -519,7 +522,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 92677b38b..f1266767e 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -1366,6 +1366,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
return true;
|
|
}
|
|
|
|
+ public void addTickable(Runnable tickable) { b(tickable); } // Purpur - OBFHELPER
|
|
public void b(Runnable runnable) {
|
|
this.tickables.add(runnable);
|
|
}
|
|
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..973b5efef
|
|
--- /dev/null
|
|
+++ 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<Runnable> 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 000000000..55feec811
|
|
--- /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;
|
|
+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.util.HashSet;
|
|
+import java.util.Set;
|
|
+
|
|
+public class JColorTextPane extends JTextPane {
|
|
+ private static final GUIColor DEFAULT_COLOR = GUIColor.BLACK;
|
|
+
|
|
+ public void append(String str) {
|
|
+ BaseComponent[] components = TextComponent.fromLegacyText(DEFAULT_COLOR.getCode() + str, ChatColor.BLACK);
|
|
+ for (BaseComponent component : components) {
|
|
+ String text = component.toPlainText();
|
|
+ if (text == null || text.isEmpty()) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ GUIColor guiColor = GUIColor.getColor(component.getColor());
|
|
+
|
|
+ StyleContext context = StyleContext.getDefaultStyleContext();
|
|
+ AttributeSet attr = context.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, guiColor.getColor());
|
|
+ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Bold, component.isBold() || guiColor != DEFAULT_COLOR);
|
|
+ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Italic, component.isItalic());
|
|
+ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Underline, component.isUnderlined());
|
|
+ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.StrikeThrough, component.isStrikethrough());
|
|
+ //attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Blink, component.isObfuscated()); // no such thing as Blink, sadly
|
|
+
|
|
+ try {
|
|
+ int pos = getDocument().getLength();
|
|
+ getDocument().insertString(pos, text, attr);
|
|
+
|
|
+ if (component.isObfuscated()) {
|
|
+ // dirty hack to blink some text
|
|
+ Blink blink = new Blink(pos, text.length(), attr, context.addAttribute(attr, StyleConstants.Foreground, getBackground()));
|
|
+ BLINKS.add(blink);
|
|
+ }
|
|
+ } catch (BadLocationException e) {
|
|
+ e.printStackTrace();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static final Set<Blink> BLINKS = new HashSet<>();
|
|
+ private static boolean SYNC_BLINK;
|
|
+
|
|
+ static {
|
|
+ Timer timer = new Timer(500, e -> {
|
|
+ SYNC_BLINK = !SYNC_BLINK;
|
|
+ BLINKS.forEach(Blink::blink);
|
|
+ });
|
|
+ timer.start();
|
|
+ }
|
|
+
|
|
+ public class Blink {
|
|
+ private final int start, length;
|
|
+ private final AttributeSet attr1;
|
|
+ private final AttributeSet attr2;
|
|
+
|
|
+ private Blink(int start, int length, AttributeSet attr1, AttributeSet attr2) {
|
|
+ this.start = start;
|
|
+ this.length = length;
|
|
+ this.attr1 = attr1;
|
|
+ this.attr2 = attr2;
|
|
+ }
|
|
+
|
|
+ private void blink() {
|
|
+ getStyledDocument().setCharacterAttributes(start, length, SYNC_BLINK ? attr1 : attr2, true);
|
|
+ }
|
|
+ }
|
|
+}
|
|
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 000000000..fb017a070
|
|
--- /dev/null
|
|
+++ b/src/main/java/net/pl3x/purpur/gui/console/JConsolePanel.java
|
|
@@ -0,0 +1,128 @@
|
|
+package net.pl3x.purpur.gui.console;
|
|
+
|
|
+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 JConsolePanel 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;
|
|
+
|
|
+ public JConsolePanel(DedicatedServer server) {
|
|
+ super(new BorderLayout());
|
|
+
|
|
+ JColorTextPane console = new JColorTextPane();
|
|
+ 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(JColorTextPane console, JScrollPane jscrollpane, String str) {
|
|
+ if (!SwingUtilities.isEventDispatchThread()) {
|
|
+ SwingUtilities.invokeLater(() -> print(console, jscrollpane, str));
|
|
+ } 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.append(str);
|
|
+
|
|
+ if (scrollToBottom) {
|
|
+ jscrollbar.setValue(Integer.MAX_VALUE);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static class CommandHistory extends LinkedList<String> {
|
|
+ @Override
|
|
+ public boolean add(String command) {
|
|
+ if (size() > 1000) {
|
|
+ remove();
|
|
+ }
|
|
+ return super.offerFirst(command);
|
|
+ }
|
|
+ }
|
|
+}
|
|
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 000000000..4e0b80fdd
|
|
--- /dev/null
|
|
+++ 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 000000000..c4903c7db
|
|
--- /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(server);
|
|
+
|
|
+ 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 000000000..1e049d575
|
|
--- /dev/null
|
|
+++ b/src/main/java/net/pl3x/purpur/gui/info/RAMDetails.java
|
|
@@ -0,0 +1,59 @@
|
|
+package net.pl3x.purpur.gui.info;
|
|
+
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+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<String> {
|
|
+ private static final DecimalFormat DECIMAL_FORMAT = SystemUtils.a(new DecimalFormat("########0.000"), (format)
|
|
+ -> format.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT)));
|
|
+
|
|
+ private final MinecraftServer server;
|
|
+
|
|
+ public 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);
|
|
+
|
|
+ setSelectionModel(new DetailsListSelectionModel());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Dimension getPreferredSize() {
|
|
+ return new Dimension(350, 100);
|
|
+ }
|
|
+
|
|
+ public void update() {
|
|
+ GraphData data = RAMGraph.DATA.peekLast();
|
|
+ Vector<String> 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(server.tickTimes5s.getAverage()) + " 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 000000000..2fdb9bdd0
|
|
--- /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 000000000..85babad9c
|
|
--- /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 000000000..0c0c73bee
|
|
--- /dev/null
|
|
+++ b/src/main/java/net/pl3x/purpur/gui/info/graph/RAMGraph.java
|
|
@@ -0,0 +1,144 @@
|
|
+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.PointerInfo;
|
|
+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<GraphData> DATA = new LinkedList<GraphData>() {
|
|
+ @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()));
|
|
+
|
|
+ PointerInfo pointerInfo = MouseInfo.getPointerInfo();
|
|
+ if (pointerInfo != null) {
|
|
+ Point point = pointerInfo.getLocation();
|
|
+ if (point != null) {
|
|
+ Point loc = new Point(point);
|
|
+ SwingUtilities.convertPointFromScreen(loc, this);
|
|
+ if (this.contains(loc)) {
|
|
+ ToolTipManager.sharedInstance().mouseMoved(
|
|
+ new MouseEvent(this, -1, System.currentTimeMillis(), 0, loc.x, loc.y,
|
|
+ point.x, point.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("<html><body>Used: %s mb (%s%%)<br/>%s</body></html>",
|
|
+ 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 000000000..17e75a207
|
|
--- /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<EntityPlayer> {
|
|
+ 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 000000000..2f02e9adc
|
|
--- /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("<html><body>%s<br/>%s</body></html>",
|
|
+ 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 000000000..edbf45416
|
|
--- /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<EntityPlayer> {
|
|
+ private final Set<EntityPlayer> datas = new HashSet<>();
|
|
+ private final JPlayerList parent;
|
|
+
|
|
+ public PlayerListModel(JPlayerList parent) {
|
|
+ this.parent = parent;
|
|
+ }
|
|
+
|
|
+ public void update(Collection<EntityPlayer> players) {
|
|
+ boolean hadFocus = parent.hasFocus();
|
|
+
|
|
+ players.forEach(this::add);
|
|
+
|
|
+ Set<EntityPlayer> 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 000000000..13a5eb3ad
|
|
--- /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 000000000..973c8ddf9
|
|
--- /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;
|
|
+
|
|
+import java.awt.Color;
|
|
+import java.util.HashMap;
|
|
+import java.util.Map;
|
|
+
|
|
+public enum GUIColor {
|
|
+ BLACK(ChatColor.BLACK, new Color(0x000000)),
|
|
+ DARK_BLUE(ChatColor.DARK_BLUE, new Color(0x0000AA)),
|
|
+ DARK_GREEN(ChatColor.DARK_GREEN, new Color(0x00AA00)),
|
|
+ DARK_AQUA(ChatColor.DARK_AQUA, new Color(0x009999)),
|
|
+ DARK_RED(ChatColor.DARK_RED, new Color(0xAA0000)),
|
|
+ DARK_PURPLE(ChatColor.DARK_PURPLE, new Color(0xAA00AA)),
|
|
+ GOLD(ChatColor.GOLD, new Color(0xBB8800)),
|
|
+ GRAY(ChatColor.GRAY, new Color(0x888888)),
|
|
+ DARK_GRAY(ChatColor.DARK_GRAY, new Color(0x444444)),
|
|
+ BLUE(ChatColor.BLUE, new Color(0x5555FF)),
|
|
+ GREEN(ChatColor.GREEN, new Color(0x55FF55)),
|
|
+ AQUA(ChatColor.AQUA, new Color(0x55DDDD)),
|
|
+ RED(ChatColor.RED, new Color(0xFF5555)),
|
|
+ LIGHT_PURPLE(ChatColor.LIGHT_PURPLE, new Color(0xFF55FF)),
|
|
+ YELLOW(ChatColor.YELLOW, new Color(0xFFBB00)),
|
|
+ WHITE(ChatColor.WHITE, new Color(0xBBBBBB));
|
|
+
|
|
+ private final ChatColor chat;
|
|
+ private final Color color;
|
|
+
|
|
+ private static final Map<ChatColor, GUIColor> BY_CHAT = new HashMap<>();
|
|
+
|
|
+ GUIColor(ChatColor chat, Color color) {
|
|
+ this.chat = chat;
|
|
+ this.color = color;
|
|
+ }
|
|
+
|
|
+ public Color getColor() {
|
|
+ return color;
|
|
+ }
|
|
+
|
|
+ public String getCode() {
|
|
+ return chat.toString();
|
|
+ }
|
|
+
|
|
+ public static GUIColor getColor(ChatColor chat) {
|
|
+ return BY_CHAT.get(chat);
|
|
+ }
|
|
+
|
|
+ static {
|
|
+ for (GUIColor color : values()) {
|
|
+ BY_CHAT.put(color.chat, color);
|
|
+ }
|
|
+ }
|
|
+}
|
|
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 000000000..4b340b88a
|
|
--- /dev/null
|
|
+++ b/src/main/java/net/pl3x/purpur/util/HighlightErrorConverter.java
|
|
@@ -0,0 +1,86 @@
|
|
+package net.pl3x.purpur.util;
|
|
+
|
|
+import org.apache.logging.log4j.Level;
|
|
+import org.apache.logging.log4j.core.LogEvent;
|
|
+import org.apache.logging.log4j.core.config.Configuration;
|
|
+import org.apache.logging.log4j.core.config.plugins.Plugin;
|
|
+import org.apache.logging.log4j.core.layout.PatternLayout;
|
|
+import org.apache.logging.log4j.core.pattern.ConverterKeys;
|
|
+import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
|
|
+import org.apache.logging.log4j.core.pattern.PatternConverter;
|
|
+import org.apache.logging.log4j.core.pattern.PatternFormatter;
|
|
+import org.apache.logging.log4j.core.pattern.PatternParser;
|
|
+import org.apache.logging.log4j.util.PerformanceSensitive;
|
|
+
|
|
+import java.util.List;
|
|
+
|
|
+@Plugin(name = "highlightGUIError", category = PatternConverter.CATEGORY)
|
|
+@ConverterKeys({"highlightGUIError"})
|
|
+@PerformanceSensitive("allocation")
|
|
+public final class HighlightErrorConverter extends LogEventPatternConverter {
|
|
+
|
|
+ private static final String ERROR = "\u00A74\u00A7l"; // Bold Red
|
|
+ private static final String WARN = "\u00A7e\u00A7l"; // Bold Yellow
|
|
+
|
|
+ private final List<PatternFormatter> formatters;
|
|
+
|
|
+ protected HighlightErrorConverter(List<PatternFormatter> formatters) {
|
|
+ super("highlightGUIError", null);
|
|
+ this.formatters = formatters;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void format(LogEvent event, StringBuilder toAppendTo) {
|
|
+ Level level = event.getLevel();
|
|
+ if (level.isMoreSpecificThan(Level.ERROR)) {
|
|
+ format(ERROR, event, toAppendTo);
|
|
+ return;
|
|
+ } else if (level.isMoreSpecificThan(Level.WARN)) {
|
|
+ format(WARN, event, toAppendTo);
|
|
+ return;
|
|
+ }
|
|
+ for (PatternFormatter formatter : formatters) {
|
|
+ formatter.format(event, toAppendTo);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private void format(String style, LogEvent event, StringBuilder toAppendTo) {
|
|
+ int start = toAppendTo.length();
|
|
+ toAppendTo.append(style);
|
|
+ int end = toAppendTo.length();
|
|
+
|
|
+ for (PatternFormatter formatter : formatters) {
|
|
+ formatter.format(event, toAppendTo);
|
|
+ }
|
|
+
|
|
+ if (toAppendTo.length() == end) {
|
|
+ toAppendTo.setLength(start);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean handlesThrowable() {
|
|
+ for (final PatternFormatter formatter : formatters) {
|
|
+ if (formatter.handlesThrowable()) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ public static HighlightErrorConverter newInstance(Configuration config, String[] options) {
|
|
+ if (options.length != 1) {
|
|
+ LOGGER.error("Incorrect number of options on highlightGUIError. Expected 1 received " + options.length);
|
|
+ return null;
|
|
+ }
|
|
+ if (options[0] == null) {
|
|
+ LOGGER.error("No pattern supplied on highlightGUIError");
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ PatternParser parser = PatternLayout.createPatternParser(config);
|
|
+ List<PatternFormatter> formatters = parser.parse(options[0]);
|
|
+ return new HighlightErrorConverter(formatters);
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
|
|
index a9bb98765..4be7613d0 100644
|
|
--- a/src/main/resources/log4j2.xml
|
|
+++ b/src/main/resources/log4j2.xml
|
|
@@ -1,8 +1,17 @@
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
-<Configuration status="WARN" packages="com.mojang.util">
|
|
+<Configuration status="WARN" packages="com.mojang.util,net.pl3x.purpur.util"><!-- Purpur -->
|
|
<Appenders>
|
|
<Queue name="ServerGuiConsole">
|
|
- <PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg%n" />
|
|
+ <!-- Purpur start -->
|
|
+ <PatternLayout>
|
|
+ <LoggerNamePatternSelector defaultPattern="%highlightGUIError{[%d{HH:mm:ss} %level]: [%logger] %msg%n%xEx}">
|
|
+ <!-- Log root, Minecraft, Mojang and Bukkit loggers without prefix -->
|
|
+ <!-- Disable prefix for various plugins that bypass the plugin logger -->
|
|
+ <PatternMatch key=",net.minecraft.,Minecraft,com.mojang.,com.sk89q.,ru.tehkode.,Minecraft.AWE"
|
|
+ pattern="%highlightGUIError{[%d{HH:mm:ss} %level]: %msg%n%xEx}" />
|
|
+ </LoggerNamePatternSelector>
|
|
+ </PatternLayout>
|
|
+ <!-- Purpur end -->
|
|
</Queue>
|
|
<TerminalConsole name="TerminalConsole">
|
|
<PatternLayout>
|
|
--
|
|
2.24.0
|
|
|