feat(link): 支持 Scaffolding 协议 (#1790)
Co-authored-by: 吴桐 <162002839+wtommy932@users.noreply.github.com> Co-authored-by: WhiteCAT <64885812+whitecat346@users.noreply.github.com>
This commit is contained in:
2
PCL.Core
2
PCL.Core
Submodule PCL.Core updated: a2dffbbb37...f863918777
26
PCL.Test/LobbyCodeGenerateTest.cs
Normal file
26
PCL.Test/LobbyCodeGenerateTest.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 退出。
|
||||
''' </summary>
|
||||
''' <param name="SendWarning">是否在还有下载任务未完成时发出警告。</param>
|
||||
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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 及其成员与龙腾猫跃无从属关系,且均不会为您的
|
||||
''' </summary>
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
<RowDefinition Height="28" />
|
||||
<RowDefinition Height="28" />
|
||||
<RowDefinition Height="28" />
|
||||
<RowDefinition Height="28" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left" Text="用户名" Margin="0,0,25,0" />
|
||||
<local:MyTextBox x:Name="TextLinkUsername" Grid.Row="0" Grid.ColumnSpan="2" Tag="LinkUsername" Grid.Column="2" ToolTip="PCL CE 会尽可能使用此处的用户名用于大厅信息展示。
若留空,则使用 Natayark ID 的用户名。

部分情况下可能始终展示 Natayark ID 用户名,而非此处设置的名称。"/>
|
||||
@@ -105,8 +106,9 @@
|
||||
<local:MyComboBoxItem Content="UDP" ToolTip="让 EasyTier 优先使用 UDP 进行通信,UDP 通常能带来更低的通信时延"/>
|
||||
</local:MyComboBox>
|
||||
<local:MyCheckBox x:Name="CheckLatencyFirstMode" Tag="LinkLatencyFirstMode" Grid.Row="12" Grid.ColumnSpan="3" Text="选择最低延迟路径而不是最短路径" ToolTip="启用后,EasyTier 将在通信时选择延迟最低的路径,而不是路由跳数最少的路径。
启用此选项在部分情况下能降低联机时的延迟,但可能会提高丢包率。"/>
|
||||
<local:MyCheckBox x:Name="CheckTryPaunchSym" Tag="LinkTryPunchSym" Grid.Row="13" Grid.ColumnSpan="3" Text="尝试在对称型 NAT 下打洞" ToolTip="让 EasyTier 尝试打通对称型 NAT。
这也许可以改善联机体验,但在部分情况下也可能导致一些问题。
一般保持开启即可。"/>
|
||||
<local:MyCheckBox x:Name="CheckTryPunchSym" Tag="LinkTryPunchSym" Grid.Row="13" Grid.ColumnSpan="3" Text="对对称型 NAT 进行端口猜测" ToolTip="让 EasyTier 尝试对对称型 NAT 进行端口猜测(生日悖论)。
这也许可以改善联机体验,但在部分情况下也可能导致一些问题。
一般保持开启即可。"/>
|
||||
<local:MyCheckBox x:Name="CheckEnableIPv6" Tag="LinkEnableIPv6" Grid.Row="14" Grid.ColumnSpan="3" Text="允许使用 IPv6 通信" ToolTip="IPv6 相比于 IPv4 更加容易进行 P2P 通信,能极大改善联机体验,除有特殊原因外不建议关闭。"/>
|
||||
<local:MyCheckBox x:Name="CheckEnableCliOutput" Tag="LinkEnableCliOutput" Grid.Row="15" Grid.ColumnSpan="3" Text="在日志中输出 Cli 信息以用于调试" ToolTip="在启动器日志中每 30s 输出一次 EasyTier Cli 信息以便于开发者和社区支持调查。
该选项仅用于调试目的,不推荐一直开启。"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user