Skip to content

feat: 自动安装模组前置#2789

Open
SALTWOOD wants to merge 3 commits intodevfrom
feat/auto-pre-deps
Open

feat: 自动安装模组前置#2789
SALTWOOD wants to merge 3 commits intodevfrom
feat/auto-pre-deps

Conversation

@SALTWOOD
Copy link
Copy Markdown
Member

@SALTWOOD SALTWOOD commented May 3, 2026

Close #557

Summary by Sourcery

在模组下载过程中,新增对所需模组依赖的自动解析和安装功能,包括配置项、UI 开关、解析核心逻辑以及相关辅助工具。

新功能:

  • 在安装模组时,基于已选择的实例、Minecraft 版本和加载器,自动解析并下载所需的模组依赖。
  • 在下载设置界面中提供用户可配置选项,用于启用或禁用模组依赖的自动安装。

错误修复:

  • 正确反序列化组件文件中的可选依赖字段,避免将必需依赖和可选依赖混淆。

改进内容:

  • 在目标模组文件夹中追踪已安装的模组,以跳过已满足的依赖,避免重复下载。
  • 为资源项目引入依赖解析引擎,用于选择兼容文件,并处理递归、循环依赖以及依赖来源分离等情况。
  • 将依赖解析结果集成到现有下载流水线中,在用户确认后,通过将依赖文件扩展加入下载队列来实现。

测试:

  • 新增单元测试,覆盖模组依赖解析器在递归、去重、循环依赖、缺失项目、可选依赖、已安装兼容性以及版本发布选择等方面的行为。
Original summary in English

Summary by Sourcery

Add automatic resolution and installation of required mod dependencies during mod downloads, including configuration, UI toggle, resolver core, and supporting utilities.

New Features:

  • Automatically resolve and download required mod dependencies when installing mods, based on the selected instance, Minecraft version, and loaders.
  • Provide a user-configurable option to enable or disable automatic installation of mod dependencies in the download settings UI.

Bug Fixes:

  • Correctly deserialize optional dependency fields for component files to avoid mixing required and optional dependencies.

Enhancements:

  • Track installed mods in the target mods folder to skip already satisfied dependencies and avoid duplicate downloads.
  • Introduce a dependency resolution engine for resource projects that selects compatible files, handles recursion, cycles, and source separation.
  • Integrate dependency resolution results into the existing download pipeline by expanding the download queue with dependency files when confirmed by the user.

Tests:

  • Add unit tests covering the mod dependency resolver’s behavior for recursion, deduplication, cycles, missing projects, optional dependencies, installed compatibility, and release selection.

@pcl-ce-automation pcl-ce-automation Bot added 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查 size: XXL PR 大小评估:巨型 labels May 3, 2026
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 3, 2026

Reviewer's Guide

在模组下载过程中实现所需前置模组依赖的自动安装,包括可复用的依赖解析引擎、与下载流程的集成、配置和 UI 开关,以及单元测试。

带自动依赖安装的模组下载顺序图

sequenceDiagram
    actor User
    participant PageDownloadCompDetail
    participant ModCompDependency
    participant ModDependencyResolver
    participant ModMain
    participant ModLoader

    User->>PageDownloadCompDetail: Click_Save_for_mod_download
    PageDownloadCompDetail->>PageDownloadCompDetail: Determine_targetDir_and_AllowedLoaders
    alt AutoInstallDependencies_enabled_and_mod_has_Dependencies
        PageDownloadCompDetail->>PageDownloadCompDetail: Detect_targetInstance_and_MC_version
        PageDownloadCompDetail->>ModCompDependency: BuildRequest(file, project, mcVersion, targetLoaders, targetDir)
        ModCompDependency-->>PageDownloadCompDetail: ModDependencyRequest

        PageDownloadCompDetail->>ModDependencyResolver: Resolve(request)
        ModDependencyResolver-->>PageDownloadCompDetail: ModDependencyResolutionResult

        PageDownloadCompDetail->>ModCompDependency: ConfirmDependencyInstall(result)
        ModCompDependency->>ModMain: MyMsgBox(confirm_message)
        ModMain-->>ModCompDependency: user_choice
        ModCompDependency-->>PageDownloadCompDetail: bool_confirmed
        alt User_confirms
            PageDownloadCompDetail->>ModCompDependency: BuildDependencyDownloads(result, targetDir)
            ModCompDependency-->>PageDownloadCompDetail: List_DownloadFile_depDownloads
            PageDownloadCompDetail->>PageDownloadCompDetail: Prepend_depDownloads_to_downloadFiles
        else User_cancels
            PageDownloadCompDetail-->>User: Abort_download
            PageDownloadCompDetail->>PageDownloadCompDetail: return
        end
    else AutoInstallDependencies_disabled_or_no_Dependencies
        PageDownloadCompDetail->>PageDownloadCompDetail: Prepare_single_mod_downloadFile
    end

    PageDownloadCompDetail->>ModLoader: Start_LoaderDownload("下载文件", downloadFiles)
    ModLoader-->>User: Download_mod_and_dependencies
Loading

ModDependencyResolver 及相关类型类图

classDiagram
    class ModDependencyReference {
        +string ProjectId
        +string Source
        +bool IsRequired
    }

    class ModDependencyRequest {
        +string TargetMinecraftVersion
        +List~string~ TargetLoaders
        +List~ModDependencyReference~ RequiredDependencies
        +List~InstalledModIdentity~ InstalledMods
        +Func~string,string,ModDependencyProject?~ ProjectResolver
    }

    class ModDependencyProject {
        +string ProjectId
        +string Source
        +string ProjectName
        +List~ModDependencyReference~ RequiredDependencies
        +List~ModDependencyFile~ Files
    }

    class ModDependencyFile {
        +string Id
        +string DisplayName
        +string Version
        +List~string~ GameVersions
        +List~string~ Loaders
        +int ReleaseType
        +DateTime ReleaseDate
        +List~ModDependencyReference~ RequiredDependencies
    }

    class InstalledModIdentity {
        +string SourceProjectId
        +string Source
        +string ModId
        +List~string~ GameVersions
        +List~string~ Loaders
    }

    class ModDependencyResolutionResult {
        +List~ResolvedDependencyInstall~ ToInstall
        +List~UnresolvedDependency~ Unresolved
        +List~IgnoredDependency~ Satisfied
    }

    class ResolvedDependencyInstall {
        +string ProjectId
        +string Source
        +string ProjectName
        +ModDependencyFile File
    }

    class UnresolvedDependency {
        +string ProjectId
        +string Source
        +string Reason
    }

    class IgnoredDependency {
        +string ProjectId
        +string Source
        +string Reason
    }

    class ModDependencyResolver {
        +Resolve(request ModDependencyRequest) ModDependencyResolutionResult
        -ResolveDependency(context ResolutionContext, dependency ModDependencyReference, depth int) void
        -SelectBestFile(files IEnumerable~ModDependencyFile~, targetMinecraftVersion string, targetLoaders HashSet~string~) ModDependencyFile
        -IsCompatibleFile(file ModDependencyFile, targetMinecraftVersion string, targetLoaders HashSet~string~) bool
        -HasExactGameVersionMatch(file ModDependencyFile, targetMinecraftVersion string) bool
        -HasLoaderMatch(file ModDependencyFile, targetLoaders HashSet~string~) bool
        -NormalizeReleaseType(releaseType int) int
    }

    class ResolutionContext {
        +ModDependencyRequest Request
        +ModDependencyResolutionResult Result
        +HashSet~string~ Visited
        +string TargetMinecraftVersion
        +HashSet~string~ TargetLoaders
        +GetVisitedKey(projectId string, source string) string
        +IsInstalledCompatible(projectId string, source string) bool
        +AddInstall(project ModDependencyProject, file ModDependencyFile) void
        +AddUnresolved(projectId string, source string, reason string) void
        +AddSatisfied(projectId string, source string, reason string) void
        -bool LoadersCompatible(installedLoaders List~string~)
        -string GetProjectKey(projectId string, source string)
    }

    ModDependencyResolver --> ResolutionContext : uses
    ModDependencyRequest --> ModDependencyReference : contains
    ModDependencyRequest --> InstalledModIdentity : contains
    ModDependencyRequest --> ModDependencyProject : uses via ProjectResolver

    ModDependencyProject --> ModDependencyReference : contains
    ModDependencyProject --> ModDependencyFile : contains

    ModDependencyFile --> ModDependencyReference : contains

    ModDependencyResolutionResult --> ResolvedDependencyInstall : contains
    ModDependencyResolutionResult --> UnresolvedDependency : contains
    ModDependencyResolutionResult --> IgnoredDependency : contains

    ResolvedDependencyInstall --> ModDependencyFile : contains

    ResolutionContext --> ModDependencyRequest : has
    ResolutionContext --> ModDependencyResolutionResult : builds
    ResolutionContext --> InstalledModIdentity : checks compatibility
Loading

ModCompDependency 集成层类图

classDiagram
    class CompFile {
        +string Id
        +string DisplayName
        +string Version
        +List~string~ GameVersions
        +List~CompLoaderType~ ModLoaders
        +List~string~ Dependencies
        +List~string~ OptionalDependencies
        +CompType Type
        +List~string~ ToNetFile(target string)
    }

    class CompProject {
        +string Id
        +bool FromCurseForge
        +string TranslatedName
        +string RawName
    }

    class LocalCompFile {
        +string ModId
        +CompProject Comp
        +CompFile CompFile
        +Load() void
        +static IsModFile(path string) bool
    }

    class ModCompDependency {
        <<static>>
        +BuildRequest(file CompFile, project CompProject, targetMinecraftVersion string, targetLoaders List~CompLoaderType~, targetModsFolder string) ModDependencyRequest
        +ScanInstalledMods(targetModsFolder string) List~InstalledModIdentity~
        +ResolveProjectFiles(source string, projectId string) ModDependencyProject
        +SelectCompatibleDependencyFile(result ModDependencyResolutionResult, projectId string, source string) ModDependencyFile
        +BuildDependencyDownloads(result ModDependencyResolutionResult, targetModsFolder string) List~DownloadFile~
        +ConfirmDependencyInstall(result ModDependencyResolutionResult) bool
        +ShowDependencyAbortMessage(reason string) void
        -string GetSource(fromCurseForge bool)
        -List~string~ ToLoaderNames(loaders IEnumerable~CompLoaderType~)
        -int MapReleaseType(status CompFileStatus)
    }

    class DownloadFile {
    }

    class ModComp {
        +static Dictionary~string,CompProject~ CompProjectCache
        +static List~CompFile~ CompFilesGet(projectId string, fromCurseForge bool)
        +static string CompFileNameGet(project CompProject, file CompFile)
    }

    class InstalledModIdentity {
        +string SourceProjectId
        +string Source
        +string ModId
        +List~string~ GameVersions
        +List~string~ Loaders
    }

    class ModDependencyRequest {
    }

    class ModDependencyProject {
    }

    class ModDependencyFile {
    }

    class ModDependencyResolutionResult {
        +List~ResolvedDependencyInstall~ ToInstall
        +List~UnresolvedDependency~ Unresolved
        +List~IgnoredDependency~ Satisfied
    }

    class ModMain {
        +static int MyMsgBox(message string, title string, Button1 string, Button2 string, bool IsWarn, bool ForceWait)
    }

    ModCompDependency --> ModDependencyRequest : builds
    ModCompDependency --> InstalledModIdentity : returns
    ModCompDependency --> ModDependencyProject : returns
    ModCompDependency --> ModDependencyFile : returns
    ModCompDependency --> ModDependencyResolutionResult : consumes
    ModCompDependency --> DownloadFile : builds
    ModCompDependency --> ModMain : shows dialogs

    ModCompDependency --> CompFile : from
    ModCompDependency --> CompProject : from
    ModCompDependency --> LocalCompFile : scans

    ModComp --> CompProject : caches
    ModComp --> CompFile : provides
    ModCompDependency --> ModComp : queries caches
Loading

文件级变更

Change Details Files
在下载单个模组文件时,将自动依赖解析与安装集成进模组下载流程。
  • 引入 DownloadFile 集合,以便可以在主模组下载项之前插入依赖文件的下载项。
  • 从所选模组文件夹和所选实例中检测目标实例和加载器,并在需要时加载实例元数据。
  • 基于当前文件/项目构建 ModDependencyRequest,解析依赖,与用户确认,并将依赖下载项合并到主文件之前。
  • 当依赖解析失败、被用户取消或抛出异常时,短路下载流程,并记录日志用于诊断。
  • 在实例加载器不可用时,复用之前计算出的允许加载器类型作为回退。
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.cs
修复从 API JSON 解析可选依赖到 CompFile 模型的逻辑。
  • 将 RawOptionalDependencies JSON 字段映射到 RawOptionalDependencies 属性,而不是 RawDependencies。
  • 将 OptionalDependencies JSON 字段映射到 OptionalDependencies 属性,而不是 Dependencies。
Plain Craft Launcher 2/Modules/Minecraft/ModComp.cs
为模组添加配置项和 UI 开关,以启用/禁用自动依赖安装。
  • 新增 Download.Comp.AutoInstallDependencies 配置标志,默认值为 true。
  • 将该标志绑定到 PageSetupGameManage 中复选框的状态。
  • 在游戏管理设置 XAML 中新增对应的 CheckDownloadAutoInstallDependencies 控件。
PCL.Core/App/Config.cs
Plain Craft Launcher 2/Pages/PageSetup/PageSetupGameManage.xaml.cs
Plain Craft Launcher 2/Pages/PageSetup/PageSetupGameManage.xaml
引入通用的模组依赖解析引擎,包括模型类型和选择规则。
  • 为依赖引用、项目、文件、已安装模组标识以及解析结果定义记录类型。
  • 实现 ModDependencyResolver,包含有界递归、访问去重,以及对必需和可选依赖的区分处理。
  • 基于精确的 Minecraft 版本和加载器交集进行兼容性检查,并在数据缺失时进行友好处理。
  • 实现文件选择规则:优先精确游戏版本、匹配加载器、稳定版本优先于 beta/alpha、并按发布时间最新优先。
  • 按项目和环境键跟踪并去重待安装项、未解析依赖以及已满足/忽略的依赖。
PCL.Core/Minecraft/ResourceProject/ModDependencyResolver.cs
将启动器数据结构与依赖解析器桥接,并为已解析依赖构建具体下载任务。
  • 基于 CompFile/CompProject 构建 ModDependencyRequest,使用依赖 ID、加载器映射以及项目解析回调。
  • 通过 LocalCompFile 扫描目标模组文件夹中的已安装模组,推断来源、版本和加载器信息。
  • 使用现有的 CompProjectCache 和 CompFilesGet 解析依赖的项目文件,并映射到解析器 DTO 和发布状态。
  • 将解析结果转换为 DownloadFile 实例,并排序以确保依赖先于被依赖的模组下载。
  • 在存在未解析的必需依赖或用户取消安装时,提供面向用户的确认与中止提示信息。
Plain Craft Launcher 2/Modules/Minecraft/ModCompDependency.cs
为依赖解析引擎行为添加单元测试覆盖。
  • 测试将单个缺失的必需依赖解析为单个安装项。
  • 验证在未请求任何依赖且未调用解析器时的空操作行为。
  • 覆盖递归、共享和循环依赖图,包括去重和递归深度控制。
  • 验证在未解析必需依赖、已安装兼容/不兼容模组以及被忽略的可选依赖等场景下的行为。
  • 测试文件选择规则,包括最新稳定版优先,以及在 Modrinth 与 CurseForge 之间按来源区分。
PCL.Core.Test/Minecraft/ResourceProject/ModDependencyResolverTest.cs

与关联 Issue 的对照评估

Issue Objective Addressed Explanation
#557 在安装模组时,检测目标模组文件夹中缺失的必需依赖,并在安装模组的同时自动下载/安装这些依赖。
#557 添加一个用户可配置的设置,用于启用或禁用模组依赖的自动安装。
#557 正确解析并存储来自模组文件 JSON 的依赖元数据(包括可选依赖),以确保依赖处理按预期工作。

可能相关的 Issue

  • 自动安装模组前置 #557:PR 新增自动解析与下载安装必需前置模组功能,完整覆盖 Issue 请求的一键前置下载需求。

技巧与命令

与 Sourcery 交互

  • 触发新一次代码审查: 在 Pull Request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 由审查评论生成 GitHub Issue: 回复 Sourcery 的审查评论,要求创建 Issue。也可以直接在评论中回复 @sourcery-ai issue 来从该评论创建 Issue。
  • 生成 Pull Request 标题: 在 Pull Request 标题任意位置写上 @sourcery-ai,即可随时生成标题。也可以在 Pull Request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 Pull Request 摘要: 在 Pull Request 正文任意位置写上 @sourcery-ai summary,即可在对应位置生成 PR 摘要。也可以在 Pull Request 中评论 @sourcery-ai summary 来(重新)生成摘要。
  • 生成审查者指南: 在 Pull Request 中评论 @sourcery-ai guide,即可(重新)生成审查者指南。
  • 一次性解决所有 Sourcery 评论: 在 Pull Request 中评论 @sourcery-ai resolve 来将所有 Sourcery 评论标记为已解决。如果你已经处理完这些评论,并且不希望再看到它们,这会很有用。
  • 清除所有 Sourcery 审查记录: 在 Pull Request 中评论 @sourcery-ai dismiss 来清除所有现有的 Sourcery 审查记录。如果你想基于最新提交从头开始审查,尤其有用——别忘了随后再评论 @sourcery-ai review 触发新一轮审查!

自定义你的使用体验

访问你的 控制面板 来:

  • 启用或禁用诸如 Sourcery 生成的 PR 摘要、审查者指南等审查功能。
  • 修改审查语言。
  • 添加、移除或编辑自定义审查指令。
  • 调整其他审查相关设置。

获取帮助

Original review guide in English

Reviewer's Guide

Implements automatic installation of required mod dependencies during mod downloads, including a reusable dependency resolution engine, wiring it into the download flow, configuration and UI toggles, and unit tests.

Sequence diagram for mod download with automatic dependency installation

sequenceDiagram
    actor User
    participant PageDownloadCompDetail
    participant ModCompDependency
    participant ModDependencyResolver
    participant ModMain
    participant ModLoader

    User->>PageDownloadCompDetail: Click_Save_for_mod_download
    PageDownloadCompDetail->>PageDownloadCompDetail: Determine_targetDir_and_AllowedLoaders
    alt AutoInstallDependencies_enabled_and_mod_has_Dependencies
        PageDownloadCompDetail->>PageDownloadCompDetail: Detect_targetInstance_and_MC_version
        PageDownloadCompDetail->>ModCompDependency: BuildRequest(file, project, mcVersion, targetLoaders, targetDir)
        ModCompDependency-->>PageDownloadCompDetail: ModDependencyRequest

        PageDownloadCompDetail->>ModDependencyResolver: Resolve(request)
        ModDependencyResolver-->>PageDownloadCompDetail: ModDependencyResolutionResult

        PageDownloadCompDetail->>ModCompDependency: ConfirmDependencyInstall(result)
        ModCompDependency->>ModMain: MyMsgBox(confirm_message)
        ModMain-->>ModCompDependency: user_choice
        ModCompDependency-->>PageDownloadCompDetail: bool_confirmed
        alt User_confirms
            PageDownloadCompDetail->>ModCompDependency: BuildDependencyDownloads(result, targetDir)
            ModCompDependency-->>PageDownloadCompDetail: List_DownloadFile_depDownloads
            PageDownloadCompDetail->>PageDownloadCompDetail: Prepend_depDownloads_to_downloadFiles
        else User_cancels
            PageDownloadCompDetail-->>User: Abort_download
            PageDownloadCompDetail->>PageDownloadCompDetail: return
        end
    else AutoInstallDependencies_disabled_or_no_Dependencies
        PageDownloadCompDetail->>PageDownloadCompDetail: Prepare_single_mod_downloadFile
    end

    PageDownloadCompDetail->>ModLoader: Start_LoaderDownload("下载文件", downloadFiles)
    ModLoader-->>User: Download_mod_and_dependencies
Loading

Class diagram for ModDependencyResolver and related types

classDiagram
    class ModDependencyReference {
        +string ProjectId
        +string Source
        +bool IsRequired
    }

    class ModDependencyRequest {
        +string TargetMinecraftVersion
        +List~string~ TargetLoaders
        +List~ModDependencyReference~ RequiredDependencies
        +List~InstalledModIdentity~ InstalledMods
        +Func~string,string,ModDependencyProject?~ ProjectResolver
    }

    class ModDependencyProject {
        +string ProjectId
        +string Source
        +string ProjectName
        +List~ModDependencyReference~ RequiredDependencies
        +List~ModDependencyFile~ Files
    }

    class ModDependencyFile {
        +string Id
        +string DisplayName
        +string Version
        +List~string~ GameVersions
        +List~string~ Loaders
        +int ReleaseType
        +DateTime ReleaseDate
        +List~ModDependencyReference~ RequiredDependencies
    }

    class InstalledModIdentity {
        +string SourceProjectId
        +string Source
        +string ModId
        +List~string~ GameVersions
        +List~string~ Loaders
    }

    class ModDependencyResolutionResult {
        +List~ResolvedDependencyInstall~ ToInstall
        +List~UnresolvedDependency~ Unresolved
        +List~IgnoredDependency~ Satisfied
    }

    class ResolvedDependencyInstall {
        +string ProjectId
        +string Source
        +string ProjectName
        +ModDependencyFile File
    }

    class UnresolvedDependency {
        +string ProjectId
        +string Source
        +string Reason
    }

    class IgnoredDependency {
        +string ProjectId
        +string Source
        +string Reason
    }

    class ModDependencyResolver {
        +Resolve(request ModDependencyRequest) ModDependencyResolutionResult
        -ResolveDependency(context ResolutionContext, dependency ModDependencyReference, depth int) void
        -SelectBestFile(files IEnumerable~ModDependencyFile~, targetMinecraftVersion string, targetLoaders HashSet~string~) ModDependencyFile
        -IsCompatibleFile(file ModDependencyFile, targetMinecraftVersion string, targetLoaders HashSet~string~) bool
        -HasExactGameVersionMatch(file ModDependencyFile, targetMinecraftVersion string) bool
        -HasLoaderMatch(file ModDependencyFile, targetLoaders HashSet~string~) bool
        -NormalizeReleaseType(releaseType int) int
    }

    class ResolutionContext {
        +ModDependencyRequest Request
        +ModDependencyResolutionResult Result
        +HashSet~string~ Visited
        +string TargetMinecraftVersion
        +HashSet~string~ TargetLoaders
        +GetVisitedKey(projectId string, source string) string
        +IsInstalledCompatible(projectId string, source string) bool
        +AddInstall(project ModDependencyProject, file ModDependencyFile) void
        +AddUnresolved(projectId string, source string, reason string) void
        +AddSatisfied(projectId string, source string, reason string) void
        -bool LoadersCompatible(installedLoaders List~string~)
        -string GetProjectKey(projectId string, source string)
    }

    ModDependencyResolver --> ResolutionContext : uses
    ModDependencyRequest --> ModDependencyReference : contains
    ModDependencyRequest --> InstalledModIdentity : contains
    ModDependencyRequest --> ModDependencyProject : uses via ProjectResolver

    ModDependencyProject --> ModDependencyReference : contains
    ModDependencyProject --> ModDependencyFile : contains

    ModDependencyFile --> ModDependencyReference : contains

    ModDependencyResolutionResult --> ResolvedDependencyInstall : contains
    ModDependencyResolutionResult --> UnresolvedDependency : contains
    ModDependencyResolutionResult --> IgnoredDependency : contains

    ResolvedDependencyInstall --> ModDependencyFile : contains

    ResolutionContext --> ModDependencyRequest : has
    ResolutionContext --> ModDependencyResolutionResult : builds
    ResolutionContext --> InstalledModIdentity : checks compatibility
Loading

Class diagram for ModCompDependency integration layer

classDiagram
    class CompFile {
        +string Id
        +string DisplayName
        +string Version
        +List~string~ GameVersions
        +List~CompLoaderType~ ModLoaders
        +List~string~ Dependencies
        +List~string~ OptionalDependencies
        +CompType Type
        +List~string~ ToNetFile(target string)
    }

    class CompProject {
        +string Id
        +bool FromCurseForge
        +string TranslatedName
        +string RawName
    }

    class LocalCompFile {
        +string ModId
        +CompProject Comp
        +CompFile CompFile
        +Load() void
        +static IsModFile(path string) bool
    }

    class ModCompDependency {
        <<static>>
        +BuildRequest(file CompFile, project CompProject, targetMinecraftVersion string, targetLoaders List~CompLoaderType~, targetModsFolder string) ModDependencyRequest
        +ScanInstalledMods(targetModsFolder string) List~InstalledModIdentity~
        +ResolveProjectFiles(source string, projectId string) ModDependencyProject
        +SelectCompatibleDependencyFile(result ModDependencyResolutionResult, projectId string, source string) ModDependencyFile
        +BuildDependencyDownloads(result ModDependencyResolutionResult, targetModsFolder string) List~DownloadFile~
        +ConfirmDependencyInstall(result ModDependencyResolutionResult) bool
        +ShowDependencyAbortMessage(reason string) void
        -string GetSource(fromCurseForge bool)
        -List~string~ ToLoaderNames(loaders IEnumerable~CompLoaderType~)
        -int MapReleaseType(status CompFileStatus)
    }

    class DownloadFile {
    }

    class ModComp {
        +static Dictionary~string,CompProject~ CompProjectCache
        +static List~CompFile~ CompFilesGet(projectId string, fromCurseForge bool)
        +static string CompFileNameGet(project CompProject, file CompFile)
    }

    class InstalledModIdentity {
        +string SourceProjectId
        +string Source
        +string ModId
        +List~string~ GameVersions
        +List~string~ Loaders
    }

    class ModDependencyRequest {
    }

    class ModDependencyProject {
    }

    class ModDependencyFile {
    }

    class ModDependencyResolutionResult {
        +List~ResolvedDependencyInstall~ ToInstall
        +List~UnresolvedDependency~ Unresolved
        +List~IgnoredDependency~ Satisfied
    }

    class ModMain {
        +static int MyMsgBox(message string, title string, Button1 string, Button2 string, bool IsWarn, bool ForceWait)
    }

    ModCompDependency --> ModDependencyRequest : builds
    ModCompDependency --> InstalledModIdentity : returns
    ModCompDependency --> ModDependencyProject : returns
    ModCompDependency --> ModDependencyFile : returns
    ModCompDependency --> ModDependencyResolutionResult : consumes
    ModCompDependency --> DownloadFile : builds
    ModCompDependency --> ModMain : shows dialogs

    ModCompDependency --> CompFile : from
    ModCompDependency --> CompProject : from
    ModCompDependency --> LocalCompFile : scans

    ModComp --> CompProject : caches
    ModComp --> CompFile : provides
    ModCompDependency --> ModComp : queries caches
Loading

File-Level Changes

Change Details Files
Add automatic dependency resolution and installation into the mod download flow when downloading a single mod file.
  • Introduce collection of DownloadFile entries so dependency files can be prepended to the main mod download.
  • Detect target instance and loaders from the chosen mods folder and selected instance, loading instance metadata if needed.
  • Build a ModDependencyRequest from the current file/project, resolve dependencies, confirm with the user, and merge dependency downloads ahead of the main file.
  • Short‑circuit download when dependency resolution fails, is cancelled by user, or throws, with logging for diagnostics.
  • Reuse the allowed loader types from the earlier calculated list for fallback when instance loaders are unavailable.
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.cs
Fix parsing of optional dependencies from API JSON into CompFile model.
  • Map RawOptionalDependencies JSON field to RawOptionalDependencies property instead of RawDependencies.
  • Map OptionalDependencies JSON field to OptionalDependencies property instead of Dependencies.
Plain Craft Launcher 2/Modules/Minecraft/ModComp.cs
Add configuration and UI toggle to enable/disable automatic dependency installation for mods.
  • Introduce Download.Comp.AutoInstallDependencies config flag with default true.
  • Bind the new flag to PageSetupGameManage checkbox state.
  • Add corresponding CheckDownloadAutoInstallDependencies control to the game management settings XAML.
PCL.Core/App/Config.cs
Plain Craft Launcher 2/Pages/PageSetup/PageSetupGameManage.xaml.cs
Plain Craft Launcher 2/Pages/PageSetup/PageSetupGameManage.xaml
Introduce a general-purpose mod dependency resolution engine, including model types and selection rules.
  • Define record types for dependency references, projects, files, installed mod identities, and resolution results.
  • Implement ModDependencyResolver with bounded recursion, visited deduping, and handling of required vs. optional dependencies.
  • Add compatibility checks based on exact Minecraft version and loader intersection, with graceful handling when data is missing.
  • Implement file selection that prefers exact game version, matching loaders, stable releases over beta/alpha, and newest by release date.
  • Track and deduplicate resolved installs, unresolved dependencies, and satisfied/ignored dependencies per project and environment key.
PCL.Core/Minecraft/ResourceProject/ModDependencyResolver.cs
Bridge launcher data structures to the dependency resolver and construct concrete download tasks for resolved dependencies.
  • Build ModDependencyRequest from CompFile/CompProject using dependency IDs, loader mapping, and a project resolver callback.
  • Scan the target mods folder for already installed mods via LocalCompFile, inferring source, versions, and loaders.
  • Resolve project files for a dependency using existing CompProjectCache and CompFilesGet, mapping to resolver DTOs and release status.
  • Convert resolution results back into DownloadFile instances ordered so dependencies download before dependents.
  • Provide user-facing confirmation and abort messaging when unresolved required dependencies exist or the user cancels installation.
Plain Craft Launcher 2/Modules/Minecraft/ModCompDependency.cs
Add unit test coverage for the dependency resolution engine behavior.
  • Test resolving a single missing required dependency into a single install.
  • Verify no-op resolution when no dependencies are requested and resolver is not invoked.
  • Cover recursive, shared, and cyclic dependency graphs, including deduplication and depth control.
  • Validate behavior for unresolved required dependencies, installed compatible/incompatible mods, and ignored optional dependencies.
  • Test file selection rules, including latest stable release preference and source separation between Modrinth and CurseForge.
PCL.Core.Test/Minecraft/ResourceProject/ModDependencyResolverTest.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#557 When installing a mod, detect missing required dependencies in the target mods folder and automatically download/install those dependencies alongside the mod.
#557 Add a user-configurable setting to enable or disable automatic installation of mod dependencies.
#557 Correctly parse and store dependency metadata (including optional dependencies) from mod file JSON so that dependency handling works as intended.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@SALTWOOD SALTWOOD marked this pull request as draft May 3, 2026 19:35
@pcl-ce-automation pcl-ce-automation Bot added 🚧 正在处理 开发人员正在对该内容进行开发、测试或修复,进展中 and removed 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查 labels May 3, 2026
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我在这里给出了一些高层面的反馈:

  • 在 Save_Click 的依赖处理代码块中,如果在解析依赖时发生任何异常,当前行为是记录日志然后直接返回,而不会通知用户或继续原始模组的下载;建议调用 ShowDependencyAbortMessage(或以其他方式向用户展示提示信息)以及/或者回退为仅下载所选模组,这样依赖解析失败时就不会在无提示的情况下静默失败。
  • ModDependencyProject.RequiredDependencies 在 ResolveProjectFiles 中被填充,但 ModDependencyResolver 从未读取该字段,而是只使用按文件(per-file)的 RequiredDependencies;如果项目级的依赖并不是必需的,建议移除该字段及其赋值逻辑,以减少关于依赖遍历行为的困惑。
给 AI 代理的提示
请根据这次代码评审中的评论进行修改:

## 总体评论
- 在 Save_Click 的依赖处理代码块中,如果在解析依赖时发生任何异常,当前行为是记录日志然后直接返回,而不会通知用户或继续原始模组的下载;建议调用 ShowDependencyAbortMessage(或以其他方式向用户展示提示信息)以及/或者回退为仅下载所选模组,这样依赖解析失败时就不会在无提示的情况下静默失败。
- ModDependencyProject.RequiredDependencies 在 ResolveProjectFiles 中被填充,但 ModDependencyResolver 从未读取该字段,而是只使用按文件(per-file)的 RequiredDependencies;如果项目级的依赖并不是必需的,建议移除该字段及其赋值逻辑,以减少关于依赖遍历行为的困惑。

Sourcery 对开源项目是免费的——如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的评审。
Original comment in English

Hey - I've left some high level feedback:

  • In the Save_Click dependency handling block, any exception during dependency resolution logs and then returns without notifying the user or proceeding with the original mod download; consider invoking ShowDependencyAbortMessage (or otherwise surfacing a message) and/or falling back to downloading just the selected mod so failures are not silent.
  • ModDependencyProject.RequiredDependencies is populated in ResolveProjectFiles but never read by ModDependencyResolver, which only uses per-file RequiredDependencies; if project-level dependencies are not needed, consider removing this field/population to reduce confusion about how dependency traversal works.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the Save_Click dependency handling block, any exception during dependency resolution logs and then returns without notifying the user or proceeding with the original mod download; consider invoking ShowDependencyAbortMessage (or otherwise surfacing a message) and/or falling back to downloading just the selected mod so failures are not silent.
- ModDependencyProject.RequiredDependencies is populated in ResolveProjectFiles but never read by ModDependencyResolver, which only uses per-file RequiredDependencies; if project-level dependencies are not needed, consider removing this field/population to reduce confusion about how dependency traversal works.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@SALTWOOD SALTWOOD marked this pull request as ready for review May 3, 2026 20:18
@pcl-ce-automation pcl-ce-automation Bot added 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查 and removed 🚧 正在处理 开发人员正在对该内容进行开发、测试或修复,进展中 labels May 3, 2026
@SALTWOOD SALTWOOD requested a review from a team May 3, 2026 20:18
SALTWOOD added 3 commits May 4, 2026 04:24
Remove the fallback to project-level dependencies when resolving nested dependencies, ensuring that only the dependencies explicitly required by the selected file are processed.
@SALTWOOD SALTWOOD force-pushed the feat/auto-pre-deps branch from e01f2e1 to 8c6fe77 Compare May 3, 2026 20:25
@Pigeon0v0
Copy link
Copy Markdown
Contributor

考虑在实例 Mod 列表内检测一下么

如果已经有了就当我没说(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: XXL PR 大小评估:巨型 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查

Projects

None yet

Development

Successfully merging this pull request may close these issues.

自动安装模组前置

2 participants