Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions MeoAsstMac.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
8341195329EC25A900044F45 /* MAALog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8341195229EC25A900044F45 /* MAALog.swift */; };
8341195529EC55C600044F45 /* CopilotConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8341195429EC55C600044F45 /* CopilotConfiguration.swift */; };
8341195729EC58CF00044F45 /* MAACopilot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8341195629EC58CF00044F45 /* MAACopilot.swift */; };
AA01F00DCAFE000000000001 /* MAAStage.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA01F00DCAFE000000000002 /* MAAStage.swift */; };
AA01F00DCAFE000000000003 /* MAAStageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA01F00DCAFE000000000004 /* MAAStageManager.swift */; };
AA01F00DCAFE000000000005 /* CopilotEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA01F00DCAFE000000000006 /* CopilotEntry.swift */; };
AA01F00DCAFE000000000007 /* CopilotListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA01F00DCAFE000000000008 /* CopilotListView.swift */; };
8341195D29EC719B00044F45 /* CopilotContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8341195C29EC719B00044F45 /* CopilotContent.swift */; };
834374AC2EB61ACA002CC2D3 /* MaaUtils.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 834374AB2EB61ACA002CC2D3 /* MaaUtils.xcframework */; };
834374AD2EB61C3E002CC2D3 /* MaaUtils.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 834374AB2EB61ACA002CC2D3 /* MaaUtils.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
Expand Down Expand Up @@ -91,7 +95,9 @@
83E9D138294B74440091D1FF /* OpenCV.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 83E9D133294B74230091D1FF /* OpenCV.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
83E9D139294B74460091D1FF /* ONNXRuntime.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83E9D135294B74270091D1FF /* ONNXRuntime.xcframework */; };
83E9D13A294B74460091D1FF /* ONNXRuntime.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 83E9D135294B74270091D1FF /* ONNXRuntime.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
83E9ED972DF693B20069CFE9 /* OTAFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83E9ED962DF693B20069CFE9 /* OTAFetcher.swift */; };
83E9ED972DF693B20069CFE9 /* GameCalendar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83E9ED962DF693B20069CFE9 /* GameCalendar.swift */; };
AA03F00DCAFE000000000001 /* StageCodeResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA03F00DCAFE000000000002 /* StageCodeResolver.swift */; };
AA02F00DCAFE000000000001 /* MaaApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA02F00DCAFE000000000002 /* MaaApiService.swift */; };
83FF5A9C2978FE27000AE831 /* resource in Resources */ = {isa = PBXBuildFile; fileRef = 83FF5A9B2978FE27000AE831 /* resource */; };
92CDF6222A065E55005669EC /* Codeable+JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92CDF6212A065E55005669EC /* Codeable+JSON.swift */; };
92EB82872A0CD315006DD9D2 /* SystemSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92EB82862A0CD315006DD9D2 /* SystemSettingsView.swift */; };
Expand Down Expand Up @@ -172,6 +178,10 @@
8341195229EC25A900044F45 /* MAALog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MAALog.swift; sourceTree = "<group>"; };
8341195429EC55C600044F45 /* CopilotConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopilotConfiguration.swift; sourceTree = "<group>"; };
8341195629EC58CF00044F45 /* MAACopilot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MAACopilot.swift; sourceTree = "<group>"; };
AA01F00DCAFE000000000002 /* MAAStage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MAAStage.swift; sourceTree = "<group>"; };
AA01F00DCAFE000000000004 /* MAAStageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MAAStageManager.swift; sourceTree = "<group>"; };
AA01F00DCAFE000000000006 /* CopilotEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopilotEntry.swift; sourceTree = "<group>"; };
AA01F00DCAFE000000000008 /* CopilotListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopilotListView.swift; sourceTree = "<group>"; };
8341195C29EC719B00044F45 /* CopilotContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopilotContent.swift; sourceTree = "<group>"; };
834374AB2EB61ACA002CC2D3 /* MaaUtils.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MaaUtils.xcframework; path = ../../build/MaaUtils.xcframework; sourceTree = "<group>"; };
83477CB129FEA12300A5FF99 /* TaskCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskCommands.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -214,7 +224,9 @@
83DD50B629BE385A000E84E7 /* UpdaterSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdaterSettingsView.swift; sourceTree = "<group>"; };
83E9D133294B74230091D1FF /* OpenCV.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = OpenCV.xcframework; path = ../../build/OpenCV.xcframework; sourceTree = "<group>"; };
83E9D135294B74270091D1FF /* ONNXRuntime.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ONNXRuntime.xcframework; path = ../../build/ONNXRuntime.xcframework; sourceTree = "<group>"; };
83E9ED962DF693B20069CFE9 /* OTAFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTAFetcher.swift; sourceTree = "<group>"; };
83E9ED962DF693B20069CFE9 /* GameCalendar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameCalendar.swift; sourceTree = "<group>"; };
AA03F00DCAFE000000000002 /* StageCodeResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StageCodeResolver.swift; sourceTree = "<group>"; };
AA02F00DCAFE000000000002 /* MaaApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaaApiService.swift; sourceTree = "<group>"; };
83FF5A9B2978FE27000AE831 /* resource */ = {isa = PBXFileReference; lastKnownFileType = folder; name = resource; path = ../../../../resource; sourceTree = "<group>"; };
92CDF6212A065E55005669EC /* Codeable+JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Codeable+JSON.swift"; sourceTree = "<group>"; };
92EB82862A0CD315006DD9D2 /* SystemSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemSettingsView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -310,6 +322,7 @@
832985A929EE9958009F7ED5 /* RecruitView.swift */,
832985AB29EE9977009F7ED5 /* DepotView.swift */,
83531D5E29F00F0A008A5DF8 /* CopilotView.swift */,
AA01F00DCAFE000000000008 /* CopilotListView.swift */,
8366DE0029F2F26100F715E8 /* OperBoxView.swift */,
83661E2B29F77CFF007793CA /* VideoRecogView.swift */,
838FF82629FDD0F9000566A0 /* GachaView.swift */,
Expand Down Expand Up @@ -423,6 +436,9 @@
isa = PBXGroup;
children = (
8341195629EC58CF00044F45 /* MAACopilot.swift */,
AA01F00DCAFE000000000006 /* CopilotEntry.swift */,
AA01F00DCAFE000000000002 /* MAAStage.swift */,
AA01F00DCAFE000000000004 /* MAAStageManager.swift */,
832985B529EF1890009F7ED5 /* MAADepot.swift */,
838FF82829FDE1FA000566A0 /* MAAError.swift */,
83531D6229F0A033008A5DF8 /* MAAInfrast.swift */,
Expand Down Expand Up @@ -474,7 +490,9 @@
8352529E2D533F9000C6D6D3 /* MirrorChyan.swift */,
835252A02D534A9E00C6D6D3 /* GitHub.swift */,
83812ED12D5B0B8100CB39D6 /* Binding+semicolonString.swift */,
83E9ED962DF693B20069CFE9 /* OTAFetcher.swift */,
83E9ED962DF693B20069CFE9 /* GameCalendar.swift */,
AA03F00DCAFE000000000002 /* StageCodeResolver.swift */,
AA02F00DCAFE000000000002 /* MaaApiService.swift */,
);
path = Utils;
sourceTree = "<group>";
Expand Down Expand Up @@ -611,7 +629,9 @@
832985AE29EEE672009F7ED5 /* UtilityContent.swift in Sources */,
838FF82929FDE1FA000566A0 /* MAAError.swift in Sources */,
832985B429EF1219009F7ED5 /* MAARecruit.swift in Sources */,
83E9ED972DF693B20069CFE9 /* OTAFetcher.swift in Sources */,
83E9ED972DF693B20069CFE9 /* GameCalendar.swift in Sources */,
AA03F00DCAFE000000000001 /* StageCodeResolver.swift in Sources */,
AA02F00DCAFE000000000001 /* MaaApiService.swift in Sources */,
83264A2629EBC9C10074E33A /* ReclamationSettingsView.swift in Sources */,
832985B629EF1890009F7ED5 /* MAADepot.swift in Sources */,
83D3283928F3048E00018D10 /* RoguelikeSettingsView.swift in Sources */,
Expand All @@ -630,6 +650,10 @@
83264A3329EBEE680074E33A /* MallConfiguration.swift in Sources */,
83D061F62CF81519008F0140 /* FileLogger.swift in Sources */,
8341195729EC58CF00044F45 /* MAACopilot.swift in Sources */,
AA01F00DCAFE000000000001 /* MAAStage.swift in Sources */,
AA01F00DCAFE000000000003 /* MAAStageManager.swift in Sources */,
AA01F00DCAFE000000000005 /* CopilotEntry.swift in Sources */,
AA01F00DCAFE000000000007 /* CopilotListView.swift in Sources */,
8347B5A82CDF85A000AEB627 /* ResourceUpdateView.swift in Sources */,
8391C9FA2B2746FA0047DB20 /* MaaToolClient.swift in Sources */,
835252A12D534A9E00C6D6D3 /* GitHub.swift in Sources */,
Expand Down
83 changes: 75 additions & 8 deletions MeoAsstMac/Configuration Views/FightSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
import SwiftUI

struct FightSettingsView: View {
@EnvironmentObject private var viewModel: MAAViewModel
@Binding var config: FightConfiguration

@State private var useCustomStage = false
@State private var dropItemList: [(name: String, id: String)] = []
@AppStorage("MAAHideUnavailableStage") private var hideUnavailableStage = false

var body: some View {
Form {
Expand All @@ -22,17 +24,21 @@ struct FightSettingsView: View {
} else {
Picker("关卡选择", selection: $config.stage) {
Text("当前/上次").tag("")
Text("1-7").tag("1-7")
Text("CE-6").tag("CE-6")
Text("AP-5").tag("AP-5")
Text("CA-5").tag("CA-5")
Text("LS-6").tag("LS-6")
Text("剿灭模式").tag("Annihilation")
ForEach(pickerStages) { stage in
stageLabel(stage).tag(stage.value)
}
}
}
Toggle("手动输入关卡名", isOn: isUsingCustomStage)
}
.animation(.default, value: config.stage)

if !useCustomStage && !stageNotListed {
Toggle("隐藏未开放关卡", isOn: $hideUnavailableStage)
if let tip = selectedStageTip {
Text(tip).font(.caption).foregroundStyle(.secondary)
}
}
}

if useCustomStage || stageNotListed {
Expand Down Expand Up @@ -210,12 +216,73 @@ 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: - Stage List

/// In-game weekday (accounts for the 04:00 server day-rollover), per current channel.
private var currentWeekday: Int {
GameCalendar.yjWeekday(channel: viewModel.clientChannel)
}

/// Stages shown in the picker, respecting the "hide unavailable" toggle.
/// The currently-selected stage is always kept visible so the picker never shows blank.
private var pickerStages: [MAAStageInfo] {
var listed = viewModel.stages.listedStages(hideClosed: hideUnavailableStage, weekday: currentWeekday)
if !config.stage.isEmpty, !listed.contains(where: { $0.value == config.stage }),
let selected = viewModel.stages.stageInfo(for: config.stage)
{
listed.insert(selected, at: 0)
}
return listed
}

/// Tip text of the currently-selected stage (e.g. open days or activity description),
/// combined with the stage's drop material when available.
private var selectedStageTip: String? {
guard let stage = viewModel.stages.stageInfo(for: config.stage) else {
return nil
}

var parts: [String] = []
if !stage.tip.isEmpty {
parts.append(stage.tip)
}
if let drop = dropDescription(for: stage) {
parts.append(String(localized: "可刷取:\(drop)"))
}
return parts.isEmpty ? nil : parts.joined(separator: "\n")
}

/// Human-readable drop material for a stage. The `drop` field is either an item id
/// (resolved to its localized name via item_index) or already a descriptive string.
private func dropDescription(for stage: MAAStageInfo) -> String? {
guard let drop = stage.drop, !drop.isEmpty else { return nil }
if let idx = FightConfiguration.id2index[drop] {
return FightConfiguration.dropItems[idx].item.name
}
return drop
}

/// Renders a stage entry: dimmed when closed today, strikethrough when its activity expired.
@ViewBuilder private func stageLabel(_ stage: MAAStageInfo) -> some View {
let isOpen = stage.isStageOpen(weekday: currentWeekday)
if stage.isOutdated {
Text(stage.display).strikethrough().foregroundStyle(.red)
} else if isOpen {
Text(stage.display)
} else {
Text(stage.display).foregroundStyle(.secondary)
}
}

private var stageNotListed: Bool {
guard !config.stage.isEmpty else { return false }
return viewModel.stages.stageInfo(for: config.stage) == nil
}
}

struct FightSettingsView_Previews: PreviewProvider {
static var previews: some View {
FightSettingsView(config: .constant(.init()))
.environmentObject(MAAViewModel())
}
}
70 changes: 70 additions & 0 deletions MeoAsstMac/Model/CopilotEntry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// CopilotEntry.swift
// MAA
//
// Model for the Copilot List (作业集) feature: an ordered, persisted list of
// copilot operations that run in sequence via the core's `copilot_list` param.
//

import Foundation

/// A single entry in the copilot list. Field names mirror the Windows persisted schema
/// (`name`/`file_path`/`copilot_id`/`is_raid`/`is_checked`) for cross-platform familiarity.
struct CopilotEntry: Codable, Identifiable, Equatable {
var id = UUID()

/// Stage code, used by the core for auto-navigation (e.g. "1-7", "main_01-07").
var name: String

/// Absolute path to the copilot JSON file.
var filePath: String

/// PRTS Plus id (0 for local files).
var copilotId: Int

/// Whether this stage should be run in raid (突袭) difficulty.
var isRaid: Bool

/// Whether this entry is selected for the next run.
var isChecked: Bool

enum CodingKeys: String, CodingKey {
case name
case filePath = "file_path"
case copilotId = "copilot_id"
case isRaid = "is_raid"
case isChecked = "is_checked"
}

init(
name: String, filePath: String, copilotId: Int = 0, isRaid: Bool = false,
isChecked: Bool = true
) {
self.name = name
self.filePath = filePath
self.copilotId = copilotId
self.isRaid = isRaid
self.isChecked = isChecked
}

init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = UUID()
self.name = try container.decode(String.self, forKey: .name)
self.filePath = try container.decode(String.self, forKey: .filePath)
self.copilotId = try container.decodeIfPresent(Int.self, forKey: .copilotId) ?? 0
self.isRaid = try container.decodeIfPresent(Bool.self, forKey: .isRaid) ?? false
self.isChecked = try container.decodeIfPresent(Bool.self, forKey: .isChecked) ?? true
}
}

/// Global options applied to a whole copilot-list run, mirroring the Windows
/// `AsstCopilotTask` global flags.
struct CopilotListOptions: Codable, Equatable {
var formation = true
var addTrust = false
var useSanityPotion = false

/// Support unit usage: 0 = none, 1 = when needed, 3 = random.
var supportUnitUsage = 0
}
Loading