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" : "跳過刷理智任務(今日不在週計劃內)" + } + } + } + }, "过完新手教程后进入前哨支点,滑动到界面最左侧。" : { },