feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
CI / Go Backend (push) Canceled after 0s

初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
xyou
2026-08-08 14:07:56 +08:00
parent 9500c4c80a
commit f70b061d1a
1972 changed files with 159760 additions and 6 deletions
@@ -0,0 +1,1089 @@
using System.Collections;
using System.Collections.ObjectModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using FluentValidation;
using Microsoft.VisualBasic;
using Microsoft.Win32;
using PCL.Core.App;
using PCL.Core.App.Configuration;
using PCL.Core.App.Localization;
using PCL.Core.UI;
using PCL.Core.Utils;
using PCL.Core.Utils.OS;
using PCL.Core.Utils.Secret;
namespace PCL;
public static class ModMain
{
public static FormMain? frmMain;
public static SplashScreen? frmStart;
public static PageLaunchLeft? frmLaunchLeft;
public static PageLaunchRight? frmLaunchRight;
public static PageLogLeft? frmLogLeft;
public static PageLogRight? frmLogRight;
public static PageSelectLeft? frmSelectLeft;
public static PageSelectRight? frmSelectRight;
public static PageSpeedLeft? frmSpeedLeft;
public static PageSpeedRight? frmSpeedRight;
public static PageToolsLeft? frmToolsLeft;
public static PageToolsGameLink? frmToolsGameLink;
public static PageToolsTest? frmToolsTest;
public static PageDownloadLeft? frmDownloadLeft;
public static PageDownloadInstall? frmDownloadInstall;
public static PageDownloadClient? frmDownloadClient;
public static PageDownloadOptiFine? frmDownloadOptiFine;
public static PageDownloadLiteLoader? frmDownloadLiteLoader;
public static PageDownloadForge? frmDownloadForge;
public static PageDownloadNeoForge? frmDownloadNeoForge;
public static PageDownloadCleanroom? frmDownloadCleanroom;
public static PageDownloadFabric? frmDownloadFabric;
public static PageDownloadLabyMod? frmDownloadLabyMod;
public static PageDownloadLegacyFabric? frmDownloadLegacyFabric;
public static PageDownloadMod? frmDownloadMod;
public static PageDownloadPack? frmDownloadPack;
public static PageDownloadDataPack? frmDownloadDataPack;
public static PageDownloadShader? frmDownloadShader;
public static PageDownloadResourcePack? frmDownloadResourcePack;
public static PageDownloadWorld? frmDownloadWorld;
public static PageDownloadCompFavorites? frmDownloadCompFavorites;
public static PageSetupLeft? frmSetupLeft;
public static PageSetupLaunch? frmSetupLaunch;
public static PageSetupUI? frmSetupUI;
public static PageSetupGameManage? frmSetupGameManage;
public static PageSetupUpdate? frmSetupUpdate;
public static PageSetupJava? frmSetupJava;
public static PageSetupAbout? frmSetupAbout;
public static PageSetupLog? frmSetupLog;
public static PageSetupFeedback? frmSetupFeedback;
public static PageSetupGameLink? frmSetupGameLink;
public static PageSetupLauncherLanguage? frmSetupLauncherLanguage;
public static PageSetupLauncherMisc? frmSetupLauncherMisc;
public static PageLoginAuth? frmLoginAuth;
public static PageLoginMs? frmLoginMs;
public static PageLoginProfile? frmLoginProfile;
public static PageLoginProfileSkin? frmLoginProfileSkin;
public static PageLoginOffline? frmLoginOffline;
public static PageInstanceLeft? frmInstanceLeft;
public static PageInstanceOverall? frmInstanceOverall;
public static PageInstanceCompResource? frmInstanceMod;
public static PageInstanceModDisabled? frmInstanceModDisabled;
public static PageInstanceScreenshot? frmInstanceScreenshot;
public static PageInstanceSaves? frmInstanceSaves;
public static PageInstanceCompResource? frmInstanceShader;
public static PageInstanceCompResource? frmInstanceSchematic;
public static PageInstanceCompResource? frmInstanceResourcePack;
public static PageInstanceSetup? frmInstanceSetup;
public static PageInstanceInstall? frmInstanceInstall;
public static PageInstanceExport? frmInstanceExport;
public static PageInstanceServer? frmInstanceServer;
public static PageInstanceSavesLeft? frmInstanceSavesLeft;
public static PageInstanceSavesInfo? frmInstanceSavesInfo;
public static PageInstanceSavesDatapack? frmInstanceSavesDatapack;
public static PageDownloadCompDetail? frmDownloadCompDetail;
public static PageHomepageNewsView? frmHomepageNews;
public static MySlider? dragControl = null;
private static int timer4Count;
private static int timer150Count;
/// <summary>
/// 等待显示的弹窗。
/// </summary>
public static List<MyMsgBoxConverter> WaitingMyMsgBox { get; } = [];
private static void TimerMain()
{
try
{
#region 50ms
HintService.Tick();
MyMsgBoxTick();
frmMain!.DragTick();
ModLoader.LoaderTaskbarProgressRefresh();
}
#endregion
catch (Exception ex)
{
ModBase.Log(
ex,
"短程主时钟执行异常",
ModBase.LogLevel.Critical,
userSummary: Lang.Text("Main.Error.OperationFailed"));
}
timer4Count += 1;
if (timer4Count == 4)
{
timer4Count = 0;
try
{
#region 250ms
}
#endregion
catch (Exception ex)
{
ModBase.Log(ex, "中程主时钟执行异常");
}
}
timer150Count += 1;
if (timer150Count == 150)
{
timer150Count = 0;
try
{
#region 7.5s
if (frmMain!.BtnExtraApril_ShowCheck() && aprilDistance != 0)
frmMain.BtnExtraApril.Ribble();
// 以未知原因窗口被丢到一边去的修复(Top、Left = -25600),还有 #745
ModBase.RunInUi(() =>
{
if (!frmMain.Hidden)
{
if (frmMain.Top < -9000) frmMain.Top = 100d;
if (frmMain.Left < -9000) frmMain.Left = 100d;
}
}); // 窗口拉至最大时 Left = -18.8
}
#endregion
catch (Exception ex)
{
ModBase.Log(
ex,
"长程主时钟执行异常",
ModBase.LogLevel.Critical,
userSummary: Lang.Text("Main.Error.OperationFailed"));
}
}
}
public static void TimerMainStart()
{
ModBase.RunInNewThread(() =>
{
try
{
while (true)
{
ModBase.RunInUiWait(TimerMain);
Thread.Sleep((int)Math.Round(50d * 0.98d));
}
}
catch (Exception ex)
{
ModBase.Log(
ex,
"程序主时钟出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Main.Error.OperationFailed"));
}
}, "Timer Main");
if (!isAprilEnabled)
return;
ModBase.RunInNewThread(() =>
{
try
{
var lastTime = Environment.TickCount;
while (true)
{
if (lastTime != Environment.TickCount)
{
lastTime = Environment.TickCount;
ModBase.RunInUiWait(TimerFool);
}
Thread.Sleep(1);
}
}
catch (Exception ex)
{
ModBase.Log(
ex,
"愚人节主时钟出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Main.Error.OperationFailed"));
}
}, "Timer Main Fool");
}
#region
/// <summary>
/// 存储弹窗信息的转换器。
/// </summary>
public class MyMsgBoxConverter
{
// 设置轮询 Url
public object AuthUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
public string Button1 = "";
/// <summary>
/// 点击第一个按钮将执行该方法,不关闭弹窗。
/// </summary>
public Action Button1Action;
public string Button2 = "";
/// <summary>
/// 点击第二个按钮将执行该方法,不关闭弹窗。
/// </summary>
public Action Button2Action;
public string Button3 = "";
/// <summary>
/// 点击第三个按钮将执行该方法,不关闭弹窗。
/// </summary>
public Action Button3Action;
/// <summary>
/// 输入模式:文本框的文本。
/// 选择模式:需要放进去的 List(Of MyListItem)。
/// 登录模式:登录步骤 1 中返回的 JSON。
/// </summary>
public object Content;
public bool ForceWait;
/// <summary>
/// 有多个按钮时,是否给第一个按钮加高亮。
/// </summary>
public bool HighLight;
/// <summary>
/// 输入模式:提示文本。
/// </summary>
public string HintText = "";
/// <summary>
/// 弹窗是否已经关闭。
/// </summary>
public bool IsExited = false;
public bool IsWarn;
/// <summary>
/// 输入模式:输入的文本。若点击了 非 第一个按钮,则为 Nothing。
/// 选择模式:点击的按钮编号,从 1 开始。
/// 登录模式:字符串数组 {AccessToken, RefreshToken} 或一个 Exception。
/// </summary>
public object Result;
public string Text;
public string Title;
public MyMsgBoxType Type;
/// <summary>
/// 输入模式:输入验证规则。
/// </summary>
public Collection<IValidator<string>> ValidateRules;
public DispatcherFrame WaitFrame = new(true);
}
public enum MyMsgBoxType
{
Text,
Select,
Input,
Login,
Markdown
}
private static string GetDefaultDialogTitle() => Lang.Text("Common.Dialog.Title");
private static string GetDefaultConfirmText() => Lang.Text("Common.Action.Confirm");
private static string GetDefaultCancelText() => Lang.Text("Common.Action.Cancel");
/// <summary>
/// 显示弹窗,返回点击按钮的编号(从 1 开始)。
/// </summary>
/// <param name="title">弹窗的标题。</param>
/// <param name="caption">弹窗的内容。</param>
/// <param name="button1">显示的第一个按钮,默认为“确定”。</param>
/// <param name="button2">显示的第二个按钮,默认为空。</param>
/// <param name="button3">显示的第三个按钮,默认为空。</param>
/// <param name="button1Action">点击第一个按钮将执行该方法,不关闭弹窗。</param>
/// <param name="button2Action">点击第二个按钮将执行该方法,不关闭弹窗。</param>
/// <param name="button3Action">点击第三个按钮将执行该方法,不关闭弹窗。</param>
/// <param name="isWarn">是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。</param>
public static int MyMsgBox(string caption, string? title = null, string? button1 = null, string? button2 = "",
string? button3 = "", bool isWarn = false, bool highLight = true, bool forceWait = false,
Action button1Action = null, Action button2Action = null, Action button3Action = null)
{
title ??= GetDefaultDialogTitle();
button1 ??= GetDefaultConfirmText();
button2 ??= "";
button3 ??= "";
// 将弹窗列入队列
var converter = new MyMsgBoxConverter
{
Type = MyMsgBoxType.Text, Button1 = button1, Button2 = button2, Button3 = button3, Text = caption,
IsWarn = isWarn, Title = title, HighLight = highLight, ForceWait = true, Button1Action = button1Action,
Button2Action = button2Action, Button3Action = button3Action
};
WaitingMyMsgBox.Add(converter);
if (ModBase.RunInUi())
// 若为 UI 线程,立即执行弹窗刻, 避免快速(连点器)点击时多次弹窗
MyMsgBoxTick();
if (button2.Length > 0 || forceWait)
{
// 若有多个按钮则开始等待
if (frmMain is null || (frmMain.PanMsg is null && ModBase.RunInUi()))
{
// 主窗体尚未加载,用老土的弹窗来替代
WaitingMyMsgBox.Remove(converter);
if (button2.Length > 0)
{
var rawResult = Interaction.MsgBox(caption,
(MsgBoxStyle)((int)(button3.Length > 0 ? MsgBoxStyle.YesNoCancel : MsgBoxStyle.YesNo) +
(int)(isWarn ? MsgBoxStyle.Critical : MsgBoxStyle.Question)), title);
switch (rawResult)
{
case MsgBoxResult.Yes:
{
converter.Result = 1;
break;
}
case MsgBoxResult.No:
{
converter.Result = 2;
break;
}
case MsgBoxResult.Cancel:
{
converter.Result = 3;
break;
}
}
}
else
{
Interaction.MsgBox(caption,
(MsgBoxStyle)((int)MsgBoxStyle.OkOnly +
(int)(isWarn ? MsgBoxStyle.Critical : MsgBoxStyle.Question)), title);
converter.Result = 1;
}
ModBase.Log("[Control] 主窗体加载完成前出现意料外的等待弹窗:" + button1 + "," + button2 + "," + button3,
ModBase.LogLevel.Debug);
}
else
{
try
{
frmMain.DragStop();
ComponentDispatcher.PushModal();
Dispatcher.PushFrame(converter.WaitFrame);
}
finally
{
ComponentDispatcher.PopModal();
}
}
ModBase.Log($"[Control] 普通弹框返回:{converter.Result ?? "null"}");
return (int)converter.Result;
}
// 不进行等待,直接返回
return 1;
}
/// <summary>
/// 显示弹窗,返回点击按钮的编号(从 1 开始)。
/// </summary>
/// <param name="title">弹窗的标题。</param>
/// <param name="caption">弹窗的内容。</param>
/// <param name="button1">显示的第一个按钮,默认为“确定”。</param>
/// <param name="button2">显示的第二个按钮,默认为空。</param>
/// <param name="button3">显示的第三个按钮,默认为空。</param>
/// <param name="button1Action">点击第一个按钮将执行该方法,不关闭弹窗。</param>
/// <param name="button2Action">点击第二个按钮将执行该方法,不关闭弹窗。</param>
/// <param name="button3Action">点击第三个按钮将执行该方法,不关闭弹窗。</param>
/// <param name="isWarn">是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。</param>
public static int MyMsgBoxMarkdown(string caption, string? title = null, string? button1 = null, string? button2 = "",
string? button3 = "", bool isWarn = false, bool highLight = true, bool forceWait = false,
Action button1Action = null, Action button2Action = null, Action button3Action = null)
{
title ??= GetDefaultDialogTitle();
button1 ??= GetDefaultConfirmText();
button2 ??= "";
button3 ??= "";
// 将弹窗列入队列
var converter = new MyMsgBoxConverter
{
Type = MyMsgBoxType.Markdown, Button1 = button1, Button2 = button2, Button3 = button3, Text = caption,
IsWarn = isWarn, Title = title, HighLight = highLight, ForceWait = true, Button1Action = button1Action,
Button2Action = button2Action, Button3Action = button3Action
};
WaitingMyMsgBox.Add(converter);
if (ModBase.RunInUi())
// 若为 UI 线程,立即执行弹窗刻, 避免快速(连点器)点击时多次弹窗
MyMsgBoxTick();
if (button2.Length > 0 || forceWait)
{
// 若有多个按钮则开始等待
if (frmMain is null || (frmMain.PanMsg is null && ModBase.RunInUi()))
{
// 主窗体尚未加载,用老土的弹窗来替代
WaitingMyMsgBox.Remove(converter);
if (button2.Length > 0)
{
var rawResult = Interaction.MsgBox(caption,
(MsgBoxStyle)((int)(button3.Length > 0 ? MsgBoxStyle.YesNoCancel : MsgBoxStyle.YesNo) +
(int)(isWarn ? MsgBoxStyle.Critical : MsgBoxStyle.Question)), title);
switch (rawResult)
{
case MsgBoxResult.Yes:
{
converter.Result = 1;
break;
}
case MsgBoxResult.No:
{
converter.Result = 2;
break;
}
case MsgBoxResult.Cancel:
{
converter.Result = 3;
break;
}
}
}
else
{
Interaction.MsgBox(caption,
(MsgBoxStyle)((int)MsgBoxStyle.OkOnly +
(int)(isWarn ? MsgBoxStyle.Critical : MsgBoxStyle.Question)), title);
converter.Result = 1;
}
ModBase.Log("[Control] 主窗体加载完成前出现意料外的等待弹窗:" + button1 + "," + button2 + "," + button3,
ModBase.LogLevel.Debug);
}
else
{
try
{
frmMain.DragStop();
ComponentDispatcher.PushModal();
Dispatcher.PushFrame(converter.WaitFrame);
}
finally
{
ComponentDispatcher.PopModal();
}
}
ModBase.Log($"[Control] 普通弹框返回:{converter.Result ?? "null"}");
return (int)converter.Result;
}
// 不进行等待,直接返回
return 1;
}
/// <summary>
/// 显示输入框并返回输入的文本。若点击第二个按钮,则返回 Nothing。
/// </summary>
/// <param name="title">弹窗的标题。</param>
/// <param name="validateRules">文本框的输入检测。</param>
/// <param name="text">弹窗的介绍文本。</param>
/// <param name="defaultInput">文本框的默认内容。</param>
/// <param name="hintText">文本框的提示内容。</param>
/// <param name="button1">显示的第一个按钮,默认为“确定”。</param>
/// <param name="button2">显示的第二个按钮,默认为“取消”。</param>
/// <param name="isWarn">是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。</param>
public static string MyMsgBoxInput(string title, string text = "", string defaultInput = "",
Collection<IValidator<string>>? validateRules = null, string hintText = "", string? button1 = null,
string? button2 = null, bool isWarn = false)
{
button1 ??= GetDefaultConfirmText();
button2 ??= GetDefaultCancelText();
// 将弹窗列入队列
var converter = new MyMsgBoxConverter
{
Text = text, HintText = hintText, Type = MyMsgBoxType.Input,
ValidateRules = validateRules ?? [], Button1 = button1, Button2 = button2,
Content = defaultInput, IsWarn = isWarn, Title = title
};
WaitingMyMsgBox.Add(converter);
// 虽然我也不知道这是啥但是能用就成了 :)
try
{
frmMain?.DragStop();
ComponentDispatcher.PushModal();
Dispatcher.PushFrame(converter.WaitFrame);
}
finally
{
ComponentDispatcher.PopModal();
}
ModBase.Log($"[Control] 输入弹框返回:{converter.Result}");
return converter.Result?.ToString();
}
/// <summary>
/// 显示选择框并返回选择的第几项(从 0 开始)。若点击第二个按钮,则返回 Nothing。
/// </summary>
/// <param name="title">弹窗的标题。</param>
/// <param name="button1">显示的第一个按钮,默认为 “确定”。</param>
/// <param name="button2">显示的第二个按钮,默认为空。</param>
/// <param name="isWarn">是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。</param>
public static int? MyMsgBoxSelect(List<IMyRadio> selections, string? title = null, string? button1 = null,
string? button2 = "", bool isWarn = false)
{
title ??= GetDefaultDialogTitle();
button1 ??= GetDefaultConfirmText();
button2 ??= "";
// 将弹窗列入队列
var converter = new MyMsgBoxConverter
{
Type = MyMsgBoxType.Select, Button1 = button1, Button2 = button2, Content = selections, IsWarn = isWarn,
Title = title
};
WaitingMyMsgBox.Add(converter);
// 虽然我也不知道这是啥但是能用就成了 :)
try
{
if (frmMain is not null)
frmMain.DragStop();
ComponentDispatcher.PushModal();
Dispatcher.PushFrame(converter.WaitFrame);
}
finally
{
ComponentDispatcher.PopModal();
}
ModBase.Log($"[Control] 选择弹框返回:{converter.Result ?? "null"}");
return (int?)converter.Result;
}
public static void MyMsgBoxTick()
{
try
{
if (frmMain is null || frmMain.PanMsg is null || frmMain.WindowState == WindowState.Minimized)
return;
if (frmMain.PanMsg.Children.Count > 0)
{
// 弹窗中
frmMain.PanMsgBackground.Visibility = Visibility.Visible;
}
else if (WaitingMyMsgBox.Any())
{
// 没有弹窗,显示一个等待的弹窗
frmMain.PanMsgBackground.Visibility = Visibility.Visible;
switch (WaitingMyMsgBox[0].Type)
{
case MyMsgBoxType.Input:
{
frmMain.PanMsg.Children.Add(new MyMsgInput(WaitingMyMsgBox[0]));
break;
}
case MyMsgBoxType.Select:
{
frmMain.PanMsg.Children.Add(new MyMsgSelect(WaitingMyMsgBox[0]));
break;
}
case MyMsgBoxType.Text:
{
frmMain.PanMsg.Children.Add(new MyMsgText(WaitingMyMsgBox[0]));
break;
}
case MyMsgBoxType.Login:
{
frmMain.PanMsg.Children.Add(new MyMsgLogin(WaitingMyMsgBox[0]));
break;
}
case MyMsgBoxType.Markdown:
{
frmMain.PanMsg.Children.Add(new MyMsgMarkdown(WaitingMyMsgBox[0]));
break;
}
}
WaitingMyMsgBox.RemoveAt(0);
}
// 没有弹窗,没有等待的弹窗
else if (!(frmMain.PanMsgBackground.Visibility == Visibility.Collapsed))
{
frmMain.PanMsgBackground.Visibility = Visibility.Collapsed;
}
}
catch (Exception ex)
{
ModBase.Log(
ex,
"处理等待中的弹窗失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Main.Error.OperationFailed"));
}
}
public static void MsgBoxWrapper_OnShow(string message, string caption, ICollection<MsgBoxButtonInfo> buttons,
MsgBoxTheme theme, bool block, ref int result)
{
var btnText1 = buttons.Count < 1 ? GetDefaultConfirmText() : buttons.ElementAt(0).Context;
var btnAct1 = (Action)(buttons.Count < 1 ? (object)null : buttons.ElementAt(0).OnClick);
var btnText2 = buttons.Count < 2 ? GetDefaultCancelText() : buttons.ElementAt(1).Context;
var btnAct2 = (Action)(buttons.Count < 2 ? (object)null : buttons.ElementAt(1).OnClick);
var btnText3 = buttons.Count < 3 ? "" : buttons.ElementAt(2).Context;
var btnAct3 = (Action)(buttons.Count < 3 ? (object)null : buttons.ElementAt(2).OnClick);
var isWarn = theme == MsgBoxTheme.Warning || theme == MsgBoxTheme.Error;
result = MyMsgBox(message, caption, btnText1, btnText2, btnText3, isWarn, forceWait: block,
button1Action: btnAct1, button2Action: btnAct2, button3Action: btnAct3);
}
#endregion
#region
public static bool isAprilEnabled = DateTime.Now.Month == 4 && DateTime.Now.Day == 1;
public static bool isAprilGiveup = false;
private static Vector aprilSpeed = new(0d, 0d);
private static int aprilIdieCount;
private static Point aprilMousePosLast = new(0d, 0d);
private static int aprilDistance;
private static void TimerFool()
{
try
{
if (frmLaunchLeft is null || frmLaunchLeft.AprilPosTrans is null || frmMain.lastMouseArg is null)
return;
if (isAprilGiveup || frmMain.pageCurrent != FormMain.PageType.Launch ||
ModAnimation.AniControlEnabled != 0 || !frmLaunchLeft.BtnLaunch.IsLoaded)
return;
// 计算是否空闲
var mousePos = frmMain.lastMouseArg.GetPosition(frmMain);
if (mousePos == aprilMousePosLast)
{
aprilIdieCount += 1;
}
else
{
aprilMousePosLast = mousePos;
aprilIdieCount = 0;
}
// 计算躲避移动
Vector direction;
double distance;
var buttonWidth = frmLaunchLeft.BtnLaunch.ActualWidth / 2d;
var buttonHeight = frmLaunchLeft.BtnLaunch.ActualHeight / 2d;
var vec = (Vector)(frmMain.lastMouseArg.GetPosition(frmLaunchLeft.BtnLaunch) -
new Vector(buttonWidth, buttonHeight));
var dir = new Vector(vec.X, vec.Y);
dir.Normalize();
direction = -dir;
distance = new Vector(Math.Max(0d, Math.Abs(vec.X) - buttonWidth),
Math.Max(0d, Math.Abs(vec.Y) - buttonHeight)).Length;
var breathScale = Math.Sin(timer150Count / 37.5d * Math.PI);
var acc = Math.Max(0d, breathScale * 0.25d - 0.65d - Math.Log((distance + 0.4d) / 200d)) * direction; // 加速度
// 计算回归移动
if (aprilIdieCount >= 64 * 5)
{
var safeDist = (Vector)(frmMain.lastMouseArg.GetPosition(frmMain.PanMain) -
new Vector(buttonWidth, frmMain.PanMain.ActualHeight - buttonHeight * 3d));
var back = new Vector(frmLaunchLeft.AprilPosTrans.X, frmLaunchLeft.AprilPosTrans.Y);
if (safeDist.Length > 250d && back.Length > 0.4d)
{
acc -= back * 0.0005d;
back.Normalize();
acc -= back * 0.15d;
}
}
// 回到边界
var relative = frmLaunchLeft.BtnLaunch.TranslatePoint(new Point(0d, 0d), frmMain.PanForm);
if (relative.X < -buttonWidth * 2d)
{
frmLaunchLeft.AprilPosTrans.X += frmMain.PanForm.ActualWidth + buttonWidth * 2d; // 离开左边界
aprilSpeed.X -= 80d;
if (relative.Y < 0d)
frmLaunchLeft.AprilPosTrans.Y += buttonHeight * 2.5d;
else if (relative.Y > frmMain.PanForm.ActualHeight - buttonHeight * 2d)
frmLaunchLeft.AprilPosTrans.Y -= buttonHeight * 2.5d;
}
else if (relative.X > frmMain.PanForm.ActualWidth)
{
frmLaunchLeft.AprilPosTrans.X -= frmMain.PanForm.ActualWidth + buttonWidth * 2d; // 离开右边界
aprilSpeed.X += 80d;
if (relative.Y < 0d)
frmLaunchLeft.AprilPosTrans.Y += buttonHeight * 2.5d;
else if (relative.Y > frmMain.PanForm.ActualHeight - buttonHeight * 2d)
frmLaunchLeft.AprilPosTrans.Y -= buttonHeight * 2.5d;
}
else if (relative.Y < -buttonHeight * 2d)
{
frmLaunchLeft.AprilPosTrans.Y += frmMain.PanForm.ActualHeight + buttonHeight * 2d; // 离开上边界
aprilSpeed.Y -= 25d;
if (relative.X < 0d)
frmLaunchLeft.AprilPosTrans.X += buttonWidth * 2d;
else if (relative.X > frmMain.PanForm.ActualWidth - buttonWidth * 2d)
frmLaunchLeft.AprilPosTrans.X -= buttonWidth * 2d;
}
else if (relative.Y > frmMain.PanForm.ActualHeight)
{
frmLaunchLeft.AprilPosTrans.Y -= frmMain.PanForm.ActualHeight + buttonHeight * 2d; // 离开下边界
aprilSpeed.Y += 25d;
if (relative.X < 0d)
frmLaunchLeft.AprilPosTrans.X += buttonWidth * 2d;
else if (relative.X > frmMain.PanForm.ActualWidth - buttonWidth * 2d)
frmLaunchLeft.AprilPosTrans.X -= buttonWidth * 2d;
}
// 移动
aprilSpeed = aprilSpeed * 0.8d + acc;
var speedValue = Math.Min(60d, aprilSpeed.Length);
if (speedValue < 0.01d)
return;
aprilSpeed.Normalize();
aprilSpeed *= speedValue;
aprilDistance = (int)Math.Round(aprilDistance + speedValue);
frmLaunchLeft.AprilPosTrans.X += aprilSpeed.X;
frmLaunchLeft.AprilPosTrans.Y += aprilSpeed.Y;
// 大小改变
frmLaunchLeft.AprilScaleTrans.ScaleX =
ModBase.MathClamp(1d - (Math.Abs(direction.X) - Math.Abs(direction.Y)) * (speedValue / 160d), 0.2d,
1.8d);
frmLaunchLeft.AprilScaleTrans.ScaleY =
ModBase.MathClamp(1d - (Math.Abs(direction.Y) - Math.Abs(direction.X)) * (speedValue / 100d), 0.2d,
1.8d);
// 放弃提示
if (aprilDistance > 4000)
{
aprilDistance = -4000;
switch (RandomUtils.NextInt(0, 3))
{
case 0:
{
HintService.Hint("放弃吧!只需要点一下右下角的小白旗……");
break;
}
case 1:
{
HintService.Hint("看到右下角的那面小白旗了吗?");
break;
}
case 2:
{
HintService.Hint("这里建议点一下右下角的小白旗投降呢.jpg");
break;
}
case 3:
{
HintService.Hint("右下角的小白旗永远等着你……");
break;
}
}
}
}
catch (Exception ex)
{
ModBase.Log(
ex,
"愚人节移动出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Main.Error.OperationFailed"));
}
}
#endregion
#region
/// <summary>
/// 把某个 PCL 窗口拖到最前面。
/// </summary>
public static void ShowWindowToTop(nint handle)
{
try
{
PostMessage(handle, 400 * 16 + 2, 0L, 0L);
SetForegroundWindow(handle); // 不在这里放不行,神秘 WinAPI,建议别动
}
catch (Exception ex)
{
ModBase.Log(
ex,
"设置窗口置顶失败",
ModBase.LogLevel.Hint,
userSummary: Lang.Text("Main.Error.OperationFailed"));
}
}
[DllImport("user32", EntryPoint = "FindWindowA")]
public static extern nint FindWindow(string className, string windowName);
[DllImport("user32")]
public static extern int SetForegroundWindow(nint hWnd);
[DllImport("user32", EntryPoint = "PostMessageA")]
private static extern bool PostMessage(nint hWnd, uint msg, long wParam, long lParam);
/// <summary>
/// 将特定程序设置为使用高性能显卡启动。
/// 如果失败,则抛出异常。
/// </summary>
public static void SetGPUPreference(string executeable, bool wantHighPerformance = true)
{
const string GPU_PERFERENCE_REG_KEY = @"Software\Microsoft\DirectX\UserGpuPreferences";
const string GPU_PERFERENCE_REG_VALUE_HIGH = "GpuPreference=2;";
const string GPU_PERFERENCE_REG_VALUE_DEFAULT = "GpuPreference=0;";
// Const GPU_PERFERENCE_REG_VALUE_POWER_SAVING As String = "GpuPreference=1;"
var isCurrentHighPerformance = false;
// 查看现有设置
// 就知道 My.Computer,改个注册表 Microsoft.Win32.Registry 几年前的 API 了不用,还在这 My.Computer 都 5202 年了 My 你大爷
using (var readOnlyKey = Registry.CurrentUser.OpenSubKey(GPU_PERFERENCE_REG_KEY, false))
{
if (readOnlyKey is not null)
{
var currentValue = readOnlyKey.GetValue(executeable);
if (GPU_PERFERENCE_REG_VALUE_HIGH == (currentValue?.ToString() ?? "")) isCurrentHighPerformance = true;
}
else
{
// 创建父级键
ModBase.Log("[System] 需要创建显卡设置的父级键");
Registry.CurrentUser.CreateSubKey(GPU_PERFERENCE_REG_KEY);
}
}
ModBase.Log($"[System] 当前程序 ({executeable}) 的显卡设置为高性能: {isCurrentHighPerformance}");
if (isCurrentHighPerformance ^ wantHighPerformance)
// 写入新设置
using (var writeKey = Registry.CurrentUser.OpenSubKey(GPU_PERFERENCE_REG_KEY, true))
{
writeKey.SetValue(executeable,
wantHighPerformance ? GPU_PERFERENCE_REG_VALUE_HIGH : GPU_PERFERENCE_REG_VALUE_DEFAULT);
ModBase.Log($"[System] 已调整程序 ({executeable}) 显卡设置: {wantHighPerformance}");
}
}
/// <summary>
/// 对替换标记进行处理。会对替换内容使用 EscapeHandler 进行转义。
/// /// </summary>
public static string ArgumentReplace(string text, Func<string, string> escapeHandler = null, bool replaceTime = true)
{
// 预处理
if (text is null) return null;
Func<string, string> replacer = (s) =>
{
if (s is null) return "";
if (escapeHandler is null) return s;
if (s.Contains(":\\")) s = ModBase.ShortenPath(s);
return escapeHandler(s);
};
// 基础
text = text.Replace("{pcl_version}", replacer(ModBase.versionBaseName));
text = text.Replace("{pcl_version_code}", replacer(ModBase.versionCode.ToString()));
text = text.Replace("{pcl_version_branch}", replacer(ModBase.versionBranchName));
text = text.Replace("{pcl_branch}", replacer(ModBase.versionBranchName));
text = text.Replace("{identify}", replacer(Identify.LauncherId));
text = text.Replace("{path}", replacer(Basics.ExecutableDirectory));
text = text.Replace("{path_with_name}", replacer(Basics.ExecutableName));
text = text.Replace("{path_temp}", replacer(ModBase.pathTemp));
// 时间
if (replaceTime) // 在窗口标题中,时间会被后续动态替换,所以此时不应该替换
{
text = text.Replace("{date}", replacer(Lang.Date(DateTime.Now, "d")));
text = text.Replace("{time}", replacer(Lang.Date(DateTime.Now, "T")));
}
// Minecraft
text = text.Replace("{java}", replacer(ModLaunch.mcLaunchJavaSelected?.Installation.JavaFolder));
text = text.Replace("{minecraft}", replacer(ModFolder.mcFolderSelected));
if (ModInstanceList.McMcInstanceSelected is not null)
{
text = text.Replace("{version_path}", replacer(ModInstanceList.McMcInstanceSelected.PathInstance));
text = text.Replace("{verpath}", replacer(ModInstanceList.McMcInstanceSelected.PathInstance));
text = text.Replace("{version_indie}", replacer(ModInstanceList.McMcInstanceSelected.PathIndie));
text = text.Replace("{verindie}", replacer(ModInstanceList.McMcInstanceSelected.PathIndie));
text = text.Replace("{name}", replacer(ModInstanceList.McMcInstanceSelected.Name));
if (new[] { "unknown", "old", "pending" }.Contains(ModInstanceList.McMcInstanceSelected.Info.VanillaName))
{
text = text.Replace("{version}", replacer(ModInstanceList.McMcInstanceSelected.Name));
}
else
{
text = text.Replace("{version}", replacer(ModInstanceList.McMcInstanceSelected.Info.VanillaName));
}
}
else
{
text = text.Replace("{version_path}", replacer(null));
text = text.Replace("{verpath}", replacer(null));
text = text.Replace("{version_indie}", replacer(null));
text = text.Replace("{verindie}", replacer(null));
text = text.Replace("{name}", replacer(null));
text = text.Replace("{version}", replacer(null));
}
// 验证信息
if (ModLaunch.mcLoginLoader.State == ModBase.LoadState.Finished)
{
text = text.Replace("{user}", replacer(ModLaunch.mcLoginLoader.output.Name));
text = text.Replace("{uuid}", replacer(ModLaunch.mcLoginLoader.output.Uuid.ToLower()));
switch (ModLaunch.mcLoginLoader.input.LoginType)
{
case ModLaunch.McLoginType.Legacy:
text = text.Replace("{login}", replacer("离线"));
break;
case ModLaunch.McLoginType.Ms:
text = text.Replace("{login}", replacer("正版"));
break;
case ModLaunch.McLoginType.Auth:
text = text.Replace("{login}", replacer("Authlib-Injector"));
break;
}
}
else
{
text = text.Replace("{user}", replacer(null));
text = text.Replace("{uuid}", replacer(null));
text = text.Replace("{login}", replacer(null));
}
// 高级
text = ModBase.RegexReplaceEach(text, @"\{hint\}", m => replacer(PageToolsTest.GetRandomHint()));
text = ModBase.RegexReplaceEach(text, @"\{cave\}", m => replacer(PageToolsTest.GetRandomCave()));
text = ModBase.RegexReplaceEach(text, @"\{setup:([a-zA-Z0-9]+)\}", m =>
{
if (ConfigService.TryGetConfigItemNoType(m.Groups[1].Value, out var item) && item.Source != ConfigSource.SharedEncrypt)
return replacer(item.GetValueNoType(ModInstanceList.McMcInstanceSelected?.PathInstance)?.ToString() ?? "");
return replacer("");
});
text = ModBase.RegexReplaceEach(text, @"\{varible:([^:\}]+)(?::([^\}]+))?\}", m => replacer(CustomEvent.GetCustomVariable(m.Groups[1].Value, m.Groups[2].Value)));
text = ModBase.RegexReplaceEach(text, @"\{variable:([^:\}]+)(?::([^\}]+))?\}", m => replacer(CustomEvent.GetCustomVariable(m.Groups[1].Value, m.Groups[2].Value)));
return text;
}
#endregion
#region
private static bool isTaskTempCleared;
private static bool isTaskTempClearing;
/// <summary>
/// 尝试清理任务缓存文件夹。
/// 在整次运行中只会实际清理一次。
/// </summary>
public static void TryClearTaskTemp()
{
if (!isTaskTempCleared)
{
isTaskTempCleared = true;
isTaskTempClearing = true;
try
{
ModBase.Log("[System] 开始清理任务缓存文件夹");
ModBase.DeleteDirectory(Path.Combine(SystemPaths.DriveLetter, "ProgramData", "PCL", "TaskTemp"));
ModBase.DeleteDirectory($@"{ModBase.pathTemp}TaskTemp\");
ModBase.Log("[System] 已清理任务缓存文件夹");
}
catch (Exception ex)
{
ModBase.Log(ex, "清理任务缓存文件夹失败");
}
finally
{
isTaskTempClearing = false;
}
}
else if (isTaskTempClearing)
{
// 等待另一个清理步骤完成
while (isTaskTempClearing)
Thread.Sleep(1);
}
}
/// <summary>
/// 申请一个可用于任务缓存的临时文件夹,以 \ 结尾。这些文件夹无需进行后续清理。
/// 若所有缓存位置均没有权限,会抛出异常。
/// </summary>
/// <param name="requireNonSpace">是否要求路径不包含空格。</param>
public static string RequestTaskTempFolder(bool requireNonSpace = false)
{
TryClearTaskTemp();
string resultFolder;
do
{
try
{
resultFolder = $@"{ModBase.pathTemp}TaskTemp\{ModBase.GetUuid()}-{RandomUtils.NextInt(0, 1000000)}\";
if (requireNonSpace && resultFolder.Contains(" "))
break; // 带空格
Directory.CreateDirectory(resultFolder);
ModBase.CheckPermissionWithException(resultFolder);
return resultFolder;
}
catch
{
}
} while (false);
// 使用备用路径
resultFolder =
Path.Combine(SystemPaths.DriveLetter, "ProgramData", "PCL", "TaskTemp", $"{ModBase.GetUuid()}-{RandomUtils.NextInt(0, 1000000)}");
Directory.CreateDirectory(resultFolder);
ModBase.CheckPermission(resultFolder);
return resultFolder;
}
#endregion
public static void RaiseCustomEvent(DependencyObject control)
{
// 收集事件列表
var events = CustomEventService.GetEvents(control).ToList();
var eventType = CustomEventService.GetEventType(control);
if (eventType != EventType.None)
events.Add(new CustomEvent(eventType, CustomEventService.GetEventData(control)));
if (!events.Any()) return;
ModBase.RunInNewThread(() =>
{
foreach (var e in events)
e.Raise();
}, $"执行自定义事件 {ModBase.GetUuid()}");
}
}