初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
@@ -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