mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
- Updated `ServerStatusDisplay` to use `ChatBot.GetVerbatim` for version name formatting. - Modified `ServerStatusPanelBuilder` to improve player name display with color parsing. - Changed translation for online player label to "Online Players:" for clarity. - Refactored protocol version checks to streamline logic in server status handling.
301 lines
11 KiB
C#
301 lines
11 KiB
C#
using System;
|
|
using Avalonia;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Controls.Documents;
|
|
using Avalonia.Layout;
|
|
using Avalonia.Media;
|
|
|
|
namespace MinecraftClient.Tui
|
|
{
|
|
internal static class ServerStatusPanelBuilder
|
|
{
|
|
private const int MaxSamplePlayers = 10;
|
|
private const int FaviconDisplaySize = 16;
|
|
|
|
internal static Border Build(Protocol.ServerStatusInfo info)
|
|
{
|
|
var contentPanel = new DockPanel { Background = Brushes.Black };
|
|
|
|
if (info.FaviconBase64 is not null)
|
|
{
|
|
var iconGrid = BuildFaviconGrid(info.FaviconBase64, FaviconDisplaySize);
|
|
iconGrid.VerticalAlignment = VerticalAlignment.Center;
|
|
DockPanel.SetDock(iconGrid, Dock.Left);
|
|
contentPanel.Children.Add(iconGrid);
|
|
}
|
|
|
|
var infoPanel = new StackPanel
|
|
{
|
|
Orientation = Orientation.Vertical,
|
|
Margin = new Thickness(1, 0, 0, 0),
|
|
};
|
|
|
|
AddMotd(infoPanel, info);
|
|
AddAddress(infoPanel, info);
|
|
AddVersion(infoPanel, info);
|
|
AddConnectingAs(infoPanel, info);
|
|
AddPing(infoPanel, info);
|
|
AddPlayers(infoPanel, info);
|
|
AddSamplePlayers(infoPanel, info);
|
|
|
|
contentPanel.Children.Add(infoPanel);
|
|
|
|
return new Border
|
|
{
|
|
BorderBrush = new SolidColorBrush(Color.FromRgb(80, 80, 80)),
|
|
BorderThickness = new Thickness(1),
|
|
Background = new SolidColorBrush(Color.FromArgb(240, 20, 20, 20)),
|
|
Padding = new Thickness(1, 0),
|
|
Child = contentPanel,
|
|
Margin = new Thickness(0, 1),
|
|
};
|
|
}
|
|
|
|
private static void AddMotd(StackPanel panel, Protocol.ServerStatusInfo info)
|
|
{
|
|
if (string.IsNullOrEmpty(info.MotdRaw))
|
|
return;
|
|
|
|
try
|
|
{
|
|
string motdFormatted = Protocol.Message.ChatParser.ParseText(info.MotdRaw);
|
|
foreach (string line in motdFormatted.Split('\n'))
|
|
panel.Children.Add(McColorParser.CreateColoredTextBlock(line, TextWrapping.NoWrap));
|
|
}
|
|
catch
|
|
{
|
|
panel.Children.Add(new TextBlock
|
|
{
|
|
Text = info.MotdRaw,
|
|
Foreground = Brushes.White,
|
|
TextWrapping = TextWrapping.NoWrap,
|
|
});
|
|
}
|
|
}
|
|
|
|
private static void AddAddress(StackPanel panel, Protocol.ServerStatusInfo info)
|
|
{
|
|
var row = new TextBlock();
|
|
row.Inlines!.Add(Label(Translations.mcc_server_info_label_server));
|
|
row.Inlines.Add(Value(info.Host, McColors.Aqua));
|
|
row.Inlines.Add(new Run($":{info.Port}") { Foreground = McColors.Gray });
|
|
panel.Children.Add(row);
|
|
}
|
|
|
|
private static void AddVersion(StackPanel panel, Protocol.ServerStatusInfo info)
|
|
{
|
|
string versionClean = Scripting.ChatBot.GetVerbatim(info.VersionName);
|
|
var row = new TextBlock();
|
|
row.Inlines!.Add(Label(Translations.mcc_server_info_label_version));
|
|
row.Inlines.Add(Value(versionClean, McColors.Aqua));
|
|
row.Inlines.Add(new Run(" (") { Foreground = McColors.Gray });
|
|
row.Inlines.Add(new Run(string.Format(Translations.mcc_server_info_label_protocol, info.ProtocolVersion))
|
|
{ Foreground = McColors.Gray });
|
|
row.Inlines.Add(new Run(")") { Foreground = McColors.Gray });
|
|
panel.Children.Add(row);
|
|
}
|
|
|
|
private static void AddConnectingAs(StackPanel panel, Protocol.ServerStatusInfo info)
|
|
{
|
|
if (info.ResolvedProtocol == 0)
|
|
return;
|
|
|
|
string resolvedMcVer = Protocol.ProtocolHandler.ProtocolVersion2MCVer(info.ResolvedProtocol);
|
|
var row = new TextBlock();
|
|
row.Inlines!.Add(Label(Translations.mcc_server_info_label_connecting_as));
|
|
row.Inlines.Add(Value(resolvedMcVer, McColors.Green));
|
|
row.Inlines.Add(new Run(" (") { Foreground = McColors.Gray });
|
|
row.Inlines.Add(new Run(string.Format(Translations.mcc_server_info_label_protocol, info.ResolvedProtocol))
|
|
{ Foreground = McColors.Gray });
|
|
row.Inlines.Add(new Run(")") { Foreground = McColors.Gray });
|
|
panel.Children.Add(row);
|
|
}
|
|
|
|
private static void AddPing(StackPanel panel, Protocol.ServerStatusInfo info)
|
|
{
|
|
if (info.PingMs < 0)
|
|
return;
|
|
|
|
var pingColor = info.PingMs < 100
|
|
? McColors.Green
|
|
: info.PingMs < 300
|
|
? McColors.Yellow
|
|
: McColors.Red;
|
|
|
|
var row = new TextBlock();
|
|
row.Inlines!.Add(Label(Translations.mcc_server_info_label_ping));
|
|
row.Inlines.Add(new Run(string.Format(Translations.mcc_server_info_label_ping_ms, info.PingMs))
|
|
{ Foreground = pingColor });
|
|
panel.Children.Add(row);
|
|
}
|
|
|
|
private static void AddPlayers(StackPanel panel, Protocol.ServerStatusInfo info)
|
|
{
|
|
var row = new TextBlock();
|
|
row.Inlines!.Add(Label(Translations.mcc_server_info_label_players));
|
|
row.Inlines.Add(Value($"{info.OnlinePlayers}", McColors.Green));
|
|
row.Inlines.Add(new Run("/") { Foreground = McColors.Gray });
|
|
row.Inlines.Add(Value($"{info.MaxPlayers}", McColors.Red));
|
|
panel.Children.Add(row);
|
|
}
|
|
|
|
private static void AddSamplePlayers(StackPanel panel, Protocol.ServerStatusInfo info)
|
|
{
|
|
if (info.SamplePlayers.Count == 0)
|
|
return;
|
|
|
|
panel.Children.Add(new TextBlock
|
|
{
|
|
Text = Translations.mcc_server_info_label_online,
|
|
Foreground = McColors.Gray,
|
|
});
|
|
|
|
int shown = Math.Min(info.SamplePlayers.Count, MaxSamplePlayers);
|
|
for (int i = 0; i < shown; i++)
|
|
{
|
|
string name = info.SamplePlayers[i].Name;
|
|
if (name.Contains('\u00a7'))
|
|
panel.Children.Add(McColorParser.CreateColoredTextBlock($" {name}", TextWrapping.NoWrap));
|
|
else
|
|
panel.Children.Add(new TextBlock
|
|
{
|
|
Text = $" {name}",
|
|
Foreground = McColors.Green,
|
|
});
|
|
}
|
|
|
|
if (info.SamplePlayers.Count > shown)
|
|
{
|
|
panel.Children.Add(new TextBlock
|
|
{
|
|
Text = $" {string.Format(Translations.mcc_server_info_sample_more, info.SamplePlayers.Count - shown)}",
|
|
Foreground = McColors.Gray,
|
|
});
|
|
}
|
|
}
|
|
|
|
private static Run Label(string text) =>
|
|
new(text + " ") { Foreground = McColors.Gray };
|
|
|
|
private static Run Value(string text, IBrush color) =>
|
|
new(text) { Foreground = color };
|
|
|
|
#region Favicon Rendering
|
|
|
|
private static Grid BuildFaviconGrid(string base64Png, int displaySize)
|
|
{
|
|
byte[] pngBytes;
|
|
try
|
|
{
|
|
pngBytes = Convert.FromBase64String(base64Png);
|
|
}
|
|
catch
|
|
{
|
|
return new Grid();
|
|
}
|
|
|
|
int srcWidth, srcHeight;
|
|
byte[] rgba;
|
|
try
|
|
{
|
|
(srcWidth, srcHeight, rgba) = DecodePngToRgba(pngBytes);
|
|
}
|
|
catch
|
|
{
|
|
return new Grid();
|
|
}
|
|
|
|
int cellCols = displaySize;
|
|
int cellRows = displaySize / 2;
|
|
|
|
var grid = new Grid();
|
|
for (int c = 0; c < cellCols; c++)
|
|
grid.ColumnDefinitions.Add(new ColumnDefinition(1, GridUnitType.Auto));
|
|
for (int r = 0; r < cellRows; r++)
|
|
grid.RowDefinitions.Add(new RowDefinition(1, GridUnitType.Auto));
|
|
|
|
for (int row = 0; row < cellRows; row++)
|
|
{
|
|
for (int col = 0; col < cellCols; col++)
|
|
{
|
|
int topPixelY = row * 2;
|
|
int bottomPixelY = row * 2 + 1;
|
|
|
|
var topColor = SamplePixel(rgba, srcWidth, srcHeight, col, topPixelY, cellCols, displaySize);
|
|
var bottomColor = SamplePixel(rgba, srcWidth, srcHeight, col, bottomPixelY, cellCols, displaySize);
|
|
|
|
var cell = new TextBlock
|
|
{
|
|
Text = "\u2580",
|
|
Foreground = new SolidColorBrush(topColor),
|
|
Background = new SolidColorBrush(bottomColor),
|
|
Padding = new Thickness(0),
|
|
Margin = new Thickness(0),
|
|
};
|
|
|
|
Grid.SetRow(cell, row);
|
|
Grid.SetColumn(cell, col);
|
|
grid.Children.Add(cell);
|
|
}
|
|
}
|
|
|
|
return grid;
|
|
}
|
|
|
|
private static Color SamplePixel(byte[] rgba, int srcW, int srcH, int dstX, int dstY, int dstW, int dstH)
|
|
{
|
|
int srcX = dstX * srcW / dstW;
|
|
int srcY = dstY * srcH / dstH;
|
|
srcX = Math.Clamp(srcX, 0, srcW - 1);
|
|
srcY = Math.Clamp(srcY, 0, srcH - 1);
|
|
|
|
int idx = (srcY * srcW + srcX) * 4;
|
|
if (idx + 3 >= rgba.Length)
|
|
return Color.FromRgb(0, 0, 0);
|
|
|
|
byte r = rgba[idx];
|
|
byte g = rgba[idx + 1];
|
|
byte b = rgba[idx + 2];
|
|
byte a = rgba[idx + 3];
|
|
|
|
return a < 128 ? Color.FromRgb(0, 0, 0) : Color.FromRgb(r, g, b);
|
|
}
|
|
|
|
private static (int Width, int Height, byte[] Rgba) DecodePngToRgba(byte[] png)
|
|
{
|
|
using var image = new ImageMagick.MagickImage(png);
|
|
int w = (int)image.Width;
|
|
int h = (int)image.Height;
|
|
|
|
using var pixels = image.GetPixelsUnsafe();
|
|
var rgba = new byte[w * h * 4];
|
|
|
|
for (int y = 0; y < h; y++)
|
|
{
|
|
for (int x = 0; x < w; x++)
|
|
{
|
|
var pixel = pixels.GetPixel(x, y)!;
|
|
int idx = (y * w + x) * 4;
|
|
var color = pixel.ToColor()!;
|
|
rgba[idx] = (byte)(color.R >> 8);
|
|
rgba[idx + 1] = (byte)(color.G >> 8);
|
|
rgba[idx + 2] = (byte)(color.B >> 8);
|
|
rgba[idx + 3] = (byte)(color.A >> 8);
|
|
}
|
|
}
|
|
|
|
return (w, h, rgba);
|
|
}
|
|
|
|
#endregion
|
|
|
|
private static class McColors
|
|
{
|
|
public static readonly IBrush Gray = new SolidColorBrush(Color.FromRgb(170, 170, 170));
|
|
public static readonly IBrush Aqua = new SolidColorBrush(Color.FromRgb(85, 255, 255));
|
|
public static readonly IBrush Green = new SolidColorBrush(Color.FromRgb(85, 255, 85));
|
|
public static readonly IBrush Red = new SolidColorBrush(Color.FromRgb(255, 85, 85));
|
|
public static readonly IBrush Yellow = new SolidColorBrush(Color.FromRgb(255, 255, 85));
|
|
}
|
|
}
|
|
}
|