diff --git a/patches/server/0275-Make-GUI-Great-Again.patch b/patches/server/0275-Make-GUI-Great-Again.patch deleted file mode 100644 index 1784bae2b..000000000 --- a/patches/server/0275-Make-GUI-Great-Again.patch +++ /dev/null @@ -1,421 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Thu, 16 Jan 2020 14:59:16 -0600 -Subject: [PATCH] Make GUI Great Again - - -diff --git a/src/log4jPlugins/java/org/purpurmc/purpur/gui/HighlightErrorConverter.java b/src/log4jPlugins/java/org/purpurmc/purpur/gui/HighlightErrorConverter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..15a226e3854d731f7724025ea3459c8ace07630c ---- /dev/null -+++ b/src/log4jPlugins/java/org/purpurmc/purpur/gui/HighlightErrorConverter.java -@@ -0,0 +1,85 @@ -+package org.purpurmc.purpur.gui.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 formatters; -+ -+ private HighlightErrorConverter(List 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 formatters = parser.parse(options[0]); -+ return new HighlightErrorConverter(formatters); -+ } -+} -diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java -index 509e67eeab1f07ecce3bce0c8c222bc2c184ca6f..df035169ea89c4078e9a9226ac6e752f29d585a1 100644 ---- a/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/net/minecraft/server/dedicated/DedicatedServer.java -@@ -111,6 +111,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - return; - } - // Paper start - Use TerminalConsoleAppender -+ if (DedicatedServer.this.gui == 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 = DedicatedServer.this.reader; -diff --git a/net/minecraft/server/gui/MinecraftServerGui.java b/net/minecraft/server/gui/MinecraftServerGui.java -index 759062d219ff490a3cb19e710c4d18e3e08288e0..8f74c2ec5252b6265549589310d742337c91cb2c 100644 ---- a/net/minecraft/server/gui/MinecraftServerGui.java -+++ b/net/minecraft/server/gui/MinecraftServerGui.java -@@ -43,6 +43,11 @@ public class MinecraftServerGui extends JComponent { - private Thread logAppenderThread; - private final Collection finalizers = Lists.newArrayList(); - final AtomicBoolean isClosing = new AtomicBoolean(); -+ // Purpur start -+ private final CommandHistory history = new CommandHistory(); -+ private String currentCommand = ""; -+ private int historyIndex = 0; -+ // Purpur end - - public static MinecraftServerGui showFrameFor(final DedicatedServer server) { - try { -@@ -51,7 +56,7 @@ public class MinecraftServerGui extends JComponent { - ; - } - -- final JFrame jframe = new JFrame("Minecraft server"); -+ final JFrame jframe = new JFrame("Purpur Minecraft server"); // Purpur - final MinecraftServerGui servergui = new MinecraftServerGui(server); - - jframe.setDefaultCloseOperation(2); -@@ -59,7 +64,7 @@ public class MinecraftServerGui extends JComponent { - jframe.pack(); - jframe.setLocationRelativeTo((Component) null); - jframe.setVisible(true); -- jframe.setName("Minecraft server"); // Paper - Improve ServerGUI -+ jframe.setName("Purpur Minecraft server"); // Paper - Improve ServerGUI // Purpur - - // Paper start - Improve ServerGUI - try { -@@ -71,7 +76,7 @@ public class MinecraftServerGui extends JComponent { - jframe.addWindowListener(new WindowAdapter() { - public void windowClosing(WindowEvent windowevent) { - if (!servergui.isClosing.getAndSet(true)) { -- jframe.setTitle("Minecraft server - shutting down!"); -+ jframe.setTitle("Purpur Minecraft server - shutting down!"); // Purpur - server.halt(true); - servergui.runFinalizers(); - } -@@ -159,7 +164,7 @@ public class MinecraftServerGui extends JComponent { - - private JComponent buildChatPanel() { - JPanel jpanel = new JPanel(new BorderLayout()); -- JTextArea jtextarea = new JTextArea(); -+ org.purpurmc.purpur.gui.JColorTextPane jtextarea = new org.purpurmc.purpur.gui.JColorTextPane(); // Purpur - JScrollPane jscrollpane = new JScrollPane(jtextarea, 22, 30); - - jtextarea.setEditable(false); -@@ -171,10 +176,43 @@ public class MinecraftServerGui extends JComponent { - - if (!s.isEmpty()) { - this.server.handleConsoleInput(s, this.server.createCommandSourceStack()); -+ // Purpur start -+ history.add(s); -+ historyIndex = -1; -+ // Purpur end - } - - jtextfield.setText(""); - }); -+ // Purpur start -+ jtextfield.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("UP"), "up"); -+ jtextfield.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("DOWN"), "down"); -+ jtextfield.getActionMap().put("up", new javax.swing.AbstractAction() { -+ @Override -+ public void actionPerformed(java.awt.event.ActionEvent actionEvent) { -+ if (historyIndex < 0) { -+ currentCommand = jtextfield.getText(); -+ } -+ if (historyIndex < history.size() - 1) { -+ jtextfield.setText(history.get(++historyIndex)); -+ } -+ } -+ }); -+ jtextfield.getActionMap().put("down", new javax.swing.AbstractAction() { -+ @Override -+ public void actionPerformed(java.awt.event.ActionEvent actionEvent) { -+ if (historyIndex >= 0) { -+ if (historyIndex == 0) { -+ --historyIndex; -+ jtextfield.setText(currentCommand); -+ } else { -+ --historyIndex; -+ jtextfield.setText(history.get(historyIndex)); -+ } -+ } -+ } -+ }); -+ // Purpur end - jtextarea.addFocusListener(new FocusAdapter() { // CraftBukkit - decompile error - public void focusGained(FocusEvent focusevent) {} - }); -@@ -210,7 +248,7 @@ public class MinecraftServerGui extends JComponent { - } - - private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper -- public void print(JTextArea textArea, JScrollPane scrollPane, String message) { -+ public void print(org.purpurmc.purpur.gui.JColorTextPane textArea, JScrollPane scrollPane, String message) { // Purpur - if (!SwingUtilities.isEventDispatchThread()) { - SwingUtilities.invokeLater(() -> { - this.print(textArea, scrollPane, message); -@@ -224,11 +262,14 @@ public class MinecraftServerGui extends JComponent { - flag = (double) jscrollbar.getValue() + jscrollbar.getSize().getHeight() + (double) (MinecraftServerGui.MONOSPACED.getSize() * 4) > (double) jscrollbar.getMaximum(); - } - -+ /* // Purpur - try { - document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(message).replaceAll(""), (AttributeSet) null); // CraftBukkit - } catch (BadLocationException badlocationexception) { - ; - } -+ */ // Purpur -+ textArea.append(message); // Purpur - - if (flag) { - jscrollbar.setValue(Integer.MAX_VALUE); -@@ -236,4 +277,16 @@ public class MinecraftServerGui extends JComponent { - - } - } -+ -+ // Purpur start -+ public static class CommandHistory extends java.util.LinkedList { -+ @Override -+ public boolean add(String command) { -+ if (size() > 1000) { -+ remove(); -+ } -+ return super.offerFirst(command); -+ } -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/purpurmc/purpur/gui/GUIColor.java b/src/main/java/org/purpurmc/purpur/gui/GUIColor.java -new file mode 100644 -index 0000000000000000000000000000000000000000..550222758bf0e7deff26a6e813a860b7be365e87 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/gui/GUIColor.java -@@ -0,0 +1,58 @@ -+package org.purpurmc.purpur.gui; -+ -+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 BY_CHAT = new HashMap<>(); -+ -+ GUIColor(ChatColor chat, Color color) { -+ this.chat = chat; -+ this.color = color; -+ } -+ -+ public Color getColor() { -+ return color; -+ } -+ -+ public ChatColor getChatColor() { -+ return chat; -+ } -+ -+ 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/org/purpurmc/purpur/gui/JColorTextPane.java b/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d75fb5e77eff27d86135ed7d605dbc250b660f7d ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java -@@ -0,0 +1,83 @@ -+package org.purpurmc.purpur.gui; -+ -+import com.google.common.collect.Sets; -+import javax.swing.UIManager; -+import net.md_5.bungee.api.chat.BaseComponent; -+import net.md_5.bungee.api.chat.TextComponent; -+ -+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.Set; -+ -+public class JColorTextPane extends JTextPane { -+ private static final GUIColor DEFAULT_COLOR; -+ static { -+ DEFAULT_COLOR = UIManager.getSystemLookAndFeelClassName().equals("com.sun.java.swing.plaf.gtk.GTKLookAndFeel") -+ ? GUIColor.WHITE : GUIColor.BLACK; -+ } -+ -+ -+ public void append(String msg) { -+ // TODO: update to use adventure instead -+ BaseComponent[] components = TextComponent.fromLegacyText(DEFAULT_COLOR.getCode() + msg, DEFAULT_COLOR.getChatColor()); -+ 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 ignore) { -+ } -+ } -+ } -+ -+ private static final Set BLINKS = Sets.newHashSet(); -+ private static boolean SYNC_BLINK; -+ -+ static { -+ new Timer(500, e -> { -+ SYNC_BLINK = !SYNC_BLINK; -+ BLINKS.forEach(Blink::blink); -+ }).start(); -+ } -+ -+ public class Blink { -+ private final int start, length; -+ private final AttributeSet attr1, 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/resources/log4j2.xml b/src/main/resources/log4j2.xml -index d2a75850af9c6ad2aca66a5f994f1b587d73eac4..a056aa167887abef9e6d531a9edd2cda433567d2 100644 ---- a/src/main/resources/log4j2.xml -+++ b/src/main/resources/log4j2.xml -@@ -2,7 +2,16 @@ - - - -- -+ -+ -+ -+ -+ -+ -+ -+ -+ - - - diff --git a/purpur-server/minecraft-patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch b/purpur-server/minecraft-patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch index d58b511b0..2eb72430f 100644 --- a/purpur-server/minecraft-patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch +++ b/purpur-server/minecraft-patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch @@ -1,5 +1,13 @@ --- a/net/minecraft/server/dedicated/DedicatedServer.java +++ b/net/minecraft/server/dedicated/DedicatedServer.java +@@ -106,6 +_,7 @@ + // CraftBukkit start + if (!org.bukkit.craftbukkit.Main.useConsole) return; + // Paper start - Use TerminalConsoleAppender ++ if (DedicatedServer.this.gui == null || System.console() != null) // Purpur - GUI Improvements - has no GUI or has console (did not double-click) + new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start(); + /* + jline.console.ConsoleReader bufferedreader = DedicatedServer.this.reader; @@ -224,6 +_,15 @@ io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark diff --git a/purpur-server/minecraft-patches/sources/net/minecraft/server/gui/MinecraftServerGui.java.patch b/purpur-server/minecraft-patches/sources/net/minecraft/server/gui/MinecraftServerGui.java.patch new file mode 100644 index 000000000..910e0b882 --- /dev/null +++ b/purpur-server/minecraft-patches/sources/net/minecraft/server/gui/MinecraftServerGui.java.patch @@ -0,0 +1,135 @@ +--- a/net/minecraft/server/gui/MinecraftServerGui.java ++++ b/net/minecraft/server/gui/MinecraftServerGui.java +@@ -39,6 +_,11 @@ + private Thread logAppenderThread; + private final Collection finalizers = Lists.newArrayList(); + final AtomicBoolean isClosing = new AtomicBoolean(); ++ // Purpur start - GUI Improvements ++ private final CommandHistory history = new CommandHistory(); ++ private String currentCommand = ""; ++ private int historyIndex = 0; ++ // Purpur end - GUI Improvements + + public static MinecraftServerGui showFrameFor(final DedicatedServer server) { + try { +@@ -46,7 +_,7 @@ + } catch (Exception var3) { + } + +- final JFrame jFrame = new JFrame("Minecraft server"); ++ final JFrame jFrame = new JFrame("Purpur Minecraft server"); // Purpur - Improve GUI + final MinecraftServerGui minecraftServerGui = new MinecraftServerGui(server); + jFrame.setDefaultCloseOperation(2); + jFrame.add(minecraftServerGui); +@@ -54,7 +_,7 @@ + jFrame.setLocationRelativeTo(null); + jFrame.setVisible(true); + // Paper start - Improve ServerGUI +- jFrame.setName("Minecraft server"); ++ jFrame.setName("Purpur Minecraft server"); // Purpur - Improve GUI + try { + jFrame.setIconImage(javax.imageio.ImageIO.read(java.util.Objects.requireNonNull(MinecraftServerGui.class.getClassLoader().getResourceAsStream("logo.png")))); + } catch (java.io.IOException ignore) { +@@ -64,7 +_,7 @@ + @Override + public void windowClosing(WindowEvent event) { + if (!minecraftServerGui.isClosing.getAndSet(true)) { +- jFrame.setTitle("Minecraft server - shutting down!"); ++ jFrame.setTitle("Purpur Minecraft server - shutting down!"); // Purpur - Improve GUI + server.halt(true); + minecraftServerGui.runFinalizers(); + } +@@ -112,7 +_,7 @@ + + private JComponent buildChatPanel() { + JPanel jPanel = new JPanel(new BorderLayout()); +- JTextArea jTextArea = new JTextArea(); ++ org.purpurmc.purpur.gui.JColorTextPane jTextArea = new org.purpurmc.purpur.gui.JColorTextPane(); // Purpur - GUI Improvements + JScrollPane jScrollPane = new JScrollPane(jTextArea, 22, 30); + jTextArea.setEditable(false); + jTextArea.setFont(MONOSPACED); +@@ -121,10 +_,43 @@ + String trimmed = jTextField.getText().trim(); + if (!trimmed.isEmpty()) { + this.server.handleConsoleInput(trimmed, this.server.createCommandSourceStack()); ++ // Purpur start - GUI Improvements ++ history.add(trimmed); ++ historyIndex = -1; ++ // Purpur end - GUI Improvements + } + + jTextField.setText(""); + }); ++ // Purpur start - GUI Improvements ++ jTextField.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("UP"), "up"); ++ jTextField.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("DOWN"), "down"); ++ jTextField.getActionMap().put("up", new javax.swing.AbstractAction() { ++ @Override ++ public void actionPerformed(java.awt.event.ActionEvent actionEvent) { ++ if (historyIndex < 0) { ++ currentCommand = jTextField.getText(); ++ } ++ if (historyIndex < history.size() - 1) { ++ jTextField.setText(history.get(++historyIndex)); ++ } ++ } ++ }); ++ jTextField.getActionMap().put("down", new javax.swing.AbstractAction() { ++ @Override ++ public void actionPerformed(java.awt.event.ActionEvent actionEvent) { ++ if (historyIndex >= 0) { ++ if (historyIndex == 0) { ++ --historyIndex; ++ jTextField.setText(currentCommand); ++ } else { ++ --historyIndex; ++ jTextField.setText(history.get(historyIndex)); ++ } ++ } ++ } ++ }); ++ // Purpur end - GUI Improvements + jTextArea.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent event) { +@@ -159,7 +_,7 @@ + } + + private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper +- public void print(JTextArea textArea, JScrollPane scrollPane, String line) { ++ public void print(org.purpurmc.purpur.gui.JColorTextPane textArea, JScrollPane scrollPane, String line) { // Purpur - GUI Improvements + if (!SwingUtilities.isEventDispatchThread()) { + SwingUtilities.invokeLater(() -> this.print(textArea, scrollPane, line)); + } else { +@@ -170,16 +_,29 @@ + flag = verticalScrollBar.getValue() + verticalScrollBar.getSize().getHeight() + MONOSPACED.getSize() * 4 > verticalScrollBar.getMaximum(); + } + +- try { ++ /*try { // Purpur - GUI Improvements + document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(line).replaceAll(""), null); // CraftBukkit + } catch (BadLocationException var8) { +- } ++ }*/ // Purpur - GUI Improvements ++ textArea.append(line); // Purpur - GUI Improvements + + if (flag) { + verticalScrollBar.setValue(Integer.MAX_VALUE); + } + } + } ++ ++ // Purpur start - GUI Improvements ++ public static class CommandHistory extends java.util.LinkedList { ++ @Override ++ public boolean add(String command) { ++ if (size() > 1000) { ++ remove(); ++ } ++ return super.offerFirst(command); ++ } ++ } ++ // Purpur end - GUI Improvements + + // Paper start - Add onboarding message for initial server start + private JComponent buildOnboardingPanel() { diff --git a/purpur-server/paper-patches/files/src/log4jPlugins/java/org/purpurmc/purpur/gui/util/HighlightErrorConverter.java.patch b/purpur-server/paper-patches/files/src/log4jPlugins/java/org/purpurmc/purpur/gui/util/HighlightErrorConverter.java.patch new file mode 100644 index 000000000..5a006103d --- /dev/null +++ b/purpur-server/paper-patches/files/src/log4jPlugins/java/org/purpurmc/purpur/gui/util/HighlightErrorConverter.java.patch @@ -0,0 +1,88 @@ +--- /dev/null ++++ b/src/log4jPlugins/java/org/purpurmc/purpur/gui/util/HighlightErrorConverter.java +@@ -1,0 +_,85 @@ ++package org.purpurmc.purpur.gui.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 formatters; ++ ++ private HighlightErrorConverter(List 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 formatters = parser.parse(options[0]); ++ return new HighlightErrorConverter(formatters); ++ } ++} diff --git a/purpur-server/paper-patches/files/src/main/resources/log4j2.xml.patch b/purpur-server/paper-patches/files/src/main/resources/log4j2.xml.patch new file mode 100644 index 000000000..8b4a31032 --- /dev/null +++ b/purpur-server/paper-patches/files/src/main/resources/log4j2.xml.patch @@ -0,0 +1,20 @@ +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -2,7 +_,16 @@ + + + +- ++ ++ ++ ++ ++ ++ ++ ++ ++ + + + diff --git a/purpur-server/src/main/java/org/purpurmc/purpur/gui/GUIColor.java b/purpur-server/src/main/java/org/purpurmc/purpur/gui/GUIColor.java new file mode 100644 index 000000000..550222758 --- /dev/null +++ b/purpur-server/src/main/java/org/purpurmc/purpur/gui/GUIColor.java @@ -0,0 +1,58 @@ +package org.purpurmc.purpur.gui; + +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 BY_CHAT = new HashMap<>(); + + GUIColor(ChatColor chat, Color color) { + this.chat = chat; + this.color = color; + } + + public Color getColor() { + return color; + } + + public ChatColor getChatColor() { + return chat; + } + + 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/purpur-server/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java b/purpur-server/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java new file mode 100644 index 000000000..d75fb5e77 --- /dev/null +++ b/purpur-server/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java @@ -0,0 +1,83 @@ +package org.purpurmc.purpur.gui; + +import com.google.common.collect.Sets; +import javax.swing.UIManager; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; + +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.Set; + +public class JColorTextPane extends JTextPane { + private static final GUIColor DEFAULT_COLOR; + static { + DEFAULT_COLOR = UIManager.getSystemLookAndFeelClassName().equals("com.sun.java.swing.plaf.gtk.GTKLookAndFeel") + ? GUIColor.WHITE : GUIColor.BLACK; + } + + + public void append(String msg) { + // TODO: update to use adventure instead + BaseComponent[] components = TextComponent.fromLegacyText(DEFAULT_COLOR.getCode() + msg, DEFAULT_COLOR.getChatColor()); + 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 ignore) { + } + } + } + + private static final Set BLINKS = Sets.newHashSet(); + private static boolean SYNC_BLINK; + + static { + new Timer(500, e -> { + SYNC_BLINK = !SYNC_BLINK; + BLINKS.forEach(Blink::blink); + }).start(); + } + + public class Blink { + private final int start, length; + private final AttributeSet attr1, 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); + } + } +}