Files
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

1017 lines
36 KiB
C#

using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Input;
using PCL.Core.App;
using PCL.Core.Link;
using PCL.Core.Link.EasyTier;
using PCL.Core.Link.Lobby;
using PCL.Core.Link.McPing;
using PCL.Core.Link.Natayark;
using PCL.Core.Link.Scaffolding.Client.Models;
using PCL.Core.Link.Scaffolding.EasyTier;
using PCL.Core.Logging;
using PCL.Core.Utils.Validate;
using PCL.Network;
using PCL.Core.App.Localization;
using PCL.Core.Utils;
namespace PCL;
public partial class PageToolsGameLink
{
static PageToolsGameLink()
{
initLoader = new ModLoader.LoaderCombo<int>(Lang.Text("Link.Mod.Task.InitLobby"),
new[] { new ModLoader.LoaderTask<int, int>(Lang.Text("Common.Action.Initialize"), InitTask) { ProgressWeight = 0.5d } });
}
public PageToolsGameLink()
{
InitializeComponent();
LoaderInit();
Loaded += (_, _) => Reload();
PageEnter += PageLinkLobby_OnPageEnter;
}
#region 初始化
// 加载器初始化
private void LoaderInit()
{
PageLoaderInit(Load, PanLoad, PanContent, null, initLoader, autoRun: false);
// 注册自定义的 OnStateChanged
initLoader.OnStateChangedUi += OnLoadStateChanged;
LobbyService.OnNeedDownloadEasyTier += () => ModLink.DownloadEasyTier();
LobbyService.DiscoveredWorlds.CollectionChanged += OnDiscoveredWorldsChanged;
LobbyService.Players.CollectionChanged += OnPlayersChanged;
LobbyService.OnUserStopGame += OnUserStopGame;
LobbyService.OnClientPing += OnClientPingHandler;
LobbyService.OnServerShutDown += OnServerShuttedDownHandler;
LobbyService.OnServerStarted += OnServerStartedHandler;
LobbyService.OnServerException += OnServerExceptionHandler;
if (lobbyAnnouncementLoader is null)
{
var loaders = new List<ModLoader.LoaderBase>();
loaders.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Link.Mod.Task.InitLobbyUi"), _ => ModBase.RunInUi(() =>
{
HintAnnounce.Visibility = Visibility.Visible;
HintAnnounce.Theme = MyHint.Themes.Blue;
HintAnnounce.Text = Lang.Text("Tools.GameLink.Loading.ConnectingServer");
})));
loaders.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Link.Mod.Task.FetchAnnouncement"), _ => GetAnnouncement()) { ProgressWeight = 0.5d });
lobbyAnnouncementLoader = new ModLoader.LoaderCombo<int>("Lobby Announcement", loaders) { show = false };
}
}
private async void OnServerExceptionHandler(Exception ex)
{
ModBase.RunInUi(() => HintService.Hint(
Lang.Text("Tools.GameLink.Error.ServerMessage", ex.Message),
HintType.Error));
try
{
await LobbyService.LeaveLobbyAsync();
ModBase.RunInUi(() =>
{
CardPlayerList.Title = Lang.Text("Tools.GameLink.Member.ListLoading");
StackPlayerList.Children.Clear();
CurrentSubpage = Subpages.PanSelect;
});
}
catch (Exception secEx)
{
ModBase.Log(secEx, "Occurred an exception when exit server.");
HintService.Hint(Lang.Text("Tools.GameLink.Error.ServerExit"), HintType.Error);
}
}
public async void Reload()
{
HintAnnounce.Visibility = Visibility.Visible;
HintAnnounce.Text = Lang.Text("Tools.GameLink.Loading.ConnectingServer");
HintAnnounce.Theme = MyHint.Themes.Blue;
// 加载公告
lobbyAnnouncementLoader.Start();
if (_linkAnnounceUpdateCancelSource is not null)
_linkAnnounceUpdateCancelSource.Cancel();
_linkAnnounceUpdateCancelSource = new CancellationTokenSource();
await Dispatcher.BeginInvoke(new Action(async () =>
await _LinkAnnounceUpdateAsync())); // 我实在不理解为啥 BeginInvoke 这个委托要 MustBeInherit
await LobbyService.InitializeAsync().ConfigureAwait(false);
}
private void BtnAgreeEula_Click(object sender, MouseButtonEventArgs e)
{
States.Link.LinkEula = true;
CurrentSubpage = Subpages.PanSelect;
}
private void BtnEulaStop_Click(object sender, EventArgs eventArgs)
{
if (ModMain.MyMsgBox(Lang.Text("Tools.GameLink.Eula.RevokeConfirm"),
Lang.Text("Tools.GameLink.Eula.RevokeTitle"),
Lang.Text("Common.Action.Confirm"),
Lang.Text("Common.Action.Cancel"),
isWarn: true
) == 1)
{
States.Link.NaidRefreshTokenConfig.Reset();
States.Link.LinkEulaConfig.Reset();
HintService.Hint(Lang.Text("Tools.GameLink.Eula.Disabled"));
CurrentSubpage = Subpages.PanEula;
}
}
private static readonly ModLoader.LoaderCombo<int> initLoader;
private static async void InitTask(ModLoader.LoaderTask<int, int> task)
{
await LobbyService.InitializeAsync();
}
#region Subscribser
private void OnServerStartedHandler()
{
ModBase.Log("Received server started event.");
ModBase.RunInUi(() =>
{
LabFinishId.Text = LobbyService.CurrentLobbyCode;
StackPlayerList.Children.Clear();
foreach (var player in LobbyService.Players)
StackPlayerList.Children.Add((UIElement)PlayerInfoItem(player, PlayerInfoClick));
});
}
private async void OnServerShuttedDownHandler()
{
try
{
await LobbyService.LeaveLobbyAsync();
ModBase.RunInUi(() =>
{
CardPlayerList.Title = Lang.Text("Tools.GameLink.Member.ListLoading");
StackPlayerList.Children.Clear();
CurrentSubpage = Subpages.PanSelect;
});
}
catch (Exception ex)
{
ModBase.Log(ex, "Occurred an exception when exit server.");
HintService.Hint(Lang.Text("Tools.GameLink.Error.ServerExit"), HintType.Error);
}
}
private void OnClientPingHandler(long latency)
{
ModBase.RunInUi(() =>
{
LabFinishQuality.Text = Lang.Text("Tools.GameLink.Finish.Connected");
LabFinishPing.Text = Lang.Text("Tools.GameLink.Finish.PingMs", latency);
LabConnectType.Text = Lang.Text("Tools.GameLink.Finish.Unavailable");
});
}
private void OnUserStopGame()
{
ModBase.RunInUi(() =>
{
CardPlayerList.Title = Lang.Text("Tools.GameLink.Member.ListLoading");
StackPlayerList.Children.Clear();
CurrentSubpage = Subpages.PanSelect;
});
ModMain.MyMsgBox(Lang.Text("Tools.GameLink.Exit.Disbanded"), Lang.Text("Tools.GameLink.Exit.DisbandedTitle"));
}
private void OnPlayersChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ModBase.Log("接收到玩家列表改变事件");
ModBase.RunInUi(() =>
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
if (e.NewItems is not null)
foreach (PlayerProfile player in e.NewItems)
StackPlayerList.Children.Add((UIElement)PlayerInfoItem(player, PlayerInfoClick));
break;
case NotifyCollectionChangedAction.Remove:
if (e.OldItems is not null)
foreach (PlayerProfile player in e.OldItems)
{
var itemToRemove = StackPlayerList.Children.OfType<MyListItem>()
.FirstOrDefault(item => ((PlayerProfile)item.Tag).MachineId == player.MachineId);
if (itemToRemove is not null) StackPlayerList.Children.Remove(itemToRemove);
}
break;
default:
StackPlayerList.Children.Clear();
foreach (var player in LobbyService.Players)
StackPlayerList.Children.Add((UIElement)PlayerInfoItem(player, PlayerInfoClick));
break;
}
LabFinishQuality.Text = Lang.Text("Tools.GameLink.Finish.Connected");
CardPlayerList.Title = Lang.Text("Tools.GameLink.Member.ListCount", LobbyService.Players.Count);
});
}
private void OnDiscoveredWorldsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
LogWrapper.Info("[Lobby] Found new world changes");
ModBase.RunInUi(() =>
{
#region 处理集合变更
switch (e.Action)
{
case NotifyCollectionChangedAction.Reset:
ComboWorldList.Items.Clear();
foreach (var world in LobbyService.DiscoveredWorlds)
ComboWorldList.Items.Add(new MyComboBoxItem
{
Tag = world.Port,
Content = world.Name
});
break;
case NotifyCollectionChangedAction.Add:
if (e.NewItems is not null)
foreach (FoundWorld world in e.NewItems)
ComboWorldList.Items.Add(new MyComboBoxItem
{
Tag = world.Port,
Content = world.Name
});
break;
case NotifyCollectionChangedAction.Remove:
if (e.OldItems is not null)
{
// 使用 HashSet 提高查询效率
var portsToRemove = e.OldItems.Cast<FoundWorld>().Select(w => w.Port).ToHashSet();
var itemsToRemove = ComboWorldList.Items
.Cast<MyComboBoxItem>()
.Where(item => portsToRemove.Contains((int)item.Tag))
.ToList();
foreach (var item in itemsToRemove) ComboWorldList.Items.Remove(item);
}
break;
}
#endregion
#region 更新 UI 状态
var hasItems = ComboWorldList.Items.Count > 0;
ComboWorldList.IsEnabled = hasItems;
BtnCreate.IsEnabled = hasItems;
if (hasItems && ComboWorldList.SelectedIndex == -1) ComboWorldList.SelectedIndex = 0;
#endregion
});
}
#endregion
#endregion
#region 公告
public static ModLoader.LoaderCombo<int> lobbyAnnouncementLoader;
private readonly ObservableCollection<LinkAnnounceInfo> _linkAnnounces = new();
private CancellationTokenSource _linkAnnounceUpdateCancelSource;
// 公告轮播实现
private async Task _LinkAnnounceUpdateAsync()
{
var currentIndex = 0;
var globalCancelToken = _linkAnnounceUpdateCancelSource.Token;
CancellationTokenSource waiterCts = null;
_linkAnnounces.CollectionChanged += (sender, e) =>
{
if (waiterCts is not null) waiterCts.Cancel();
};
while (!globalCancelToken.IsCancellationRequested)
{
waiterCts = CancellationTokenSource.CreateLinkedTokenSource(globalCancelToken);
var waiterCancelToken = waiterCts.Token;
if (_linkAnnounces.Count > 0)
{
var info = _linkAnnounces[currentIndex];
string prefix;
if (info.Type == LinkAnnounceType.Important)
{
HintAnnounce.Theme = MyHint.Themes.Red;
prefix = Lang.Text("Tools.GameLink.Announcement.Important");
}
else if (info.Type == LinkAnnounceType.Warning)
{
HintAnnounce.Theme = MyHint.Themes.Yellow;
prefix = Lang.Text("Tools.GameLink.Announcement.Warning");
}
else
{
HintAnnounce.Theme = MyHint.Themes.Blue;
prefix = Lang.Text("Tools.GameLink.Announcement.Notice");
}
HintAnnounce.Text = Lang.Text("Tools.GameLink.Announcement.Format", prefix,
info.Content.Replace("\n", "\r\n"));
}
else
{
HintAnnounce.Visibility = Visibility.Collapsed;
}
try
{
await Task.Delay(10000, waiterCancelToken);
}
catch (TaskCanceledException)
{
// 忽略取消任务的异常
}
if (!waiterCancelToken.IsCancellationRequested)
currentIndex += 1;
if (currentIndex >= _linkAnnounces.Count)
currentIndex = 0;
waiterCts = null;
}
}
// 获取公告信息
private void GetAnnouncement()
{
ModBase.RunInNewThread(() =>
{
try
{
var serverNumber = 0;
JsonObject jObj = null;
#region 多服务器轮询获取公告
while (serverNumber < Secrets.LinkServers.Length)
try
{
// 获取缓存版本号
var cacheRes = Requester.Fetch($"{Secrets.LinkServers[serverNumber]}/api/link/v2/cache.ini",
new FetchParam
{
Method = "GET",
ContentType = "application/json",
Timeout = 7000
}).Trim();
var cacheVer = int.Parse(cacheRes);
if (cacheVer == States.Link.AnnounceCacheVer)
{
LogWrapper.Info("[Link] Using cached announcement data");
jObj = (JsonObject)ModBase.GetJson(States.Link.AnnounceCache);
}
else
{
LogWrapper.Info("[Link] Fetching new announcement data");
var received = Requester.Fetch(
$"{Secrets.LinkServers[serverNumber]}/api/link/v2/announce.json",
new FetchParam
{
Method = "GET",
ContentType = "application/json",
Timeout = 7000
});
jObj = (JsonObject)ModBase.GetJson(received);
// 更新缓存
States.Link.AnnounceCache = received;
States.Link.AnnounceCacheVer = cacheVer;
}
break; // 成功获取,跳出轮询
}
catch (Exception ex)
{
LogWrapper.Error(ex, $"[Link] Failed to get announcement from server {serverNumber}");
States.Link.AnnounceCacheConfig.Reset();
States.Link.AnnounceCacheVerConfig.Reset();
serverNumber++;
}
#endregion
if (jObj is null) throw new Exception("Failed to fetch lobby data");
#region 解析基础状态与版本限制
LobbyInfoProvider.IsLobbyAvailable = (bool)jObj["available"];
LobbyInfoProvider.AllowCustomName = (bool)jObj["allowCustomName"];
LobbyInfoProvider.RequiresLogin = (bool)jObj["requireLogin"];
LobbyInfoProvider.RequiresRealName = (bool)jObj["requireRealname"];
if (jObj["version"].ToObject<double>() > LobbyInfoProvider.ProtocolVersion)
{
ModBase.RunInUi(() =>
{
HintAnnounce.Theme = MyHint.Themes.Red;
HintAnnounce.Text = Lang.Text("Tools.GameLink.Error.UpdateRequired");
LobbyInfoProvider.IsLobbyAvailable = false;
});
return;
}
#endregion
#region 解析公告列表 (Notices)
var notices = (JsonArray)jObj["notices"];
foreach (JsonObject notice in notices)
{
var content = notice["content"]?.ToString();
if (string.IsNullOrWhiteSpace(content)) continue;
// 版本过滤
var minVer = notice["minVer"].ToObject<double>();
var maxVer = notice["maxVer"].ToObject<double>();
if (ModBase.versionCode < minVer || ModBase.versionCode > maxVer) continue;
// 类型映射
var type = LinkAnnounceType.Notice;
var typeStr = notice["type"]?.ToString().ToLower();
if (typeStr == "important" || typeStr == "red") type = LinkAnnounceType.Important;
else if (typeStr == "warning" || typeStr == "yellow") type = LinkAnnounceType.Warning;
// 按行拆分公告
foreach (var announce in content.Split('\n'))
{
if (string.IsNullOrWhiteSpace(announce)) continue;
_linkAnnounces.Add(new LinkAnnounceInfo(type, announce));
}
}
#endregion
#region 解析中继服务器 (Relays)
var relays = (JsonArray)jObj["relays"];
ETRelay.RelayList = new List<ETRelay>();
foreach (var relay in relays)
ETRelay.RelayList.Add(new ETRelay
{
Name = relay["name"]?.ToString(),
Url = relay["url"]?.ToString(),
Type = relay["type"]?.ToString() == "official" ? ETRelayType.Selfhosted : ETRelayType.Community
});
#endregion
#region 处理账户登录状态显示
if (string.IsNullOrWhiteSpace(States.Link.NaidRefreshToken))
{
ModBase.RunInUi(() => LabNatayarkUserName.Text = Lang.Text("Tools.GameLink.Natayark.Login"));
}
else
{
ModBase.RunInUi(() => LabNatayarkUserName.Text = Lang.Text("Tools.GameLink.Natayark.Loading"));
if (string.IsNullOrEmpty(NatayarkProfileManager.NaidProfile.Username))
ReloadNaidData();
else
ModBase.RunInUi(() =>
{
if (NatayarkProfileManager.NaidProfile.Status == 0)
{
LabNatayarkUserName.Text = NatayarkProfileManager.NaidProfile.Username;
LabNatayarkUserName.Opacity = 1;
}
else
{
LabNatayarkUserName.Text = $"{NatayarkProfileManager.NaidProfile.Username} {Lang.Text("Tools.GameLink.Natayark.Abnormal")}";
LabNatayarkUserName.Opacity = 0.6;
}
});
}
#endregion
}
catch (Exception ex)
{
LobbyInfoProvider.IsLobbyAvailable = false;
ModBase.RunInUi(() =>
{
HintAnnounce.Theme = MyHint.Themes.Red;
HintAnnounce.Text = Lang.Text("Tools.GameLink.Error.ConnectFailed");
});
LogWrapper.Error(ex, "[Link] Failed to get lobby announcement");
}
});
}
#endregion
#region 信息获取与展示
#region UI 元素
private object PlayerInfoItem(PlayerProfile info, MyListItem.ClickEventHandler onClick)
{
var details = info.Kind == PlayerKind.HOST
? Lang.Text("Tools.GameLink.Player.Details", Lang.Text("Tools.GameLink.Player.Host"), info.Vendor)
: info.Vendor;
var newItem = new MyListItem
{
Title = info.Name,
Info = details,
Type = MyListItem.CheckType.Clickable,
Tag = info
};
newItem.Click += onClick;
return newItem;
}
private void PlayerInfoClick(object sender, MouseButtonEventArgs e)
{
var info = (PlayerProfile)((MyListItem)sender).Tag;
ModMain.MyMsgBox(Lang.Text("Tools.GameLink.Player.InfoMessage", info.Name, info.Vendor), Lang.Text("Tools.GameLink.Player.InfoTitle", info.Name));
}
#endregion
#region Natayark 账户相关功能
private void ReloadNaidData()
{
ModBase.RunInNewThread(() =>
{
try
{
#region 1. 登录令牌有效期检查
// 检查 Token 是否过期
var expireTime = Convert.ToDateTime(States.Link.NaidRefreshExpireTime);
if (expireTime.CompareTo(DateTime.Now) < 0)
{
States.Link.NaidRefreshToken = "";
HintService.Hint(Lang.Text("Tools.GameLink.Natayark.TokenExpired"), HintType.Error);
return;
}
#endregion
#region 2. 异步获取数据并同步等待
// 调用异步方法并阻塞获取结果
NatayarkProfileManager.GetNaidDataAsync(States.Link.NaidRefreshToken, true).GetAwaiter().GetResult();
// 等待用户名加载,设置 10 秒超时防止线程卡死
var retryCount = 0;
while (string.IsNullOrWhiteSpace(NatayarkProfileManager.NaidProfile.Username) && retryCount < 10)
{
Thread.Sleep(1000);
retryCount++;
}
if (string.IsNullOrWhiteSpace(NatayarkProfileManager.NaidProfile.Username))
throw new Exception("Timeout waiting for username");
#endregion
#region 3. UI 状态更新
ModBase.RunInUi(() =>
{
var profile = NatayarkProfileManager.NaidProfile;
// 状态 0 为正常
if (profile.Status == 0)
{
LabNatayarkUserName.Text = profile.Username;
LabNatayarkUserName.Opacity = 1.0;
}
else
{
LabNatayarkUserName.Text = $"{profile.Username} {Lang.Text("Tools.GameLink.Natayark.Abnormal")}";
LabNatayarkUserName.Opacity = 0.6;
}
});
#endregion
}
catch (Exception ex)
{
#region 错误处理
ModBase.Log(ex, "Failed to refresh Natayark ID info, re-login required");
ModBase.RunInUi(() =>
{
LabNatayarkUserName.Text = Lang.Text("Tools.GameLink.Natayark.FetchFailed");
LabNatayarkUserName.Opacity = 0.6;
});
#endregion
}
}, "Natayark Profile Refresh");
}
private void LabNatayarkUserName_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// If Not IsLobbyAvailable Then
// Hint("大厅功能暂不可用,请稍后再试", HintType.Critical)
// Exit Sub
// End If
if (string.IsNullOrWhiteSpace(States.Link.NaidRefreshToken))
{
// 当前未登录,显示登录选项
if (ModMain.MyMsgBox(Lang.Text("Tools.GameLink.Natayark.LoginPrompt"), Lang.Text("Tools.GameLink.Natayark.LoginTitle"), Lang.Text("Tools.GameLink.Natayark.Continue"), Lang.Text("Common.Action.Cancel")) == 1)
{
LabNatayarkUserName.Text = Lang.Text("Tools.GameLink.Natayark.BrowserContinue");
LabNatayarkUserName.Opacity = 0.6d;
BtnNatayarkUserName.IsEnabled = false;
ModWebServer.StartNaidAuthorize(() =>
{
ModBase.RunInUi(() => BtnNatayarkUserName.IsEnabled = true);
HintService.Hint(Lang.Text("Tools.GameLink.Natayark.LoginComplete"), HintType.Success);
ReloadNaidData();
});
}
}
// 当前已登录,显示登出选项
else if (ModMain.MyMsgBox(Lang.Text("Tools.GameLink.Natayark.LogoutConfirm"), Lang.Text("Tools.GameLink.Natayark.LogoutTitle"), Lang.Text("Common.Action.Confirm"), Lang.Text("Common.Action.Cancel")) == 1)
{
States.Link.NaidRefreshTokenConfig.Reset();
States.Link.NaidRefreshToken = "";
LabNatayarkUserName.Text = Lang.Text("Tools.GameLink.Natayark.Login");
ModBase.Log("[Link] 已退出登录 Natayark Network");
HintService.Hint(Lang.Text("Tools.GameLink.Natayark.LogoutComplete"), HintType.Success, false);
}
}
#endregion
// 网络测试功能
private async void BtnNetTest_Click(object sender, MouseButtonEventArgs e)
{
try
{
BtnNatTest.IsEnabled = false;
LabNatType.Text = Lang.Text("Tools.GameLink.Nat.Testing");
var status = await CliNetTest.GetNetStatusAsync();
ModBase.RunInUi(() => LabNatType.Text = Lang.Text("Tools.GameLink.Nat.Result",
CliNetTest.GetNatTypeString(status.UdpNatType),
CliNetTest.GetNatTypeString(status.TcpNatType)));
}
catch (Exception ex)
{
ModBase.Log(
ex,
"[Link] 获取网络测试结果失败",
ModBase.LogLevel.Hint,
userSummary: Lang.Text("Tools.GameLink.Error.NetworkTestFailed"));
BtnNatTest.IsEnabled = true;
LabNatType.Text = Lang.Text("Tools.GameLink.Nat.Failed");
}
finally
{
BtnNatTest.IsEnabled = true;
}
}
private void PasteLobbyId(object sender, MouseButtonEventArgs e)
{
string lobbyId;
try
{
lobbyId = Clipboard.GetText(TextDataFormat.Text);
}
catch (Exception ex)
{
ModBase.Log(ex, "从剪贴板识别大厅编号出错");
return;
}
if (!string.IsNullOrEmpty(lobbyId))
TextJoinLobbyId.Text = lobbyId;
else
HintService.Hint(Lang.Text("Tools.GameLink.Join.InvalidText"));
}
private void ClearLobbyId(object sender, MouseButtonEventArgs e)
{
TextJoinLobbyId.Text = string.Empty;
}
#endregion
#region PanSelect | 种类选择页面
// 刷新按钮
private void BtnRefresh_Click(object sender, MouseButtonEventArgs e)
{
var lobby = LobbyService.DiscoverWorldAsync();
}
private async void BtnInputPort_Click(object sender, MouseButtonEventArgs e)
{
try
{
BtnInputPort.IsEnabled = false;
if (!ModLink.LobbyPrecheck()) return;
var input = ModMain.MyMsgBoxInput(Lang.Text("Tools.GameLink.Create.EnterPort"),
validateRules: [new IntValidator(65535,1024)]);
int port;
if (int.TryParse(input, out port))
using (var ping = McPingServiceFactory.CreateService("127.0.0.1", port, 5000))
{
var res = await ping.PingAsync();
if (res is not null && res.Version.Protocol != 0)
await CreateLobbyAsync(port);
else
HintService.Hint(Lang.Text("Tools.GameLink.Create.NotMcPort"), HintType.Error);
}
}
finally
{
BtnInputPort.IsEnabled = true;
}
}
// 创建大厅
private async void BtnCreate_Click(object sender, MouseButtonEventArgs e)
{
if (ComboWorldList.SelectedItem is null)
{
HintService.Hint(Lang.Text("Tools.GameLink.Create.NoWorld"));
return;
}
BtnCreate.IsEnabled = false;
if (!ModLink.LobbyPrecheck())
{
BtnCreate.IsEnabled = true;
return;
}
var port = (int)((MyComboBoxItem)ComboWorldList.SelectedItem).Tag;
await CreateLobbyAsync(port);
}
private async Task CreateLobbyAsync(int port)
{
ModBase.Log("[Link] 创建大厅,端口:" + port);
var username = LobbyInfoProvider.GetUsername();
ModBase.RunInUi(() =>
{
BtnFinishPing.Visibility = Visibility.Collapsed;
LabFinishPing.Text = "-ms";
BtnConnectType.Visibility = Visibility.Collapsed;
LabConnectType.Text = Lang.Text("Tools.GameLink.Finish.Connecting");
CardPlayerList.Title = Lang.Text("Tools.GameLink.Member.ListLoading");
StackPlayerList.Children.Clear();
LabConnectUserName.Text = username;
LabConnectUserType.Text = Lang.Text("Tools.GameLink.Finish.Host");
LabFinishId.Text = LobbyService.CurrentLobbyCode;
BtnFinishCopyIp.Visibility = Visibility.Collapsed;
BtnCreate.IsEnabled = true;
BtnFinishExit.Text = Lang.Text("Tools.GameLink.Finish.CloseLobby");
CurrentSubpage = Subpages.PanFinish;
});
var res = await LobbyService.CreateLobbyAsync(port, username).ConfigureAwait(true);
if (!res)
ModBase.RunInUi(() =>
{
CardPlayerList.Title = Lang.Text("Tools.GameLink.Member.ListLoading");
StackPlayerList.Children.Clear();
CurrentSubpage = Subpages.PanSelect;
});
}
// 加入大厅
private async void BtnJoin_Click(object sender, MouseButtonEventArgs e)
{
if (!ModLink.LobbyPrecheck())
return;
ModBase.Log("Start to join lobby.");
var id = TextJoinLobbyId.Text;
var username = LobbyInfoProvider.GetUsername();
ModBase.RunInUi(() =>
{
BtnFinishPing.Visibility = Visibility.Visible;
LabFinishPing.Text = "-ms";
BtnConnectType.Visibility = Visibility.Visible;
LabConnectType.Text = Lang.Text("Tools.GameLink.Finish.Connecting");
CardPlayerList.Title = Lang.Text("Tools.GameLink.Member.ListLoading");
StackPlayerList.Children.Clear();
LabConnectUserName.Text = username;
LabConnectUserType.Text = Lang.Text("Tools.GameLink.Finish.Guest");
LabFinishId.Text = id;
BtnFinishCopyIp.Visibility = Visibility.Visible;
CurrentSubpage = Subpages.PanFinish;
});
var res = await LobbyService.JoinLobbyAsync(id, username).ConfigureAwait(true);
if (!res)
ModBase.RunInUi(() =>
{
CardPlayerList.Title = Lang.Text("Tools.GameLink.Member.ListLoading");
StackPlayerList.Children.Clear();
CurrentSubpage = Subpages.PanSelect;
});
}
private void TextJoinLobbyId_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
BtnJoin_Click(sender, null);
}
#endregion
#region PanLoad | 加载中页面
// 承接状态切换的 UI 改变
private void OnLoadStateChanged(ModLoader.LoaderBase loader, ModBase.LoadState newState, ModBase.LoadState oldState)
{
}
private static string _loadStep = "准备初始化";
private static void SetLoadDesc(string intro, string step)
{
ModBase.Log("连接步骤:" + intro);
_loadStep = step;
ModBase.RunInUiWait(() =>
{
if (ModMain.frmToolsGameLink is null || !ModMain.frmToolsGameLink.LabLoadDesc.IsLoaded)
return;
ModMain.frmToolsGameLink.LabLoadDesc.Text = intro;
ModMain.frmToolsGameLink.UpdateProgress();
});
}
// 承接重试
private void CardLoad_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (initLoader.State != ModBase.LoadState.Failed)
return;
initLoader.Start(isForceRestart: true);
}
// 取消加载
private void CancelLoad(object sender, EventArgs eventArgs)
{
if (initLoader.State == ModBase.LoadState.Loading)
{
CurrentSubpage = Subpages.PanSelect;
initLoader.Abort();
}
else
{
initLoader.State = ModBase.LoadState.Waiting;
}
}
// 进度改变
private void UpdateProgress(double value = -1)
{
if (value == -1)
value = initLoader.Progress;
var displayingProgress = ColumnProgressA.Width.Value;
if (Math.Round(value - displayingProgress, 3) == 0d)
return;
if (displayingProgress > value)
{
ColumnProgressA.Width = new GridLength(value, GridUnitType.Star);
ColumnProgressB.Width = new GridLength(1d - value, GridUnitType.Star);
ModAnimation.AniStop("LobbyController Progress");
}
else
{
var newProgress = value == 1d ? 1d : (value - displayingProgress) * 0.2d + displayingProgress;
ModAnimation.AniStart(
new[]
{
ModAnimation.AaGridLengthWidth(ColumnProgressA, newProgress - ColumnProgressA.Width.Value, 300,
ease: new ModAnimation.AniEaseOutFluent()),
ModAnimation.AaGridLengthWidth(ColumnProgressB, 1d - newProgress - ColumnProgressB.Width.Value, 300,
ease: new ModAnimation.AniEaseOutFluent())
}, "LobbyController Progress");
}
}
private void CardResized(object sender, SizeChangedEventArgs sizeChangedEventArgs)
{
RectProgressClip.Rect = new Rect(0d, 0d, CardLoad.ActualWidth, 12d);
}
#endregion
#region PanFinish | 加载完成页面
// 退出
private async void BtnFinishExit_Click(object sender, ModBase.RouteEventArgs routeEventArgs)
{
if (ModMain.MyMsgBox(
Lang.Text(LobbyService.IsHost
? "Tools.GameLink.Exit.ConfirmMessageWithHost"
: "Tools.GameLink.Exit.ConfirmMessage"),
Lang.Text("Tools.GameLink.Exit.ConfirmTitle"),
Lang.Text("Common.Action.Confirm"),
Lang.Text("Common.Action.Cancel"),
isWarn: true
) == 1)
{
CurrentSubpage = Subpages.PanSelect;
BtnFinishExit.Text = Lang.Text("Tools.GameLink.Finish.Exit");
await LobbyService.LeaveLobbyAsync().ConfigureAwait(true);
}
}
// 复制大厅编号
private void BtnFinishCopy_Click(object sender, ModBase.RouteEventArgs routeEventArgs)
{
ModBase.ClipboardSet(LabFinishId.Text);
}
// 复制 IP
private void BtnFinishCopyIp_Click(object sender, ModBase.RouteEventArgs routeEventArgs)
{
var ip = $"127.0.0.1:{LobbyInfoProvider.McForward.LocalPort}";
ModMain.MyMsgBox(Lang.Text("Tools.GameLink.CopyIp.Message", ip),
Lang.Text("Tools.GameLink.CopyIp.Title"),
Lang.Text("Common.Action.Copy"),
Lang.Text("Tools.GameLink.CopyIp.Back"),
button1Action: () => ModBase.ClipboardSet(ip));
}
#endregion
#region 子页面管理
public enum Subpages
{
PanEula,
PanSelect,
PanFinish
}
public Subpages CurrentSubpage
{
get => field;
set
{
if (field == value)
return;
field = value;
ModBase.Log("[Link] 子页面更改为 " + ModBase.GetStringFromEnum(value));
PageOnContentExit();
}
} = States.Link.LinkEula ? Subpages.PanSelect : Subpages.PanEula;
private void PageLinkLobby_OnPageEnter()
{
ModMain.frmToolsGameLink.PanEula.Visibility =
CurrentSubpage == Subpages.PanEula ? Visibility.Visible : Visibility.Collapsed;
ModMain.frmToolsGameLink.PanSelect.Visibility =
CurrentSubpage == Subpages.PanSelect ? Visibility.Visible : Visibility.Collapsed;
ModMain.frmToolsGameLink.PanFinish.Visibility =
CurrentSubpage == Subpages.PanFinish ? Visibility.Visible : Visibility.Collapsed;
}
#endregion
}