Files
xyou f70b061d1a
CI / Go Backend (push) Canceled after 0s
feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

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

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

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
2026-08-08 14:07:56 +08:00

370 lines
15 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.IO;
using PCL.Core.App;
using PCL.Core.App.Configuration;
using PCL.Core.App.Localization;
using PCL.Core.Utils.OS;
namespace PCL
{
public class CustomEvent
{
public EventType Type { get; set; } = EventType.None;
public string Data { get; set; } = string.Empty;
public CustomEvent() { }
public CustomEvent(EventType type, string data)
{
Type = type;
Data = data;
}
public void Raise() => Raise(Type, Data);
/// <summary>
/// 静态入口:根据 EventType 分发给对应的 action 执行。
/// </summary>
public static void Raise(EventType type, string arg)
{
if (type == EventType.None) return;
ModBase.Log($"[Control] 执行自定义事件: {type}, {arg}");
try
{
if (ActionMap.TryGetValue(type, out var action))
action(arg, type);
else
ModMain.MyMsgBox(
Lang.Text("Event.Error.UnknownType", type.ToString()),
Lang.Text("Event.Error.Title"));
}
catch (Exception ex)
{
ModBase.Log(
ex,
Lang.Text("Event.Error.ExecutionFailed", type, arg),
ModBase.LogLevel.Msgbox,
userSummary: Lang.Text("Event.Error.ExecutionFailed", type, arg));
}
}
public static string GetCustomVariable(string name, string defaultValue = "") =>
States.CustomVariables.GetValueOrDefault(name, defaultValue);
/// <summary>
/// 将 arg 按 '|' 分割为参数数组,空串统一返回 [""]。
/// </summary>
private static string[] SplitArgs(string arg) => arg.Split('|');
/// <summary>
/// 将 \\n 替换为 Windows 换行符 \r\n。
/// </summary>
private static string FixNewlines(string s) => s.Replace("\\n", "\r\n");
/// <summary>
/// 禁止自定义主页写入的高危设置黑名单
/// </summary>
private static readonly HashSet<string> SecuritySensitiveSettingKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"LaunchAdvanceRun",
"VersionAdvanceRun"
};
/// <summary>
/// EventType → 执行逻辑 的字典映射,O(1) 分发。
/// </summary>
private static readonly Dictionary<EventType, Action<string, EventType>> ActionMap = new()
{
[EventType.OpenUrl] = _OpenUrl,
[EventType.OpenFile] = _OpenFileOrCommand,
[EventType.ExecuteCommand] = _OpenFileOrCommand,
[EventType.LaunchGame] = _LaunchGame,
[EventType.CopyText] = _CopyText,
[EventType.RefreshHomepage] = _Refresh,
[EventType.RefreshPage] = _Refresh,
[EventType.DailyFortune] = _DailyFortune,
[EventType.ClearTrash] = _ClearTrash,
[EventType.ShowDialog] = _ShowDialog,
[EventType.ShowHint] = _ShowHint,
[EventType.SwitchPage] = _SwitchPage,
[EventType.ImportModpack] = _ModpackInstall,
[EventType.InstallModpack] = _ModpackInstall,
[EventType.DownloadFile] = _DownloadFile,
[EventType.ModifySetting] = _WriteSetting,
[EventType.WriteSetting] = _WriteSetting,
[EventType.ModifyVariable] = _WriteVariable,
[EventType.WriteVariable] = _WriteVariable,
[EventType.OpenHelp] = (_, __) => ModBase.OpenWebsite("https://docs.pclc.cc/ce"),
};
/// <summary>
/// 打开网页。校验 https?:// 前缀,非 file 协议。
/// </summary>
private static void _OpenUrl(string arg, EventType type)
{
arg = arg.Replace('\\', '/');
if (!arg.Contains("://") || arg.StartsWithF("file", true))
{
ModMain.MyMsgBox(Lang.Text("Event.Error.UrlRequired"), Lang.Text("Event.Error.Title"));
return;
}
HintService.Hint(Lang.Text("Event.OpenUrl.Opening", arg));
ModBase.RunInThread(() => ModBase.OpenWebsite(arg));
}
/// <summary>
/// 打开文件 / 执行命令。复用 GetAbsoluteUrls 解析路径后 ProcessInterop.Start 启动。
/// </summary>
private static void _OpenFileOrCommand(string arg, EventType type)
{
var args = SplitArgs(arg);
ModBase.RunInThread(() =>
{
try
{
var urls = GetAbsoluteUrls(args[0], type);
if (!EventSafetyConfirm($"{urls[0]}{(args.Length >= 2 ? " " + args[1] : "")}"))
return;
ProcessInterop.Start(urls[0], args.Length >= 2 ? args[1] : "");
}
catch (Exception ex)
{
ModBase.Log(
ex,
Lang.Text("Event.Error.ExecutionFailed", type, arg),
ModBase.LogLevel.Msgbox,
userSummary: Lang.Text("Event.Error.ExecutionFailed", type, arg));
}
});
}
/// <summary>
/// 启动游戏。支持 \current 指代当前选中实例,可选 ServerIp。
/// </summary>
private static void _LaunchGame(string arg, EventType type)
{
var args = SplitArgs(arg);
if (args[0] == "\\current")
{
if (ModInstanceList.McMcInstanceSelected is null)
throw new InvalidOperationException(Lang.Text("Event.LaunchGame.SelectVersion"));
args[0] = ModInstanceList.McMcInstanceSelected.Name;
}
ModBase.RunInUi(() =>
{
var launchOptions = new ModLaunch.McLaunchOptions
{
ServerIp = args.Length >= 2 ? args[1] : null,
instance = new McInstance(args[0])
};
if (ModLaunch.McLaunchStart(launchOptions))
HintService.Hint(Lang.Text("Event.LaunchGame.Starting", args[0]));
});
}
/// <summary>
/// 复制文本到剪贴板。
/// </summary>
private static void _CopyText(string arg, EventType _) => ModBase.ClipboardSet(arg);
/// <summary>
/// 刷新主页 / 刷新当前页面。要求当前 pageRight 实现 IRefreshable。
/// </summary>
private static void _Refresh(string arg, EventType _)
{
if (ModMain.frmMain?.pageRight is IRefreshable refreshable)
{
ModBase.RunInUiWait(() => refreshable.Refresh());
if (string.IsNullOrEmpty(arg))
HintService.Hint(Lang.Text("Event.Refresh.Success"), HintType.Success);
}
else
HintService.Hint(Lang.Text("Event.Refresh.NotSupported"), HintType.Error);
}
/// <summary>
/// 今日人品。
/// </summary>
private static void _DailyFortune(string _, EventType __) => PageToolsTest.Jrrp();
/// <summary>
/// 清理垃圾。异步执行 RubbishClear。
/// </summary>
private static void _ClearTrash(string _, EventType __) =>
ModBase.RunInThread(PageToolsTest.RubbishClear);
/// <summary>
/// 弹出消息框。参数:Title|Content[|ButtonText]。
/// </summary>
private static void _ShowDialog(string arg, EventType type)
{
var args = SplitArgs(arg);
if (args.Length == 1)
throw new ArgumentException(Lang.Text("Event.Error.MissingArgs", type.ToString(), "Title|Content"));
ModMain.MyMsgBox(
FixNewlines(args[1]),
FixNewlines(args[0]),
args.Length > 2 ? args[2] : Lang.Text("Common.Action.Confirm"));
}
/// <summary>
/// 弹出提示条。参数:Message[|HintType]HintType = Info / Success / Error,兼容旧版 Finish / Critical)。
/// </summary>
private static void _ShowHint(string arg, EventType _)
{
var args = SplitArgs(arg);
var hintType = args.Length == 1
? HintType.Info
: (HintType)Enum.Parse(typeof(HintType), NormalizeHintTypeName(args[1]), true);
HintService.Hint(FixNewlines(args[0]), hintType);
}
/// <summary>
/// 将旧版 HintType 名称映射到当前名称,保持对旧主页的兼容。
/// </summary>
private static string NormalizeHintTypeName(string name) => name.ToLowerInvariant() switch
{
"finish" => nameof(HintType.Success),
"critical" => nameof(HintType.Error),
_ => name
};
/// <summary>
/// 切换页面。参数:PageType[|PageSubType]。
/// </summary>
private static void _SwitchPage(string arg, EventType _)
{
var args = SplitArgs(arg);
ModBase.RunInUi(() =>
{
var page = (FormMain.PageType)Enum.Parse(typeof(FormMain.PageType), args[0], true);
var sub = args.Length == 1
? FormMain.PageSubType.Default
: (FormMain.PageSubType)Enum.Parse(typeof(FormMain.PageSubType), args[1], true);
ModMain.frmMain?.PageChange(page, sub);
});
}
/// <summary>
/// 导入 / 安装整合包。触发 ModModpack.ModpackInstall()。
/// </summary>
private static void _ModpackInstall(string _, EventType __) =>
ModBase.RunInUi(ModModpack.ModpackInstall);
/// <summary>
/// 下载文件。参数:Url[|SavePath[|FileName]],校验 http/https 前缀并弹安全确认。
/// </summary>
private static void _DownloadFile(string arg, EventType _)
{
var args = SplitArgs(arg);
args[0] = args[0].Replace('\\', '/');
if (!args[0].StartsWithF("http://", true) && !args[0].StartsWithF("https://", true))
{
ModMain.MyMsgBox(Lang.Text("Event.Error.DownloadUrlRequired"), Lang.Text("Event.Error.Title"));
return;
}
if (!EventSafetyConfirm(Lang.Text("Event.Download.Confirm", args[0])))
return;
try
{
PageToolsTest.StartCustomDownload(args[0],
args.Length >= 2 ? args[1] : ModBase.GetFileNameFromPath(args[0]),
args.Length >= 3 ? args[2] : null);
}
catch
{
PageToolsTest.StartCustomDownload(args[0], Lang.Text("Common.State.Unknown"));
}
}
/// <summary>
/// 写入 / 修改设置。参数:SettingName|Value。
/// </summary>
private static void _WriteSetting(string arg, EventType type)
{
var args = SplitArgs(arg);
if (args.Length == 1)
throw new ArgumentException(Lang.Text("Event.Error.MissingArgs", type.ToString(), "SettingName|Value"));
if (ConfigService.TryGetConfigItemNoType(args[0], out var item) && item.Source != ConfigSource.SharedEncrypt)
{
if (!CanWriteSettingFromCustomEvent(args[0]))
return;
item.SetValueNoType(args[1], ModInstanceList.McMcInstanceSelected?.PathInstance);
}
if (args.Length == 2)
HintService.Hint(Lang.Text("Event.Setting.Written", args[0], args[1]), HintType.Success);
}
private static bool CanWriteSettingFromCustomEvent(string key)
{
if (!SecuritySensitiveSettingKeys.Contains(key))
return true;
ModBase.Log($"[Control] 已阻止自定义事件写入高危设置:{key}", ModBase.LogLevel.Developer);
HintService.Hint(Lang.Text("Event.Safety.DangerousSettingBlocked", key), HintType.Error);
return false;
}
/// <summary>
/// 写入 / 修改自定义变量。参数:VariableName|Value。
/// </summary>
private static void _WriteVariable(string arg, EventType type)
{
var args = SplitArgs(arg);
if (args.Length == 1)
throw new ArgumentException(Lang.Text("Event.Error.MissingArgs", type.ToString(), "VariableName|Value"));
States.CustomVariables[args[0]] = args[1];
States.CustomVariables = States.CustomVariables; // 触发 ConfigPropertyChanged
if (args.Length == 2)
HintService.Hint(Lang.Text("Event.Variable.Written", args[0], args[1]), HintType.Success);
}
public static string[] GetAbsoluteUrls(string relativeUrl, EventType type)
{
relativeUrl = relativeUrl.Replace('/', '\\').ToLower().TrimStart('\\');
var pclDir = Path.Combine(Basics.ExecutableDirectory, "PCL");
if (relativeUrl.Contains(":\\"))
{
ModBase.Log($"[Control] 自定义事件中由绝对路径 {type}: {relativeUrl}");
return [relativeUrl, pclDir];
}
if (File.Exists(Path.Combine(pclDir, relativeUrl)))
{
var fullPath = Path.Combine(pclDir, relativeUrl);
var resolved = Path.GetFullPath(fullPath);
if (!resolved.StartsWith(pclDir, StringComparison.OrdinalIgnoreCase))
throw new UnauthorizedAccessException(Lang.Text("Event.Error.FileNotFound", relativeUrl));
ModBase.Log($"[Control] 自定义事件中由相对 PCL 文件夹的路径 {type}: {fullPath}");
return [fullPath, pclDir];
}
if (type is EventType.OpenFile or EventType.ExecuteCommand)
{
ModBase.Log($"[Control] 自定义事件中直接 {type}: {relativeUrl}");
return [relativeUrl, pclDir];
}
throw new FileNotFoundException(Lang.Text("Event.Error.FileNotFound", relativeUrl), relativeUrl);
}
/// <summary>
/// 安全确认对话框。已由用户勾选"不再询问"时跳过。
/// </summary>
private static bool EventSafetyConfirm(string message)
{
if (States.Hint.HomepageCommand)
return true;
return ModMain.MyMsgBox(
Lang.Text("Event.Safety.ConfirmMessage", message),
Lang.Text("Event.Safety.Title"),
Lang.Text("Common.Action.Continue"),
Lang.Text("Event.Safety.ContinueAlways"),
Lang.Text("Common.Action.Cancel")) switch
{
1 => true,
2 => States.Hint.HomepageCommand = true,
_ => false,
};
}
}
}