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

323 lines
12 KiB
C#

using System.IO;
using CompFile = PCL.ModComp.CompFile;
using CompFileStatus = PCL.ModComp.CompFileStatus;
using CompLoaderType = PCL.ModComp.CompLoaderType;
using CompProject = PCL.ModComp.CompProject;
using LocalCompFile = PCL.ModLocalComp.LocalCompFile;
using PCL.Core.Minecraft.ResourceProject;
using PCL.Core.App.Localization;
using PCL.Network;
namespace PCL;
public static class ModCompDependency
{
public static ModDependencyRequest BuildRequest(
CompFile file,
CompProject project,
string targetMinecraftVersion,
List<CompLoaderType> targetLoaders,
string targetModsFolder)
{
ArgumentNullException.ThrowIfNull(file);
ArgumentNullException.ThrowIfNull(project);
targetLoaders ??= new List<CompLoaderType>();
var source = GetSource(project.FromCurseForge);
var dependencies = file.Dependencies
.Where(static dependencyId => !string.IsNullOrWhiteSpace(dependencyId))
.Select(dependencyId => new ModDependencyReference
{
ProjectId = dependencyId,
Source = source,
IsRequired = true,
})
.Concat(file.OptionalDependencies
.Where(static dependencyId => !string.IsNullOrWhiteSpace(dependencyId))
.Select(dependencyId => new ModDependencyReference
{
ProjectId = dependencyId,
Source = source,
IsRequired = false,
}))
.ToList();
return new ModDependencyRequest
{
TargetMinecraftVersion = targetMinecraftVersion ?? string.Empty,
TargetLoaders = ToLoaderNames(targetLoaders),
RequiredDependencies = dependencies,
InstalledMods = ScanInstalledMods(targetModsFolder),
ProjectResolver = ResolveProjectFiles,
};
}
public static List<InstalledModIdentity> ScanInstalledMods(string targetModsFolder)
{
var result = new List<InstalledModIdentity>();
if (string.IsNullOrWhiteSpace(targetModsFolder) || !Directory.Exists(targetModsFolder))
{
return result;
}
foreach (var path in Directory.GetFiles(targetModsFolder))
{
if (!LocalCompFile.IsModFile(path))
{
continue;
}
var localFile = new LocalCompFile(path);
localFile.Load();
var source = localFile.Comp is null ? null : GetSource(localFile.Comp.FromCurseForge);
var gameVersions = localFile.compFile?.GameVersions?.Where(static version => !string.IsNullOrWhiteSpace(version)).ToList()
?? new List<string>();
var loaders = ToLoaderNames(localFile.compFile?.ModLoaders);
if (!string.IsNullOrWhiteSpace(localFile.Comp?.Id) && !string.IsNullOrWhiteSpace(source))
{
result.Add(new InstalledModIdentity
{
SourceProjectId = localFile.Comp.Id,
Source = source,
ModId = localFile.ModId,
GameVersions = gameVersions,
Loaders = loaders,
});
continue;
}
if (!string.IsNullOrWhiteSpace(localFile.compFile?.ProjectId))
{
var fileSource = GetSource(localFile.compFile.FromCurseForge);
result.Add(new InstalledModIdentity
{
SourceProjectId = localFile.compFile.ProjectId,
Source = fileSource,
ModId = localFile.ModId,
GameVersions = gameVersions,
Loaders = loaders,
});
continue;
}
result.Add(new InstalledModIdentity
{
SourceProjectId = null,
Source = null,
ModId = localFile.ModId,
GameVersions = gameVersions,
Loaders = loaders,
});
}
return result;
}
public static ModDependencyProject? ResolveProjectFiles(string source, string projectId)
{
if (string.IsNullOrWhiteSpace(source) || string.IsNullOrWhiteSpace(projectId))
{
return null;
}
var fromCurseForge = string.Equals(source, "CurseForge", StringComparison.OrdinalIgnoreCase);
var files = ModComp.CompFilesGet(projectId, fromCurseForge);
if (!ModComp.compProjectCache.TryGetValue(projectId, out var compProject))
{
return null;
}
if (compProject.FromCurseForge != fromCurseForge)
{
return null;
}
return new ModDependencyProject
{
ProjectId = compProject.Id,
Source = source,
ProjectName = compProject.TranslatedName ?? compProject.RawName,
Files = files.Select(compFile => new ModDependencyFile
{
Id = compFile.Id,
DisplayName = compFile.DisplayName,
Version = compFile.Version,
GameVersions = compFile.GameVersions?.Where(static version => !string.IsNullOrWhiteSpace(version)).ToList()
?? new List<string>(),
Loaders = ToLoaderNames(compFile.ModLoaders),
ReleaseType = MapReleaseType(compFile.Status),
ReleaseDate = compFile.ReleaseDate,
RequiredDependencies = compFile.Dependencies
.Where(static dependencyId => !string.IsNullOrWhiteSpace(dependencyId))
.Select(dependencyId => new ModDependencyReference
{
ProjectId = dependencyId,
Source = source,
IsRequired = true,
})
.ToList(),
}).ToList(),
};
}
public static ModDependencyFile? SelectCompatibleDependencyFile(
ModDependencyResolutionResult result,
string projectId,
string source)
{
ArgumentNullException.ThrowIfNull(result);
return result.ToInstall
.FirstOrDefault(install =>
string.Equals(install.ProjectId, projectId, StringComparison.OrdinalIgnoreCase)
&& string.Equals(install.Source, source, StringComparison.OrdinalIgnoreCase))
?.File;
}
public static List<(string Filename, DownloadFile File)> BuildDependencyDownloads(
ModDependencyResolutionResult result,
string targetModsFolder)
{
ArgumentNullException.ThrowIfNull(result);
var downloads = new List<(string, DownloadFile)>();
foreach (var install in result.ToInstall.AsEnumerable().Reverse())
{
if (!ModComp.compProjectCache.TryGetValue(install.ProjectId, out var depProject))
{
continue;
}
var fromCurseForge = string.Equals(install.Source, "CurseForge", StringComparison.OrdinalIgnoreCase);
if (depProject.FromCurseForge != fromCurseForge)
{
continue;
}
var depCompFile = ModComp.CompFilesGet(install.ProjectId, fromCurseForge)
.FirstOrDefault(file => string.Equals(file.Id, install.File.Id, StringComparison.OrdinalIgnoreCase));
if (depCompFile is null)
{
continue;
}
var depFileName = ModComp.CompFileNameGet(depProject, depCompFile);
var targetPath = Path.Combine(targetModsFolder ?? string.Empty, depFileName);
downloads.Add((depFileName, depCompFile.ToNetFile(targetPath, ModComp.DownloadReason.Dependency)));
}
return downloads;
}
/// <summary>
/// Shows confirmation dialog for required dependency installs.
/// Returns: CompDepsInstallTypes.WithDeps if user chooses to install with deps,
/// CompDepsInstallTypes.WithoutDeps if user chooses to install without deps,
/// CompDepsInstallTypes.Cancel if user cancels,
/// CompDepsInstallTypes.Unresolved if there are unresolved required deps.
/// </summary>
public static ModComp.CompDepsInstallTypes ConfirmDependencyInstall(ModDependencyResolutionResult result)
{
ArgumentNullException.ThrowIfNull(result);
if (result.Unresolved is { Count: > 0 })
{
ModBase.Log($"[CompDeps] 无法解析: {result.Unresolved.Count} 个必需前置");
var dependencies = string.Join(
Environment.NewLine,
result.Unresolved.Select(dep => Lang.Text(
"Download.Comp.Dependency.Unresolved.ListItem",
dep.Source,
dep.ProjectId,
dep.Reason)));
var message = Lang.Text("Download.Comp.Dependency.Unresolved.Message", dependencies);
var selectedButton = ModMain.MyMsgBox(
message,
Lang.Text("Download.Comp.Dependency.Unresolved.Title"),
Lang.Text("Download.Comp.Dependency.Unresolved.ContinueWithoutDependencies"),
Lang.Text("Common.Action.Cancel"),
isWarn: true,
forceWait: true);
return selectedButton == 1
? ModComp.CompDepsInstallTypes.Unresolved
: ModComp.CompDepsInstallTypes.Cancel;
}
if (result.ToInstall is { Count: > 0 })
{
var dependencies = string.Join(
Environment.NewLine,
result.ToInstall.Select(install => Lang.Text(
"Download.Comp.Dependency.Install.ListItem",
install.ProjectName,
install.Source,
install.File.DisplayName,
install.File.Version)));
var message = Lang.Text("Download.Comp.Dependency.Install.Message", dependencies);
var dialogResult = ModMain.MyMsgBox(
message,
Lang.Text("Download.Comp.Dependency.Install.Title"),
Lang.Text("Download.Comp.Dependency.Install.WithDependencies"),
Lang.Text("Download.Comp.Dependency.Install.WithoutDependencies"),
Lang.Text("Download.Comp.Dependency.Install.Cancel"),
forceWait: true);
return dialogResult switch
{
1 => ModComp.CompDepsInstallTypes.WithDeps,
2 => ModComp.CompDepsInstallTypes.WithoutDeps,
3 => ModComp.CompDepsInstallTypes.Cancel,
_ => ModComp.CompDepsInstallTypes.Cancel
};
}
return ModComp.CompDepsInstallTypes.WithDeps;
}
/// <summary>
/// Shows abort message when dependency resolution was cancelled by user or failed.
/// </summary>
public static void ShowDependencyAbortMessage(string reason)
{
ModMain.MyMsgBox(
Lang.Text("Download.Comp.Dependency.Abort.Message", reason),
Lang.Text("Download.Comp.Dependency.Abort.Title"),
Lang.Text("Common.Action.Confirm"),
isWarn: false,
forceWait: true);
}
private static string GetSource(bool fromCurseForge)
{
return fromCurseForge ? "CurseForge" : "Modrinth";
}
private static List<string> ToLoaderNames(IEnumerable<CompLoaderType>? loaders)
{
if (loaders is null)
{
return new List<string>();
}
return loaders
.Where(static loader => loader != CompLoaderType.Any)
.Select(static loader => loader.ToString())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
}
private static int MapReleaseType(CompFileStatus status)
{
return status switch
{
CompFileStatus.Release => 1,
CompFileStatus.Beta => 2,
CompFileStatus.Alpha => 3,
_ => 1,
};
}
}