初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
<local:MyPageRight
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:svgIcon="clr-namespace:PCL.Core.UI.Controls.SvgIcon;assembly=PCL.Core"
|
||||
mc:Ignorable="d" x:Class="PCL.PageToolsGameLink"
|
||||
d:DesignWidth="800" d:DesignHeight="800">
|
||||
<Grid>
|
||||
<Grid x:Name="PanLoad" Visibility="Collapsed">
|
||||
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="CardLoad"
|
||||
MouseLeftButtonUp="CardLoad_MouseLeftButtonUp"
|
||||
SizeChanged="CardResized">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="30" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="25" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="20" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="18" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="1*" />
|
||||
<RowDefinition Height="23" />
|
||||
</Grid.RowDefinitions>
|
||||
<local:MyLoading Grid.Column="1" Grid.Row="1" x:Name="Load" Grid.RowSpan="3" Margin="0,0,0,-30"
|
||||
HorizontalAlignment="Center"
|
||||
Text="" TextError="" TextErrorInherit="False" AutoRun="False"
|
||||
IsHitTestVisible="False" />
|
||||
<TextBlock x:Name="LabLoadTitle" Grid.Column="3" Grid.Row="1" Grid.ColumnSpan="2"
|
||||
VerticalAlignment="Center" Text="{DynamicResource Tools.GameLink.Loading.Creating}"
|
||||
FontSize="19"
|
||||
Margin="0,0,25,1" Foreground="{Binding Foreground, ElementName=Load}"
|
||||
IsHitTestVisible="False" />
|
||||
<TextBlock x:Name="LabLoadDesc" Grid.Column="3" Grid.ColumnSpan="3" Grid.Row="2"
|
||||
VerticalAlignment="Center" Margin="0,5,10,-5"
|
||||
Text="{DynamicResource Tools.GameLink.Loading.DownloadingModule}" FontSize="14"
|
||||
LineHeight="19" MaxWidth="400" MaxHeight="220" TextWrapping="Wrap"
|
||||
Foreground="{StaticResource ColorBrushGray3}" IsHitTestVisible="False" />
|
||||
<local:MyIconButton Grid.Column="5" Grid.Row="1" x:Name="BtnLoadCancel" Height="28" Width="28"
|
||||
Click="CancelLoad"
|
||||
VerticalAlignment="Top"
|
||||
Theme="Black" Opacity="0.5"
|
||||
ToolTip="{DynamicResource Common.Action.Cancel}"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTipService.InitialShowDelay="100"
|
||||
LogoScale="0.72"
|
||||
SvgIcon="lucide/x" />
|
||||
<Grid Height="12" VerticalAlignment="Bottom" Grid.ColumnSpan="100" Grid.RowSpan="100"
|
||||
Margin="0,0,0,-0.6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="0*" x:Name="ColumnProgressA" />
|
||||
<ColumnDefinition Width="1*" x:Name="ColumnProgressB" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.Clip>
|
||||
<RectangleGeometry RadiusX="6" RadiusY="6" x:Name="RectProgressClip" />
|
||||
</Grid.Clip>
|
||||
<Rectangle Fill="{Binding Foreground, ElementName=Load}" VerticalAlignment="Bottom" Height="2" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</local:MyCard>
|
||||
</Grid>
|
||||
<local:MyScrollViewer x:Name="PanContent">
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<local:MyHint x:Name="HintAnnounce" Margin="10" MinHeight="36" MaxHeight="64" Grid.Row="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{DynamicResource Tools.GameLink.Loading.ConnectingServer}" Theme="Blue" />
|
||||
<StackPanel x:Name="PanEula" Grid.Row="1" HorizontalAlignment="Stretch" Orientation="Vertical">
|
||||
<local:MyCard Title="{DynamicResource Tools.GameLink.Agreement.Title}" Margin="10">
|
||||
<StackPanel Margin="20,40,18,18">
|
||||
<TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
|
||||
Text="{DynamicResource Tools.GameLink.Agreement.Description}" />
|
||||
<local:MyListItem Title="{DynamicResource Tools.GameLink.Agreement.PrivacyPolicy}"
|
||||
Info="{DynamicResource Tools.GameLink.Agreement.PrivacyPolicyInfo}"
|
||||
Type="Clickable" local:CustomEventService.EventType="OpenUrl"
|
||||
local:CustomEventService.EventData="https://www.pclc.cc/privacy/personal-info-brief.html"
|
||||
Logo="pack://application:,,,/images/Blocks/Grass.png" />
|
||||
<local:MyListItem Title="{DynamicResource Tools.GameLink.Agreement.NatayarkTerms}"
|
||||
Info="{DynamicResource Tools.GameLink.Agreement.NatayarkTermsInfo}"
|
||||
Type="Clickable" local:CustomEventService.EventType="OpenUrl"
|
||||
local:CustomEventService.EventData="https://account.naids.com/policy"
|
||||
Logo="pack://application:,,,/images/Blocks/Grass.png" />
|
||||
<TextBlock TextWrapping="Wrap" Margin="0,10,0,0"
|
||||
Text="{DynamicResource Tools.GameLink.Agreement.Body}" />
|
||||
<local:MyButton x:Name="BtnEulaAgree"
|
||||
Text="{DynamicResource Tools.GameLink.Agreement.Accept}"
|
||||
ColorType="Highlight" HorizontalAlignment="Center" Padding="13,8"
|
||||
Margin="0,10,0,0" Click="BtnAgreeEula_Click" />
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
</StackPanel>
|
||||
<StackPanel x:Name="PanSelect" Grid.Row="1" HorizontalAlignment="Stretch" Orientation="Vertical">
|
||||
<StackPanel Margin="10,4" HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
<local:MyCard Margin="12,0" x:Name="BtnNatTest">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}"
|
||||
MouseLeftButtonUp="BtnNetTest_Click"
|
||||
ToolTip="{DynamicResource Tools.GameLink.Nat.Title}"
|
||||
ToolTipService.Placement="Center" ToolTipService.InitialShowDelay="100"
|
||||
ToolTipService.VerticalOffset="-35">
|
||||
<svgIcon:SvgIcon IsHitTestVisible="False"
|
||||
Width="14" Stretch="Uniform"
|
||||
IconBrush="{DynamicResource ColorBrushGray1}"
|
||||
Margin="12,0,7,0"
|
||||
Icon="lucide/ethernet-port" />
|
||||
<TextBlock x:Name="LabNatType" IsHitTestVisible="False"
|
||||
Text="{DynamicResource Tools.GameLink.Nat.Test}" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource ColorBrushGray1}" Margin="0,0,12,0" />
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
|
||||
<local:MyCard x:Name="BtnNatayarkUserName">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}"
|
||||
ToolTip="{DynamicResource Tools.GameLink.Natayark.Account}"
|
||||
ToolTipService.Placement="Center"
|
||||
ToolTipService.InitialShowDelay="100" ToolTipService.VerticalOffset="-35">
|
||||
<svgIcon:SvgIcon IsHitTestVisible="False"
|
||||
Width="14" Stretch="Uniform"
|
||||
IconBrush="{DynamicResource ColorBrushGray1}"
|
||||
Margin="12,0,7,0"
|
||||
Icon="lucide/user" />
|
||||
<TextBlock x:Name="LabNatayarkUserName"
|
||||
MouseLeftButtonUp="LabNatayarkUserName_MouseLeftButtonUp"
|
||||
Text="{DynamicResource Tools.GameLink.Natayark.Login}"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource ColorBrushGray1}" Margin="0,0,12,0" />
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
</StackPanel>
|
||||
<local:MyCard x:Name="BtnSelectJoin" Margin="10"
|
||||
Title="{DynamicResource Tools.GameLink.Join.Title}">
|
||||
<StackPanel Margin="10,30,10,10">
|
||||
<TextBlock LineHeight="23" FontSize="14" TextWrapping="Wrap" Margin="10,10,10,-2"
|
||||
Text="{DynamicResource Tools.GameLink.Join.Description}" />
|
||||
<DockPanel Margin="10" Height="28">
|
||||
<local:MyButton x:Name="BtnJoin" DockPanel.Dock="Right"
|
||||
Text="{DynamicResource Tools.GameLink.Join.Action}"
|
||||
ColorType="Highlight"
|
||||
Width="50" Margin="10,0,0,0" Click="BtnJoin_Click" />
|
||||
<local:MyButton x:Name="BtnPaste" DockPanel.Dock="Right"
|
||||
Text="{DynamicResource Tools.GameLink.Join.Paste}" ColorType="Normal"
|
||||
Width="50" Margin="10,0,0,0" Click="PasteLobbyId" />
|
||||
<local:MyButton x:Name="BtnClearLobbyId" DockPanel.Dock="Right"
|
||||
Text="{DynamicResource Tools.GameLink.Join.Clear}"
|
||||
Click="ClearLobbyId"
|
||||
ColorType="Normal" Width="50" Margin="10,0,0,0" />
|
||||
<local:MyTextBox x:Name="TextJoinLobbyId"
|
||||
HintText="{DynamicResource Tools.GameLink.Join.IdHint}"
|
||||
MaxLines="1" AcceptsReturn="False" KeyDown="TextJoinLobbyId_KeyDown" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<!--<local:MyHint IsWarn="True" Text="这是错误文本。" x:Name="HintTypeError" Visibility="Collapsed" Grid.ColumnSpan="5" Grid.RowSpan="5" Margin="15,15,15,0" VerticalAlignment="Top" />-->
|
||||
<local:MyCard x:Name="BtnSelectCreate" Margin="10"
|
||||
Title="{DynamicResource Tools.GameLink.Create.Title}" CanSwap="True">
|
||||
<StackPanel Margin="10,30,10,10">
|
||||
<TextBlock LineHeight="23" FontSize="14" TextWrapping="Wrap" Margin="10,10,10,0"
|
||||
Text="{DynamicResource Tools.GameLink.Create.Description}" />
|
||||
<DockPanel Margin="10" Height="28">
|
||||
<local:MyButton x:Name="BtnCreate" DockPanel.Dock="Right"
|
||||
Text="{DynamicResource Tools.GameLink.Create.Action}"
|
||||
ColorType="Highlight" Width="50" Margin="10,0,0,0"
|
||||
Click="BtnCreate_Click" />
|
||||
<local:MyButton x:Name="BtnRefresh" DockPanel.Dock="Right"
|
||||
Text="{DynamicResource Common.Action.Refresh}" ColorType="Normal"
|
||||
Width="50" Margin="10,0,0,0" Click="BtnRefresh_Click" />
|
||||
<local:MyButton x:Name="BtnInputPort" DockPanel.Dock="Right"
|
||||
Text="{DynamicResource Tools.GameLink.Create.ManualInput}"
|
||||
ColorType="Normal" Width="80" Margin="10,0,0,0"
|
||||
Click="BtnInputPort_Click" />
|
||||
<local:MyComboBox x:Name="ComboWorldList" DropDownWidthSync="False" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
</StackPanel>
|
||||
<local:MyCard x:Name="PanFooter" Grid.Row="2" Margin="10">
|
||||
<StackPanel HorizontalAlignment="Stretch" Orientation="Vertical" Opacity="0.75" Margin="8">
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="15,6"
|
||||
Orientation="Horizontal">
|
||||
<local:MyTextButton Text="{DynamicResource Tools.GameLink.Link.Report}"
|
||||
local:CustomEventService.EventType="OpenUrl"
|
||||
local:CustomEventService.EventData="https://qm.qq.com/q/zfml1KaPWS" />
|
||||
<TextBlock Text=" | " />
|
||||
<local:MyTextButton Text="{DynamicResource Tools.GameLink.Link.Terms}"
|
||||
local:CustomEventService.EventType="OpenUrl"
|
||||
local:CustomEventService.EventData="https://account.naids.com/policy" />
|
||||
<TextBlock Text=" | " />
|
||||
<local:MyTextButton Text="{DynamicResource Tools.GameLink.Link.Privacy}"
|
||||
local:CustomEventService.EventType="OpenUrl"
|
||||
local:CustomEventService.EventData="https://www.pclc.cc/privacy/personal-info-brief.html" />
|
||||
<TextBlock Text=" | " />
|
||||
<local:MyTextButton Text="{DynamicResource Tools.GameLink.Link.Disable}"
|
||||
x:Name="BtnEulaStop" Click="BtnEulaStop_Click" />
|
||||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="15,6"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock Text="{DynamicResource Tools.GameLink.Link.Friends}" />
|
||||
<local:MyTextButton Text="{DynamicResource Tools.GameLink.Link.EasyTier}"
|
||||
local:CustomEventService.EventType="OpenUrl"
|
||||
local:CustomEventService.EventData="https://easytier.cn/" />
|
||||
<TextBlock Text=" | " />
|
||||
<local:MyTextButton Text="Pysio's Home" local:CustomEventService.EventType="OpenUrl"
|
||||
local:CustomEventService.EventData="https://pysio.online/" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<Grid x:Name="PanFinish" Visibility="Collapsed" Grid.Row="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Grid.ColumnSpan="2">
|
||||
<local:MyHint Margin="0,0,0,16" x:Name="HintEasyTierVersion"
|
||||
Text="{DynamicResource Tools.GameLink.EasyTierVersion}" Theme="Yellow"
|
||||
Visibility="Collapsed" />
|
||||
</StackPanel>
|
||||
<!--大厅信息栏-->
|
||||
<local:MyCard x:Name="PanNetInfo" Title="{DynamicResource Tools.GameLink.Finish.InfoTitle}"
|
||||
Margin="10" Grid.Row="2">
|
||||
<StackPanel Orientation="Vertical" Margin="10,35,10,10">
|
||||
<StackPanel x:Name="BtnFinishQuality" Margin="5" Orientation="Horizontal"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}"
|
||||
ToolTip="{DynamicResource Tools.GameLink.Finish.ConnectionStatus}"
|
||||
ToolTipService.Placement="MousePoint"
|
||||
ToolTipService.InitialShowDelay="100" ToolTipService.VerticalOffset="-35">
|
||||
<Path IsHitTestVisible="False" Width="14" Stretch="Uniform"
|
||||
Fill="{StaticResource ColorBrushGray1}" Margin="0,0,7,0"
|
||||
Data="M512 128c212.064 0 384 171.936 384 384s-173.632 384-384 384C299.936 896 128 724.064 128 512S299.936 128 512 128z m-126.4 575.968c-1.728 40.8 3.52 76.8 36.32 115.2 28.576 8.32 58.816 12.832 90.08 12.832 81.984 0 156.8-30.848 213.408-81.536-77.12 7.2-161.6-5.952-289.216-61.856-11.616 7.52-18.976 12.608-50.592 15.36z m-193.44-202.368c0 123.744 53.536 214.272 135.744 272.16-6.08-25.6-8.352-48.448-4.96-91.68-21.344-17.6-34.944-39.264-34.624-82.08-50.816-42.88-82.88-75.712-96.16-98.4zM544 448c-14.944 0-29.12-3.424-41.728-9.504-10.976 12.96-21.376 26.432-31.2 40.512-4.832 6.912-15.36 23.776-31.616 50.624a95.872 95.872 0 0 1 36.384 106.432c155.52 68.16 302.56 62.048 325.952 11.904 23.36-50.112-35.968-139.776-189.504-228.48A95.68 95.68 0 0 1 544 448z m-160 128a32 32 0 1 0 0 64 32 32 0 0 0 0-64z m-161.92-199.2c-16.576 35.52 16.96 99.68 94.08 163.2a95.936 95.936 0 0 1 59.616-27.648c19.936-35.008 30.72-52.736 42.88-70.08 10.944-15.648 22.528-30.656 38.432-49.44-5.696-12.128-8.96-25.6-9.088-39.872-118.176-30.4-206.88-16.96-225.92 23.84z m415.136-47.84c1.824 7.36 3.296 19.104 2.336 32.416 88.96 48.704 153.76 105.056 192.32 160.704 0-118.656-34.208-176.064-75.52-210.976-49.792-5.92-69.952-4.064-119.136 17.856zM544 320a32 32 0 1 0 0 64 32 32 0 0 0 0-64z m-32-128c-82.016 0-156.8 30.848-213.44 81.6 47.264-4.384 96-0.544 170.304 18.624A95.84 95.84 0 0 1 544 256c21.568 0 41.472 7.104 57.504 19.104 35.968-17.28 62.016-25.824 87.136-29.984A318.528 318.528 0 0 0 512 192z" />
|
||||
<TextBlock x:Name="LabFinishQuality" IsHitTestVisible="False"
|
||||
Text="{DynamicResource Tools.GameLink.Finish.Checking}"
|
||||
VerticalAlignment="Center" Foreground="{StaticResource ColorBrushGray1}"
|
||||
Margin="0,0,12,0" />
|
||||
</StackPanel>
|
||||
<StackPanel x:Name="BtnFinishPing" Margin="5" Orientation="Horizontal"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}" Visibility="Collapsed"
|
||||
ToolTip="{DynamicResource Tools.GameLink.Finish.PingToHost}"
|
||||
ToolTipService.Placement="MousePoint"
|
||||
ToolTipService.InitialShowDelay="100" ToolTipService.VerticalOffset="-35">
|
||||
<Path IsHitTestVisible="False" Width="14" Stretch="Uniform"
|
||||
Fill="{StaticResource ColorBrushGray1}" Margin="0,0,7,0"
|
||||
Data="M512 409.6a102.4 102.4 0 0 0-51.2 190.464V1024h102.4v-423.936A102.4 102.4 0 0 0 512 409.6z M512 0a512 512 0 0 0-263.168 950.272l51.2-87.552a409.6 409.6 0 1 1 420.864 0l51.2 87.552A512 512 0 0 0 512 0z M819.2 512a307.2 307.2 0 1 0-464.896 263.168l51.2-88.064a204.8 204.8 0 1 1 209.92 0l51.2 88.064A307.2 307.2 0 0 0 819.2 512z" />
|
||||
<TextBlock x:Name="LabFinishPing" IsHitTestVisible="False" Text="-ms"
|
||||
VerticalAlignment="Center" Foreground="{StaticResource ColorBrushGray1}"
|
||||
Margin="0,0,12,0" />
|
||||
</StackPanel>
|
||||
<StackPanel x:Name="BtnFinishId" Margin="5" Orientation="Horizontal"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}"
|
||||
ToolTip="{DynamicResource Tools.GameLink.Finish.LobbyId}"
|
||||
ToolTipService.Placement="MousePoint"
|
||||
ToolTipService.InitialShowDelay="100" ToolTipService.VerticalOffset="-35">
|
||||
<Path IsHitTestVisible="False" Width="14" Stretch="Uniform"
|
||||
Fill="{StaticResource ColorBrushGray1}" Margin="0,0,7,0"
|
||||
Data="M21.15,2.86a2.89,2.89,0,0,0-3-.71L4,6.88a2.9,2.9,0,0,0-.12,5.47l5.24,2h0a.93.93,0,0,1,.53.52l2,5.25A2.87,2.87,0,0,0,14.36,22h.07a2.88,2.88,0,0,0,2.69-2L21.85,5.83A2.89,2.89,0,0,0,21.15,2.86ZM20,5.2,15.22,19.38a.88.88,0,0,1-.84.62.92.92,0,0,1-.87-.58l-2-5.25a2.91,2.91,0,0,0-1.67-1.68l-5.25-2A.9.9,0,0,1,4,9.62a.88.88,0,0,1,.62-.84L18.8,4.05A.91.91,0,0,1,20,5.2Z" />
|
||||
<TextBlock x:Name="LabFinishId" IsHitTestVisible="False" Text=""
|
||||
VerticalAlignment="Center" Foreground="{StaticResource ColorBrushGray1}"
|
||||
Margin="0,0,12,0" />
|
||||
</StackPanel>
|
||||
<StackPanel x:Name="BtnConnectType" Margin="5" Orientation="Horizontal"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}" Visibility="Collapsed"
|
||||
ToolTip="{DynamicResource Tools.GameLink.Finish.ConnectionType}"
|
||||
ToolTipService.Placement="MousePoint"
|
||||
ToolTipService.InitialShowDelay="100" ToolTipService.VerticalOffset="-35">
|
||||
<Path IsHitTestVisible="False" Width="14" Stretch="Uniform"
|
||||
Fill="{StaticResource ColorBrushGray1}" Margin="0,0,7,0"
|
||||
Data="M215.625 18.75A9.375 9.375 0 0 1 225 28.125V249.24375L283.9875 190.2375A9.375 9.375 0 0 1 297.2625000000001 203.5125L222.2625 278.5125000000001A9.375 9.375 0 0 1 208.9875 278.5125000000001L133.9875 203.5125A9.375 9.375 0 1 1 147.2625 190.2375L206.25 249.24375V28.125A9.375 9.375 0 0 1 215.625 18.75zM84.375 281.25A9.375 9.375 0 0 0 93.75 271.875V50.75625L152.7375 109.7625A9.375 9.375 0 0 0 166.0125 96.4875L91.0125 21.4875A9.375 9.375 0 0 0 77.7375 21.4875L2.7375 96.4875A9.375 9.375 0 0 0 16.0125 109.7625L75 50.75625V271.875A9.375 9.375 0 0 0 84.375 281.25z" />
|
||||
<TextBlock x:Name="LabConnectType" IsHitTestVisible="False"
|
||||
Text="{DynamicResource Tools.GameLink.Finish.Connecting}"
|
||||
VerticalAlignment="Center" Foreground="{StaticResource ColorBrushGray1}"
|
||||
Margin="0,0,12,0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<!--用户信息-->
|
||||
<local:MyCard Margin="10" Grid.Row="1">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<StackPanel x:Name="BtnConnectUserName" Orientation="Horizontal"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}"
|
||||
ToolTip="{DynamicResource Tools.GameLink.Finish.UserName}"
|
||||
ToolTipService.Placement="Center"
|
||||
ToolTipService.InitialShowDelay="100" ToolTipService.VerticalOffset="-35">
|
||||
<Path IsHitTestVisible="False" Width="14" Stretch="Uniform"
|
||||
Fill="{StaticResource ColorBrushGray1}" Margin="12,0,7,0"
|
||||
Data="M512 447.223c-88.224 0-160-71.776-160-160s71.776-160 160-160c88.225 0 160 71.776 160 160s-71.775 160-160 160z m0-256c-52.935 0-96 43.065-96 96s43.065 96 96 96 96-43.065 96-96-43.065-96-96-96zM454.901 870.594c-96.594 0-184.933-3.802-231.263-49.955C203.308 800.386 193 774.164 193 742.701c0-31.629 10.247-62.812 30.457-92.686 17.978-26.573 42.908-50.741 74.098-71.833C359.256 536.46 437.418 512.53 512 512.53c74.55 0 152.55 23.943 214.002 65.691 31.05 21.094 55.861 45.273 73.746 71.867C819.822 679.937 830 711.096 830 742.701c0 31.552-10.317 57.827-30.664 78.097-50.714 50.521-151.822 50.128-258.88 49.723a7395.45 7395.45 0 0 0-56.914-0.001c-9.605 0.037-19.163 0.074-28.641 0.074zM512 806.447c9.567 0 19.149 0.037 28.701 0.073 49.52 0.191 96.284 0.37 135.808-4.396 38.418-4.633 64.546-13.604 77.659-26.668 5.079-5.06 11.832-13.96 11.832-32.755 0-38.089-27.688-78.744-75.963-111.54C638.933 596.442 574.04 576.53 512 576.53c-126.309 0-255 83.862-255 166.171 0 18.675 6.738 27.547 11.807 32.596 32.045 31.922 128.975 31.55 214.491 31.224 9.556-0.037 19.139-0.074 28.702-0.074z" />
|
||||
<TextBlock x:Name="LabConnectUserName" IsHitTestVisible="False" Text="UserName"
|
||||
VerticalAlignment="Center" Foreground="{StaticResource ColorBrushGray1}"
|
||||
Margin="0,0,12,0" />
|
||||
</StackPanel>
|
||||
<TextBlock x:Name="SplitLineBeforeUserType" Text="|" Opacity="0.3" Margin="0,8.8,0,8"
|
||||
RenderTransformOrigin="0.5,0.5"
|
||||
Visibility="{Binding ElementName=LabConnectUserType,Path=Visibility}">
|
||||
<TextBlock.RenderTransform>
|
||||
<ScaleTransform ScaleX="1" ScaleY="2.75" CenterX="0.5" CenterY="0.5" />
|
||||
</TextBlock.RenderTransform>
|
||||
</TextBlock>
|
||||
<StackPanel x:Name="BtnConnectUserType" Orientation="Horizontal"
|
||||
Background="{StaticResource ColorBrushSemiTransparent}"
|
||||
ToolTip="{DynamicResource Tools.GameLink.Finish.UserType}"
|
||||
ToolTipService.Placement="Center"
|
||||
ToolTipService.InitialShowDelay="100" ToolTipService.VerticalOffset="-35">
|
||||
<Path IsHitTestVisible="False" Height="13" Width="13" Stretch="Uniform"
|
||||
Fill="{StaticResource ColorBrushGray1}" Margin="12,0,7,0"
|
||||
Data="M274.773333 419.498667h154.624v76.8H274.773333v-76.8z m0-154.965334h399.701334V341.333333H274.773333V264.533333z M92.16 105.472v832.170667h179.541333v-76.8H168.96V182.272h678.229333v227.669333h76.8V105.472H92.16z M645.12 720.213333c91.818667-1.024 165.888-75.776 165.888-167.936 0-92.842667-75.093333-167.936-167.936-167.936s-167.936 75.093333-167.936 167.936c0 92.16 74.069333 166.912 165.888 167.936-129.365333 1.024-238.933333 90.794667-279.210667 215.722667h81.92c35.84-81.92 111.616-138.922667 198.997334-138.922667s163.498667 56.661333 198.997333 138.922667h82.261333c-40.277333-124.586667-149.845333-214.698667-278.869333-215.722667z m-2.389333-76.8c-50.176 0-91.477333-40.96-91.477334-91.477333 0-50.176 40.96-91.477333 91.477334-91.477333 50.176 0 91.477333 40.96 91.477333 91.477333s-40.96 91.477333-91.477333 91.477333z" />
|
||||
<TextBlock x:Name="LabConnectUserType" IsHitTestVisible="False" Text="UserType"
|
||||
VerticalAlignment="Center" Foreground="{StaticResource ColorBrushGray1}"
|
||||
Margin="0,0,12,0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<!--操作栏-->
|
||||
<local:MyCard x:Name="PanOperation" Title="{DynamicResource Tools.GameLink.Finish.Actions}"
|
||||
Margin="10" Grid.Row="3">
|
||||
<StackPanel Orientation="Vertical" Margin="10,35,10,10">
|
||||
<local:MyIconTextButton Margin="-2,1" HorizontalAlignment="Left" x:Name="BtnFinishCopyIp"
|
||||
Text="{DynamicResource Tools.GameLink.Finish.CopyVirtualIp}"
|
||||
Click="BtnFinishCopyIp_Click"
|
||||
SvgIcon="lucide/copy" />
|
||||
<local:MyIconTextButton Margin="-2,1" HorizontalAlignment="Left" x:Name="BtnFinishCopy"
|
||||
Text="{DynamicResource Tools.GameLink.Finish.CopyLobbyId}"
|
||||
Click="BtnFinishCopy_Click"
|
||||
SvgIcon="lucide/copy" />
|
||||
<local:MyIconTextButton Margin="-2,1" HorizontalAlignment="Left" x:Name="BtnFinishExit"
|
||||
Text="{DynamicResource Tools.GameLink.Finish.Exit}"
|
||||
Click="BtnFinishExit_Click"
|
||||
SvgIcon="lucide/power" />
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<!--成员-->
|
||||
<local:MyCard x:Name="CardPlayerList" Title="{DynamicResource Tools.GameLink.Member.ListLoading}"
|
||||
Margin="10" MinWidth="300"
|
||||
Grid.Row="1" Grid.RowSpan="4" Grid.Column="1">
|
||||
<StackPanel x:Name="StackPlayerList" Margin="5,35,6,8" />
|
||||
</local:MyCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</local:MyScrollViewer>
|
||||
</Grid>
|
||||
</local:MyPageRight>
|
||||
@@ -0,0 +1,1016 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using PCL.Core.App;
|
||||
using PCL.Core.Link;
|
||||
using PCL.Core.Link.EasyTier;
|
||||
using PCL.Core.Link.Lobby;
|
||||
using PCL.Core.Link.McPing;
|
||||
using PCL.Core.Link.Natayark;
|
||||
using PCL.Core.Link.Scaffolding.Client.Models;
|
||||
using PCL.Core.Link.Scaffolding.EasyTier;
|
||||
using PCL.Core.Logging;
|
||||
using PCL.Core.Utils.Validate;
|
||||
using PCL.Network;
|
||||
using PCL.Core.App.Localization;
|
||||
using PCL.Core.Utils;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageToolsGameLink
|
||||
{
|
||||
static PageToolsGameLink()
|
||||
{
|
||||
initLoader = new ModLoader.LoaderCombo<int>(Lang.Text("Link.Mod.Task.InitLobby"),
|
||||
new[] { new ModLoader.LoaderTask<int, int>(Lang.Text("Common.Action.Initialize"), InitTask) { ProgressWeight = 0.5d } });
|
||||
}
|
||||
|
||||
public PageToolsGameLink()
|
||||
{
|
||||
InitializeComponent();
|
||||
LoaderInit();
|
||||
Loaded += (_, _) => Reload();
|
||||
PageEnter += PageLinkLobby_OnPageEnter;
|
||||
}
|
||||
|
||||
#region 初始化
|
||||
|
||||
// 加载器初始化
|
||||
private void LoaderInit()
|
||||
{
|
||||
PageLoaderInit(Load, PanLoad, PanContent, null, initLoader, autoRun: false);
|
||||
// 注册自定义的 OnStateChanged
|
||||
initLoader.OnStateChangedUi += OnLoadStateChanged;
|
||||
|
||||
LobbyService.OnNeedDownloadEasyTier += () => ModLink.DownloadEasyTier();
|
||||
LobbyService.DiscoveredWorlds.CollectionChanged += OnDiscoveredWorldsChanged;
|
||||
LobbyService.Players.CollectionChanged += OnPlayersChanged;
|
||||
LobbyService.OnUserStopGame += OnUserStopGame;
|
||||
LobbyService.OnClientPing += OnClientPingHandler;
|
||||
LobbyService.OnServerShutDown += OnServerShuttedDownHandler;
|
||||
LobbyService.OnServerStarted += OnServerStartedHandler;
|
||||
LobbyService.OnServerException += OnServerExceptionHandler;
|
||||
|
||||
if (lobbyAnnouncementLoader is null)
|
||||
{
|
||||
var loaders = new List<ModLoader.LoaderBase>();
|
||||
loaders.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Link.Mod.Task.InitLobbyUi"), _ => ModBase.RunInUi(() =>
|
||||
{
|
||||
HintAnnounce.Visibility = Visibility.Visible;
|
||||
HintAnnounce.Theme = MyHint.Themes.Blue;
|
||||
HintAnnounce.Text = Lang.Text("Tools.GameLink.Loading.ConnectingServer");
|
||||
})));
|
||||
loaders.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Link.Mod.Task.FetchAnnouncement"), _ => GetAnnouncement()) { ProgressWeight = 0.5d });
|
||||
lobbyAnnouncementLoader = new ModLoader.LoaderCombo<int>("Lobby Announcement", loaders) { show = false };
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnServerExceptionHandler(Exception ex)
|
||||
{
|
||||
ModBase.RunInUi(() => HintService.Hint(
|
||||
Lang.Text("Tools.GameLink.Error.ServerMessage", ex.Message),
|
||||
HintType.Error));
|
||||
|
||||
try
|
||||
{
|
||||
await LobbyService.LeaveLobbyAsync();
|
||||
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
CardPlayerList.Title = Lang.Text("Tools.GameLink.Member.ListLoading");
|
||||
StackPlayerList.Children.Clear();
|
||||
CurrentSubpage = Subpages.PanSelect;
|
||||
});
|
||||
}
|
||||
catch (Exception secEx)
|
||||
{
|
||||
ModBase.Log(secEx, "Occurred an exception when exit server.");
|
||||
HintService.Hint(Lang.Text("Tools.GameLink.Error.ServerExit"), HintType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public async void Reload()
|
||||
{
|
||||
HintAnnounce.Visibility = Visibility.Visible;
|
||||
HintAnnounce.Text = Lang.Text("Tools.GameLink.Loading.ConnectingServer");
|
||||
HintAnnounce.Theme = MyHint.Themes.Blue;
|
||||
|
||||
// 加载公告
|
||||
lobbyAnnouncementLoader.Start();
|
||||
if (_linkAnnounceUpdateCancelSource is not null)
|
||||
_linkAnnounceUpdateCancelSource.Cancel();
|
||||
_linkAnnounceUpdateCancelSource = new CancellationTokenSource();
|
||||
await Dispatcher.BeginInvoke(new Action(async () =>
|
||||
await _LinkAnnounceUpdateAsync())); // 我实在不理解为啥 BeginInvoke 这个委托要 MustBeInherit
|
||||
|
||||
await LobbyService.InitializeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void BtnAgreeEula_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
States.Link.LinkEula = true;
|
||||
CurrentSubpage = Subpages.PanSelect;
|
||||
}
|
||||
|
||||
private void BtnEulaStop_Click(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (ModMain.MyMsgBox(Lang.Text("Tools.GameLink.Eula.RevokeConfirm"),
|
||||
Lang.Text("Tools.GameLink.Eula.RevokeTitle"),
|
||||
Lang.Text("Common.Action.Confirm"),
|
||||
Lang.Text("Common.Action.Cancel"),
|
||||
isWarn: true
|
||||
) == 1)
|
||||
{
|
||||
States.Link.NaidRefreshTokenConfig.Reset();
|
||||
States.Link.LinkEulaConfig.Reset();
|
||||
HintService.Hint(Lang.Text("Tools.GameLink.Eula.Disabled"));
|
||||
CurrentSubpage = Subpages.PanEula;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ModLoader.LoaderCombo<int> initLoader;
|
||||
|
||||
private static async void InitTask(ModLoader.LoaderTask<int, int> task)
|
||||
{
|
||||
await LobbyService.InitializeAsync();
|
||||
}
|
||||
|
||||
#region Subscribser
|
||||
|
||||
private void OnServerStartedHandler()
|
||||
{
|
||||
ModBase.Log("Received server started event.");
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
LabFinishId.Text = LobbyService.CurrentLobbyCode;
|
||||
StackPlayerList.Children.Clear();
|
||||
foreach (var player in LobbyService.Players)
|
||||
StackPlayerList.Children.Add((UIElement)PlayerInfoItem(player, PlayerInfoClick));
|
||||
});
|
||||
}
|
||||
|
||||
private async void OnServerShuttedDownHandler()
|
||||
{
|
||||
try
|
||||
{
|
||||
await LobbyService.LeaveLobbyAsync();
|
||||
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
CardPlayerList.Title = Lang.Text("Tools.GameLink.Member.ListLoading");
|
||||
StackPlayerList.Children.Clear();
|
||||
CurrentSubpage = Subpages.PanSelect;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "Occurred an exception when exit server.");
|
||||
HintService.Hint(Lang.Text("Tools.GameLink.Error.ServerExit"), HintType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClientPingHandler(long latency)
|
||||
{
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
LabFinishQuality.Text = Lang.Text("Tools.GameLink.Finish.Connected");
|
||||
LabFinishPing.Text = Lang.Text("Tools.GameLink.Finish.PingMs", latency);
|
||||
LabConnectType.Text = Lang.Text("Tools.GameLink.Finish.Unavailable");
|
||||
});
|
||||
}
|
||||
|
||||
private void OnUserStopGame()
|
||||
{
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
CardPlayerList.Title = Lang.Text("Tools.GameLink.Member.ListLoading");
|
||||
StackPlayerList.Children.Clear();
|
||||
CurrentSubpage = Subpages.PanSelect;
|
||||
});
|
||||
ModMain.MyMsgBox(Lang.Text("Tools.GameLink.Exit.Disbanded"), Lang.Text("Tools.GameLink.Exit.DisbandedTitle"));
|
||||
}
|
||||
|
||||
|
||||
private void OnPlayersChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
ModBase.Log("接收到玩家列表改变事件");
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
if (e.NewItems is not null)
|
||||
foreach (PlayerProfile player in e.NewItems)
|
||||
StackPlayerList.Children.Add((UIElement)PlayerInfoItem(player, PlayerInfoClick));
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
if (e.OldItems is not null)
|
||||
foreach (PlayerProfile player in e.OldItems)
|
||||
{
|
||||
var itemToRemove = StackPlayerList.Children.OfType<MyListItem>()
|
||||
.FirstOrDefault(item => ((PlayerProfile)item.Tag).MachineId == player.MachineId);
|
||||
if (itemToRemove is not null) StackPlayerList.Children.Remove(itemToRemove);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
StackPlayerList.Children.Clear();
|
||||
foreach (var player in LobbyService.Players)
|
||||
StackPlayerList.Children.Add((UIElement)PlayerInfoItem(player, PlayerInfoClick));
|
||||
break;
|
||||
}
|
||||
|
||||
LabFinishQuality.Text = Lang.Text("Tools.GameLink.Finish.Connected");
|
||||
CardPlayerList.Title = Lang.Text("Tools.GameLink.Member.ListCount", LobbyService.Players.Count);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void OnDiscoveredWorldsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
LogWrapper.Info("[Lobby] Found new world changes");
|
||||
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
#region 处理集合变更
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
ComboWorldList.Items.Clear();
|
||||
foreach (var world in LobbyService.DiscoveredWorlds)
|
||||
ComboWorldList.Items.Add(new MyComboBoxItem
|
||||
{
|
||||
Tag = world.Port,
|
||||
Content = world.Name
|
||||
});
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
if (e.NewItems is not null)
|
||||
foreach (FoundWorld world in e.NewItems)
|
||||
ComboWorldList.Items.Add(new MyComboBoxItem
|
||||
{
|
||||
Tag = world.Port,
|
||||
Content = world.Name
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
if (e.OldItems is not null)
|
||||
{
|
||||
// 使用 HashSet 提高查询效率
|
||||
var portsToRemove = e.OldItems.Cast<FoundWorld>().Select(w => w.Port).ToHashSet();
|
||||
var itemsToRemove = ComboWorldList.Items
|
||||
.Cast<MyComboBoxItem>()
|
||||
.Where(item => portsToRemove.Contains((int)item.Tag))
|
||||
.ToList();
|
||||
|
||||
foreach (var item in itemsToRemove) ComboWorldList.Items.Remove(item);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 更新 UI 状态
|
||||
|
||||
var hasItems = ComboWorldList.Items.Count > 0;
|
||||
ComboWorldList.IsEnabled = hasItems;
|
||||
BtnCreate.IsEnabled = hasItems;
|
||||
|
||||
if (hasItems && ComboWorldList.SelectedIndex == -1) ComboWorldList.SelectedIndex = 0;
|
||||
|
||||
#endregion
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region 公告
|
||||
|
||||
public static ModLoader.LoaderCombo<int> lobbyAnnouncementLoader;
|
||||
private readonly ObservableCollection<LinkAnnounceInfo> _linkAnnounces = new();
|
||||
|
||||
private CancellationTokenSource _linkAnnounceUpdateCancelSource;
|
||||
|
||||
// 公告轮播实现
|
||||
private async Task _LinkAnnounceUpdateAsync()
|
||||
{
|
||||
var currentIndex = 0;
|
||||
var globalCancelToken = _linkAnnounceUpdateCancelSource.Token;
|
||||
CancellationTokenSource waiterCts = null;
|
||||
|
||||
_linkAnnounces.CollectionChanged += (sender, e) =>
|
||||
{
|
||||
if (waiterCts is not null) waiterCts.Cancel();
|
||||
};
|
||||
|
||||
while (!globalCancelToken.IsCancellationRequested)
|
||||
{
|
||||
waiterCts = CancellationTokenSource.CreateLinkedTokenSource(globalCancelToken);
|
||||
var waiterCancelToken = waiterCts.Token;
|
||||
|
||||
if (_linkAnnounces.Count > 0)
|
||||
{
|
||||
var info = _linkAnnounces[currentIndex];
|
||||
string prefix;
|
||||
if (info.Type == LinkAnnounceType.Important)
|
||||
{
|
||||
HintAnnounce.Theme = MyHint.Themes.Red;
|
||||
prefix = Lang.Text("Tools.GameLink.Announcement.Important");
|
||||
}
|
||||
else if (info.Type == LinkAnnounceType.Warning)
|
||||
{
|
||||
HintAnnounce.Theme = MyHint.Themes.Yellow;
|
||||
prefix = Lang.Text("Tools.GameLink.Announcement.Warning");
|
||||
}
|
||||
else
|
||||
{
|
||||
HintAnnounce.Theme = MyHint.Themes.Blue;
|
||||
prefix = Lang.Text("Tools.GameLink.Announcement.Notice");
|
||||
}
|
||||
|
||||
HintAnnounce.Text = Lang.Text("Tools.GameLink.Announcement.Format", prefix,
|
||||
info.Content.Replace("\n", "\r\n"));
|
||||
}
|
||||
else
|
||||
{
|
||||
HintAnnounce.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(10000, waiterCancelToken);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// 忽略取消任务的异常
|
||||
}
|
||||
|
||||
if (!waiterCancelToken.IsCancellationRequested)
|
||||
currentIndex += 1;
|
||||
if (currentIndex >= _linkAnnounces.Count)
|
||||
currentIndex = 0;
|
||||
waiterCts = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取公告信息
|
||||
private void GetAnnouncement()
|
||||
{
|
||||
ModBase.RunInNewThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var serverNumber = 0;
|
||||
JsonObject jObj = null;
|
||||
|
||||
#region 多服务器轮询获取公告
|
||||
|
||||
while (serverNumber < Secrets.LinkServers.Length)
|
||||
try
|
||||
{
|
||||
// 获取缓存版本号
|
||||
var cacheRes = Requester.Fetch($"{Secrets.LinkServers[serverNumber]}/api/link/v2/cache.ini",
|
||||
new FetchParam
|
||||
{
|
||||
Method = "GET",
|
||||
ContentType = "application/json",
|
||||
Timeout = 7000
|
||||
}).Trim();
|
||||
var cacheVer = int.Parse(cacheRes);
|
||||
|
||||
if (cacheVer == States.Link.AnnounceCacheVer)
|
||||
{
|
||||
LogWrapper.Info("[Link] Using cached announcement data");
|
||||
jObj = (JsonObject)ModBase.GetJson(States.Link.AnnounceCache);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogWrapper.Info("[Link] Fetching new announcement data");
|
||||
var received = Requester.Fetch(
|
||||
$"{Secrets.LinkServers[serverNumber]}/api/link/v2/announce.json",
|
||||
new FetchParam
|
||||
{
|
||||
Method = "GET",
|
||||
ContentType = "application/json",
|
||||
Timeout = 7000
|
||||
});
|
||||
jObj = (JsonObject)ModBase.GetJson(received);
|
||||
|
||||
// 更新缓存
|
||||
States.Link.AnnounceCache = received;
|
||||
States.Link.AnnounceCacheVer = cacheVer;
|
||||
}
|
||||
|
||||
break; // 成功获取,跳出轮询
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogWrapper.Error(ex, $"[Link] Failed to get announcement from server {serverNumber}");
|
||||
States.Link.AnnounceCacheConfig.Reset();
|
||||
States.Link.AnnounceCacheVerConfig.Reset();
|
||||
serverNumber++;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
if (jObj is null) throw new Exception("Failed to fetch lobby data");
|
||||
|
||||
#region 解析基础状态与版本限制
|
||||
|
||||
LobbyInfoProvider.IsLobbyAvailable = (bool)jObj["available"];
|
||||
LobbyInfoProvider.AllowCustomName = (bool)jObj["allowCustomName"];
|
||||
LobbyInfoProvider.RequiresLogin = (bool)jObj["requireLogin"];
|
||||
LobbyInfoProvider.RequiresRealName = (bool)jObj["requireRealname"];
|
||||
|
||||
if (jObj["version"].ToObject<double>() > LobbyInfoProvider.ProtocolVersion)
|
||||
{
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
HintAnnounce.Theme = MyHint.Themes.Red;
|
||||
HintAnnounce.Text = Lang.Text("Tools.GameLink.Error.UpdateRequired");
|
||||
LobbyInfoProvider.IsLobbyAvailable = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 解析公告列表 (Notices)
|
||||
|
||||
var notices = (JsonArray)jObj["notices"];
|
||||
foreach (JsonObject notice in notices)
|
||||
{
|
||||
var content = notice["content"]?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(content)) continue;
|
||||
|
||||
// 版本过滤
|
||||
var minVer = notice["minVer"].ToObject<double>();
|
||||
var maxVer = notice["maxVer"].ToObject<double>();
|
||||
if (ModBase.versionCode < minVer || ModBase.versionCode > maxVer) continue;
|
||||
|
||||
// 类型映射
|
||||
var type = LinkAnnounceType.Notice;
|
||||
var typeStr = notice["type"]?.ToString().ToLower();
|
||||
if (typeStr == "important" || typeStr == "red") type = LinkAnnounceType.Important;
|
||||
else if (typeStr == "warning" || typeStr == "yellow") type = LinkAnnounceType.Warning;
|
||||
|
||||
// 按行拆分公告
|
||||
foreach (var announce in content.Split('\n'))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(announce)) continue;
|
||||
_linkAnnounces.Add(new LinkAnnounceInfo(type, announce));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 解析中继服务器 (Relays)
|
||||
|
||||
var relays = (JsonArray)jObj["relays"];
|
||||
ETRelay.RelayList = new List<ETRelay>();
|
||||
foreach (var relay in relays)
|
||||
ETRelay.RelayList.Add(new ETRelay
|
||||
{
|
||||
Name = relay["name"]?.ToString(),
|
||||
Url = relay["url"]?.ToString(),
|
||||
Type = relay["type"]?.ToString() == "official" ? ETRelayType.Selfhosted : ETRelayType.Community
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
#region 处理账户登录状态显示
|
||||
|
||||
if (string.IsNullOrWhiteSpace(States.Link.NaidRefreshToken))
|
||||
{
|
||||
ModBase.RunInUi(() => LabNatayarkUserName.Text = Lang.Text("Tools.GameLink.Natayark.Login"));
|
||||
}
|
||||
else
|
||||
{
|
||||
ModBase.RunInUi(() => LabNatayarkUserName.Text = Lang.Text("Tools.GameLink.Natayark.Loading"));
|
||||
if (string.IsNullOrEmpty(NatayarkProfileManager.NaidProfile.Username))
|
||||
ReloadNaidData();
|
||||
else
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
if (NatayarkProfileManager.NaidProfile.Status == 0)
|
||||
{
|
||||
LabNatayarkUserName.Text = NatayarkProfileManager.NaidProfile.Username;
|
||||
LabNatayarkUserName.Opacity = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
LabNatayarkUserName.Text = $"{NatayarkProfileManager.NaidProfile.Username} {Lang.Text("Tools.GameLink.Natayark.Abnormal")}";
|
||||
LabNatayarkUserName.Opacity = 0.6;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LobbyInfoProvider.IsLobbyAvailable = false;
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
HintAnnounce.Theme = MyHint.Themes.Red;
|
||||
HintAnnounce.Text = Lang.Text("Tools.GameLink.Error.ConnectFailed");
|
||||
});
|
||||
LogWrapper.Error(ex, "[Link] Failed to get lobby announcement");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 信息获取与展示
|
||||
|
||||
#region UI 元素
|
||||
|
||||
private object PlayerInfoItem(PlayerProfile info, MyListItem.ClickEventHandler onClick)
|
||||
{
|
||||
var details = info.Kind == PlayerKind.HOST
|
||||
? Lang.Text("Tools.GameLink.Player.Details", Lang.Text("Tools.GameLink.Player.Host"), info.Vendor)
|
||||
: info.Vendor;
|
||||
|
||||
var newItem = new MyListItem
|
||||
{
|
||||
Title = info.Name,
|
||||
Info = details,
|
||||
Type = MyListItem.CheckType.Clickable,
|
||||
Tag = info
|
||||
};
|
||||
newItem.Click += onClick;
|
||||
|
||||
return newItem;
|
||||
}
|
||||
|
||||
private void PlayerInfoClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var info = (PlayerProfile)((MyListItem)sender).Tag;
|
||||
ModMain.MyMsgBox(Lang.Text("Tools.GameLink.Player.InfoMessage", info.Name, info.Vendor), Lang.Text("Tools.GameLink.Player.InfoTitle", info.Name));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Natayark 账户相关功能
|
||||
|
||||
private void ReloadNaidData()
|
||||
{
|
||||
ModBase.RunInNewThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
#region 1. 登录令牌有效期检查
|
||||
|
||||
// 检查 Token 是否过期
|
||||
var expireTime = Convert.ToDateTime(States.Link.NaidRefreshExpireTime);
|
||||
if (expireTime.CompareTo(DateTime.Now) < 0)
|
||||
{
|
||||
States.Link.NaidRefreshToken = "";
|
||||
HintService.Hint(Lang.Text("Tools.GameLink.Natayark.TokenExpired"), HintType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 2. 异步获取数据并同步等待
|
||||
|
||||
// 调用异步方法并阻塞获取结果
|
||||
NatayarkProfileManager.GetNaidDataAsync(States.Link.NaidRefreshToken, true).GetAwaiter().GetResult();
|
||||
|
||||
// 等待用户名加载,设置 10 秒超时防止线程卡死
|
||||
var retryCount = 0;
|
||||
while (string.IsNullOrWhiteSpace(NatayarkProfileManager.NaidProfile.Username) && retryCount < 10)
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
retryCount++;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(NatayarkProfileManager.NaidProfile.Username))
|
||||
throw new Exception("Timeout waiting for username");
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3. UI 状态更新
|
||||
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
var profile = NatayarkProfileManager.NaidProfile;
|
||||
|
||||
// 状态 0 为正常
|
||||
if (profile.Status == 0)
|
||||
{
|
||||
LabNatayarkUserName.Text = profile.Username;
|
||||
LabNatayarkUserName.Opacity = 1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
LabNatayarkUserName.Text = $"{profile.Username} {Lang.Text("Tools.GameLink.Natayark.Abnormal")}";
|
||||
LabNatayarkUserName.Opacity = 0.6;
|
||||
}
|
||||
});
|
||||
|
||||
#endregion
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
#region 错误处理
|
||||
|
||||
ModBase.Log(ex, "Failed to refresh Natayark ID info, re-login required");
|
||||
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
LabNatayarkUserName.Text = Lang.Text("Tools.GameLink.Natayark.FetchFailed");
|
||||
LabNatayarkUserName.Opacity = 0.6;
|
||||
});
|
||||
|
||||
#endregion
|
||||
}
|
||||
}, "Natayark Profile Refresh");
|
||||
}
|
||||
|
||||
private void LabNatayarkUserName_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// If Not IsLobbyAvailable Then
|
||||
// Hint("大厅功能暂不可用,请稍后再试", HintType.Critical)
|
||||
// Exit Sub
|
||||
// End If
|
||||
|
||||
if (string.IsNullOrWhiteSpace(States.Link.NaidRefreshToken))
|
||||
{
|
||||
// 当前未登录,显示登录选项
|
||||
if (ModMain.MyMsgBox(Lang.Text("Tools.GameLink.Natayark.LoginPrompt"), Lang.Text("Tools.GameLink.Natayark.LoginTitle"), Lang.Text("Tools.GameLink.Natayark.Continue"), Lang.Text("Common.Action.Cancel")) == 1)
|
||||
{
|
||||
LabNatayarkUserName.Text = Lang.Text("Tools.GameLink.Natayark.BrowserContinue");
|
||||
LabNatayarkUserName.Opacity = 0.6d;
|
||||
BtnNatayarkUserName.IsEnabled = false;
|
||||
ModWebServer.StartNaidAuthorize(() =>
|
||||
{
|
||||
ModBase.RunInUi(() => BtnNatayarkUserName.IsEnabled = true);
|
||||
HintService.Hint(Lang.Text("Tools.GameLink.Natayark.LoginComplete"), HintType.Success);
|
||||
ReloadNaidData();
|
||||
});
|
||||
}
|
||||
}
|
||||
// 当前已登录,显示登出选项
|
||||
else if (ModMain.MyMsgBox(Lang.Text("Tools.GameLink.Natayark.LogoutConfirm"), Lang.Text("Tools.GameLink.Natayark.LogoutTitle"), Lang.Text("Common.Action.Confirm"), Lang.Text("Common.Action.Cancel")) == 1)
|
||||
{
|
||||
States.Link.NaidRefreshTokenConfig.Reset();
|
||||
States.Link.NaidRefreshToken = "";
|
||||
LabNatayarkUserName.Text = Lang.Text("Tools.GameLink.Natayark.Login");
|
||||
ModBase.Log("[Link] 已退出登录 Natayark Network");
|
||||
HintService.Hint(Lang.Text("Tools.GameLink.Natayark.LogoutComplete"), HintType.Success, false);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// 网络测试功能
|
||||
private async void BtnNetTest_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
BtnNatTest.IsEnabled = false;
|
||||
LabNatType.Text = Lang.Text("Tools.GameLink.Nat.Testing");
|
||||
var status = await CliNetTest.GetNetStatusAsync();
|
||||
ModBase.RunInUi(() => LabNatType.Text = Lang.Text("Tools.GameLink.Nat.Result",
|
||||
CliNetTest.GetNatTypeString(status.UdpNatType),
|
||||
CliNetTest.GetNatTypeString(status.TcpNatType)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"[Link] 获取网络测试结果失败",
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Tools.GameLink.Error.NetworkTestFailed"));
|
||||
BtnNatTest.IsEnabled = true;
|
||||
LabNatType.Text = Lang.Text("Tools.GameLink.Nat.Failed");
|
||||
}
|
||||
finally
|
||||
{
|
||||
BtnNatTest.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void PasteLobbyId(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
string lobbyId;
|
||||
try
|
||||
{
|
||||
lobbyId = Clipboard.GetText(TextDataFormat.Text);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "从剪贴板识别大厅编号出错");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(lobbyId))
|
||||
TextJoinLobbyId.Text = lobbyId;
|
||||
else
|
||||
HintService.Hint(Lang.Text("Tools.GameLink.Join.InvalidText"));
|
||||
}
|
||||
|
||||
private void ClearLobbyId(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
TextJoinLobbyId.Text = string.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PanSelect | 种类选择页面
|
||||
|
||||
// 刷新按钮
|
||||
private void BtnRefresh_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var lobby = LobbyService.DiscoverWorldAsync();
|
||||
}
|
||||
|
||||
private async void BtnInputPort_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
BtnInputPort.IsEnabled = false;
|
||||
if (!ModLink.LobbyPrecheck()) return;
|
||||
var input = ModMain.MyMsgBoxInput(Lang.Text("Tools.GameLink.Create.EnterPort"),
|
||||
validateRules: [new IntValidator(65535,1024)]);
|
||||
int port;
|
||||
if (int.TryParse(input, out port))
|
||||
using (var ping = McPingServiceFactory.CreateService("127.0.0.1", port, 5000))
|
||||
{
|
||||
var res = await ping.PingAsync();
|
||||
if (res is not null && res.Version.Protocol != 0)
|
||||
await CreateLobbyAsync(port);
|
||||
else
|
||||
HintService.Hint(Lang.Text("Tools.GameLink.Create.NotMcPort"), HintType.Error);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
BtnInputPort.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建大厅
|
||||
private async void BtnCreate_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (ComboWorldList.SelectedItem is null)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Tools.GameLink.Create.NoWorld"));
|
||||
return;
|
||||
}
|
||||
|
||||
BtnCreate.IsEnabled = false;
|
||||
|
||||
if (!ModLink.LobbyPrecheck())
|
||||
{
|
||||
BtnCreate.IsEnabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var port = (int)((MyComboBoxItem)ComboWorldList.SelectedItem).Tag;
|
||||
await CreateLobbyAsync(port);
|
||||
}
|
||||
|
||||
private async Task CreateLobbyAsync(int port)
|
||||
{
|
||||
ModBase.Log("[Link] 创建大厅,端口:" + port);
|
||||
|
||||
|
||||
var username = LobbyInfoProvider.GetUsername();
|
||||
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
BtnFinishPing.Visibility = Visibility.Collapsed;
|
||||
LabFinishPing.Text = "-ms";
|
||||
BtnConnectType.Visibility = Visibility.Collapsed;
|
||||
LabConnectType.Text = Lang.Text("Tools.GameLink.Finish.Connecting");
|
||||
CardPlayerList.Title = Lang.Text("Tools.GameLink.Member.ListLoading");
|
||||
StackPlayerList.Children.Clear();
|
||||
LabConnectUserName.Text = username;
|
||||
LabConnectUserType.Text = Lang.Text("Tools.GameLink.Finish.Host");
|
||||
LabFinishId.Text = LobbyService.CurrentLobbyCode;
|
||||
BtnFinishCopyIp.Visibility = Visibility.Collapsed;
|
||||
BtnCreate.IsEnabled = true;
|
||||
BtnFinishExit.Text = Lang.Text("Tools.GameLink.Finish.CloseLobby");
|
||||
CurrentSubpage = Subpages.PanFinish;
|
||||
});
|
||||
|
||||
var res = await LobbyService.CreateLobbyAsync(port, username).ConfigureAwait(true);
|
||||
|
||||
if (!res)
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
CardPlayerList.Title = Lang.Text("Tools.GameLink.Member.ListLoading");
|
||||
StackPlayerList.Children.Clear();
|
||||
CurrentSubpage = Subpages.PanSelect;
|
||||
});
|
||||
}
|
||||
|
||||
// 加入大厅
|
||||
private async void BtnJoin_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!ModLink.LobbyPrecheck())
|
||||
return;
|
||||
|
||||
ModBase.Log("Start to join lobby.");
|
||||
|
||||
var id = TextJoinLobbyId.Text;
|
||||
var username = LobbyInfoProvider.GetUsername();
|
||||
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
BtnFinishPing.Visibility = Visibility.Visible;
|
||||
LabFinishPing.Text = "-ms";
|
||||
BtnConnectType.Visibility = Visibility.Visible;
|
||||
LabConnectType.Text = Lang.Text("Tools.GameLink.Finish.Connecting");
|
||||
CardPlayerList.Title = Lang.Text("Tools.GameLink.Member.ListLoading");
|
||||
StackPlayerList.Children.Clear();
|
||||
LabConnectUserName.Text = username;
|
||||
LabConnectUserType.Text = Lang.Text("Tools.GameLink.Finish.Guest");
|
||||
LabFinishId.Text = id;
|
||||
BtnFinishCopyIp.Visibility = Visibility.Visible;
|
||||
CurrentSubpage = Subpages.PanFinish;
|
||||
});
|
||||
|
||||
var res = await LobbyService.JoinLobbyAsync(id, username).ConfigureAwait(true);
|
||||
|
||||
if (!res)
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
CardPlayerList.Title = Lang.Text("Tools.GameLink.Member.ListLoading");
|
||||
StackPlayerList.Children.Clear();
|
||||
CurrentSubpage = Subpages.PanSelect;
|
||||
});
|
||||
}
|
||||
|
||||
private void TextJoinLobbyId_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
BtnJoin_Click(sender, null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PanLoad | 加载中页面
|
||||
|
||||
// 承接状态切换的 UI 改变
|
||||
private void OnLoadStateChanged(ModLoader.LoaderBase loader, ModBase.LoadState newState, ModBase.LoadState oldState)
|
||||
{
|
||||
}
|
||||
|
||||
private static string _loadStep = "准备初始化";
|
||||
|
||||
private static void SetLoadDesc(string intro, string step)
|
||||
{
|
||||
ModBase.Log("连接步骤:" + intro);
|
||||
_loadStep = step;
|
||||
ModBase.RunInUiWait(() =>
|
||||
{
|
||||
if (ModMain.frmToolsGameLink is null || !ModMain.frmToolsGameLink.LabLoadDesc.IsLoaded)
|
||||
return;
|
||||
ModMain.frmToolsGameLink.LabLoadDesc.Text = intro;
|
||||
ModMain.frmToolsGameLink.UpdateProgress();
|
||||
});
|
||||
}
|
||||
|
||||
// 承接重试
|
||||
private void CardLoad_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (initLoader.State != ModBase.LoadState.Failed)
|
||||
return;
|
||||
initLoader.Start(isForceRestart: true);
|
||||
}
|
||||
|
||||
// 取消加载
|
||||
private void CancelLoad(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (initLoader.State == ModBase.LoadState.Loading)
|
||||
{
|
||||
CurrentSubpage = Subpages.PanSelect;
|
||||
initLoader.Abort();
|
||||
}
|
||||
else
|
||||
{
|
||||
initLoader.State = ModBase.LoadState.Waiting;
|
||||
}
|
||||
}
|
||||
|
||||
// 进度改变
|
||||
private void UpdateProgress(double value = -1)
|
||||
{
|
||||
if (value == -1)
|
||||
value = initLoader.Progress;
|
||||
var displayingProgress = ColumnProgressA.Width.Value;
|
||||
if (Math.Round(value - displayingProgress, 3) == 0d)
|
||||
return;
|
||||
if (displayingProgress > value)
|
||||
{
|
||||
ColumnProgressA.Width = new GridLength(value, GridUnitType.Star);
|
||||
ColumnProgressB.Width = new GridLength(1d - value, GridUnitType.Star);
|
||||
ModAnimation.AniStop("LobbyController Progress");
|
||||
}
|
||||
else
|
||||
{
|
||||
var newProgress = value == 1d ? 1d : (value - displayingProgress) * 0.2d + displayingProgress;
|
||||
ModAnimation.AniStart(
|
||||
new[]
|
||||
{
|
||||
ModAnimation.AaGridLengthWidth(ColumnProgressA, newProgress - ColumnProgressA.Width.Value, 300,
|
||||
ease: new ModAnimation.AniEaseOutFluent()),
|
||||
ModAnimation.AaGridLengthWidth(ColumnProgressB, 1d - newProgress - ColumnProgressB.Width.Value, 300,
|
||||
ease: new ModAnimation.AniEaseOutFluent())
|
||||
}, "LobbyController Progress");
|
||||
}
|
||||
}
|
||||
|
||||
private void CardResized(object sender, SizeChangedEventArgs sizeChangedEventArgs)
|
||||
{
|
||||
RectProgressClip.Rect = new Rect(0d, 0d, CardLoad.ActualWidth, 12d);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PanFinish | 加载完成页面
|
||||
|
||||
// 退出
|
||||
private async void BtnFinishExit_Click(object sender, ModBase.RouteEventArgs routeEventArgs)
|
||||
{
|
||||
if (ModMain.MyMsgBox(
|
||||
Lang.Text(LobbyService.IsHost
|
||||
? "Tools.GameLink.Exit.ConfirmMessageWithHost"
|
||||
: "Tools.GameLink.Exit.ConfirmMessage"),
|
||||
Lang.Text("Tools.GameLink.Exit.ConfirmTitle"),
|
||||
Lang.Text("Common.Action.Confirm"),
|
||||
Lang.Text("Common.Action.Cancel"),
|
||||
isWarn: true
|
||||
) == 1)
|
||||
{
|
||||
CurrentSubpage = Subpages.PanSelect;
|
||||
BtnFinishExit.Text = Lang.Text("Tools.GameLink.Finish.Exit");
|
||||
await LobbyService.LeaveLobbyAsync().ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
|
||||
// 复制大厅编号
|
||||
private void BtnFinishCopy_Click(object sender, ModBase.RouteEventArgs routeEventArgs)
|
||||
{
|
||||
ModBase.ClipboardSet(LabFinishId.Text);
|
||||
}
|
||||
|
||||
// 复制 IP
|
||||
private void BtnFinishCopyIp_Click(object sender, ModBase.RouteEventArgs routeEventArgs)
|
||||
{
|
||||
var ip = $"127.0.0.1:{LobbyInfoProvider.McForward.LocalPort}";
|
||||
ModMain.MyMsgBox(Lang.Text("Tools.GameLink.CopyIp.Message", ip),
|
||||
Lang.Text("Tools.GameLink.CopyIp.Title"),
|
||||
Lang.Text("Common.Action.Copy"),
|
||||
Lang.Text("Tools.GameLink.CopyIp.Back"),
|
||||
button1Action: () => ModBase.ClipboardSet(ip));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 子页面管理
|
||||
|
||||
public enum Subpages
|
||||
{
|
||||
PanEula,
|
||||
PanSelect,
|
||||
PanFinish
|
||||
}
|
||||
|
||||
public Subpages CurrentSubpage
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
if (field == value)
|
||||
return;
|
||||
field = value;
|
||||
ModBase.Log("[Link] 子页面更改为 " + ModBase.GetStringFromEnum(value));
|
||||
PageOnContentExit();
|
||||
}
|
||||
} = States.Link.LinkEula ? Subpages.PanSelect : Subpages.PanEula;
|
||||
|
||||
private void PageLinkLobby_OnPageEnter()
|
||||
{
|
||||
ModMain.frmToolsGameLink.PanEula.Visibility =
|
||||
CurrentSubpage == Subpages.PanEula ? Visibility.Visible : Visibility.Collapsed;
|
||||
ModMain.frmToolsGameLink.PanSelect.Visibility =
|
||||
CurrentSubpage == Subpages.PanSelect ? Visibility.Visible : Visibility.Collapsed;
|
||||
ModMain.frmToolsGameLink.PanFinish.Visibility =
|
||||
CurrentSubpage == Subpages.PanFinish ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<local:MyPageLeft x:Class="PCL.PageToolsLeft"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:PCL"
|
||||
mc:Ignorable="d">
|
||||
<StackPanel Orientation="Vertical" x:Name="PanItem" Margin="0,12,0,0">
|
||||
|
||||
<TextBlock Text="{DynamicResource Tools.Left.Multiplayer}"
|
||||
Margin="13,5,5,3"
|
||||
Opacity="0.6" FontSize="12"
|
||||
x:Name="TextGameLinkCategory" />
|
||||
|
||||
<local:MyListItem x:Name="ItemGameLink"
|
||||
IsScaleAnimationEnabled="False"
|
||||
Type="RadioBox" Tag="1"
|
||||
Check="PageCheck"
|
||||
MinPaddingRight="35" Height="36"
|
||||
VerticalAlignment="Top"
|
||||
Title="{DynamicResource Tools.Left.Lobby}"
|
||||
LogoScale="0.85"
|
||||
SvgIcon="lucide/link-2">
|
||||
<local:MyListItem.Buttons>
|
||||
<x:Array Type="{x:Type local:MyIconButton}">
|
||||
<local:MyIconButton Tag="1"
|
||||
ToolTip="{DynamicResource Common.Action.Refresh}"
|
||||
ToolTipService.Placement="Right"
|
||||
ToolTipService.InitialShowDelay="200"
|
||||
ToolTipService.VerticalOffset="-1"
|
||||
LogoScale="0.85"
|
||||
Click="Refresh"
|
||||
SvgIcon="lucide/refresh-cw" />
|
||||
</x:Array>
|
||||
</local:MyListItem.Buttons>
|
||||
</local:MyListItem>
|
||||
|
||||
<TextBlock Text="{DynamicResource Tools.Left.Utilities}" Margin="13,5,5,3" Opacity="0.6" FontSize="12"
|
||||
x:Name="TextToolsCategory" />
|
||||
<local:MyListItem x:Name="ItemTest"
|
||||
IsScaleAnimationEnabled="False"
|
||||
Type="RadioBox" Tag="3"
|
||||
MinPaddingRight="35" Height="36"
|
||||
VerticalAlignment="Top"
|
||||
Title="{DynamicResource Tools.Left.Utilities.Title}" Check="PageCheck"
|
||||
LogoScale="0.9"
|
||||
SvgIcon="lucide/tool-case" />
|
||||
|
||||
</StackPanel>
|
||||
</local:MyPageLeft>
|
||||
@@ -0,0 +1,168 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using PCL.Core.App;
|
||||
|
||||
using PCL.Core.App.Localization;
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageToolsLeft
|
||||
{
|
||||
private bool isLoad;
|
||||
private bool isPageSwitched; // 如果在 Loaded 前切换到其他页面,会导致触发 Loaded 时再次切换一次
|
||||
|
||||
public PageToolsLeft()
|
||||
{
|
||||
InitializeComponent();
|
||||
AnimatedControl = PanItem;
|
||||
Loaded += PageLinkLeft_Loaded;
|
||||
Unloaded += PageOtherLeft_Unloaded;
|
||||
}
|
||||
|
||||
private void PageLinkLeft_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var isHiddenPage = false;
|
||||
var hide = Config.Preference.Hide;
|
||||
|
||||
if (ItemGameLink.Checked && hide.ToolsGameLink) isHiddenPage = true;
|
||||
if (ItemTest.Checked && hide.ToolsTest) isHiddenPage = true;
|
||||
if (PageSetupUI.HiddenForceShow)
|
||||
isHiddenPage = false;
|
||||
// 若页面错误,或尚未加载,则继续
|
||||
if (isLoad && !isHiddenPage)
|
||||
return;
|
||||
isLoad = true;
|
||||
// 刷新子页面隐藏情况
|
||||
PageSetupUI.HiddenRefresh();
|
||||
// 选择第一个未被禁用的子页面
|
||||
if (isPageSwitched)
|
||||
return;
|
||||
var hideCfg = Config.Preference.Hide;
|
||||
if (!hideCfg.ToolsGameLink)
|
||||
ItemGameLink.SetChecked(true, false, false);
|
||||
else if (!hideCfg.ToolsTest)
|
||||
ItemTest.SetChecked(true, false, false);
|
||||
else
|
||||
ItemGameLink.SetChecked(true, false, false);
|
||||
}
|
||||
|
||||
private void PageOtherLeft_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
isPageSwitched = false;
|
||||
}
|
||||
|
||||
public void Refresh(object sender, EventArgs e)
|
||||
{
|
||||
var button = (MyIconButton)sender;
|
||||
if (button.Tag is null)
|
||||
return;
|
||||
double id = ModBase.Val(button.Tag);
|
||||
switch (id)
|
||||
{
|
||||
case (double)FormMain.PageSubType.ToolsGameLink:
|
||||
{
|
||||
if (ModMain.frmToolsGameLink is null)
|
||||
ModMain.frmToolsGameLink = new PageToolsGameLink();
|
||||
ModMain.frmToolsGameLink.Reload();
|
||||
ItemGameLink.Checked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region 页面切换
|
||||
|
||||
/// <summary>
|
||||
/// 当前页面的编号。
|
||||
/// </summary>
|
||||
public FormMain.PageSubType pageID = FormMain.PageSubType.ToolsGameLink;
|
||||
|
||||
/// <summary>
|
||||
/// 勾选事件改变页面。
|
||||
/// </summary>
|
||||
private void PageCheck(object senderRaw, ModBase.RouteEventArgs e)
|
||||
{
|
||||
var sender = (MyListItem)senderRaw;
|
||||
// 尚未初始化控件属性时,sender.Tag 为 Nothing,会导致切换到页面 0
|
||||
// 若使用 IsLoaded,则会导致模拟点击不被执行(模拟点击切换页面时,控件的 IsLoaded 为 False)
|
||||
if (sender.Tag is not null)
|
||||
PageChange((FormMain.PageSubType)ModBase.Val(sender.Tag));
|
||||
}
|
||||
|
||||
public object PageGet(FormMain.PageSubType? id = null)
|
||||
{
|
||||
var targetID = id ?? pageID;
|
||||
switch (id)
|
||||
{
|
||||
case FormMain.PageSubType.ToolsGameLink:
|
||||
{
|
||||
if (ModMain.frmToolsGameLink is null)
|
||||
ModMain.frmToolsGameLink = new PageToolsGameLink();
|
||||
return ModMain.frmToolsGameLink;
|
||||
}
|
||||
case FormMain.PageSubType.ToolsTest:
|
||||
{
|
||||
if (ModMain.frmToolsTest is null)
|
||||
ModMain.frmToolsTest = new PageToolsTest();
|
||||
return ModMain.frmToolsTest;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new Exception("未知的更多子页面种类:" + (int)id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换现有页面。
|
||||
/// </summary>
|
||||
public void PageChange(FormMain.PageSubType id)
|
||||
{
|
||||
if (pageID == id)
|
||||
return;
|
||||
ModAnimation.AniControlEnabled += 1;
|
||||
isPageSwitched = true;
|
||||
try
|
||||
{
|
||||
PageChangeRun((MyPageRight)PageGet(id));
|
||||
pageID = id;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"切换分页面失败(ID {(int)id})",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Tools.Error.OperationFailed"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
ModAnimation.AniControlEnabled -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void PageChangeRun(MyPageRight target)
|
||||
{
|
||||
ModAnimation.AniStop("FrmMain PageChangeRight"); // 停止主页面的右页面切换动画,防止它与本动画一起触发多次 PageOnEnter
|
||||
if (target.Parent is not null)
|
||||
target.SetValue(ContentPresenter.ContentProperty, null);
|
||||
ModMain.frmMain.pageRight = target;
|
||||
((MyPageRight)ModMain.frmMain.PanMainRight.Child).PageOnExit();
|
||||
ModAnimation.AniStart(new[]
|
||||
{
|
||||
ModAnimation.AaCode(() =>
|
||||
{
|
||||
((MyPageRight)ModMain.frmMain.PanMainRight.Child).PageOnForceExit();
|
||||
ModMain.frmMain.PanMainRight.Child = ModMain.frmMain.pageRight;
|
||||
ModMain.frmMain.pageRight.Opacity = 0d;
|
||||
}, 130),
|
||||
ModAnimation.AaCode(() =>
|
||||
{
|
||||
// 延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
|
||||
ModMain.frmMain.pageRight.Opacity = 1d;
|
||||
ModMain.frmMain.pageRight.PageOnEnter();
|
||||
}, 30, true)
|
||||
}, "PageLeft PageChange");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
<local:MyPageRight x:Class="PCL.PageToolsTest"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL"
|
||||
xmlns:val="https://ce.pclc.cc/core/utils/validate"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
PanScroll="{Binding ElementName=PanBack}">
|
||||
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
|
||||
<StackPanel x:Name="PanMain" Margin="25,10" Grid.IsSharedSizeScope="True">
|
||||
<local:MyCard Margin="0,15" Title="{DynamicResource Tools.Test.Title}">
|
||||
<WrapPanel Margin="25,40,21,5" ItemHeight="50">
|
||||
<local:MyButton x:Name="BtnClear" MinWidth="120" Text="{DynamicResource Tools.Test.Clean.Title}"
|
||||
ToolTip="{DynamicResource Tools.Test.Clean.ToolTip}" Margin="0,0,15,15"
|
||||
Click="BtnClear_Click" />
|
||||
<local:MyButton MinWidth="100" Text="{DynamicResource Tools.Test.Luck.Title}" Margin="0,0,15,15"
|
||||
Click="BtnLuck_Click" />
|
||||
<local:MyButton MinWidth="100" Text="{DynamicResource Tools.Test.Crash.Title}"
|
||||
ToolTip="{DynamicResource Tools.Test.Crash.ManualCrash}" ColorType="Red"
|
||||
Margin="0,0,15,15" Click="BtnCrash_Click"
|
||||
Visibility="Collapsed" x:Name="BtnCrash" />
|
||||
<local:MyButton MinWidth="120" Text="{DynamicResource Tools.Test.Shortcut.Title}"
|
||||
ToolTip="{DynamicResource Tools.Test.Shortcut.ToolTip}" Margin="0,0,15,15"
|
||||
Click="BtnCreateShortcut_Click" />
|
||||
<local:MyButton MinWidth="120" Text="{DynamicResource Tools.Test.LaunchCount.Title}"
|
||||
Margin="0,0,15,15" Click="BtnLaunchCount_Click" />
|
||||
</WrapPanel>
|
||||
</local:MyCard>
|
||||
<local:MyCard Margin="0,0,0,15" Title="{DynamicResource Tools.Test.CustomDownload.Title}">
|
||||
<StackPanel Orientation="Vertical" Margin="25,40,25,20">
|
||||
<TextBlock Text="{DynamicResource Tools.Test.CustomDownload.Description}"
|
||||
TextWrapping="Wrap" />
|
||||
<Grid Margin="0,10,0,0">
|
||||
<Grid.Resources>
|
||||
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource BasedOnTextBlock}">
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="Margin" Value="0,0,25,0" />
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.Row="0" Text="{DynamicResource Tools.Test.CustomDownload.Url}" />
|
||||
<local:MyTextBox x:Name="TextDownloadUrl" Grid.Column="1" Grid.Row="0" Grid.ColumnSpan="2"
|
||||
TextChanged="TextDownloadUrl_TextChanged"
|
||||
ValidatedTextChanged="TextDownloadUrl_ValidateChanged">
|
||||
<local:MyTextBox.ValidateRules>
|
||||
<val:HttpAndUncValidator />
|
||||
</local:MyTextBox.ValidateRules>
|
||||
</local:MyTextBox>
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.Row="2"
|
||||
Text="{DynamicResource Tools.Test.CustomDownload.SaveTo}" />
|
||||
<local:MyTextBox x:Name="TextDownloadFolder" Grid.Column="1" Grid.Row="2" Margin="0,10"
|
||||
ValidatedTextChanged="TextDownloadFolder_OnValidatedTextChanged">
|
||||
<local:MyTextBox.ValidateRules>
|
||||
<val:FolderPathValidator />
|
||||
</local:MyTextBox.ValidateRules>
|
||||
</local:MyTextBox>
|
||||
<local:MyTextButton Grid.Column="2" Grid.Row="2" Margin="10,0,5,0" VerticalAlignment="Center"
|
||||
Text="{DynamicResource Tools.Test.CustomDownload.Select}"
|
||||
Click="MyTextButton_Click" />
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.Row="3"
|
||||
Text="{DynamicResource Tools.Test.CustomDownload.FileName}" />
|
||||
<local:MyTextBox x:Name="TextDownloadName" Grid.Column="1" Grid.Row="3" Grid.ColumnSpan="2">
|
||||
<local:MyTextBox.ValidateRules>
|
||||
<val:FileNameValidator />
|
||||
</local:MyTextBox.ValidateRules>
|
||||
</local:MyTextBox>
|
||||
</Grid>
|
||||
<Grid Margin="0,15,0,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="35" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<local:MyButton x:Name="BtnDownloadStart" Grid.Column="0" Width="140"
|
||||
Text="{DynamicResource Tools.Test.CustomDownload.Start}"
|
||||
ColorType="Highlight" Margin="0,0,15,0" HorizontalAlignment="Right"
|
||||
Click="BtnDownloadStart_Click" />
|
||||
<local:MyButton x:Name="BtnDownloadOpen" Grid.Column="1" Width="140"
|
||||
Text="{DynamicResource Common.Action.OpenFolder}"
|
||||
Margin="0,0,15,0" HorizontalAlignment="Left" Click="BtnDownloadOpen_Click" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<local:MyCard x:Name="CardSkin" Margin="0,0,0,15" Title="{DynamicResource Tools.Test.Skin.Title}">
|
||||
<StackPanel Margin="25,40,25,15">
|
||||
<Grid x:Name="PanSkinID" Margin="0,14,0,2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Name" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Tools.Test.Skin.PlayerName}" Margin="0,0,25,0" />
|
||||
<local:MyTextBox x:Name="TextSkinID" Grid.Column="1" MaxLength="50" />
|
||||
<Grid Height="35" Grid.Row="1" Grid.ColumnSpan="2" Margin="0,14,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<local:MyButton x:Name="BtnSkinSave" Text="{DynamicResource Tools.Test.Skin.Save}"
|
||||
MinWidth="140" Padding="13,0"
|
||||
Margin="0,0,20,0" Grid.Row="1" Click="BtnSkinSave_Click" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<local:MyCard Margin="0,0,0,15" Title="{DynamicResource Tools.Test.ServerQuery.Title}">
|
||||
<local:MinecraftServerQuery Margin="15,40,15,15" />
|
||||
</local:MyCard>
|
||||
<local:MyCard Margin="0,0,0,15" Title="{DynamicResource Tools.Test.Achievement.Title}" CanSwap="True"
|
||||
IsSwapped="True">
|
||||
<StackPanel Margin="25,30,25,15">
|
||||
<Grid Margin="0,4,0,2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Column="0" Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Tools.Test.Achievement.ItemId}" Margin="0,0,25,0" />
|
||||
<local:MyTextBox Grid.Column="1" Grid.Row="0" Grid.ColumnSpan="2"
|
||||
x:Name="AchievementBlockTextBox" MaxLength="50" Margin="0,10">
|
||||
<local:MyTextBox.ValidateRules>
|
||||
<val:RegexValidator Pattern="^[a-z0-9_]+$"
|
||||
ErrorKey="Tools.Test.Achievement.InvalidId" />
|
||||
</local:MyTextBox.ValidateRules>
|
||||
</local:MyTextBox>
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Tools.Test.Achievement.Name}" Margin="0,0,25,0" />
|
||||
<local:MyTextBox Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="2"
|
||||
x:Name="AchievementTitleTextBox" MaxLength="50">
|
||||
<local:MyTextBox.ValidateRules>
|
||||
<val:NullOrEmptyValidator />
|
||||
</local:MyTextBox.ValidateRules>
|
||||
</local:MyTextBox>
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Tools.Test.Achievement.Line1}" Margin="0,0,25,0" />
|
||||
<local:MyTextBox Grid.Column="1" Grid.Row="2" Grid.ColumnSpan="2"
|
||||
x:Name="AchievementString1TextBox" MaxLength="50" Margin="0,10">
|
||||
<local:MyTextBox.ValidateRules>
|
||||
<val:NullOrEmptyValidator />
|
||||
</local:MyTextBox.ValidateRules>
|
||||
</local:MyTextBox>
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.Row="3" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
Text="{DynamicResource Tools.Test.Achievement.Line2}" Margin="0,0,25,0" />
|
||||
<local:MyTextBox Grid.Column="1" Grid.Row="3" Grid.ColumnSpan="2"
|
||||
x:Name="AchievementString2TextBox" MaxLength="50" />
|
||||
</Grid>
|
||||
<Grid Margin="0,15,0,10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="35" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<local:MyButton x:Name="BtnAchievementPreview" Grid.Column="0" Width="140"
|
||||
Text="{DynamicResource Tools.Test.Achievement.Preview}"
|
||||
ColorType="Highlight" Margin="0,0,15,0" HorizontalAlignment="Right"
|
||||
Click="BtnAchievementPreview_Click" />
|
||||
<local:MyButton x:Name="BtnAchievementSave" Grid.Column="1" Width="140"
|
||||
Text="{DynamicResource Tools.Test.Achievement.Save}"
|
||||
Margin="0,0,15,0" HorizontalAlignment="Left" Click="BtnAchievementSave_Click" />
|
||||
</Grid>
|
||||
<Image x:Name="AchievementImage" Width="320" Height="64" Stretch="Uniform" Visibility="Collapsed" />
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
<local:MyCard Margin="0,0,0,15" Title="{DynamicResource Tools.Test.Avatar.Title}" CanSwap="True"
|
||||
IsSwapped="True">
|
||||
<StackPanel Orientation="Vertical" Margin="25,40,25,20">
|
||||
<Grid Margin="0,0,0,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="{DynamicResource Tools.Test.Avatar.Size}" VerticalAlignment="Center"
|
||||
Margin="0,0,10,0" />
|
||||
<local:MyComboBox x:Name="CmbHeadSize" Grid.Column="1" Height="26" SelectedIndex="0">
|
||||
<sys:String>64x64</sys:String>
|
||||
<sys:String>96x96</sys:String>
|
||||
<sys:String>128x128</sys:String>
|
||||
</local:MyComboBox>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<local:MyButton x:Name="BtnSelectSkin" Grid.Column="1"
|
||||
Text="{DynamicResource Tools.Test.Avatar.SelectSkin}" Width="140" Height="35"
|
||||
Margin="0,0,10,0" />
|
||||
<local:MyButton Grid.Column="2" Text="{DynamicResource Tools.Test.Avatar.Save}" Width="140"
|
||||
Height="35" Click="BtnSaveHead_Click" />
|
||||
</Grid>
|
||||
<Border x:Name="SkinPreviewBorder" Margin="0,15,0,0" HorizontalAlignment="Center" Padding="10"
|
||||
Visibility="Collapsed">
|
||||
<Grid x:Name="SkinPreviewContainer" Width="80" Height="80">
|
||||
<Image x:Name="ImgFace" Stretch="Uniform" RenderOptions.BitmapScalingMode="NearestNeighbor" />
|
||||
<Image x:Name="ImgHair" Stretch="Uniform" RenderOptions.BitmapScalingMode="NearestNeighbor" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</local:MyCard>
|
||||
</StackPanel>
|
||||
</local:MyScrollViewer>
|
||||
</local:MyPageRight>
|
||||
@@ -0,0 +1,761 @@
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media.Imaging;
|
||||
using PCL.Core.App;
|
||||
using PCL.Core.App.Tools;
|
||||
using PCL.Core.IO;
|
||||
using PCL.Core.IO.Net;
|
||||
using PCL.Core.UI;
|
||||
using PCL.Core.Utils.OS;
|
||||
using PCL.Core.Utils.Secret;
|
||||
using PCL.Core.Utils.Validate;
|
||||
using PCL.Network;
|
||||
using PCL.Network.Loaders;
|
||||
using PCL.Core.App.Localization;
|
||||
using System.Globalization;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageToolsTest
|
||||
{
|
||||
private Bitmap currentSkinBitmap;
|
||||
private Bitmap generatedHeadBitmap;
|
||||
|
||||
private int headSize = 64;
|
||||
private string skinPath = "";
|
||||
|
||||
public PageToolsTest()
|
||||
{
|
||||
InitializeComponent();
|
||||
BtnSelectSkin.Click += BtnSelectSkin_Click;
|
||||
CmbHeadSize.SelectionChanged += CmbHeadSize_SelectionChanged;
|
||||
Loaded += (_, _) => MeLoaded();
|
||||
#if DEBUG
|
||||
BtnCrash.Visibility = Visibility.Visible;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void MeLoaded()
|
||||
{
|
||||
BtnDownloadStart.IsEnabled = false;
|
||||
|
||||
TextDownloadFolder.Text = States.Tool.DownloadFolder;
|
||||
TextDownloadFolder.Validate();
|
||||
|
||||
if (!string.IsNullOrEmpty(TextDownloadFolder.ValidateResult) || string.IsNullOrEmpty(TextDownloadFolder.Text))
|
||||
TextDownloadFolder.Text = ModBase.exePath + @"PCL\MyDownload\";
|
||||
|
||||
TextDownloadFolder.Validate();
|
||||
TextDownloadName.Validate();
|
||||
TextDownloadUrl.Validate();
|
||||
StartButtonRefresh();
|
||||
}
|
||||
|
||||
private void StartButtonRefresh()
|
||||
{
|
||||
BtnDownloadStart.IsEnabled = string.IsNullOrEmpty(TextDownloadFolder.ValidateResult) &&
|
||||
string.IsNullOrEmpty(TextDownloadUrl.ValidateResult) &&
|
||||
string.IsNullOrEmpty(TextDownloadName.ValidateResult);
|
||||
|
||||
BtnDownloadOpen.IsEnabled = string.IsNullOrEmpty(TextDownloadFolder.ValidateResult);
|
||||
|
||||
BtnAchievementPreview.IsEnabled = string.IsNullOrEmpty(AchievementBlockTextBox.ValidateResult) &&
|
||||
string.IsNullOrEmpty(AchievementTitleTextBox.ValidateResult) &&
|
||||
string.IsNullOrEmpty(AchievementString1TextBox.ValidateResult);
|
||||
|
||||
BtnAchievementSave.IsEnabled = string.IsNullOrEmpty(AchievementBlockTextBox.ValidateResult) &&
|
||||
string.IsNullOrEmpty(AchievementTitleTextBox.ValidateResult) &&
|
||||
string.IsNullOrEmpty(AchievementString1TextBox.ValidateResult);
|
||||
}
|
||||
|
||||
private void SaveCacheDownloadFolder(object sender, RoutedEventArgs e)
|
||||
{
|
||||
States.Tool.DownloadFolder = TextDownloadFolder.Text;
|
||||
TextDownloadName.Validate();
|
||||
}
|
||||
|
||||
private static void DownloadState(ModLoader.LoaderCombo<int> loader)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (loader.State)
|
||||
{
|
||||
case ModBase.LoadState.Finished:
|
||||
{
|
||||
HintService.Hint(Lang.Text("Tools.Test.CustomDownload.Finished", loader.name), HintType.Success);
|
||||
Console.Beep();
|
||||
break;
|
||||
}
|
||||
case ModBase.LoadState.Failed:
|
||||
{
|
||||
ModBase.Log(
|
||||
loader.Error,
|
||||
$"{loader.name}失败",
|
||||
ModBase.LogLevel.Msgbox,
|
||||
userSummary: Lang.Text("Tools.Test.Error.OperationFailed"));
|
||||
Console.Beep();
|
||||
break;
|
||||
}
|
||||
case ModBase.LoadState.Aborted:
|
||||
{
|
||||
HintService.Hint(Lang.Text("Tools.Test.CustomDownload.Aborted", loader.name));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static void StartCustomDownload(string url, string fileName, string folder = null, string userAgent = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(folder))
|
||||
{
|
||||
folder = SystemDialogs.SelectSaveFile(Lang.Text("Tools.Test.CustomDownload.SelectLocation"), fileName);
|
||||
if (!folder.Contains(@"\")) return;
|
||||
if (folder.EndsWith(fileName)) folder = folder[..^fileName.Length];
|
||||
}
|
||||
|
||||
folder = folder.Replace("/", @"\").TrimEnd(new[] { '\\' }) + @"\";
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(folder);
|
||||
ModBase.CheckPermissionWithException(folder);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
$"访问文件夹失败({folder})",
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Tools.Test.Error.OperationFailed"));
|
||||
return;
|
||||
}
|
||||
|
||||
ModBase.Log("[Download] 自定义下载文件名:" + fileName);
|
||||
ModBase.Log("[Download] 自定义下载文件目标:" + folder);
|
||||
var uuid = ModBase.GetUuid();
|
||||
ModLoader.LoaderBase loaderdownload;
|
||||
if (new HttpValidator().Validate(url).IsValid)
|
||||
loaderdownload = new LoaderDownload(Lang.Text("Tools.Test.CustomDownload.LoaderName", fileName),
|
||||
new List<DownloadFile> { new(new[] { url }, folder + fileName, null, true, userAgent) });
|
||||
else // UNC 路径
|
||||
loaderdownload = new LoaderDownloadUnc(Lang.Text("Tools.Test.CustomDownload.LoaderName", fileName),
|
||||
new Tuple<string, string>(url, folder + fileName));
|
||||
var loaderCombo = new ModLoader.LoaderCombo<int>(Lang.Text("Tools.Test.CustomDownload.LoaderTitle", uuid), new[] { loaderdownload })
|
||||
{ OnStateChanged = a => DownloadState((ModLoader.LoaderCombo<int>)a) };
|
||||
loaderCombo.Start();
|
||||
ModLoader.LoaderTaskbarAdd(loaderCombo);
|
||||
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
|
||||
ModMain.frmMain.BtnExtraDownload.Ribble();
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"开始自定义下载失败",
|
||||
ModBase.LogLevel.Feedback,
|
||||
userSummary: Lang.Text("Tools.Test.Error.OperationFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Jrrp()
|
||||
{
|
||||
var random = new Random(GenerateDailySeed());
|
||||
var luckValue = random.Next(0, 101);
|
||||
var rating = GetRating(luckValue);
|
||||
var title = Lang.Text("Tools.Test.Luck.MsgboxTitle", Lang.Date(DateTime.Now, "d"));
|
||||
|
||||
if (luckValue >= 60)
|
||||
ModMain.MyMsgBox(Lang.Text("Tools.Test.Luck.MessageGood", luckValue, rating), title);
|
||||
else
|
||||
ModMain.MyMsgBox(Lang.Text("Tools.Test.Luck.MessageBad", luckValue, rating), title, isWarn: luckValue <= 30);
|
||||
}
|
||||
|
||||
public static void RubbishClear()
|
||||
{
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
if (ModMain.frmToolsTest is not null && ModMain.frmToolsTest.BtnClear is not null)
|
||||
ModMain.frmToolsTest.BtnClear.IsEnabled = false;
|
||||
});
|
||||
// 只有当没有运行中的Minecraft游戏且启动器不在加载状态时才能清理
|
||||
|
||||
// 清理的文件数量
|
||||
// 所有 Minecraft 文件夹
|
||||
|
||||
|
||||
// 寻找所有 Minecraft 文件夹
|
||||
|
||||
// 删除 Minecraft 的缓存
|
||||
// 删除日志和崩溃报告并计数
|
||||
|
||||
// 删除 Natives 文件
|
||||
|
||||
// 删除 PCL 的缓存
|
||||
|
||||
ModBase.RunInNewThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!ModWatcher.hasRunningMinecraft && ModLaunch.mcLaunchLoader.State != ModBase.LoadState.Loading)
|
||||
{
|
||||
if (ModNet.HasDownloadingTask())
|
||||
{
|
||||
HintService.Hint(Lang.Text("Tools.Test.Clean.WaitForDownload"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ModFolder.mcFolderList.Any()) ModFolder.mcFolderListLoader.Start();
|
||||
if (States.Hint.CleanJunkFile <= 2)
|
||||
{
|
||||
if (ModMain.MyMsgBox(
|
||||
Lang.Text("Tools.Test.Clean.ConfirmMessage"),
|
||||
Lang.Text("Tools.Test.Clean.ConfirmTitle"),
|
||||
Lang.Text("Common.Action.Confirm"),
|
||||
Lang.Text("Common.Action.Cancel")
|
||||
) == 2) return;
|
||||
States.Hint.CleanJunkFile += 1;
|
||||
}
|
||||
|
||||
var num = 0;
|
||||
var cleanMcFolderList = new List<DirectoryInfo>();
|
||||
if (!ModFolder.mcFolderList.Any()) ModFolder.mcFolderListLoader.WaitForExit();
|
||||
foreach (var mcFolder in ModFolder.mcFolderList)
|
||||
{
|
||||
cleanMcFolderList.Add(new DirectoryInfo(mcFolder.Location));
|
||||
var dirInfo = new DirectoryInfo(mcFolder.Location + "versions");
|
||||
if (dirInfo.Exists)
|
||||
foreach (var item in dirInfo.EnumerateDirectories())
|
||||
cleanMcFolderList.Add(item);
|
||||
}
|
||||
|
||||
foreach (var dirInfo in cleanMcFolderList)
|
||||
{
|
||||
num += ModBase.DeleteDirectory(
|
||||
dirInfo.FullName + (dirInfo.FullName.EndsWith(@"\") ? "" : @"\") + @"crash-reports\", true);
|
||||
num += ModBase.DeleteDirectory(
|
||||
dirInfo.FullName + (dirInfo.FullName.EndsWith(@"\") ? "" : @"\") + @"logs\", true);
|
||||
foreach (var fileInfo in dirInfo.EnumerateFiles("*"))
|
||||
if (fileInfo.Name.StartsWith("hs_err_pid") || fileInfo.Name.EndsWith(".log") ||
|
||||
fileInfo.Name == "WailaErrorOutput.txt")
|
||||
{
|
||||
fileInfo.Delete();
|
||||
num += 1;
|
||||
}
|
||||
|
||||
foreach (var dirInfo2 in dirInfo.EnumerateDirectories())
|
||||
if ((dirInfo2.Name ?? "") == (dirInfo2.Name + "-natives" ?? "") ||
|
||||
dirInfo2.Name == "natives-windows-x86_64")
|
||||
num += ModBase.DeleteDirectory(dirInfo2.FullName, true);
|
||||
}
|
||||
|
||||
num += ModBase.DeleteDirectory(ModBase.pathTemp, true);
|
||||
num += ModBase.DeleteDirectory(Path.Combine(SystemPaths.DriveLetter, "ProgramData", "PCL"), true);
|
||||
if (num != 0)
|
||||
{
|
||||
ModMain.MyMsgBox(Lang.Text("Tools.Test.Clean.ClearedMessage", num),
|
||||
Lang.Text("Tools.Test.Clean.Cleared"), Lang.Text("Common.Action.Confirm"), "", "", false, true, true);
|
||||
Process.Start(new ProcessStartInfo(Basics.ExecutablePath));
|
||||
FormMain.EndProgramForce();
|
||||
}
|
||||
else
|
||||
{
|
||||
HintService.Hint(Lang.Text("Tools.Test.Clean.NoFiles"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
HintService.Hint(Lang.Text("Tools.Test.Clean.CloseGameFirst"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(
|
||||
ex,
|
||||
"清理垃圾失败",
|
||||
ModBase.LogLevel.Hint,
|
||||
userSummary: Lang.Text("Tools.Test.Error.OperationFailed"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
ModBase.RunInUiWait(() =>
|
||||
{
|
||||
if (ModMain.frmToolsTest is not null && ModMain.frmToolsTest.BtnClear is not null)
|
||||
ModMain.frmToolsTest.BtnClear.IsEnabled = true;
|
||||
});
|
||||
}
|
||||
}, "Rubbish Clear");
|
||||
}
|
||||
|
||||
public static string GetRandomCave()
|
||||
{
|
||||
return Lang.Text("Tools.Test.CeNotice");
|
||||
}
|
||||
|
||||
public static string GetRandomHint()
|
||||
{
|
||||
return Lang.Text("Tools.Test.CeNotice");
|
||||
}
|
||||
|
||||
public static string GetRandomPresetHint()
|
||||
{
|
||||
return Lang.Text("Tools.Test.CeNotice");
|
||||
}
|
||||
|
||||
private void TextDownloadUrl_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(TextDownloadName.Text) || string.IsNullOrEmpty(TextDownloadUrl.Text)) return;
|
||||
TextDownloadName.Text = ModBase.GetFileNameFromPath(WebUtility.UrlDecode(TextDownloadUrl.Text));
|
||||
StartButtonRefresh();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void MyTextButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
var text = SystemDialogs.SelectFolder();
|
||||
if (!string.IsNullOrEmpty(text)) TextDownloadFolder.Text = text;
|
||||
}
|
||||
|
||||
private void BtnDownloadOpen_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var text = TextDownloadFolder.Text;
|
||||
Directory.CreateDirectory(text);
|
||||
Basics.OpenPath(text);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "打开下载文件夹失败");
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnDownloadStart_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
StartCustomDownload(TextDownloadUrl.Text, TextDownloadName.Text, TextDownloadFolder.Text);
|
||||
TextDownloadUrl.Text = "";
|
||||
TextDownloadUrl.Validate();
|
||||
TextDownloadUrl.ForceShowAsSuccess();
|
||||
TextDownloadName.Text = "";
|
||||
TextDownloadName.Validate();
|
||||
TextDownloadName.ForceShowAsSuccess();
|
||||
StartButtonRefresh();
|
||||
}
|
||||
|
||||
private void TextDownloadUrl_ValidateChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
StartButtonRefresh();
|
||||
}
|
||||
|
||||
private void TextDownloadFolder_ValidateChanged(object sender, EventArgs e)
|
||||
{
|
||||
StartButtonRefresh();
|
||||
}
|
||||
|
||||
private void TextDownloadName_ValidateChanged(object sender, EventArgs e)
|
||||
{
|
||||
StartButtonRefresh();
|
||||
}
|
||||
|
||||
private void BtnClear_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
RubbishClear();
|
||||
}
|
||||
|
||||
// 下载正版玩家皮肤
|
||||
private void BtnSkinSave_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var id = TextSkinID.Text;
|
||||
HintService.Hint(Lang.Text("Tools.Test.Skin.Fetching"));
|
||||
ModBase.RunInNewThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (id.Length < 3)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Tools.Test.Skin.InvalidId"));
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = (string)ModProfile.McLoginMojangUuid(id, true);
|
||||
result = ModSkin.McSkinGetAddress(result, "Mojang");
|
||||
result = ModSkin.McSkinDownload(result);
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
var path = SystemDialogs.SelectSaveFile(Lang.Text("Tools.Test.Skin.Save"), $"{id}.png", Lang.Text("Tools.Test.Skin.FileFilter"));
|
||||
ModBase.CopyFile(result, path);
|
||||
HintService.Hint(Lang.Text("Tools.Test.Skin.Saved", id), HintType.Success);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex.ToString().Contains("429"))
|
||||
{
|
||||
HintService.Hint(Lang.Text("Tools.Test.Skin.TooFrequent"), HintType.Error);
|
||||
ModBase.Log($"获取正版皮肤失败({id}):获取皮肤太过频繁,请 5 分钟后再试!");
|
||||
}
|
||||
else
|
||||
{
|
||||
ModBase.Log(ex, $"获取正版皮肤失败({id})");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 今日人品
|
||||
private void BtnLuck_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
Jrrp();
|
||||
}
|
||||
|
||||
public static int GenerateDailySeed()
|
||||
{
|
||||
var datePart = DateTime.Today.ToString("yyyyMMdd", CultureInfo.InvariantCulture);
|
||||
|
||||
return DJB2Hash(datePart + Identify.LauncherId);
|
||||
}
|
||||
|
||||
private static int DJB2Hash(string str)
|
||||
{
|
||||
var hash = 5381L;
|
||||
var prime = 33L;
|
||||
foreach (var c in str)
|
||||
{
|
||||
long charValue = c;
|
||||
hash = (hash * prime + charValue) % 0x100000000L;
|
||||
}
|
||||
|
||||
return (int)(hash & 0x7FFFFFFFL);
|
||||
}
|
||||
|
||||
public static string GetRating(int luckValue)
|
||||
{
|
||||
var key = luckValue switch
|
||||
{
|
||||
100 => "Tools.Test.Luck.Rating100",
|
||||
>= 95 => "Tools.Test.Luck.Rating95",
|
||||
>= 90 => "Tools.Test.Luck.Rating90",
|
||||
>= 60 => "Tools.Test.Luck.Rating60",
|
||||
>= 40 => "Tools.Test.Luck.Rating40",
|
||||
>= 30 => "Tools.Test.Luck.Rating30",
|
||||
>= 10 => "Tools.Test.Luck.Rating10",
|
||||
_ => "Tools.Test.Luck.Rating0"
|
||||
};
|
||||
|
||||
return Lang.Text(key);
|
||||
}
|
||||
|
||||
private void BtnCreateShortcut_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var shortcutName = Lang.Text("Tools.Test.Shortcut.FileName", ".lnk");
|
||||
var desktopName = Lang.Text("Tools.Test.Shortcut.Desktop");
|
||||
var startName = Lang.Text("Tools.Test.Shortcut.StartMenu");
|
||||
var desktop = Paths.GetSpecialPath(Environment.SpecialFolder.Desktop, shortcutName);
|
||||
var start = Paths.GetSpecialPath(Environment.SpecialFolder.StartMenu, @"Programs\" + shortcutName);
|
||||
var choice =
|
||||
ModMain.MyMsgBox(
|
||||
Lang.Text("Tools.Test.Shortcut.ConfirmMessage", desktopName, desktop, startName, start),
|
||||
Lang.Text("Tools.Test.Shortcut.SelectLocation"), Lang.Text("Common.Action.Cancel"), desktopName, startName);
|
||||
if (choice == 1)
|
||||
return;
|
||||
var shortcutPath = choice == 2 ? desktop : start;
|
||||
var locationName = choice == 2 ? desktopName : startName;
|
||||
Files.CreateShortcut(shortcutPath, Basics.ExecutablePath);
|
||||
HintService.Hint(Lang.Text("Tools.Test.Shortcut.Created", locationName), HintType.Success);
|
||||
}
|
||||
|
||||
// 启动计数显示
|
||||
private void BtnLaunchCount_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
ModMain.MyMsgBox(Lang.Text("Tools.Test.LaunchCount.Message", States.System.LaunchCount), Lang.Text("Tools.Test.LaunchCount.Title"));
|
||||
}
|
||||
|
||||
private async void BtnAchievementPreview_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var url = GetAchievementUrl();
|
||||
ModBase.Log("[Net] 获取网络结果" + url);
|
||||
await LoadImageAsync(url);
|
||||
}
|
||||
|
||||
private async Task LoadImageAsync(string imageUrl)
|
||||
{
|
||||
var client = NetworkService.GetClient();
|
||||
try
|
||||
{
|
||||
var response = await client.GetAsync(imageUrl);
|
||||
if (response.IsSuccessStatusCode)
|
||||
using (var stream = await response.Content.ReadAsStreamAsync())
|
||||
{
|
||||
var bitmapImage = new BitmapImage();
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapImage.StreamSource = stream;
|
||||
bitmapImage.EndInit();
|
||||
bitmapImage.Freeze();
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
AchievementImage.Source = bitmapImage;
|
||||
AchievementImage.Visibility = Visibility.Visible;
|
||||
});
|
||||
}
|
||||
else if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
ModBase.Log("获取成就图片失败(404)");
|
||||
HintService.Hint(Lang.Text("Tools.Test.Achievement.FetchFailed"), HintType.Error);
|
||||
});
|
||||
|
||||
else
|
||||
Dispatcher.Invoke(() => ModBase.Log("获取成就图片失败(" + (int)response.StatusCode + ")"));
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Dispatcher.Invoke(() => ModBase.Log(ex, "获取成就图片失败"));
|
||||
}
|
||||
}
|
||||
|
||||
private async void BtnAchievementSave_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var url = GetAchievementUrl();
|
||||
await DownloadImageToLocalAsync(url);
|
||||
}
|
||||
|
||||
private async Task DownloadImageToLocalAsync(string imageUrl)
|
||||
{
|
||||
var savePath = ModBase.pathTemp + @"Download\" + ModBase.GetHash(imageUrl) + ".png";
|
||||
var client = NetworkService.GetClient();
|
||||
try
|
||||
{
|
||||
// 异步发送 GET 请求
|
||||
var response = await client.GetAsync(imageUrl);
|
||||
|
||||
// 如果响应状态码是成功的,则继续
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
// 异步读取响应内容为字节流
|
||||
var imageBytes = await response.Content.ReadAsByteArrayAsync();
|
||||
|
||||
// 将字节写入本地文件
|
||||
File.WriteAllBytes(savePath, imageBytes);
|
||||
|
||||
var path =
|
||||
SystemDialogs.SelectSaveFile(Lang.Text("Tools.Test.Achievement.Save"), AchievementTitleTextBox.Text + ".png", Lang.Text("Tools.Test.Achievement.FileFilter"));
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
ModBase.Log("用户取消了保存操作");
|
||||
File.Delete(savePath);
|
||||
return;
|
||||
}
|
||||
|
||||
ModBase.CopyFile(savePath, path);
|
||||
File.Delete(savePath);
|
||||
HintService.Hint(Lang.Text("Tools.Test.Achievement.Saved"), HintType.Success);
|
||||
}
|
||||
// 下载成功,返回 True
|
||||
else if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
// 捕获 404 错误
|
||||
ModBase.Log("获取成就图片失败(404)");
|
||||
HintService.Hint(Lang.Text("Tools.Test.Achievement.FetchFailed"), HintType.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 处理其他非成功状态码
|
||||
ModBase.Log("获取成就图片失败(" + (int)response.StatusCode + ")");
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 捕获所有其他异常(如网络连接问题)
|
||||
ModBase.Log(ex, "获取成就图片失败");
|
||||
}
|
||||
}
|
||||
|
||||
private string GetAchievementUrl()
|
||||
{
|
||||
var block = AchievementBlockTextBox.Text.Trim();
|
||||
var title = AchievementTitleTextBox.Text.Replace(" ", "..");
|
||||
var str1 = AchievementString1TextBox.Text.Replace(" ", "..");
|
||||
var str2 = AchievementString2TextBox.Text.Replace(" ", "..");
|
||||
var url = $"https://minecraft-api.com/api/achivements/{block}/{title}/{str1}";
|
||||
if (!string.IsNullOrEmpty(str2)) url += $"/{str2}";
|
||||
return url;
|
||||
}
|
||||
|
||||
private void BtnCrash_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
throw new Exception(Lang.Text("Tools.Test.Crash.ManualCrash"));
|
||||
}
|
||||
|
||||
private int GetHeadSize() => CmbHeadSize.SelectedIndex switch
|
||||
{
|
||||
0 => 64,
|
||||
1 => 96,
|
||||
2 => 128,
|
||||
_ => 64
|
||||
};
|
||||
|
||||
private void BtnSelectSkin_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var filePath = SystemDialogs.SelectFile(Lang.Text("Tools.Test.Avatar.FileFilter"),
|
||||
Lang.Text("Tools.Test.Avatar.SelectSkinFile"));
|
||||
if (!string.IsNullOrEmpty(filePath)) LoadAndGenerateHead(filePath);
|
||||
}
|
||||
|
||||
private void LoadAndGenerateHead(string skinPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var stream = new FileStream(skinPath, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
currentSkinBitmap = new Bitmap(stream);
|
||||
}
|
||||
|
||||
this.skinPath = skinPath;
|
||||
|
||||
if (currentSkinBitmap.Width != currentSkinBitmap.Height)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Tools.Test.Avatar.InvalidSize"), HintType.Error);
|
||||
SkinPreviewBorder.Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
|
||||
generatedHeadBitmap = GenerateHeadFromSkin(currentSkinBitmap);
|
||||
|
||||
ImgFace.Source = BitmapToBitmapImage(generatedHeadBitmap);
|
||||
ImgHair.Source = null;
|
||||
|
||||
SkinPreviewBorder.Visibility = Visibility.Visible;
|
||||
HintService.Hint(Lang.Text("Tools.Test.Avatar.Generated"), HintType.Success);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "生成头像失败");
|
||||
HintService.Hint(Lang.Text("Tools.Test.Avatar.GenerateFailed", ex.Message), HintType.Error);
|
||||
SkinPreviewBorder.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap GenerateHeadFromSkin(Bitmap skinBitmap)
|
||||
{
|
||||
var scale = skinBitmap.Width / 64;
|
||||
headSize = GetHeadSize();
|
||||
var headBitmap = new Bitmap(headSize, headSize);
|
||||
|
||||
using (var g = Graphics.FromImage(headBitmap))
|
||||
{
|
||||
g.InterpolationMode = InterpolationMode.NearestNeighbor;
|
||||
g.PixelOffsetMode = PixelOffsetMode.Half;
|
||||
|
||||
DrawFaceLayer(g, skinBitmap, scale);
|
||||
if (skinBitmap.Width >= 64) DrawHairLayer(headBitmap, skinBitmap, scale);
|
||||
}
|
||||
|
||||
return headBitmap;
|
||||
}
|
||||
|
||||
private void DrawFaceLayer(Graphics g, Bitmap skinBitmap, int scale)
|
||||
{
|
||||
var faceRect = new Rectangle(8 * scale, 8 * scale, 8 * scale, 8 * scale);
|
||||
var faceSize = headSize - headSize / 8;
|
||||
var faceScaled = new Bitmap(faceSize, faceSize);
|
||||
|
||||
using (var gFace = Graphics.FromImage(faceScaled))
|
||||
{
|
||||
gFace.InterpolationMode = InterpolationMode.NearestNeighbor;
|
||||
gFace.PixelOffsetMode = PixelOffsetMode.Half;
|
||||
gFace.DrawImage(skinBitmap, new Rectangle(0, 0, faceSize, faceSize), faceRect, GraphicsUnit.Pixel);
|
||||
}
|
||||
|
||||
var offset = headSize / 16;
|
||||
g.DrawImage(faceScaled, offset, offset, faceSize, faceSize);
|
||||
}
|
||||
|
||||
private void DrawHairLayer(Bitmap headBitmap, Bitmap skinBitmap, int scale)
|
||||
{
|
||||
var hairRect = new Rectangle(40 * scale, 8 * scale, 8 * scale, 8 * scale);
|
||||
var hairScaled = new Bitmap(headSize, headSize);
|
||||
|
||||
using (var gHair = Graphics.FromImage(hairScaled))
|
||||
{
|
||||
gHair.InterpolationMode = InterpolationMode.NearestNeighbor;
|
||||
gHair.PixelOffsetMode = PixelOffsetMode.Half;
|
||||
gHair.DrawImage(skinBitmap, new Rectangle(0, 0, headSize, headSize), hairRect, GraphicsUnit.Pixel);
|
||||
}
|
||||
|
||||
for (int x = 0, loopTo = headSize - 1; x <= loopTo; x++)
|
||||
for (int y = 0, loopTo1 = headSize - 1; y <= loopTo1; y++)
|
||||
{
|
||||
var pixel = hairScaled.GetPixel(x, y);
|
||||
if (pixel.A > 0) headBitmap.SetPixel(x, y, pixel);
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnSaveHead_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (generatedHeadBitmap is null)
|
||||
{
|
||||
HintService.Hint(Lang.Text("Tools.Test.Avatar.SelectFirst"), HintType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var savePath = SystemDialogs.SelectSaveFile(Lang.Text("Tools.Test.Avatar.Save"), "Head.png");
|
||||
|
||||
if (string.IsNullOrEmpty(savePath))
|
||||
return;
|
||||
|
||||
generatedHeadBitmap.Save(savePath, ImageFormat.Png);
|
||||
HintService.Hint(Lang.Text("Tools.Test.Avatar.Saved"), HintType.Success);
|
||||
}
|
||||
|
||||
private void CmbHeadSize_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (currentSkinBitmap is not null && skinPath is not null) LoadAndGenerateHead(skinPath);
|
||||
}
|
||||
|
||||
private BitmapImage BitmapToBitmapImage(Bitmap bitmap)
|
||||
{
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
bitmap.Save(memoryStream, ImageFormat.Png);
|
||||
memoryStream.Position = 0L;
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapImage.StreamSource = memoryStream;
|
||||
bitmapImage.EndInit();
|
||||
bitmapImage.Freeze();
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
}
|
||||
|
||||
private void TextDownloadFolder_OnValidatedTextChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SaveCacheDownloadFolder(sender, e);
|
||||
TextDownloadName_ValidateChanged(sender, e);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user