Files
MRCC/launcher/PCL-CE/Plain Craft Launcher 2/Modules/Minecraft/ModInstanceList.cs
T
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

637 lines
28 KiB
C#
Raw 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;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using PCL.Core.App;
using PCL.Core.App.Localization;
using PCL.Core.UI;
using PCL.Core.Utils;
using PCL.Core.Utils.Exts;
using PCL.Network;
namespace PCL;
public static class ModInstanceList
{
#region 实例处理
public const int mcInstanceCacheVersion = 30;
private static object _McInstanceSelected_mcInstanceSelectedLast = 0; // 为 0 以保证与 Nothing 不相同,使得 UI 显示可以正常初始化
/// <summary>
/// 当前的 Minecraft 版本。
/// </summary>
public static McInstance McMcInstanceSelected
{
get => field;
set
{
if (ReferenceEquals(_McInstanceSelected_mcInstanceSelectedLast, value))
return;
field = value; // 由于有可能是 Nothing,导致无法初始化,才得这样弄一圈
_McInstanceSelected_mcInstanceSelectedLast = value;
if (value is null)
return;
// 重置缓存的 Mod 文件夹
PageDownloadCompDetail.cachedFolder.Clear();
}
}
/// <summary>
/// 当前按卡片分类的所有版本列表。
/// </summary>
public static Dictionary<McInstanceCardType, List<PCL.McInstance>> mcInstanceList = new();
#endregion
#region 实例列表加载
/// <summary>
/// 是否要求本次加载强制刷新实例列表。
/// </summary>
public static bool mcInstanceListForceRefresh;
/// <summary>
/// 是否为本次打开 PCL 后第一次加载实例列表。
/// 这会清理所有 .pclignore 文件,而非跳过这些对应实例。
/// </summary>
private static bool _isFirstMcInstanceListLoad = true;
/// <summary>
/// 加载 Minecraft 文件夹的实例列表。
/// </summary>
public static ModLoader.LoaderTask<string, int> mcInstanceListLoader =
new("Minecraft Instance List", InitMcInstanceList) { reloadTimeout = 1 };
private static void InitMcInstanceList(ModLoader.LoaderTask<string, int> loader)
{
var path = loader.input;
try
{
// 初始化
mcInstanceList = new Dictionary<McInstanceCardType, List<PCL.McInstance>>();
var versionsPath = Path.Combine(path, "versions");
var folderList = new List<string>();
// 读取版本文件夹
if (Directory.Exists(versionsPath))
try
{
foreach (var folder in new DirectoryInfo(versionsPath).GetDirectories())
folderList.Add(folder.Name);
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Error.CannotReadInstanceFolder", versionsPath), ex);
}
// 如果没有可用实例,清空缓存并跳过后续处理
if (!folderList.Any())
{
ModBase.WriteIni(Path.Combine(path, "PCL.ini"), "InstanceCache", "");
McMcInstanceSelected = null;
States.Game.SelectedInstance = "";
ModBase.Log("[Minecraft] 未找到可用 Minecraft 实例");
return;
}
// 根据文件夹名列表生成辨识码
var folderListHash = ModBase.GetHash(mcInstanceCacheVersion + "#" + string.Join("#", folderList));
var folderListCheck = (int)(folderListHash % (int.MaxValue - 1));
// 尝试使用缓存
var useCache = !mcInstanceListForceRefresh &&
ModBase.Val(ModBase.ReadIni(Path.Combine(path, "PCL.ini"), "InstanceCache")) ==
folderListCheck;
if (useCache)
{
var cachedResult = InitMcInstanceListWithCache(path);
if (cachedResult is not null)
mcInstanceList = cachedResult;
else
useCache = false; // 缓存无效,需要重载
}
// 如果不能使用缓存,重新加载
if (!useCache)
{
mcInstanceListForceRefresh = false;
ModBase.Log("[Minecraft] 文件夹列表变更或缓存无效,重载所有实例");
ModBase.WriteIni(Path.Combine(path, "PCL.ini"), "InstanceCache", folderListCheck.ToString());
mcInstanceList = InitMcInstanceListWithoutCache(path);
}
_isFirstMcInstanceListLoad = false;
if (loader.IsAborted)
return;
// 尝试读取已储存的选择
var savedSelection = ModBase.ReadIni(Path.Combine(path, "PCL.ini"), "Version");
if (!string.IsNullOrEmpty(savedSelection))
foreach (var card in mcInstanceList)
foreach (var instance in card.Value)
if ((instance.Name ?? "") == savedSelection && instance.state != McInstanceState.Error)
{
McMcInstanceSelected = instance;
States.Game.SelectedInstance = McMcInstanceSelected.Name;
ModBase.Log("[Minecraft] 选择该文件夹储存的 Minecraft 实例:" + McMcInstanceSelected.PathInstance);
return;
}
// 自动选择第一项
var firstInstance = mcInstanceList
.SelectMany(kv => kv.Value)
.FirstOrDefault(i => i.state != McInstanceState.Error);
if (firstInstance is not null)
{
McMcInstanceSelected = firstInstance;
States.Game.SelectedInstance = McMcInstanceSelected.Name;
ModBase.Log("[Launch] 自动选择 Minecraft 实例:" + McMcInstanceSelected.PathInstance);
}
else
{
McMcInstanceSelected = null;
States.Game.SelectedInstance = "";
ModBase.Log("[Minecraft] 未找到可用 Minecraft 实例");
}
// 调试延迟
if (Config.Debug.AddRandomDelay is bool debugDelay && debugDelay)
Thread.Sleep(RandomUtils.NextInt(200, 3000));
}
catch (ThreadInterruptedException)
{
// 中断线程时什么也不做
}
catch (Exception ex)
{
ModBase.WriteIni(Path.Combine(path, "PCL.ini"), "InstanceCache", ""); // 要求下次重新加载
ModBase.Log(
ex,
Lang.Text("Select.Instance.Error.ListLoad"),
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Select.Instance.Error.ListLoad"));
}
}
// 获取实例列表
private static Dictionary<McInstanceCardType, List<PCL.McInstance>> InitMcInstanceListWithCache(string path)
{
var results = new Dictionary<McInstanceCardType, List<PCL.McInstance>>();
try
{
var cardCount = int.Parse(ModBase.ReadIni(path + "PCL.ini", "CardCount", (-1).ToString()));
if (cardCount == -1)
return null;
for (int i = 0, loopTo = cardCount - 1; i <= loopTo; i++)
{
var cardType =
(McInstanceCardType)int.Parse(ModBase.ReadIni(path + "PCL.ini", "CardKey" + (i + 1),
"0"));
var instanceList = new List<PCL.McInstance>();
// 循环读取实例
foreach (var folder in ModBase.ReadIni(path + "PCL.ini", "CardValue" + (i + 1), ":").Split(":"))
{
if (string.IsNullOrEmpty(folder))
continue;
var versionFolder = $@"{path}versions\{folder}\";
if (File.Exists(versionFolder + ".pclignore"))
{
if (_isFirstMcInstanceListLoad)
{
ModBase.Log("[Minecraft] 清理残留的忽略项目:" + versionFolder); // #2781
File.Delete(versionFolder + ".pclignore");
}
else
{
ModBase.Log("[Minecraft] 跳过要求忽略的项目:" + versionFolder);
continue;
}
}
try
{
// 读取单个实例
var instance = new PCL.McInstance(versionFolder);
instanceList.Add(instance);
var instanceCfg = States.Instance;
instance.Desc = instanceCfg.CustomInfo[instance.PathInstance];
if (string.IsNullOrEmpty(instance.Desc))
instance.Desc = instanceCfg.Info[instance.PathInstance];
if (!instanceCfg.LogoPathConfig.IsDefault(instance.PathInstance))
instance.Logo = instanceCfg.LogoPath[instance.PathInstance];
if (!instanceCfg.ReleaseTimeConfig.IsDefault(instance.PathInstance))
instance.releaseTime = DateTime.Parse(instanceCfg.ReleaseTime[instance.PathInstance]);
if (!instanceCfg.StateConfig.IsDefault(instance.PathInstance))
instance.state =
(McInstanceState)(int)instanceCfg.State[instance.PathInstance];
instance.IsStar = instanceCfg.Starred[instance.PathInstance];
instance.displayType =
(McInstanceCardType)(int)instanceCfg.CardType[instance.PathInstance];
if (instance.state != McInstanceState.Error &&
!instanceCfg.VanillaVersionNameConfig.IsDefault(instance.PathInstance) &&
!instanceCfg.VanillaVersionConfig
.IsDefault(instance.PathInstance)) // 旧版本可能没有这一项,导致 Instance 不加载(#643
{
var instanceInfo = new McInstanceInfo
{
Fabric = instanceCfg.FabricVersion[instance.PathInstance],
LegacyFabric = instanceCfg.LegacyFabricVersion[instance.PathInstance],
Quilt = instanceCfg.QuiltVersion[instance.PathInstance],
Forge = instanceCfg.ForgeVersion[instance.PathInstance],
LabyMod = instanceCfg.LabyModVersion[instance.PathInstance],
NeoForge = instanceCfg.NeoForgeVersion[instance.PathInstance],
Cleanroom = instanceCfg.CleanroomVersion[instance.PathInstance],
OptiFine = instanceCfg.OptiFineVersion[instance.PathInstance],
HasLiteLoader = instanceCfg.HasLiteLoader[instance.PathInstance],
VanillaName = instanceCfg.VanillaVersionName[instance.PathInstance],
vanilla = new Version(instanceCfg.VanillaVersion[instance.PathInstance])
};
instanceInfo.HasFabric = instanceInfo.Fabric.Any();
instanceInfo.HasLegacyFabric = instanceInfo.LegacyFabric.Any();
instanceInfo.HasQuilt = instanceInfo.Quilt.Any();
instanceInfo.HasForge = instanceInfo.Forge.Any();
instanceInfo.HasNeoForge = instanceInfo.NeoForge.Any();
instanceInfo.HasCleanroom = instanceInfo.Cleanroom.Any();
instanceInfo.HasOptiFine = instanceInfo.OptiFine.Any();
instance.Info = instanceInfo;
}
// 重新检查错误实例
if (instance.state == McInstanceState.Error)
{
// 重新获取实例错误信息
var oldDesc = instance.Desc;
instance.state = McInstanceState.Original;
instance.Check();
// 校验错误原因是否改变
var customInfo = States.Instance.CustomInfo[instance.PathInstance];
if (instance.state == McInstanceState.Original || (string.IsNullOrEmpty(customInfo) &&
!((oldDesc ?? "") ==
(instance.Desc ?? ""))))
{
ModBase.Log("[Minecraft] 实例 " + instance.Name + " 的错误状态已变更,新的状态为:" + instance.Desc);
return null;
}
}
// 校验未加载的实例
if (string.IsNullOrEmpty(instance.Logo))
{
ModBase.Log("[Minecraft] 实例 " + instance.Name + " 未被加载");
return null;
}
}
catch (Exception ex)
{
ModBase.Log(ex, "读取实例加载缓存失败(" + folder + "");
return null;
}
}
if (instanceList.Any())
results.Add(cardType, instanceList);
}
return results;
}
catch (Exception ex)
{
ModBase.Log(ex, "读取实例缓存失败");
return null;
}
}
private static Dictionary<McInstanceCardType, List<PCL.McInstance>> InitMcInstanceListWithoutCache(string path)
{
var instanceList = new List<PCL.McInstance>();
#region 循环加载每个实例的信息
foreach (var folder in new DirectoryInfo(path + "versions").GetDirectories())
{
if (!folder.Exists || !folder.EnumerateFiles().Any())
{
ModBase.Log("[Minecraft] 跳过空文件夹:" + folder.FullName);
continue;
}
if ((folder.Name == "cache" || folder.Name == "BLClient" || folder.Name == "PCL") &&
!File.Exists(Path.Combine(folder.FullName, folder.Name + ".json")))
{
ModBase.Log("[Minecraft] 跳过可能不是实例文件夹的项目:" + folder.FullName);
continue;
}
var instanceFolder = folder.FullName + @"\";
if (File.Exists(instanceFolder + ".pclignore"))
{
if (_isFirstMcInstanceListLoad)
{
ModBase.Log("[Minecraft] 清理残留的忽略项目:" + instanceFolder); // #2781
try
{
File.Delete(instanceFolder + ".pclignore");
}
catch (Exception ex)
{
ModBase.Log(
ex,
Lang.Text("Select.Folder.Error.Cleanup", instanceFolder),
ModBase.LogLevel.Hint,
userSummary: Lang.Text("Select.Folder.Error.Cleanup", instanceFolder));
}
}
else
{
ModBase.Log("[Minecraft] 跳过要求忽略的项目:" + instanceFolder);
continue;
}
}
var instance = new PCL.McInstance(instanceFolder);
instanceList.Add(instance);
instance.Load();
}
#endregion
var results = new Dictionary<McInstanceCardType, List<PCL.McInstance>>();
#region 将实例分类到各个卡片
try
{
// 未经过自定义的实例列表
var instanceListOriginal = new Dictionary<McInstanceCardType, List<PCL.McInstance>>();
// 单独列出收藏的实例
var staredInstances = new List<PCL.McInstance>();
foreach (var instance in instanceList.ToList())
{
if (!instance.IsStar)
continue;
if (instance.displayType == McInstanceCardType.Hidden)
continue;
staredInstances.Add(instance);
instanceList.Remove(instance);
}
if (staredInstances.Any())
instanceListOriginal.Add(McInstanceCardType.Star, staredInstances);
// 预先筛选出愚人节和错误的实例
McInstanceFilter(ref instanceList, ref instanceListOriginal, new[] { McInstanceState.Error },
McInstanceCardType.Error);
McInstanceFilter(ref instanceList, ref instanceListOriginal, new[] { McInstanceState.Fool },
McInstanceCardType.Fool);
// 筛选 API 实例
McInstanceFilter(ref instanceList, ref instanceListOriginal,
new[]
{
McInstanceState.Forge, McInstanceState.NeoForge, McInstanceState.LiteLoader, McInstanceState.Fabric,
McInstanceState.LegacyFabric, McInstanceState.Quilt, McInstanceState.Cleanroom,
McInstanceState.LabyMod
}, McInstanceCardType.API);
// 将老实例预先分类入不常用,只剩余原版、快照、OptiFine
var instanceUseful = new List<PCL.McInstance>();
var instanceRubbish = new List<PCL.McInstance>();
McInstanceFilter(ref instanceList, new[] { McInstanceState.Old }, ref instanceRubbish);
// 确认最新实例,若为快照则加入常用列表
var latestInstance = instanceList
.Where(v => v.state == McInstanceState.Original || v.state == McInstanceState.Snapshot)
.MaxOrDefault(v => v.releaseTime);
if (latestInstance is not null && latestInstance.state == McInstanceState.Snapshot)
{
instanceUseful.Add(latestInstance);
instanceList.Remove(latestInstance);
}
// 将剩余的快照全部拖进不常用列表
McInstanceFilter(ref instanceList, new[] { McInstanceState.Snapshot }, ref instanceRubbish);
// 获取每个 Drop 下最新的原版与 OptiFine
var newerInstance = new Dictionary<string, PCL.McInstance>();
var existDrops = new List<int>();
foreach (var instance in instanceList)
{
if (!instance.Info.Valid)
continue;
if (!existDrops.Contains(instance.Info.Drop))
existDrops.Add(instance.Info.Drop);
var key = instance.Info.Drop + "-" + (int)instance.state;
if (!newerInstance.ContainsKey(key))
{
newerInstance.Add(key, instance);
continue;
}
if (instance.Info.HasOptiFine)
{
if (instance.Info.OptiFineCode > newerInstance[key].Info.OptiFineCode)
newerInstance[key] = instance; // OptiFine 根据版本号判断
}
else if (instance.releaseTime > newerInstance[key].releaseTime)
{
newerInstance[key] = instance; // 原版根据发布时间判断
}
}
// 将每个 Drop 下的最常规版本加入
foreach (var drop in existDrops)
if (newerInstance.ContainsKey(drop + "-" + (int)McInstanceState.OptiFine) &&
newerInstance.ContainsKey(drop + "-" + (int)McInstanceState.Original))
{
// 同时存在 OptiFine 与原版
var vanillaInstance = newerInstance[drop + "-" + (int)McInstanceState.Original];
var optiFineInstance = newerInstance[drop + "-" + (int)McInstanceState.OptiFine];
if (vanillaInstance.Info.Drop > optiFineInstance.Info.Drop)
{
// 仅在原版比 OptiFine 更新时才加入原版
instanceUseful.Add(vanillaInstance);
instanceList.Remove(vanillaInstance);
}
instanceUseful.Add(optiFineInstance);
instanceList.Remove(optiFineInstance);
}
else if (newerInstance.ContainsKey(drop + "-" + (int)McInstanceState.OptiFine))
{
// 没有原版,直接加入 OptiFine
instanceUseful.Add(newerInstance[drop + "-" + (int)McInstanceState.OptiFine]);
instanceList.Remove(newerInstance[drop + "-" + (int)McInstanceState.OptiFine]);
}
else if (newerInstance.ContainsKey(drop + "-" + (int)McInstanceState.Original))
{
// 没有 OptiFine,直接加入原版
instanceUseful.Add(newerInstance[drop + "-" + (int)McInstanceState.Original]);
instanceList.Remove(newerInstance[drop + "-" + (int)McInstanceState.Original]);
}
// 将剩余的东西添加进去
instanceRubbish.AddRange(instanceList);
if (instanceUseful.Any())
instanceListOriginal.Add(McInstanceCardType.OriginalLike, instanceUseful);
if (instanceRubbish.Any())
instanceListOriginal.Add(McInstanceCardType.Rubbish, instanceRubbish);
// 按照自定义实例分类重新添加
foreach (var instancePair in instanceListOriginal)
foreach (var instance in instancePair.Value)
{
var realType = instance.displayType == 0 || instancePair.Key == McInstanceCardType.Star
? instancePair.Key
: instance.displayType;
if (!results.ContainsKey(realType))
results.Add(realType, new List<PCL.McInstance>());
results[realType].Add(instance);
}
}
catch (Exception ex)
{
results.Clear();
ModBase.Log(
ex,
Lang.Text("Select.Instance.Error.Classify"),
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Select.Instance.Error.Classify"));
}
#endregion
#region 对卡片与实例进行排序
// 卡片排序
var sortedInstanceList = new Dictionary<McInstanceCardType, List<PCL.McInstance>>();
foreach (var sortRule in new[]
{
McInstanceCardType.Star, McInstanceCardType.API, McInstanceCardType.OriginalLike,
McInstanceCardType.Rubbish, McInstanceCardType.Fool, McInstanceCardType.Error,
McInstanceCardType.Hidden
})
if (results.ContainsKey(sortRule))
sortedInstanceList.Add(sortRule,
results[sortRule]);
results = sortedInstanceList;
// 版本排序
foreach (var cardType in new[]
{
McInstanceCardType.Star, McInstanceCardType.API, McInstanceCardType.OriginalLike,
McInstanceCardType.Rubbish, McInstanceCardType.Fool
})
{
if (!results.ContainsKey(cardType))
continue;
int getComponentCode(PCL.McInstance instance)
{
if (instance.Info.ForgelikeCode > 0)
return instance.Info.ForgelikeCode;
if (instance.Info.HasOptiFine)
return instance.Info.OptiFineCode;
return 0;
}
;
results[cardType] = SortUtils.Sort(results[cardType], (left, right) =>
{
// 发布时间
if ((left.releaseTime.Year >= 2000 || right.releaseTime.Year >= 2000) &&
left.releaseTime != right.releaseTime)
return left.releaseTime > right.releaseTime;
// 附加组件种类
if (left.Info.HasFabric != right.Info.HasFabric)
return left.Info.HasFabric;
if (left.Info.HasQuilt != right.Info.HasQuilt)
return left.Info.HasQuilt;
if (left.Info.HasLegacyFabric != right.Info.HasLegacyFabric)
return left.Info.HasLegacyFabric;
if (left.Info.HasNeoForge != right.Info.HasNeoForge)
return left.Info.HasNeoForge;
if (left.Info.HasForge != right.Info.HasForge)
return left.Info.HasForge;
if (left.Info.HasCleanroom != right.Info.HasCleanroom)
return left.Info.HasCleanroom;
if (left.Info.HasLabyMod != right.Info.HasLabyMod)
return left.Info.HasLabyMod;
if (left.Info.HasOptiFine != right.Info.HasOptiFine)
return left.Info.HasOptiFine;
if (left.Info.HasLiteLoader != right.Info.HasLiteLoader)
return left.Info.HasLiteLoader;
// 附加组件版本
if (getComponentCode(left) != getComponentCode(right))
return getComponentCode(left) > getComponentCode(right);
// 名称
return string.CompareOrdinal(left.Name, right.Name) > 0;
});
}
#endregion
#region 保存卡片缓存
ModBase.WriteIni(path + "PCL.ini", "CardCount", results.Count.ToString());
for (int i = 0, loopTo = results.Count - 1; i <= loopTo; i++)
{
ModBase.WriteIni(path + "PCL.ini", "CardKey" + (i + 1),
((int)results.Keys.ElementAtOrDefault(i)).ToString());
var value = "";
foreach (var Instance in results.Values.ElementAtOrDefault(i))
value += Instance.Name + ":";
ModBase.WriteIni(path + "PCL.ini", "CardValue" + (i + 1), value);
}
#endregion
return results;
}
/// <summary>
/// 筛选特定种类的实例,并直接添加为卡片。
/// </summary>
/// <param name="instanceList">用于筛选的列表。</param>
/// <param name="formula">需要筛选出的实例类型。-2 代表隐藏的实例。</param>
/// <param name="cardType">卡片的名称。</param>
private static void McInstanceFilter(ref List<PCL.McInstance> instanceList,
ref Dictionary<McInstanceCardType, List<PCL.McInstance>> target, McInstanceState[] formula,
McInstanceCardType cardType)
{
var keepList = instanceList.Where(v => formula.Contains(v.state)).ToList();
// 加入实例列表,并从剩余中删除
if (keepList.Any())
{
target.Add(cardType, keepList);
instanceList = instanceList.Except(keepList).ToList();
}
}
/// <summary>
/// 筛选特定种类的实例,并增加入一个已有列表中。
/// </summary>
/// <param name="instanceList">用于筛选的列表。</param>
/// <param name="formula">需要筛选出的实例类型。-2 代表隐藏的实例。</param>
/// <param name="keepList">传入需要增加入的列表。</param>
private static void McInstanceFilter(ref List<PCL.McInstance> instanceList, McInstanceState[] formula,
ref List<McInstance> keepList)
{
keepList.AddRange(instanceList.Where(v => formula.Contains(v.state)));
// 加入实例列表,并从剩余中删除
if (keepList.Any()) instanceList = instanceList.Except(keepList).ToList();
}
#endregion
}