From 5781b144a6329109600de56a55bfb01a832131ef Mon Sep 17 00:00:00 2001 From: Pigeon0v0 <60414767+Pigeon0v0@users.noreply.github.com> Date: Sun, 2 Nov 2025 09:56:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(link):=20=E6=94=AF=E6=8C=81=20Scaffolding?= =?UTF-8?q?=20=E5=8D=8F=E8=AE=AE=20(#1790)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 吴桐 <162002839+wtommy932@users.noreply.github.com> Co-authored-by: WhiteCAT <64885812+whitecat346@users.noreply.github.com> --- PCL.Core | 2 +- PCL.Test/LobbyCodeGenerateTest.cs | 26 + Plain Craft Launcher 2/FormMain.xaml.vb | 9 +- Plain Craft Launcher 2/Modules/ModLink.vb | 68 +- Plain Craft Launcher 2/Modules/ModSecret.vb | 23 +- .../Pages/PageLink/PageLinkLobby.xaml.vb | 674 ++++++++---------- .../Pages/PageLink/PageLinkSetup.xaml | 4 +- .../Pages/PageLink/PageLinkSetup.xaml.vb | 15 +- 8 files changed, 393 insertions(+), 428 deletions(-) create mode 100644 PCL.Test/LobbyCodeGenerateTest.cs diff --git a/PCL.Core b/PCL.Core index a2dffbbb..f8639187 160000 --- a/PCL.Core +++ b/PCL.Core @@ -1 +1 @@ -Subproject commit a2dffbbb37a33a4f738901648996902d31d937bc +Subproject commit f86391877735b1e24a726656e30136da66aa5821 diff --git a/PCL.Test/LobbyCodeGenerateTest.cs b/PCL.Test/LobbyCodeGenerateTest.cs new file mode 100644 index 00000000..48f04c25 --- /dev/null +++ b/PCL.Test/LobbyCodeGenerateTest.cs @@ -0,0 +1,26 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using PCL.Core.Link.Scaffolding; +using System; + +namespace PCL.Test; + +[TestClass] +public class LobbyCodeGenerateTest +{ + [TestMethod] + public void GenerateTest() + { + var code = LobbyCodeGenerator.Generate(); + } + + [TestMethod] + public void ParseTest() + { + var code = LobbyCodeGenerator.Generate(); + Console.WriteLine($"Try to parse: {code.FullCode}"); + + var success = LobbyCodeGenerator.TryParse(code.FullCode, out _); + + Assert.IsTrue(success); + } +} \ No newline at end of file diff --git a/Plain Craft Launcher 2/FormMain.xaml.vb b/Plain Craft Launcher 2/FormMain.xaml.vb index c8505d32..6b0942f9 100644 --- a/Plain Craft Launcher 2/FormMain.xaml.vb +++ b/Plain Craft Launcher 2/FormMain.xaml.vb @@ -4,7 +4,6 @@ Imports System.Windows.Interop Imports System.Windows.Media.Effects Imports PCL.Core.App Imports PCL.Core.Logging -Imports PCL.Core.Link.Lobby Imports PCL.Core.Utils Imports PCL.Core.Utils.OS @@ -367,7 +366,7 @@ Public Class FormMain ''' 正常关闭程序。程序将在执行此方法后约 0.3s 退出。 ''' ''' 是否在还有下载任务未完成时发出警告。 - Public Sub EndProgram(SendWarning As Boolean) + Public Async Sub EndProgram(SendWarning As Boolean) '发出警告 If SendWarning AndAlso HasDownloadingTask() Then If MyMsgBox("还有下载任务尚未完成,是否确定退出?", "提示", "确定", "取消") = 1 Then @@ -384,7 +383,7 @@ Public Class FormMain End If End If '关闭联机大厅 - LobbyController.Close() + 'Await LobbyController.CloseAsync().ConfigureAwait(False) '存储上次使用的档案编号 SaveProfile() '关闭 @@ -424,10 +423,10 @@ Public Class FormMain End Sub) End Sub Private Shared IsLogShown As Boolean = False - Public Shared Sub EndProgramForce(Optional ReturnCode As ProcessReturnValues = ProcessReturnValues.Success, Optional force As Boolean = True) + Public Shared Async Sub EndProgramForce(Optional ReturnCode As ProcessReturnValues = ProcessReturnValues.Success, Optional force As Boolean = True) 'On Error Resume Next '关闭联机大厅 - LobbyController.Close() + 'Await LobbyController.CloseAsync().ConfigureAwait(False) IsProgramEnded = True AniControlEnabled += 1 If IsUpdateWaitingRestart Then UpdateRestart(False) diff --git a/Plain Craft Launcher 2/Modules/ModLink.vb b/Plain Craft Launcher 2/Modules/ModLink.vb index 116824ee..a59f5640 100644 --- a/Plain Craft Launcher 2/Modules/ModLink.vb +++ b/Plain Craft Launcher 2/Modules/ModLink.vb @@ -1,5 +1,4 @@ -Imports System.Runtime.InteropServices -Imports System.Threading.Tasks +Imports System.Runtime.InteropServices Imports PCL.Core.IO Imports PCL.Core.Link Imports PCL.Core.Link.EasyTier @@ -167,40 +166,35 @@ Public Module ModLink #Region "EasyTier" Public DlEasyTierLoader As LoaderCombo(Of JObject) = Nothing - Public Function DownloadEasyTier(Optional LaunchAfterDownload As Boolean = False, Optional isHost As Boolean = False, Optional boardcastDesc As String = Nothing) - Dim DlTargetPath As String = PathTemp + $"EasyTier\EasyTier-{ETInfoProvider.ETVersion}.zip" + Public Function DownloadEasyTier() + Dim dlTargetPath As String = PathTemp + $"EasyTier\EasyTier-{ETInfoProvider.ETVersion}.zip" RunInNewThread(Sub() - Try - '构造步骤加载器 - Dim Loaders As New List(Of LoaderBase) - '下载 - Dim Address As New List(Of String) - Address.Add($"https://staticassets.naids.com/resources/pclce/static/easytier/easytier-windows-{If(IsArm64System, "arm64", "x86_64")}-v{ETInfoProvider.ETVersion}.zip") - Address.Add($"https://s3.pysio.online/pcl2-ce/static/easytier/easytier-windows-{If(IsArm64System, "arm64", "x86_64")}-v{ETInfoProvider.ETVersion}.zip") + Try + '构造步骤加载器 + Dim loaders As New List(Of LoaderBase) + '下载 + Dim address As New List(Of String) + address.Add($"https://staticassets.naids.com/resources/pclce/static/easytier/easytier-windows-{If(IsArm64System, "arm64", "x86_64")}-v{ETInfoProvider.ETVersion}.zip") + address.Add($"https://s3.pysio.online/pcl2-ce/static/easytier/easytier-windows-{If(IsArm64System, "arm64", "x86_64")}-v{ETInfoProvider.ETVersion}.zip") - Loaders.Add(New LoaderDownload("下载 EasyTier", New List(Of NetFile) From {New NetFile(Address.ToArray, DlTargetPath, New FileChecker(MinSize:=1024 * 64))}) With {.ProgressWeight = 15}) - Loaders.Add(New LoaderTask(Of Integer, Integer)("解压文件", Sub() ExtractFile(DlTargetPath, IO.Path.Combine(FileService.LocalDataPath, "EasyTier", ETInfoProvider.ETVersion))) With {.Block = True}) - Loaders.Add(New LoaderTask(Of Integer, Integer)("清理缓存与冗余组件", Sub() - File.Delete(DlTargetPath) - CleanupEasyTierCache() - End Sub)) - If LaunchAfterDownload Then - Loaders.Add(New LoaderTask(Of Integer, Integer)("启动大厅", Sub() - LobbyController.Launch(isHost, If(SelectedProfile IsNot Nothing, SelectedProfile.Username, "")) - End Sub)) - End If - Loaders.Add(New LoaderTask(Of Integer, Integer)("刷新界面", Sub() Hint("联机组件下载完成!", HintType.Finish)) With {.Show = False}) - '启动 - DlEasyTierLoader = New LoaderCombo(Of JObject)("大厅初始化", Loaders) - DlEasyTierLoader.Start() - LoaderTaskbarAdd(DlEasyTierLoader) - FrmMain.BtnExtraDownload.ShowRefresh() - FrmMain.BtnExtraDownload.Ribble() - Catch ex As Exception - Log(ex, "[Link] 下载 EasyTier 依赖文件失败", LogLevel.Hint) - Hint("下载 EasyTier 依赖文件失败,请检查网络连接", HintType.Critical) - End Try - End Sub) + loaders.Add(New LoaderDownload("下载 EasyTier", New List(Of NetFile) From {New NetFile(address.ToArray, dlTargetPath, New FileChecker(MinSize:=1024 * 64))}) With {.ProgressWeight = 15}) + loaders.Add(New LoaderTask(Of Integer, Integer)("解压文件", Sub() ExtractFile(dlTargetPath, IO.Path.Combine(FileService.LocalDataPath, "EasyTier", ETInfoProvider.ETVersion))) With {.Block = True}) + loaders.Add(New LoaderTask(Of Integer, Integer)("清理缓存与冗余组件", Sub() + File.Delete(dlTargetPath) + CleanupEasyTierCache() + End Sub)) + loaders.Add(New LoaderTask(Of Integer, Integer)("刷新界面", Sub() Hint("联机组件下载完成!", HintType.Finish)) With {.Show = False}) + '启动 + DlEasyTierLoader = New LoaderCombo(Of JObject)("大厅初始化", loaders) + DlEasyTierLoader.Start() + LoaderTaskbarAdd(DlEasyTierLoader) + FrmMain.BtnExtraDownload.ShowRefresh() + FrmMain.BtnExtraDownload.Ribble() + Catch ex As Exception + Log(ex, "[Link] 下载 EasyTier 依赖文件失败", LogLevel.Hint) + Hint("下载 EasyTier 依赖文件失败,请检查网络连接", HintType.Critical) + End Try + End Sub) Return 0 End Function Private Sub CleanupEasyTierCache() @@ -243,11 +237,11 @@ Public Module ModLink Hint("请重新登录 Natayark Network 账号再试!", HintType.Critical) Return False End Try - Dim WaitCount As Integer = 0 + Dim waitCount As Integer = 0 While String.IsNullOrWhiteSpace(NaidProfile.Username) - If WaitCount > 30 Then Exit While + If waitCount > 30 Then Exit While Thread.Sleep(500) - WaitCount += 1 + waitCount += 1 End While If String.IsNullOrWhiteSpace(NaidProfile.Username) Then Hint("尝试获取 Natayark ID 信息失败", HintType.Critical) diff --git a/Plain Craft Launcher 2/Modules/ModSecret.vb b/Plain Craft Launcher 2/Modules/ModSecret.vb index 6e89ad05..9c300608 100644 --- a/Plain Craft Launcher 2/Modules/ModSecret.vb +++ b/Plain Craft Launcher 2/Modules/ModSecret.vb @@ -1,15 +1,14 @@ Imports System.ComponentModel -Imports System.Net.Http -Imports System.Security.Cryptography Imports System.Management +Imports System.Net.Http Imports System.Runtime.InteropServices +Imports System.Security.Cryptography Imports PCL.Core.IO Imports PCL.Core.UI Imports PCL.Core.Utils Imports PCL.Core.Utils.Exts Imports PCL.Core.Utils.OS Imports PCL.Core.Utils.Secret -Imports PCL.Core.Net Friend Module ModSecret @@ -21,17 +20,17 @@ Friend Module ModSecret Public Const RegFolder As String = "PCLCE" 'PCL 社区版的注册表与 PCL 的注册表隔离,以防数据冲突 #End If '用于微软登录的 ClientId - Public ReadOnly OAuthClientId As String = EnvironmentInterop.GetSecret("MS_CLIENT_ID", readEnvDebugOnly := True).ReplaceNullOrEmpty() + Public ReadOnly OAuthClientId As String = EnvironmentInterop.GetSecret("MS_CLIENT_ID", readEnvDebugOnly:=True).ReplaceNullOrEmpty() 'CurseForge API Key - Public ReadOnly CurseForgeAPIKey As String = EnvironmentInterop.GetSecret("CURSEFORGE_API_KEY", readEnvDebugOnly := True).ReplaceNullOrEmpty() + Public ReadOnly CurseForgeAPIKey As String = EnvironmentInterop.GetSecret("CURSEFORGE_API_KEY", readEnvDebugOnly:=True).ReplaceNullOrEmpty() '遥测鉴权密钥 - Public ReadOnly TelemetryKey As String = EnvironmentInterop.GetSecret("TELEMETRY_KEY", readEnvDebugOnly := True).ReplaceNullOrEmpty() + Public ReadOnly TelemetryKey As String = EnvironmentInterop.GetSecret("TELEMETRY_KEY", readEnvDebugOnly:=True).ReplaceNullOrEmpty() 'Natayark ID Client Id - Public ReadOnly NatayarkClientId As String = EnvironmentInterop.GetSecret("NAID_CLIENT_ID", readEnvDebugOnly := True).ReplaceNullOrEmpty() + Public ReadOnly NatayarkClientId As String = EnvironmentInterop.GetSecret("NAID_CLIENT_ID", readEnvDebugOnly:=True).ReplaceNullOrEmpty() 'Natayark ID Client Secret,需要经过 PASSWORD HASH 处理(https://uutool.cn/php-password/) - Public ReadOnly NatayarkClientSecret As String = EnvironmentInterop.GetSecret("NAID_CLIENT_SECRET", readEnvDebugOnly := True).ReplaceNullOrEmpty() + Public ReadOnly NatayarkClientSecret As String = EnvironmentInterop.GetSecret("NAID_CLIENT_SECRET", readEnvDebugOnly:=True).ReplaceNullOrEmpty() '联机服务根地址 - Public ReadOnly LinkServers As String() = EnvironmentInterop.GetSecret("LINK_SERVER_ROOT", readEnvDebugOnly := True).ReplaceNullOrEmpty().Split("|") + Public ReadOnly LinkServers As String() = EnvironmentInterop.GetSecret("LINK_SERVER_ROOT", readEnvDebugOnly:=True).ReplaceNullOrEmpty().Split("|") Friend Sub SecretOnApplicationStart() '提升 UI 线程优先级 @@ -1019,7 +1018,7 @@ PCL-Community 及其成员与龙腾猫跃无从属关系,且均不会为您的 Try ' 注册全局的ContextMenu主题刷新事件处理器 EventManager.RegisterClassHandler(GetType(ContextMenu), ContextMenu.OpenedEvent, New RoutedEventHandler(AddressOf OnContextMenuOpened)) - + ' 刷新当前打开的ContextMenu RunInUi(Sub() ' 获取当前应用程序中所有的窗口 @@ -1053,7 +1052,7 @@ PCL-Community 及其成员与龙腾猫跃无从属关系,且均不会为您的 ''' Private Sub RefreshContextMenusInElement(element As DependencyObject) If element Is Nothing Then Return - + Try ' 检查当前元素是否有ContextMenu If TypeOf element Is FrameworkElement Then @@ -1064,7 +1063,7 @@ PCL-Community 及其成员与龙腾猫跃无从属关系,且均不会为您的 fe.ContextMenu.UpdateDefaultStyle() End If End If - + ' 递归处理子元素 Dim childrenCount As Integer = VisualTreeHelper.GetChildrenCount(element) For i As Integer = 0 To childrenCount - 1 diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkLobby.xaml.vb b/Plain Craft Launcher 2/Pages/PageLink/PageLinkLobby.xaml.vb index 69751441..6b5605be 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkLobby.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkLobby.xaml.vb @@ -1,19 +1,13 @@ -Imports System.Collections.ObjectModel -Imports System.Threading.Tasks +Imports System.Collections.ObjectModel +Imports System.Collections.Specialized +Imports PCL.Core.App Imports PCL.Core.Link -Imports PCL.Core.UI -Imports PCL.Core.Utils.Exts Imports PCL.Core.Link.EasyTier Imports PCL.Core.Link.Lobby Imports PCL.Core.Link.Lobby.LobbyInfoProvider -Imports PCL.Core.Link.Natayark.NatayarkProfileManager -Imports PCL.Core.Utils -Imports PCL.Core.App +Imports PCL.Core.Link.Scaffolding.Client.Models Public Class PageLinkLobby - '记录的启动情况 - Private IsHost As Boolean = False - Private HostInfo As ETPlayerInfo = Nothing #Region "初始化" @@ -22,6 +16,17 @@ Public Class PageLinkLobby PageLoaderInit(Load, PanLoad, PanContent, Nothing, InitLoader, AutoRun:=False) '注册自定义的 OnStateChanged AddHandler InitLoader.OnStateChangedUi, AddressOf OnLoadStateChanged + + AddHandler LobbyService.OnNeedDownloadEasyTier, AddressOf DownloadEasyTier + AddHandler LobbyService.OnHint, AddressOf ShowHintFromService + AddHandler LobbyService.DiscoveredWorlds.CollectionChanged, AddressOf OnDiscoveredWorldsChanged + AddHandler LobbyService.Players.CollectionChanged, AddressOf OnPlayersChanged + AddHandler LobbyService.OnUserStopGame, AddressOf OnUserStopGame + AddHandler LobbyService.OnClientPing, AddressOf OnClientPingHandler + AddHandler LobbyService.OnServerShutDown, AddressOf OnServerShuttedDownHandler + AddHandler LobbyService.OnServerStarted, AddressOf OnServerStartedHandler + AddHandler LobbyService.OnServerException, AddressOf OnServerExceptionHandler + If LobbyAnnouncementLoader Is Nothing Then Dim loaders As New List(Of LoaderBase) loaders.Add(New LoaderTask(Of Integer, Integer)("大厅界面初始化", Sub() RunInUi(Sub() @@ -34,85 +39,220 @@ Public Class PageLinkLobby End If End Sub - Private IsLoad As Boolean = False - Private IsLoading As Boolean = False - Public Sub Reload() Handles Me.Loaded - If IsLoad OrElse IsLoading Then Exit Sub - IsLoad = True - IsLoading = True + Private Async Sub OnServerExceptionHandler(ex As Exception) + RunInUi(Sub() Hint(ex.Message, HintType.Critical)) + + Try + Await LobbyService.LeaveLobbyAsync() + + RunInUi(Sub() + CardPlayerList.Title = "大厅成员列表(正在获取信息)" + StackPlayerList.Children.Clear() + CurrentSubpage = Subpages.PanSelect + End Sub) + Catch secEx As Exception + Log(secEx, "Occured an exception when exit server.") + Hint("在服务器退出时发生了错误!", HintType.Critical) + End Try + End Sub + + + Public Async Sub Reload() Handles Me.Loaded HintAnnounce.Visibility = Visibility.Visible HintAnnounce.Text = "正在连接到大厅服务器..." HintAnnounce.Theme = MyHint.Themes.Blue + RunInNewThread( Sub() If Not Setup.Get("LinkEula") Then Select Case MyMsgBox($"在使用 PCL CE 大厅之前,请阅读并同意以下条款:{vbCrLf}{vbCrLf}我承诺严格遵守中国大陆相关法律法规,不会将大厅功能用于违法违规用途。{vbCrLf}我已知晓大厅功能使用途中可能需要提供管理员权限以用于必要的操作,并会确保 PCL CE 为从官方发布渠道下载的副本。{vbCrLf}我承诺使用大厅功能带来的一切风险自行承担。{vbCrLf}我已知晓并同意 PCL CE 收集经处理的本机识别码、Natayark ID 与其他信息并在必要时提供给执法部门。{vbCrLf}为保护未成年人个人信息,使用联机大厅前,我确认我已满十四周岁。{vbCrLf}{vbCrLf}另外,你还需要同意 PCL CE 大厅相关隐私政策及《Natayark OpenID 服务条款》。", "联机大厅协议授权", - "我已阅读并同意", "拒绝并返回", "查看相关隐私协议", - Button3Action:=Sub() OpenWebsite("https://www.pclc.cc/privacy/personal-info-brief.html")) + "我已阅读并同意", "拒绝并返回", "查看相关隐私协议", + Button3Action:=Sub() OpenWebsite("https://www.pclc.cc/privacy/personal-info-brief.html")) Case 1 Setup.Set("LinkEula", True) Case 2 RunInUi( - Sub() - FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.Launch}) - FrmLinkLobby = Nothing - End Sub) + Sub() + FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.Launch}) + FrmLinkLobby = Nothing + End Sub) End Select End If End Sub) + '加载公告 LobbyAnnouncementLoader.Start() If _linkAnnounceUpdateCancelSource IsNot Nothing Then _linkAnnounceUpdateCancelSource.Cancel() _linkAnnounceUpdateCancelSource = New CancellationTokenSource() - Dispatcher.BeginInvoke(Async Sub() Await _LinkAnnounceUpdate()) '我实在不理解为啥 BeginInvoke 这个委托要 MustBeInherit - '刷新 NAID 令牌 - If Not String.IsNullOrWhiteSpace(Setup.Get("LinkNaidRefreshToken")) Then - If Not String.IsNullOrWhiteSpace(Setup.Get("LinkNaidRefreshExpiresAt")) AndAlso Convert.ToDateTime(Setup.Get("LinkNaidRefreshExpiresAt")).CompareTo(DateTime.Now) < 0 Then - Setup.Set("LinkNaidRefreshToken", "") - Hint("Natayark ID 令牌已过期,请重新登录", HintType.Critical) - Else - GetNaidData(Setup.Get("LinkNaidRefreshToken"), True) - End If - End If - DetectMcInstance() - IsLoading = False + Await Dispatcher.BeginInvoke(Async Sub() Await _LinkAnnounceUpdate()) '我实在不理解为啥 BeginInvoke 这个委托要 MustBeInherit + + Await LobbyService.InitializeAsync().ConfigureAwait(False) End Sub + #End Region #Region "加载步骤" - Public Shared WithEvents InitLoader As New LoaderCombo(Of Integer)("大厅初始化", { - New LoaderTask(Of Integer, Integer)("检查 EasyTier 文件", AddressOf InitFileCheck) With {.ProgressWeight = 0.5} + Private Shared WithEvents InitLoader As New LoaderCombo(Of Integer)("大厅初始化", { + New LoaderTask(Of Integer, Integer)("初始化", AddressOf InitTask) With {.ProgressWeight = 0.5} }) - Private Shared Sub InitFileCheck(Task As LoaderTask(Of Integer, Integer)) - If Not File.Exists(ETInfoProvider.ETPath & "\easytier-core.exe") OrElse Not File.Exists(ETInfoProvider.ETPath & "\Packet.dll") OrElse - Not File.Exists(ETInfoProvider.ETPath & "\easytier-cli.exe") Then - Log("[Link] EasyTier 不存在,开始下载") - DownloadEasyTier() - Else - Log("[Link] EasyTier 文件检查完毕") - End If + Private Shared Async Sub InitTask(task As LoaderTask(Of Integer, Integer)) + Await LobbyService.InitializeAsync() End Sub +#Region "Subscribser" + Private Sub OnServerStartedHandler() + Log("Received server started event.") + RunInUi(Sub() + LabFinishId.Text = LobbyService.CurrentLobbyCode + StackPlayerList.Children.Clear() + For Each player As PlayerProfile In LobbyService.Players + StackPlayerList.Children.Add(PlayerInfoItem(player, AddressOf PlayerInfoClick)) + Next + End Sub) + End Sub + + Private Async Sub OnServerShuttedDownHandler() + Try + Await LobbyService.LeaveLobbyAsync() + + RunInUi(Sub() + CardPlayerList.Title = "大厅成员列表(正在获取信息)" + StackPlayerList.Children.Clear() + CurrentSubpage = Subpages.PanSelect + End Sub) + Catch ex As Exception + Log(ex, "Occured an exception when exit server.") + Hint("在服务器退出时发生了错误!", HintType.Critical) + End Try + End Sub + Private Sub OnClientPingHandler(latency As Long) + RunInUi(Sub() + LabFinishQuality.Text = "已连接" + LabFinishPing.Text = latency.ToString() + "ms" + LabConnectType.Text = "暂不可用" + End Sub) + End Sub + + Private Sub OnUserStopGame() + RunInUi(Sub() + CardPlayerList.Title = "大厅成员列表(正在获取信息)" + StackPlayerList.Children.Clear() + CurrentSubpage = Subpages.PanSelect + End Sub) + MyMsgBox("由于你关闭了联机中的 MC 实例,大厅已自动解散。", "大厅已解散") + End Sub + + + Private Sub OnPlayersChanged(sender As Object, e As NotifyCollectionChangedEventArgs) + Log("接收到玩家列表改变事件") + RunInUi(Sub() + Select Case e.Action + Case NotifyCollectionChangedAction.Add + For Each player As PlayerProfile In e.NewItems + StackPlayerList.Children.Add(PlayerInfoItem(player, AddressOf PlayerInfoClick)) + Next + + Case NotifyCollectionChangedAction.Remove + For Each player As PlayerProfile In e.OldItems + Dim itemToRemove = StackPlayerList.Children.OfType(Of MyListItem)(). + FirstOrDefault(Function(item) item.Tag.MachineId = player.MachineId) + If itemToRemove IsNot Nothing Then + StackPlayerList.Children.Remove(itemToRemove) + End If + Next + + Case Else + StackPlayerList.Children.Clear() + For Each player As PlayerProfile In LobbyService.Players + StackPlayerList.Children.Add(PlayerInfoItem(player, AddressOf PlayerInfoClick)) + Next + End Select + + LabFinishQuality.Text = "已连接" + CardPlayerList.Title = $"大厅成员列表(共 {LobbyService.Players.Count} 人)" + End Sub) + End Sub + + Private Sub OnDiscoveredWorldsChanged(sender As Object, e As NotifyCollectionChangedEventArgs) + Log("Found new world.") + + RunInUi(Sub() + If e.Action = NotifyCollectionChangedAction.Reset Then + ComboWorldList.Items.Clear() + For Each world As FoundWorld In LobbyService.DiscoveredWorlds + ComboWorldList.Items.Add(New MyComboBoxItem() With { + .Tag = world.Port, + .Content = world.Name + }) + Next + End If + + ' 当有新项目添加时 + If e.NewItems IsNot Nothing Then + For Each world As FoundWorld In e.NewItems + ComboWorldList.Items.Add(New MyComboBoxItem() With { + .Tag = world.Port, + .Content = world.Name + }) + Next + End If + + ' 当有项目被移除时 + If e.OldItems IsNot Nothing Then + Dim portsToRemove = e.OldItems.Cast(Of FoundWorld)().Select(Function(w) w.Port).ToHashSet() + Dim itemsToRemove = ComboWorldList.Items.Cast(Of MyComboBoxItem)().Where(Function(item) portsToRemove.Contains(CType(item.Tag, Integer))).ToList() + For Each item In itemsToRemove + ComboWorldList.Items.Remove(item) + Next + End If + + ' 更新UI状态 + Dim hasItems = ComboWorldList.Items.Count > 0 + ComboWorldList.IsEnabled = hasItems + BtnCreate.IsEnabled = hasItems + If hasItems AndAlso ComboWorldList.SelectedIndex = -1 Then + ComboWorldList.SelectedIndex = 0 + End If + End Sub) + End Sub + + Private Shared Sub ShowHintFromService(msg As String, type As CoreHintType) + RunInUi(Sub() + Select Case type + Case CoreHintType.Info + Hint(msg, HintType.Info) + Case CoreHintType.Finish + Hint(msg, HintType.Finish) + Case CoreHintType.Critical + Hint(msg, HintType.Critical) + End Select + End Sub) + End Sub +#End Region + + #End Region #Region "公告" Public Shared LobbyAnnouncementLoader As LoaderCombo(Of Integer) = Nothing - Private _linkAnnounces As New ObservableCollection(Of LinkAnnounceInfo) + Private ReadOnly _linkAnnounces As New ObservableCollection(Of LinkAnnounceInfo) Private _linkAnnounceUpdateCancelSource As CancellationTokenSource = Nothing '公告轮播实现 Private Async Function _LinkAnnounceUpdate() As Task Dim currentIndex = 0 Dim globalCancelToken As CancellationToken = _linkAnnounceUpdateCancelSource.Token - Dim waiterCancelSource As CancellationTokenSource = Nothing - Dim contentChanged As Boolean = False + Dim waiterCts As CancellationTokenSource = Nothing + AddHandler _linkAnnounces.CollectionChanged, Sub(sender, e) - If waiterCancelSource IsNot Nothing Then waiterCancelSource.Cancel() + If waiterCts IsNot Nothing Then waiterCts.Cancel() End Sub + While Not globalCancelToken.IsCancellationRequested - waiterCancelSource = CancellationTokenSource.CreateLinkedTokenSource(globalCancelToken) - Dim waiterCancelToken = waiterCancelSource.Token + waiterCts = CancellationTokenSource.CreateLinkedTokenSource(globalCancelToken) + Dim waiterCancelToken = waiterCts.Token + If _linkAnnounces.Count > 0 Then Dim info As LinkAnnounceInfo = _linkAnnounces(currentIndex) Dim prefix As String @@ -130,14 +270,16 @@ Public Class PageLinkLobby Else HintAnnounce.Visibility = Visibility.Collapsed End If + Try Await Task.Delay(10000, waiterCancelToken) Catch ex As TaskCanceledException '忽略取消任务的异常 End Try + If Not waiterCancelToken.IsCancellationRequested Then currentIndex += 1 If currentIndex >= _linkAnnounces.Count Then currentIndex = 0 - waiterCancelSource = Nothing + waiterCts = Nothing End While End Function '获取公告信息 @@ -148,9 +290,11 @@ Public Class PageLinkLobby Dim serverNumber = 0 Dim jObj As JObject = Nothing Dim cache As Integer + While serverNumber < LinkServers.Length Try cache = Integer.Parse(NetRequestOnce($"{LinkServers(serverNumber)}/api/link/v2/cache.ini", "GET", Nothing, "application/json", Timeout:=7000).Trim()) + If cache = Config.Link.AnnounceCacheVer Then Log("[Link] 使用缓存的公告数据") jObj = GetJson(Config.Link.AnnounceCache) @@ -161,6 +305,7 @@ Public Class PageLinkLobby Config.Link.AnnounceCache = received Config.Link.AnnounceCacheVer = cache End If + Exit While Catch ex As Exception Log(ex, $"[Link] 从服务器 {serverNumber} 获取公告缓存失败") @@ -169,6 +314,7 @@ Public Class PageLinkLobby serverNumber += 1 End Try End While + If jObj Is Nothing Then Throw New Exception("获取联机数据失败") IsLobbyAvailable = jObj("available") AllowCustomName = jObj("allowCustomName") @@ -183,13 +329,17 @@ Public Class PageLinkLobby End Sub) Exit Sub End If + '公告 Dim notices As JArray = jObj("notices") For Each notice As JObject In notices Dim announceContent = notice("content").ToString() If Not String.IsNullOrWhiteSpace(announceContent) Then + If VersionCode < Val(notice("minVer")) OrElse VersionCode > Val(notice("maxVer")) Then Continue For + Dim type As LinkAnnounceType + If notice("type") = "important" OrElse notice("type") = "red" Then type = LinkAnnounceType.Important ElseIf notice("type") = "warning" OrElse notice("type") = "yellow" Then @@ -197,12 +347,15 @@ Public Class PageLinkLobby Else type = LinkAnnounceType.Notice End If + Dim announces As String() = announceContent.Split(vbLf) + For Each announce As String In announces _linkAnnounces.Add(New LinkAnnounceInfo(type, announce)) Next End If Next + '中继服务器 Dim relays As JArray = jObj("relays") ETRelay.RelayList = New List(Of ETRelay) @@ -228,17 +381,17 @@ Public Class PageLinkLobby #Region "信息获取与展示" #Region "UI 元素" - Private Function PlayerInfoItem(info As ETPlayerInfo, onClick As MyListItem.ClickEventHandler) + Private Function PlayerInfoItem(info As PlayerProfile, onClick As MyListItem.ClickEventHandler) Dim details As String = Nothing - If info.IsHost Then details += "[主机] " - If String.IsNullOrEmpty(info.Username) Then details += "[第三方] " - If info.Cost = ETConnectionType.Local Then - details += $"[本机] NAT {LobbyTextHandler.GetNatTypeChinese(info.NatType)}" - Else - details += $"{info.Ping}ms / {LobbyTextHandler.GetConnectTypeChinese(info.Cost)}" - End If + If info.Kind = PlayerKind.HOST Then details += "[主机] " + details += info.Vendor + 'If info.Cost = ETConnectionType.Local Then + 'details += $"[本机] NAT {LobbyTextHandler.GetNatTypeChinese(info.NatType)}" + 'Else + 'details += $"{info.Ping}ms / {LobbyTextHandler.GetConnectTypeChinese(info.Cost)}" + 'End If Dim newItem As New MyListItem With { - .Title = If(Not String.IsNullOrEmpty(info.Username), info.Username, info.Hostname), + .Title = info.Name, .Info = details, .Type = MyListItem.CheckType.Clickable, .Tag = info @@ -247,210 +400,24 @@ Public Class PageLinkLobby Return newItem End Function Private Sub PlayerInfoClick(sender As MyListItem, e As EventArgs) - Dim info As ETPlayerInfo = sender.Tag + Dim info As PlayerProfile = sender.Tag Dim msg As String = Nothing - If Not String.IsNullOrEmpty(info.Username) Then - msg += $"启动器用户名:{info.Username}" - If Not String.IsNullOrEmpty(info.McName) Then - msg += $",启动器使用的 MC 档案名称:{info.McName}" - End If - Else - msg += $"主机名称:{info.Hostname}" - End If + msg += $"用户名:{info.Name}" msg += vbCrLf - msg += $"{If(info.Cost = ETConnectionType.Local, "本机 ", $"延迟:{info.Ping}ms,丢包率:{info.Loss}%,连接方式:{LobbyTextHandler.GetConnectTypeChinese(info.Cost)},")}NAT 类型:{LobbyTextHandler.GetNatTypeChinese(info.NatType)}" + msg += $"联机协议客户端标识:{info.Vendor}" + 'msg += $"{If(info.Cost = ETConnectionType.Local, "本机 ", $"延迟:{info.Ping}ms,丢包率:{info.Loss}%,连接方式:{LobbyTextHandler.GetConnectTypeChinese(info.Cost)},")}NAT 类型:{LobbyTextHandler.GetNatTypeChinese(info.NatType)}" msg += vbCrLf msg += "此处数据仅供参考,请以实际游玩体验为准。" msg += vbCrLf + vbCrLf msg += "若想了解 NAT 类型与其如何影响联机体验,请前往界面左侧的常见问题一栏。" - MyMsgBox(msg, $"玩家 {If(Not String.IsNullOrEmpty(info.Username), info.Username, info.Hostname)} 的详细信息") + MyMsgBox(msg, $"玩家 {info.Name} 的详细信息") End Sub #End Region - - Private IsWatcherStarted As Boolean = False - Private IsETFirstCheckFinished As Boolean = False - Private IsDetectingMc As Boolean = False - '检测本地 MC 局域网实例 - Private Sub DetectMcInstance() Handles BtnRefresh.Click - If IsDetectingMc Then Return - IsDetectingMc = True - ComboWorldList.Items.Clear() - ComboWorldList.SelectedIndex = 0 - BtnRefresh.Text = "寻找中" - BtnRefresh.IsEnabled = False - BtnCreate.IsEnabled = False - ComboWorldList.IsEnabled = False - RunInNewThread( - Sub() - recordedSourcePort.Clear() - Using ls As New BroadcastListener() - AddHandler ls.OnReceive, AddressOf _onReceiveNewServer - ls.Start() - Thread.Sleep(3000) - RemoveHandler ls.OnReceive, AddressOf _onReceiveNewServer - End Using - 'Dim Worlds As List(Of Tuple(Of Integer, McPingResult, String)) = MCInstanceFinding.GetAwaiter().GetResult() - IsDetectingMc = False - RunInUi( - Sub() - ComboWorldList.IsEnabled = True - BtnRefresh.Text = "刷新" - BtnRefresh.IsEnabled = True - End Sub) - End Sub, "Minecraft Port Detect") - End Sub - - Private ReadOnly Property recordedSourcePort As New ConcurrentSet(Of Integer) - Private Sub _onReceiveNewServer(info As BroadcastRecord, sender As IPEndPoint) - If recordedSourcePort.TryAdd(info.Address.Port) Then - RunInNewThread(Sub() - Using ping As New McPing(New IPEndPoint(IPAddress.Loopback, info.Address.Port)) - Using cts As New CancellationTokenSource() - cts.CancelAfter(5000) - Dim pingRes = ping.PingAsync(cts.Token).GetAwaiter().GetResult() - RunInUi(Sub() - ComboWorldList.Items.Add(New MyComboBoxItem() With { - .Tag = info.Address.Port, - .Content = $"{pingRes.Description} / {pingRes.Version.Name} ({info.Address.Port})" - }) - If ComboWorldList.Items.Count = 0 Then - BtnCreate.IsEnabled = False - ComboWorldList.IsEnabled = False - Else - BtnCreate.IsEnabled = True - ComboWorldList.IsEnabled = True - End If - End Sub) - End Using - End Using - End Sub) - End If - End Sub - 'EasyTier Cli 轮询 - Private Sub StartETWatcher() - RunInNewThread(Sub() - If IsWatcherStarted Then Return - Log("[Link] 启动 EasyTier 轮询") - IsWatcherStarted = True - Dim retryCount = 0 - While ETInfoProvider.CheckETStatusAsync().GetAwaiter().GetResult() = 0 AndAlso retryCount <= 15 - retryCount += GetETInfo() - If RequiresLogin AndAlso String.IsNullOrWhiteSpace(NaidProfile.AccessToken) Then - Hint("请先登录 Natayark ID 再使用大厅!", HintType.Critical) - LobbyController.Close() - End If - Thread.Sleep(2000) - End While - RunInUi(Sub() CurrentSubpage = Subpages.PanSelect) - LobbyController.Close() - Log("[Link] EasyTier 轮询已结束") - IsWatcherStarted = False - End Sub, "EasyTier Status Watcher", ThreadPriority.BelowNormal) - End Sub - 'EasyTier Cli 信息获取 - Private Function GetETInfo(Optional RemainRetry As Integer = 8) As Integer - Try - Dim info = ETInfoProvider.GetPlayerList() - Dim playerList = info.Item1 - Dim localInfo = info.Item2 - If playerList Is Nothing OrElse Not playerList(0).IsHost OrElse localInfo Is Nothing Then - If RemainRetry > 0 Then - Log($"[Link] 未找到大厅创建者或本机信息,放弃前再重试 {RemainRetry} 次") - Thread.Sleep(800) - GetETInfo(RemainRetry - 1) - Return 1 - End If - If IsETFirstCheckFinished Then - MyMsgBox($"大厅创建者关闭了大厅。{vbCrLf}有可能是创建者累了,或者是他的游戏 / 网络连接炸了。", "大厅已解散") - ToastNotification.SendToast("大厅已解散", "PCL CE 大厅") - Else - If IsHost Then - Hint("大厅创建失败", HintType.Critical) - Else - Hint("该大厅不存在", HintType.Critical) - End If - End If - RunInUi(Sub() - CardPlayerList.Title = "大厅成员列表(正在获取信息)" - StackPlayerList.Children.Clear() - CurrentSubpage = Subpages.PanSelect - Log("[Link] [ETInfo] 大厅不存在或已被解散,返回选择界面") - End Sub) - LobbyController.Close() - Return 1 - End If - Dim hostInfo = playerList(0) - If hostInfo.ETVersion <> localInfo.ETVersion Then - RunInUi(Sub() HintEasyTierVersion.Visibility = Visibility.Visible) - Else - RunInUi(Sub() HintEasyTierVersion.Visibility = Visibility.Collapsed) - End If - - '本地网络质量评估 - Dim quality - 'NAT 评估 - If localInfo.NatType.ContainsF("OpenInternet", True) OrElse localInfo.NatType.ContainsF("NoPAT", True) OrElse localInfo.NatType.ContainsF("FullCone", True) Then - quality = 3 - ElseIf localInfo.NatType.ContainsF("Restricted", True) OrElse localInfo.NatType.ContainsF("PortRestricted", True) Then - quality = 2 - Else - quality = 1 - End If - '到主机延迟评估 - If hostInfo.Ping > 150 Then - quality -= 1 - End If - RunInUi(Sub() - Dim texts = LobbyTextHandler.GetQualityDesc(quality) - LabFinishQuality.Text = texts.Keyword - BtnFinishQuality.ToolTip = "连接状况" & vbCrLf & texts.Desc - End Sub) - - If IsHost AndAlso Not LobbyController.IsHostInstanceAvailable(TargetLobby.Port) Then '确认创建者实例存活状态 - RunInUi(Sub() - CardPlayerList.Title = "大厅成员列表(正在获取信息)" - StackPlayerList.Children.Clear() - CurrentSubpage = Subpages.PanSelect - End Sub) - LobbyController.Close() - MyMsgBox("由于你关闭了联机中的 MC 实例,大厅已自动解散。", "大厅已解散") - End If - - '加入方刷新连接信息 - Dim etStatus = ETController.Status - RunInUi(Sub() - If Not etStatus = ETState.Ready AndAlso Not hostInfo.Ping = 1000 Then - etStatus = ETState.Ready - ElseIf Not etStatus = ETState.Ready AndAlso hostInfo.Ping = 1000 Then '如果 ET 还未就绪,则显示延迟为 0,防止用户找茬 - hostInfo.Ping = 0 - End If - LabFinishPing.Text = hostInfo.Ping.ToString() & "ms" - LabConnectType.Text = LobbyTextHandler.GetConnectTypeChinese(hostInfo.Cost) - End Sub) - - '刷新大厅成员列表 UI - RunInUi(Sub() - StackPlayerList.Children.Clear() - For Each player In playerList - If Not etStatus = ETState.Ready AndAlso player.Ping = 1000 Then player.Ping = 0 '如果 ET 还未就绪,则显示延迟为 0,防止用户找茬 - Dim newItem = PlayerInfoItem(player, AddressOf PlayerInfoClick) - StackPlayerList.Children.Add(newItem) - Next - CardPlayerList.Title = $"大厅成员列表(共 {playerList.Count} 人)" - End Sub) - IsETFirstCheckFinished = True - Return 0 - Catch ex As Exception - Log(ex, "[Link] EasyTier Cli 线程异常") - If ETController.Status = ETState.Stopped Then LobbyController.Close() - Return 1 - End Try - End Function Private Sub PasteLobbyId() Handles BtnPaste.Click Dim lobbyId As String Try Dim clipText = Clipboard.GetText(TextDataFormat.Text) - lobbyId = ParseCode(clipText).OriginalCode + lobbyId = clipText Catch ex As Exception Log(ex, "从剪贴板识别大厅编号出错") Exit Sub @@ -468,122 +435,92 @@ Public Class PageLinkLobby #Region "PanSelect | 种类选择页面" + '刷新按钮 + Private Sub BtnRefresh_Click(sender As Object, e As EventArgs) Handles BtnRefresh.Click + Dim lobby = LobbyService.DiscoverWorldAsync() + End Sub + '创建大厅 - Private Sub BtnCreate_Click(sender As Object, e As EventArgs) Handles BtnCreate.Click + Private Async Sub BtnCreate_Click(sender As Object, e As EventArgs) Handles BtnCreate.Click + If ComboWorldList.SelectedItem Is Nothing Then + Hint("请先选择一个要联机的世界!", HintType.Info) + Return + End If + BtnCreate.IsEnabled = False + If Not LobbyPrecheck() Then BtnCreate.IsEnabled = True Exit Sub End If + Dim port = CType(ComboWorldList.SelectedItem.Tag, Integer) Log("[Link] 创建大厅,端口:" & port) - IsHost = True - RunInNewThread(Sub() - Dim id As String = RandomUtils.NextInt(10000000, 99999999).ToString() - Dim secret As String = RandomUtils.NextInt(10, 99).ToString() - TargetLobby = New LobbyInfo With { - .NetworkName = id, - .NetworkSecret = secret, - .OriginalCode = $"{id}{secret}{port}".FromB10ToB32, - .Type = LobbyType.PCLCE, - .Port = port - } - RunInUi(Sub() - BtnFinishPing.Visibility = Visibility.Collapsed - BtnConnectType.Visibility = Visibility.Collapsed - CardPlayerList.Title = "大厅成员列表(正在获取信息)" - StackPlayerList.Children.Clear() - LabConnectUserName.Text = GetUsername() - LabConnectUserType.Text = "创建者" - LabFinishId.Text = TargetLobby.OriginalCode - BtnFinishCopyIp.Visibility = Visibility.Collapsed - BtnCreate.IsEnabled = True - BtnFinishExit.Text = "关闭大厅" - CurrentSubpage = Subpages.PanFinish - End Sub) - Dim result = LobbyController.Launch(True, If(SelectedProfile IsNot Nothing, SelectedProfile.Username, "")) - If result = 1 Then - RunInUi(Sub() CurrentSubpage = Subpages.PanSelect) - Hint("创建大厅失败,请向开发者反馈", HintType.Critical) - Return - End If + Dim username = GetUsername() - Dim retryCount As Integer = 0 - While ETController.Status = ETState.Stopped - Thread.Sleep(300) - If DlEasyTierLoader IsNot Nothing AndAlso DlEasyTierLoader.State = LoadState.Loading Then Continue While - If retryCount > 10 Then - Hint("EasyTier 启动失败", HintType.Critical) - RunInUi(Sub() BtnCreate.IsEnabled = True) - LobbyController.Close() - BtnCreate.IsEnabled = True - RunInUi(Sub() CurrentSubpage = Subpages.PanSelect) - Exit Sub - End If - retryCount += 1 - End While - Thread.Sleep(1000) - StartETWatcher() - End Sub, "Link Create Lobby") + RunInUi(Sub() + BtnFinishPing.Visibility = Visibility.Collapsed + LabFinishPing.Text = "-ms" + BtnConnectType.Visibility = Visibility.Collapsed + LabConnectType.Text = "连接中" + CardPlayerList.Title = "大厅成员列表(正在获取信息)" + StackPlayerList.Children.Clear() + LabConnectUserName.Text = username + LabConnectUserType.Text = "创建者" + LabFinishId.Text = LobbyService.CurrentLobbyCode + BtnFinishCopyIp.Visibility = Visibility.Collapsed + BtnCreate.IsEnabled = True + BtnFinishExit.Text = "关闭大厅" + CurrentSubpage = Subpages.PanFinish + End Sub) + + Dim res = Await LobbyService.CreateLobbyAsync(port, username).ConfigureAwait(True) + + If res = False Then + RunInUi(Sub() + CardPlayerList.Title = "大厅成员列表(正在获取信息)" + StackPlayerList.Children.Clear() + CurrentSubpage = Subpages.PanSelect + End Sub) + Else + + End If End Sub '加入大厅 - Private Sub BtnJoin_Click(sender As Object, e As EventArgs) Handles BtnJoin.Click + Private Async Sub BtnJoin_Click(sender As Object, e As EventArgs) Handles BtnJoin.Click If Not LobbyPrecheck() Then Exit Sub + + Log("Start to join lobby.") + Dim id = TextJoinLobbyId.Text - IsHost = False - RunInNewThread(Sub() - TargetLobby = ParseCode(id) + Dim username = GetUsername() - If TargetLobby Is Nothing Then - Hint("大厅编号不正确,请检查后重新输入", HintType.Critical) - Return - End If + RunInUi(Sub() + BtnFinishPing.Visibility = Visibility.Visible + LabFinishPing.Text = "-ms" + BtnConnectType.Visibility = Visibility.Visible + LabConnectType.Text = "连接中" + CardPlayerList.Title = "大厅成员列表(正在获取信息)" + StackPlayerList.Children.Clear() + LabConnectUserName.Text = username + LabConnectUserType.Text = "加入者" + LabFinishId.Text = id + BtnFinishCopyIp.Visibility = Visibility.Visible + CurrentSubpage = Subpages.PanFinish + End Sub) - RunInUi(Sub() - BtnFinishPing.Visibility = Visibility.Visible - LabFinishPing.Text = "-ms" - BtnConnectType.Visibility = Visibility.Visible - LabConnectType.Text = "连接中" - CardPlayerList.Title = "大厅成员列表(正在获取信息)" - StackPlayerList.Children.Clear() - LabConnectUserName.Text = GetUsername() - LabConnectUserType.Text = "加入者" - LabFinishId.Text = TargetLobby.OriginalCode - BtnFinishCopyIp.Visibility = Visibility.Visible - CurrentSubpage = Subpages.PanFinish - End Sub) + Dim res = Await LobbyService.JoinLobbyAsync(id, username).ConfigureAwait(True) - Dim result = LobbyController.Launch(False, If(SelectedProfile IsNot Nothing, SelectedProfile.Username, "")) - If result = 1 Then - RunInUi(Sub() CurrentSubpage = Subpages.PanSelect) - Hint("加入大厅失败,请向开发者反馈", HintType.Critical) - Return - End If - - Dim retryCount As Integer = 0 - While ETController.Status = ETState.Stopped - Thread.Sleep(300) - If DlEasyTierLoader IsNot Nothing AndAlso DlEasyTierLoader.State = LoadState.Loading Then Continue While - If retryCount > 10 Then - Hint("EasyTier 启动失败", HintType.Critical) - RunInUi(Sub() BtnCreate.IsEnabled = True) - LobbyController.Close() - Exit Sub - End If - retryCount += 1 - End While - Thread.Sleep(1000) - StartETWatcher() - Thread.Sleep(500) - While Not IsWatcherStarted OrElse McForward Is Nothing OrElse HostInfo Is Nothing - Thread.Sleep(500) - End While - Dim hostname As String = If(String.IsNullOrWhiteSpace(HostInfo.Username), HostInfo.Hostname, HostInfo.Username) - RunInUi(Sub() PanNetInfo.Title = $"{hostname} 的大厅") - End Sub, "Link Join Lobby") + If res = False Then + RunInUi(Sub() + CardPlayerList.Title = "大厅成员列表(正在获取信息)" + StackPlayerList.Children.Clear() + CurrentSubpage = Subpages.PanSelect + End Sub) + End If End Sub Private Sub TextJoinLobbyId_KeyDown(sender As Object, e As KeyEventArgs) Handles TextJoinLobbyId.KeyDown If e.Key = Key.Enter Then BtnJoin_Click(sender, e) @@ -594,15 +531,15 @@ Public Class PageLinkLobby #Region "PanLoad | 加载中页面" '承接状态切换的 UI 改变 - Private Sub OnLoadStateChanged(Loader As LoaderBase, NewState As LoadState, OldState As LoadState) + Private Sub OnLoadStateChanged(loader As LoaderBase, newState As LoadState, oldState As LoadState) End Sub - Private Shared LoadStep As String = "准备初始化" - Private Shared Sub SetLoadDesc(Intro As String, [Step] As String) - Log("连接步骤:" & Intro) - LoadStep = [Step] + Private Shared _loadStep As String = "准备初始化" + Private Shared Sub SetLoadDesc(intro As String, [step] As String) + Log("连接步骤:" & intro) + _loadStep = [step] RunInUiWait(Sub() If FrmLinkLobby Is Nothing OrElse Not FrmLinkLobby.LabLoadDesc.IsLoaded Then Exit Sub - FrmLinkLobby.LabLoadDesc.Text = Intro + FrmLinkLobby.LabLoadDesc.Text = intro FrmLinkLobby.UpdateProgress() End Sub) End Sub @@ -624,19 +561,19 @@ Public Class PageLinkLobby End Sub '进度改变 - Private Sub UpdateProgress(Optional Value As Double = -1) - If Value = -1 Then Value = InitLoader.Progress - Dim DisplayingProgress As Double = ColumnProgressA.Width.Value - If Math.Round(Value - DisplayingProgress, 3) = 0 Then Exit Sub - If DisplayingProgress > Value Then - ColumnProgressA.Width = New GridLength(Value, GridUnitType.Star) - ColumnProgressB.Width = New GridLength(1 - Value, GridUnitType.Star) + Private Sub UpdateProgress(Optional value As Double = -1) + If value = -1 Then value = InitLoader.Progress + Dim displayingProgress As Double = ColumnProgressA.Width.Value + If Math.Round(value - displayingProgress, 3) = 0 Then Exit Sub + If displayingProgress > value Then + ColumnProgressA.Width = New GridLength(value, GridUnitType.Star) + ColumnProgressB.Width = New GridLength(1 - value, GridUnitType.Star) AniStop("LobbyController Progress") Else - Dim NewProgress As Double = If(Value = 1, 1, (Value - DisplayingProgress) * 0.2 + DisplayingProgress) + Dim newProgress As Double = If(value = 1, 1, (value - displayingProgress) * 0.2 + displayingProgress) AniStart({ - AaGridLengthWidth(ColumnProgressA, NewProgress - ColumnProgressA.Width.Value, 300, Ease:=New AniEaseOutFluent), - AaGridLengthWidth(ColumnProgressB, (1 - NewProgress) - ColumnProgressB.Width.Value, 300, Ease:=New AniEaseOutFluent) + AaGridLengthWidth(ColumnProgressA, newProgress - ColumnProgressA.Width.Value, 300, Ease:=New AniEaseOutFluent), + AaGridLengthWidth(ColumnProgressB, (1 - newProgress) - ColumnProgressB.Width.Value, 300, Ease:=New AniEaseOutFluent) }, "LobbyController Progress") End If End Sub @@ -648,13 +585,12 @@ Public Class PageLinkLobby #Region "PanFinish | 加载完成页面" '退出 - Private Sub BtnFinishExit_Click(sender As Object, e As EventArgs) Handles BtnFinishExit.Click - Dim creatorHint = If(IsHost, vbCrLf & "由于你是大厅创建者,退出后此大厅将会自动解散。", "") + Private Async Sub BtnFinishExit_Click(sender As Object, e As EventArgs) Handles BtnFinishExit.Click + Dim creatorHint = If(LobbyService.IsHost, vbCrLf & "由于你是大厅创建者,退出后此大厅将会自动解散。", "") If MyMsgBox($"你确定要退出大厅吗?{creatorHint}", "确认退出", "确定", "取消", IsWarn:=True) = 1 Then CurrentSubpage = Subpages.PanSelect BtnFinishExit.Text = "退出大厅" - LobbyController.Close() - DetectMcInstance() + Await LobbyService.LeaveLobbyAsync().ConfigureAwait(True) End If End Sub diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkSetup.xaml b/Plain Craft Launcher 2/Pages/PageLink/PageLinkSetup.xaml index fe51f571..2c342ad4 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkSetup.xaml +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkSetup.xaml @@ -76,6 +76,7 @@ + @@ -105,8 +106,9 @@ - + + diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkSetup.xaml.vb b/Plain Craft Launcher 2/Pages/PageLink/PageLinkSetup.xaml.vb index 8096e042..e421035c 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkSetup.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkSetup.xaml.vb @@ -29,8 +29,9 @@ Class PageLinkSetup ComboServerType.SelectedIndex = Config.Link.ServerType CheckLatencyFirstMode.Checked = Config.Link.LatencyFirstMode ComboPreferProtocol.SelectedIndex = CInt(Config.Link.ProtocolPreference) - CheckTryPaunchSym.Checked = Config.Link.TryPunchSym + CheckTryPunchSym.Checked = Config.Link.TryPunchSym CheckEnableIPv6.Checked = Config.Link.EnableIPv6 + CheckEnableCliOutput.Checked = Config.Link.EnableCliOutput If String.IsNullOrWhiteSpace(Config.Link.NaidRefreshToken) Then CardLogged.Visibility = Visibility.Collapsed CardNotLogged.Visibility = Visibility.Visible @@ -54,7 +55,15 @@ Class PageLinkSetup If ETRelay.RelayList.Count > 0 Then TextRelays.Text = "" For Each Relay In ETRelay.RelayList - TextRelays.Text += If(Relay.Type = ETRelayType.Community, "[社区] ", "[自有] ") & Relay.Name & "," + Select Case Relay.Type + Case ETRelayType.Community + TextRelays.Text += "[社区] " + Case ETRelayType.Selfhosted + TextRelays.Text += "[自有] " + Case Else 'ETRelayType.Custom + TextRelays.Text += "[自定义] " + End Select + TextRelays.Text += Relay.Name & "," Next TextRelays.Text = TextRelays.Text.BeforeLast(",") Else @@ -169,7 +178,7 @@ Class PageLinkSetup Private Shared Sub ComboBoxChange(sender As MyComboBox, e As Object) Handles ComboRelayType.SelectionChanged, ComboServerType.SelectionChanged If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex) End Sub - Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckLatencyFirstMode.Change, CheckEnableIPv6.Change, CheckTryPaunchSym.Change + Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckLatencyFirstMode.Change, CheckEnableIPv6.Change, CheckTryPunchSym.Change, CheckEnableCliOutput.Change If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Checked) End Sub Private Shared Sub LinkProtocolPerferenceChange(sender As MyComboBox, e As Object) Handles ComboPreferProtocol.SelectionChanged