Files
MRCC/launcher/PCL-CE/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceServer.xaml.cs
T
xyou f70b061d1a
CI / Go Backend (push) Canceled after 0s
feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
2026-08-08 14:07:56 +08:00

498 lines
16 KiB
C#

using System.Collections.ObjectModel;
using System.IO;
using System.Windows;
using System.Windows.Input;
using FluentValidation;
using fNbt;
using PCL.Core.Link.McPing;
using PCL.Core.Link.McPing.Model;
using PCL.Core.Minecraft;
using PCL.Core.Utils.Validate;
using PCL.Core.App.Localization;
namespace PCL;
public partial class PageInstanceServer : MyPageRight
{
private const int debounceInterval = 2000;
public static readonly List<MinecraftServerInfo> serverList = new();
private static readonly List<ServerCard> serverCardList = new();
private CancellationTokenSource _cts;
private DateTime _lastRefresh = DateTime.MinValue;
public PageInstanceServer()
{
InitializeComponent();
Loaded += PageLoaded;
IsVisibleChanged += PageInstanceServer_IsVisibleChanged;
}
private async void PageLoaded(object e, RoutedEventArgs sender)
{
serverList.Clear();
serverCardList.Clear();
PanServers.Children.Clear();
await LoadServersFromFileAsync();
RefreshTip();
foreach (var server in serverList)
{
var serverCard = new ServerCard();
serverCard.RemoveServer += RemoveServerEvent;
serverCard.EditServer += (a, b) => this.EditServer(a, (ServerCard.ResultEventArgs)b);
serverCard.UpdateServerInfo(server);
serverCardList.Add(serverCard);
PanServers.Children.Add(serverCard);
}
PingAllServers();
}
private void PageInstanceServer_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (!IsVisible)
if (_cts is not null)
{
_cts.Cancel();
_cts.Dispose(); // 清理旧的 CancellationTokenSource
_cts = null;
}
}
private async void RemoveServerEvent(object sender, EventArgs e)
{
// Get server index
var index = PanServers.Children.IndexOf((UIElement)sender);
if (index < 0)
{
HintService.Hint(Lang.Text("Instance.Server.IndexNotFound"), HintType.Error);
return;
}
// Read NBT file
var nbtData =
await NbtFileHandler.ReadTagInNbtFileAsync<NbtList>(
Path.Combine(PageInstanceLeft.McInstance.PathIndie, "servers.dat"), "servers");
if (nbtData is null)
{
HintService.Hint(Lang.Text("Instance.Server.ReadDataFailed"), HintType.Error);
return;
}
// Remove server from NBT data
nbtData.RemoveAt(index);
var clonedNbtData = (NbtList)nbtData.Clone();
// Write back to NBT file
if (!await NbtFileHandler.WriteTagInNbtFileAsync(clonedNbtData,
Path.Combine(PageInstanceLeft.McInstance.PathIndie, "servers.dat")))
{
HintService.Hint(Lang.Text("Instance.Server.WriteDataFailed"), HintType.Error);
return;
}
// Remove server from list and UI
serverList.RemoveAt(index);
serverCardList.Remove((ServerCard)sender);
if (serverList.Count == 0) RefreshTip();
// Remove UI element
PanServers.Children.Remove((UIElement)sender);
// Success message
HintService.Hint(Lang.Text("Instance.Server.Removed"), HintType.Success);
}
private async void EditServer(object sender, ServerCard.ResultEventArgs e)
{
// Read NBT file
var nbtData =
await NbtFileHandler.ReadTagInNbtFileAsync<NbtList>(Path.Combine(PageInstanceLeft.McInstance.PathIndie, "servers.dat"),
"servers");
if (nbtData is null)
{
HintService.Hint(Lang.Text("Instance.Server.ReadDataFailed"), HintType.Error);
return;
}
// Get server index
var index = PanServers.Children.IndexOf((UIElement)sender);
if (index < 0 || index >= nbtData.Count)
{
HintService.Hint(Lang.Text("Instance.Server.IndexNotFound"), HintType.Error);
return;
}
// Verify server data
var server = nbtData[index] as NbtCompound;
// Update server data
server["name"] = new NbtString("name", e.Param1);
server["ip"] = new NbtString("ip", e.Param2);
// Write updated NBT data
var clonedNbtData = (NbtList)nbtData.Clone();
if (!await NbtFileHandler.WriteTagInNbtFileAsync(clonedNbtData,
Path.Combine(PageInstanceLeft.McInstance.PathIndie, "servers.dat")))
{
HintService.Hint(Lang.Text("Instance.Server.WriteDataFailed"), HintType.Error);
return;
}
var serverCard = sender as ServerCard;
serverCard.server.Name = e.Param1;
serverCard.server.Address = e.Param2;
await serverCard.RefreshServerStatusAsync(true);
// Success message
HintService.Hint(Lang.Text("Instance.Server.Updated"), HintType.Success);
}
/// <summary>
/// 刷新服务器列表
/// </summary>
public async void RefreshServers()
{
ModBase.Log("刷新服务器列表");
try
{
// 读取服务器信息
await LoadServersFromFileAsync();
// 在UI线程中更新界面
ModBase.RunInUi(() => UpdateServerUi());
// 异步ping所有服务器
PingAllServers();
}
catch (Exception ex)
{
ModBase.Log(
ex,
Lang.Text("Instance.Server.RefreshFailed"),
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Instance.Server.RefreshFailed"));
ModBase.RunInUi(() => HintService.Hint(
Lang.Text("Instance.Server.RefreshFailed.WithDetail", ex.ToString()),
HintType.Error));
}
}
private void BtnRefresh_Click(object sender, MouseButtonEventArgs e)
{
if ((DateTime.Now - _lastRefresh).TotalMilliseconds < debounceInterval)
{
HintService.Hint(Lang.Text("Instance.Server.NoFrequentRefresh"));
return;
}
_lastRefresh = DateTime.Now;
HintService.Hint(Lang.Text("Instance.Server.RefreshingList"));
try
{
RefreshServers();
}
catch (Exception ex)
{
ModBase.Log(
ex,
Lang.Text("Instance.Server.RefreshFailed"),
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Instance.Server.RefreshFailed"));
HintService.Hint(
Lang.Text("Instance.Server.RefreshFailed.WithDetail", ex.ToString()),
HintType.Error);
}
}
private async void BtnAddServer_Click(object sender, MouseButtonEventArgs e)
{
var result = GetServerInfo(new MinecraftServerInfo { Name = Lang.Text("Instance.Server.DefaultName"), Address = "" });
if (result.Success)
{
var newServer = new MinecraftServerInfo
{
Name = result.Name,
Address = result.Address,
Status = ServerStatus.Unknown
};
serverList.Add(newServer);
RefreshTip();
var serverCard = new ServerCard();
serverCard.RemoveServer += RemoveServerEvent;
serverCard.EditServer += (a, b) => this.EditServer(a, (ServerCard.ResultEventArgs)b);
serverCard.UpdateServerInfo(newServer);
serverCardList.Add(serverCard);
PanServers.Children.Add(serverCard);
await serverCard.RefreshServerStatusAsync(false);
var serversDatPath = Path.Combine(PageInstanceLeft.McInstance.PathIndie, "servers.dat");
NbtList nbtData;
if (!File.Exists(serversDatPath))
{
nbtData = new NbtList("servers", NbtTagType.Compound);
RefreshTip();
}
else
{
nbtData = await NbtFileHandler.ReadTagInNbtFileAsync<NbtList>(serversDatPath, "servers");
}
if (nbtData is not null)
{
var server = new NbtCompound();
server["name"] = new NbtString("name", result.Name);
server["ip"] = new NbtString("ip", result.Address);
if (nbtData.Count == 0) nbtData.ListType = NbtTagType.Compound;
nbtData.Add(server);
var clonedNbtData = (NbtList)nbtData.Clone();
await NbtFileHandler.WriteTagInNbtFileAsync(clonedNbtData, serversDatPath);
}
}
}
public static (string Name, string Address, bool Success) GetServerInfo(MinecraftServerInfo server)
{
var newName = ModMain.MyMsgBoxInput(Lang.Text("Instance.Server.EditTitle"), Lang.Text("Instance.Server.NamePrompt"), server.Name,
[new NullOrWhiteSpaceValidator()]);
if (string.IsNullOrEmpty(newName)) return (string.Empty, string.Empty, false);
var newAddress = ModMain.MyMsgBoxInput(Lang.Text("Instance.Server.EditTitle"), Lang.Text("Instance.Server.AddressPrompt"), server.Address,
[new NullOrWhiteSpaceValidator()]);
if (string.IsNullOrEmpty(newAddress)) return (string.Empty, string.Empty, false);
return (newName, newAddress, true);
}
/// <summary>
/// 从servers.dat文件读取服务器信息
/// </summary>
private async Task LoadServersFromFileAsync()
{
serverList.Clear();
var serversFile = Path.Combine(PageInstanceLeft.McInstance.PathIndie, "servers.dat");
if (!File.Exists(serversFile))
return;
try
{
// 读取NBT格式的servers.dat文件
var nbtData = await NbtFileHandler.ReadTagInNbtFileAsync<NbtList>(serversFile, "servers");
ParseServersFromNBT(nbtData);
}
catch (Exception ex)
{
ModBase.Log(ex, Lang.Text("Instance.Server.ReadFileFailed"));
}
}
/// <summary>
/// 解析NBT格式的服务器数据
/// </summary>
private void ParseServersFromNBT(NbtList serversList)
{
if (serversList is not null)
{
ModBase.Log($"Found {serversList.Count} servers:");
// 遍历 servers 列表中的每个服务器
for (int i = 0, loopTo = serversList.Count - 1; i <= loopTo; i++)
{
var server = serversList[i] as NbtCompound;
if (server is not null)
{
// 提取服务器信息
// Dim hidden As Byte = If(server.Get(Of NbtByte)("hidden")?.Value, 0)
var ip = server.Get<NbtString>("ip")?.Value ?? "Unknown";
var name = server.Get<NbtString>("name")?.Value ?? "Unknown";
var iconBase64 = server.Get<NbtString>("icon")?.Value;
ModBase.Log($"服务器 {i + 1}:");
ModBase.Log($" 名字: {name}");
ModBase.Log($" IP: {ip}");
// Log($" Hidden: {If(hidden = 1, "Yes", "No")}")
serverList.Add(new MinecraftServerInfo
{
Name = name,
Address = ip,
Status = ServerStatus.Unknown,
Icon = iconBase64
});
}
}
}
else
{
ModBase.Log("No 'servers' list found in servers.dat.");
}
}
/// <summary>
/// 更新服务器UI显示
/// </summary>
private void UpdateServerUi()
{
PanServers.Children.Clear();
RefreshTip();
foreach (var server in serverList)
{
var serverCard = new ServerCard();
serverCard.RemoveServer += RemoveServerEvent;
serverCard.EditServer += (a, b) => this.EditServer(a, (ServerCard.ResultEventArgs)b);
serverCard.UpdateServerInfo(server);
serverCardList.Add(serverCard);
PanServers.Children.Add(serverCard);
}
}
private void RefreshTip()
{
if (serverList.Count == 0)
{
ModBase.Log(Lang.Text("Instance.Server.NoServersFound"));
PanNoServer.Visibility = Visibility.Visible;
PanContent.Visibility = Visibility.Collapsed;
PanServers.Visibility = Visibility.Collapsed;
return;
}
ModBase.Log(Lang.Text("Instance.Server.FoundServers"));
PanNoServer.Visibility = Visibility.Collapsed;
PanContent.Visibility = Visibility.Visible;
PanServers.Visibility = Visibility.Visible;
}
private async void PingAllServers()
{
if (_cts is not null)
{
_cts.Cancel();
_cts.Dispose();
}
_cts = new CancellationTokenSource();
var token = _cts.Token;
var semaphore = new SemaphoreSlim(5); // 限制最多 5 个并发任务
var tasks = new List<Task>();
try
{
var snapshot = serverCardList.ToList();
foreach (var server in snapshot)
{
var currentServer = server;
await semaphore.WaitAsync(token);
tasks.Add(Task.Run(async () =>
{
try
{
await currentServer.RefreshServerStatusAsync(false, token);
}
catch (Exception ex)
{
ModBase.Log(ex, $"Ping 服务器失败: {currentServer}");
}
finally
{
semaphore.Release();
}
}, token));
}
await Task.WhenAll(tasks); // 等待所有任务完成
}
catch (OperationCanceledException ex)
{
ModBase.Log("PingAllServers 被取消", ModBase.LogLevel.Debug);
}
catch (Exception ex)
{
ModBase.Log(ex, "PingAllServers 失败");
}
}
/// <summary>
/// ping单个服务器
/// </summary>
public static async Task<MinecraftServerInfo> PingServerAsync(MinecraftServerInfo server, CancellationToken token)
{
try
{
var addr = await ServerAddressResolver.GetResolvedServerAddressAsync(server.Address, token);
using (var query = McPingServiceFactory.CreateService(addr.Host, addr.Ip, addr.Port))
{
McPingResult? result;
ModBase.Log("Pinging server: " + server.Address + ":" + addr.Port);
result = await query.PingAsync(token); // 传递 token
ModBase.Log("Ping result: " + (result is not null ? "Success" : "Failed"));
if (result is not null)
{
server.Status = ServerStatus.Online;
server.PlayerCount = result.Players.Online;
server.MaxPlayers = result.Players.Max;
server.Description = result.Description ?? string.Empty;
server.Version = result.Version.Name;
server.Ping = (int)result.Latency;
server.Icon = result.Favicon;
}
else
{
server.Status = ServerStatus.Offline;
}
}
}
catch (OperationCanceledException ex)
{
server.Status = ServerStatus.Offline;
ModBase.Log("Ping 服务器被取消: " + server.Address, ModBase.LogLevel.Debug);
}
catch (Exception ex)
{
server.Status = ServerStatus.Offline;
ModBase.Log(ex, $"Ping 服务器失败: {server.Address}:{server.Port}");
}
return server;
}
}
/// <summary>
/// Minecraft服务器信息类
/// </summary>
public class MinecraftServerInfo
{
public string Name { get; set; }
public string Address { get; set; }
public int Port { get; set; } = 25565;
public ServerStatus Status { get; set; } = ServerStatus.Unknown;
public int PlayerCount { get; set; }
public int MaxPlayers { get; set; }
public string Description { get; set; } = "";
public string Version { get; set; } = "";
public int Ping { get; set; }
public string Icon { get; set; } = "";
}
/// <summary>
/// 服务器状态枚举
/// </summary>
public enum ServerStatus
{
Unknown,
Online,
Offline,
Pinging
}