Skip to content

feat(macOS): 作业集、实时开放关卡与掉落材料显示、关卡数据热更新 - #92

Open
Gotit-zhx wants to merge 3 commits into
MaaAssistantArknights:masterfrom
Gotit-zhx:feature/copilot-list-and-stage-display
Open

feat(macOS): 作业集、实时开放关卡与掉落材料显示、关卡数据热更新#92
Gotit-zhx wants to merge 3 commits into
MaaAssistantArknights:masterfrom
Gotit-zhx:feature/copilot-list-and-stage-display

Conversation

@Gotit-zhx

@Gotit-zhx Gotit-zhx commented Jun 3, 2026

Copy link
Copy Markdown

简介

将 Windows GUI 已有但 macOS 端缺失的几项功能移植到 macOS,并尽量与 Windows 保持一致的逻辑与维护成本。

改动内容

1. 刷理智实时显示开放关卡 + 掉落材料

  • 关卡下拉改为数据驱动:根据当前开放情况动态展示,关闭关卡置灰、过期活动关加删除线,支持「隐藏未开放关卡」。
  • 关卡提示补充可刷取的掉落材料(item id 经 item_index 转材料名,纯文字说明透传)。
  • 解析 gui/StageActivityV2.json(V2 schema),合并常驻资源本与活动关。

2. 关卡数据热更新

  • 新增 MaaApiService:主备地址 + ETag/Last-Modified 协商 + 本地缓存,替换原 OTAFetcher
  • 运行时按游戏日(服务器 04:00 切换)定时刷新关卡数据。
  • 新增 GameCalendar 处理各服务器游戏日时区换算,资源本周开放判断改用游戏日星期,避免跨午夜/跨服误判。

3. 作业集(Copilot List)

  • 自动战斗页新增「单个作业 / 作业集」切换。
  • 支持从 PRTS 作业集神秘代码批量导入(逐份下载并校验,下载到独立目录,不与单个作业混淆)。
  • 支持勾选、逐项突袭、拖动排序、删除、持久化;选中条目时右侧预览作业文档。
  • 全局选项:自动编队、信赖干员、自动使用理智药、助战干员。
  • 运行时打包为 copilot_list 参数交核心顺序执行,导航关卡名经 tile-pos 解析为游戏内代号(如 act50side_01TD-1)。

测试

  • macOS 真机(PlayCover + 官服)验证:作业集导入/运行、开放关卡显示、掉落材料显示均正常。
  • 纯逻辑(时区换算、V2 解析、掉落映射等)命令行单测通过。
  • 多语言已补充 zh-Hans/en/ko。

🤖 Generated with Claude Code

Summary by Sourcery

将 Windows GUI 的相关功能移植到 macOS:新增作业集列表支持、可热更新数据的动态关卡活动处理,以及与全新基于 MaaApiService 的 OTA 流水线打通的更丰富关卡选择体验。

New Features:

  • 在 macOS 上新增 Copilot List(作业集)功能,支持带有全局选项与 PRTS 关卡集导入的有序多关卡作业运行。

Enhancements:

  • 用全新的 MaaApiService 替换旧的 OTAFetcher,通过基于 ETag 的缓存与备用主机,实现轻量级 JSON 资源的热更新。
  • 在 macOS 上引入关卡活动管理,将 StageActivityV2 数据解析为统一的关卡列表,并供战斗配置界面使用。
  • 更新战斗配置界面,使用动态的、可感知可用性的关卡选择器,可选隐藏已关闭关卡,并显示掉落材料。
  • 调整作业详情与导航行为,以支持作业集预览、添加、删除与持久化,同时不影响单作业模式的状态。
Original summary in English

Summary by Sourcery

Port Windows GUI functionality to macOS by adding copilot list support, dynamic stage activity handling with hot-updated data, and a richer stage selection experience wired to a new MaaApiService-backed OTA pipeline.

New Features:

  • Add a Copilot List (作业集) feature on macOS, allowing ordered multi-stage copilot runs with global options and PRTS set import.

Enhancements:

  • Replace the legacy OTAFetcher with a new MaaApiService that hot-updates lightweight JSON resources using ETag-based caching and fallback hosts.
  • Introduce stage activity management on macOS, parsing StageActivityV2 data into a unified stage list used by the fight configuration UI.
  • Update the fight configuration UI to use a dynamic, availability-aware stage picker with optional hiding of closed stages and display of drop materials.
  • Align copilot detail and navigation behavior to support list previews, adding, deletion, and persistence without affecting single-copilot state.

Gotit-zhx and others added 2 commits June 3, 2026 23:32
对齐 Windows GUI,为 macOS 端补充以下能力:

- 刷理智的关卡下拉改为数据驱动:根据当前开放情况动态展示,
  关闭的关卡置灰、过期活动关加删除线,并支持「隐藏未开放关卡」。
- 关卡提示补充可刷取的掉落材料(item id 经 item_index 转为材料名,
  纯文字说明直接透传)。
- 解析 gui/StageActivityV2.json(V2 schema),合并常驻资源本与活动关。
- 关卡数据热更新:新增 MaaApiService(主备地址 + ETag/Last-Modified
  协商 + 本地缓存),运行时按游戏日(服务器 04:00 切换)定时刷新,
  替换原 OTAFetcher。
- 新增 GameCalendar 处理各服务器的游戏日时区换算,资源本周开放
  判断改用游戏日星期,避免跨午夜/跨服误判。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
对齐 Windows GUI 的作业集能力:

- 自动战斗页新增「单个作业 / 作业集」切换。
- 支持从 PRTS 作业集神秘代码批量导入,逐份下载并校验,
  作业下载到独立目录,不与单个作业列表混淆。
- 作业集支持勾选、逐项突袭开关、拖动排序、滑动/工具栏删除,
  并持久化到用户目录。
- 选中作业集条目时右侧详情面板预览该作业文档。
- 全局选项:自动编队、信赖干员、自动使用理智药、助战干员。
- 运行时把勾选项打包为 copilot_list 参数交由核心顺序执行,
  导航关卡名经 tile-pos 解析为游戏内代号(如 act50side_01 → TD-1)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - 我发现了 1 个问题,并且给了一些整体性的反馈:

  • GameCalendar 的 API 声明为 yjDay(for:channel:)yjWeekday(for:channel:)yjHourMinute(for:channel:),但所有调用处使用的是 GameCalendar.yjDay(channel:) / yjWeekday(channel:) / yjHourMinute(channel:),这样是无法编译的;请统一外部参数标签(或者添加重载),让调用方与函数签名保持一致。
  • copilotList 属性在每次变更时都会通过 didSet { saveCopilotList() } 进行持久化,这意味着每次勾选/拖动复选框都会在主线程执行文件 IO;建议对写入进行防抖处理,或将 saveCopilotList() 放到后台队列执行,以避免在编辑大型列表时出现 UI 卡顿。
给 AI Agent 的提示
Please address the comments from this code review:

## Overall Comments
- GameCalendar’s APIs are declared as `yjDay(for:channel:)`, `yjWeekday(for:channel:)`, and `yjHourMinute(for:channel:)`, but all call sites use `GameCalendar.yjDay(channel:)` / `yjWeekday(channel:)` / `yjHourMinute(channel:)`, which won’t compile; align the external parameter labels (or add overloads) so the callers match the function signatures.
- The `copilotList` property persists on every mutation via `didSet { saveCopilotList() }`, which means file IO on the main thread for each checkbox toggle/reorder; consider debouncing writes or offloading `saveCopilotList()` to a background queue to avoid UI jank when editing large lists.

## Individual Comments

### Comment 1
<location path="MeoAsstMac/Model/MAAStage.swift" line_range="88-97" />
<code_context>
+    var isResourceCollection = false
+
+    /// Activity currently open: started and not yet expired.
+    var beingOpen: Bool { !notOpenYet && !isExpired }
+
+    var isExpired: Bool { Date() >= expireTime }
+
+    var notOpenYet: Bool { Date() <= startTime }
+
+    init(
</code_context>
<issue_to_address>
**suggestion:** 避免在计算开启/过期标记时多次调用 `Date()`,以防止边界时间上的不一致。

这三个计算属性各自独立调用了一次 `Date()`,因此在边界时间附近,它们可能会因为几毫秒的差异而产生矛盾。建议重构为只捕获一次 `let now = Date()`(例如通过 `func status(at date: Date = Date())`),并基于这一共享值推导出这三个布尔值。

```suggestion
    var isResourceCollection = false

    /// Activity currently open: started and not yet expired.
    var beingOpen: Bool { status().beingOpen }

    var isExpired: Bool { status().isExpired }

    var notOpenYet: Bool { status().notOpenYet }

    /// Compute open/expired status at a specific point in time.
    /// Using a shared `date` avoids inconsistencies around boundary times.
    func status(at date: Date = Date()) -> (beingOpen: Bool, isExpired: Bool, notOpenYet: Bool) {
        let isExpired = date >= expireTime
        let notOpenYet = date <= startTime
        let beingOpen = !notOpenYet && !isExpired
        return (beingOpen, isExpired, notOpenYet)
    }

    init(
```
</issue_to_address>

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

Hey - I've found 1 issue, and left some high level feedback:

  • GameCalendar’s APIs are declared as yjDay(for:channel:), yjWeekday(for:channel:), and yjHourMinute(for:channel:), but all call sites use GameCalendar.yjDay(channel:) / yjWeekday(channel:) / yjHourMinute(channel:), which won’t compile; align the external parameter labels (or add overloads) so the callers match the function signatures.
  • The copilotList property persists on every mutation via didSet { saveCopilotList() }, which means file IO on the main thread for each checkbox toggle/reorder; consider debouncing writes or offloading saveCopilotList() to a background queue to avoid UI jank when editing large lists.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- GameCalendar’s APIs are declared as `yjDay(for:channel:)`, `yjWeekday(for:channel:)`, and `yjHourMinute(for:channel:)`, but all call sites use `GameCalendar.yjDay(channel:)` / `yjWeekday(channel:)` / `yjHourMinute(channel:)`, which won’t compile; align the external parameter labels (or add overloads) so the callers match the function signatures.
- The `copilotList` property persists on every mutation via `didSet { saveCopilotList() }`, which means file IO on the main thread for each checkbox toggle/reorder; consider debouncing writes or offloading `saveCopilotList()` to a background queue to avoid UI jank when editing large lists.

## Individual Comments

### Comment 1
<location path="MeoAsstMac/Model/MAAStage.swift" line_range="88-97" />
<code_context>
+    var isResourceCollection = false
+
+    /// Activity currently open: started and not yet expired.
+    var beingOpen: Bool { !notOpenYet && !isExpired }
+
+    var isExpired: Bool { Date() >= expireTime }
+
+    var notOpenYet: Bool { Date() <= startTime }
+
+    init(
</code_context>
<issue_to_address>
**suggestion:** Avoid multiple `Date()` calls when computing open/expired flags to prevent edge inconsistencies.

These three computed properties each call `Date()` independently, so at boundary times they can disagree by a few milliseconds. Refactor to capture `let now = Date()` once (for example via `func status(at date: Date = Date())`) and derive all three booleans from that shared value.

```suggestion
    var isResourceCollection = false

    /// Activity currently open: started and not yet expired.
    var beingOpen: Bool { status().beingOpen }

    var isExpired: Bool { status().isExpired }

    var notOpenYet: Bool { status().notOpenYet }

    /// Compute open/expired status at a specific point in time.
    /// Using a shared `date` avoids inconsistencies around boundary times.
    func status(at date: Date = Date()) -> (beingOpen: Bool, isExpired: Bool, notOpenYet: Bool) {
        let isExpired = date >= expireTime
        let notOpenYet = date <= startTime
        let beingOpen = !notOpenYet && !isExpired
        return (beingOpen, isExpired, notOpenYet)
    }

    init(
```
</issue_to_address>

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.

Comment thread MeoAsstMac/Model/MAAStage.swift
- StageActivityInfo 新增 status(at:),单次捕获 Date() 推导
  beingOpen/isExpired/notOpenYet,避免边界时间下三者不一致;
  isStageOpen 也复用同一快照。
- saveCopilotList 的写盘 IO 移到后台串行队列,避免勾选/排序
  作业集时在主线程同步写文件造成 UI 卡顿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Gotit-zhx

Copy link
Copy Markdown
Author

感谢 review,已逐条处理:

  1. GameCalendar 参数标签yjDay(for:channel:) / yjWeekday(for:channel:) / yjHourMinute(for:channel:) 的首个参数 for date: 带有默认值 Date(),因此调用方写成 yjDay(channel:)(省略带默认值的首参)是合法的 Swift 语法,项目可正常编译(已多次 xcodebuild 验证)。保留 for: 参数是为了单元测试能注入特定时间。故此条无需改动。

  2. StageActivityInfo 多次调用 Date():已采纳。新增 status(at:) 单次捕获时间并推导 beingOpen / isExpired / notOpenYetisStageOpen 也复用同一快照,避免边界时间下三者不一致。

  3. copilotList 主线程同步写盘:已采纳。saveCopilotList() 的写盘 IO 已移到后台串行队列,避免勾选/排序作业集时阻塞主线程造成 UI 卡顿。

后两条修改见 commit refactor(macOS): 处理 code review 意见

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant