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