From 62c9c613d24f925247db01eabca5e3eae339634e Mon Sep 17 00:00:00 2001 From: HosamaJJF <2210072005@qq.com> Date: Sun, 7 Jun 2026 22:18:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=8C=E6=AD=A5=20WPF=20=E7=89=88?= =?UTF-8?q?=E5=91=A8=E8=AE=A1=E5=88=92=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=B8=BA?= =?UTF-8?q?=E5=88=B7=E7=90=86=E6=99=BA=E4=BB=BB=E5=8A=A1=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=AF=8F=E5=91=A8=E6=89=A7=E8=A1=8C=E6=97=A5=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 macOS GUI 的刷理智任务设置中新增周计划选项,与 WPF 版本行为一致。 用户可启用周计划并选择每周哪几天执行该任务,非启用日自动跳过并记录日志。 星期按游戏时间计算(每日 4:00 刷新),根据设置中的游戏区服自动适配时区。 - FightConfiguration: 新增 WeeklySchedule 结构体及 useWeeklySchedule、weeklySchedule 属性 - FightSettingsView: 新增周计划设置区域(主开关 + 7 天勾选 + 提示文字) - MAAViewModel: 新增 inGameDayOfWeek 游戏星期计算及 startTasks() 跳过逻辑 - LegacyConfigurations: 旧配置迁移路径初始化新字段 - Localizable.xcstrings: 新增中/英/日/韩/繁翻译 --- .../FightSettingsView.swift | 34 ++++++++ MeoAsstMac/Model/MAAViewModel.swift | 30 +++++++ .../FightConfiguration.swift | 44 ++++++++++ .../LegacyConfigurations.swift | 2 + Resources/Localizable.xcstrings | 84 +++++++++++++++++++ 5 files changed, 194 insertions(+) diff --git a/MeoAsstMac/Configuration Views/FightSettingsView.swift b/MeoAsstMac/Configuration Views/FightSettingsView.swift index 694ea72d1..994499044 100644 --- a/MeoAsstMac/Configuration Views/FightSettingsView.swift +++ b/MeoAsstMac/Configuration Views/FightSettingsView.swift @@ -105,9 +105,26 @@ struct FightSettingsView: View { TextField(text: $config.penguin_id) { Toggle("企鹅物流汇报ID", isOn: $config.report_to_penguin) } + + Divider() + + Section { + Toggle(String(localized: "启用周计划"), isOn: $config.useWeeklySchedule) + if config.useWeeklySchedule { + Text((String(localized: "此处星期根据游戏时间计算(每日 4:00 刷新),而非现实时间。例如游戏在 4:00 刷新,则 3:59 仍算作前一天。"))) + .font(.caption) + .foregroundStyle(.secondary) + LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]) { + ForEach(weekdayRange, id: \.self) { day in + Toggle(weekdayLabel(for: day), isOn: weekdayBinding(for: day)) + } + } + } + } } .padding() .animation(.default, value: useCustomStage) + .animation(.default, value: config.useWeeklySchedule) } private var useExpiringMedicine: Binding { @@ -212,6 +229,23 @@ struct FightSettingsView: View { private var stageNotListed: Bool { !listedStages.contains(config.stage) } private let listedStages = ["", "1-7", "CE-6", "AP-5", "CA-5", "LS-6", "Annihilation"] + + // MARK: - Weekly Schedule + + private let weekdayRange = 1...7 + private let weekdaySymbols = Calendar.current.shortWeekdaySymbols + + private func weekdayLabel(for day: Int) -> String { + weekdaySymbols[day - 1] + } + + private func weekdayBinding(for day: Int) -> Binding { + Binding { + config.weeklySchedule[day] + } set: { + config.weeklySchedule[day] = $0 + } + } } struct FightSettingsView_Previews: PreviewProvider { diff --git a/MeoAsstMac/Model/MAAViewModel.swift b/MeoAsstMac/Model/MAAViewModel.swift index a47c21cfa..3eb456ed2 100644 --- a/MeoAsstMac/Model/MAAViewModel.swift +++ b/MeoAsstMac/Model/MAAViewModel.swift @@ -464,9 +464,19 @@ extension MAAViewModel { try await ensureHandle() + let today = Self.inGameDayOfWeek(for: clientChannel) + for task in tasks { guard task.enabled else { continue } + if case .fight(let config) = task.task, + config.useWeeklySchedule, + !config.weeklySchedule.isEnabled(for: today) + { + logInfo("跳过刷理智任务(今日不在周计划内)") + continue + } + if let coreID = try await handle?.appendTask(task.task) { taskIDMap[coreID] = task.id } @@ -710,3 +720,23 @@ extension MAAViewModel { try await client.terminate() } } + +extension MAAViewModel { + private static let serverUTCOffsets: [MAAClientChannel: Int] = [ + .Official: 8, + .Bilibili: 8, + .txwy: 8, + .YoStarEN: -7, + .YoStarJP: 9, + .YoStarKR: 9, + ] + + static func inGameDayOfWeek(for channel: MAAClientChannel) -> Int { + let offset = serverUTCOffsets[channel] ?? 8 + let yjShift = (offset - 4) * 3600 + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = TimeZone(secondsFromGMT: 0)! + let adjusted = Date().addingTimeInterval(TimeInterval(yjShift)) + return calendar.component(.weekday, from: adjusted) + } +} diff --git a/MeoAsstMac/Task Configurations/FightConfiguration.swift b/MeoAsstMac/Task Configurations/FightConfiguration.swift index 953be9966..512db5820 100644 --- a/MeoAsstMac/Task Configurations/FightConfiguration.swift +++ b/MeoAsstMac/Task Configurations/FightConfiguration.swift @@ -7,6 +7,45 @@ import Foundation +struct WeeklySchedule: Codable, Equatable, Hashable { + var sunday: Bool = true + var monday: Bool = true + var tuesday: Bool = true + var wednesday: Bool = true + var thursday: Bool = true + var friday: Bool = true + var saturday: Bool = true + + func isEnabled(for weekday: Int) -> Bool { + switch weekday { + case 1: return sunday + case 2: return monday + case 3: return tuesday + case 4: return wednesday + case 5: return thursday + case 6: return friday + case 7: return saturday + default: return true + } + } + + subscript(_ index: Int) -> Bool { + get { isEnabled(for: index) } + set { + switch index { + case 1: sunday = newValue + case 2: monday = newValue + case 3: tuesday = newValue + case 4: wednesday = newValue + case 5: thursday = newValue + case 6: friday = newValue + case 7: saturday = newValue + default: break + } + } + } +} + struct FightConfiguration: MAATaskConfiguration { var type: MAATaskType { .Fight } @@ -24,6 +63,9 @@ struct FightConfiguration: MAATaskConfiguration { var client_type: String var DrGrandet: Bool + var useWeeklySchedule: Bool + var weeklySchedule: WeeklySchedule + var title: String { type.description } @@ -141,5 +183,7 @@ extension FightConfiguration { self.server = try container.decodeIfPresent(String.self, forKey: .server) ?? "CN" self.client_type = try container.decodeIfPresent(String.self, forKey: .client_type) ?? "" self.DrGrandet = try container.decodeIfPresent(Bool.self, forKey: .DrGrandet) ?? false + self.useWeeklySchedule = try container.decodeIfPresent(Bool.self, forKey: .useWeeklySchedule) ?? false + self.weeklySchedule = try container.decodeIfPresent(WeeklySchedule.self, forKey: .weeklySchedule) ?? WeeklySchedule() } } diff --git a/MeoAsstMac/Task Configurations/LegacyConfigurations.swift b/MeoAsstMac/Task Configurations/LegacyConfigurations.swift index 6ee0baad5..af40a730d 100644 --- a/MeoAsstMac/Task Configurations/LegacyConfigurations.swift +++ b/MeoAsstMac/Task Configurations/LegacyConfigurations.swift @@ -100,6 +100,8 @@ extension FightConfiguration { self.server = config.server self.client_type = config.client_type self.DrGrandet = config.DrGrandet + self.useWeeklySchedule = false + self.weeklySchedule = WeeklySchedule() } } diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index aee47be4d..ea75d5095 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -2681,6 +2681,34 @@ } } }, + "启用周计划" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enable Weekly Schedule" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "週間スケジュールを有効にする" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "주간 일정 활성화" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "啟用週計劃" + } + } + } + }, "周常任务" : { "localizations" : { "ko" : { @@ -3689,6 +3717,34 @@ } } }, + "此处星期根据游戏时间计算(每日 4:00 刷新),而非现实时间。例如游戏在 4:00 刷新,则 3:59 仍算作前一天。" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The day of week here is based on in-game time (daily reset at 4:00), not real time. For example, if the game resets at 4:00, 3:59 is still counted as the previous day." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ここでの曜日はゲーム内時間(毎日4:00リセット)に基づいて計算され、実際の時間ではありません。例えば、ゲームが4:00にリセットされる場合、3:59はまだ前日としてカウントされます。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "여기서 요일은 게임 내 시간(매일 4:00 초기화)을 기준으로 계산되며 실제 시간이 아닙니다. 예를 들어 게임이 4:00에 초기화되면 3:59는 여전히 전날로 계산됩니다." + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "此處星期根據遊戲時間計算(每日 4:00 刷新),而非現實時間。例如遊戲在 4:00 刷新,則 3:59 仍算作前一天。" + } + } + } + }, "每次执行时最大招募次数: " : { "localizations" : { "ko" : { @@ -4628,6 +4684,34 @@ } } }, + "跳过刷理智任务(今日不在周计划内)" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skipped fight task (not in today's weekly schedule)" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "戦闘任務をスキップしました(本日の週間スケジュールに含まれていません)" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "전투 작업을 건너뜁니다 (오늘의 주간 일정에 없음)" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "跳過刷理智任務(今日不在週計劃內)" + } + } + } + }, "过完新手教程后进入前哨支点,滑动到界面最左侧。" : { },