diff --git a/src/MaaWpfGui/Models/AsstTasks/AsstCopilotTask.cs b/src/MaaWpfGui/Models/AsstTasks/AsstCopilotTask.cs index ea66691f65..24e3b8edfe 100644 --- a/src/MaaWpfGui/Models/AsstTasks/AsstCopilotTask.cs +++ b/src/MaaWpfGui/Models/AsstTasks/AsstCopilotTask.cs @@ -127,8 +127,19 @@ public class AsstCopilotTask : AsstBaseTask [JsonProperty("name")] public string Name { get; set; } = string.Empty; + /// + /// Gets or sets 技能序号,可选,默认为 1,取值范围 [1, 3] + /// [JsonProperty("skill")] - public int Skill { get; set; } + public int Skill { get; set; } = 1; + + /// + /// Gets or sets 模组编号,可选,默认为 0 + /// -1: 不切换模组 / 无要求, 0: 不使用模组, 1: 模组 χ, 2: 模组 γ, 3: 模组 α, 4: 模组 Δ + /// 当前核心仅使用 name 和 skill 字段,module 作为预留字段 + /// + [JsonProperty("module")] + public int Module { get; set; } } public class MultiTask diff --git a/src/MaaWpfGui/Res/Localizations/en-us.xaml b/src/MaaWpfGui/Res/Localizations/en-us.xaml index ca9ab4143f..ac0dc6525c 100644 --- a/src/MaaWpfGui/Res/Localizations/en-us.xaml +++ b/src/MaaWpfGui/Res/Localizations/en-us.xaml @@ -842,14 +842,21 @@ Only level rewards can be farmed; to get the Engraved Medal, all 「Key Objectiv Thanks for the likes!\nThe comment section is already open on the web page, please feel free to leave your comment! Auto Squad 「Favourite」 marked {key=Operator}s may not be recognized - Support Unit Borrowing Strategy - Don't Borrow - Only for Filling Gaps + Use Support Unit + Missing one operator is manageable, but if two or more are missing, consider switching to a different job. + Never + When Needed Random Add low-trust {key=Operator}s Ignore {key=Operator} requirements + Some jobs require specific Modules or other prerequisites. +Enabling this will skip these checks, but may cause the job to fail. Add custom {key=Operator}s Use 「;」 as the separator, 「,」 to separate the {key=Operator} name and skills, for example: W, 3; Ash, 2 + {key=Operator} Name + Skill + Action + Add Loop Times Battle list Only supported in the following modes: @@ -859,12 +866,12 @@ Only level rewards can be farmed; to get the Engraved Medal, all 「Key Objectiv 4. {key=ParadoxSimulation}: Start from the {key=Operator} list Please start from the corresponding screen. Cross-chapter navigation is not supported. -- When 「{key=UseCopilotList}」 is enabled, individual copilot will be automatically added to 「{key=UseCopilotList}」. +When 「{key=UseCopilotList}」 is enabled, individual jobs will be automatically added to 「{key=UseCopilotList}」. (Challenge) Left-click to add Normal difficulty Right click to add challenge difficulty - Left-click to clear all tasks -Right-click to clear inactive tasks + Left-click to clear all jobs +Right-click to clear inactive jobs Start Video Link Skill @@ -894,21 +901,21 @@ Right-click to clear inactive tasks Tips: \n\n - 1. Before using, please confirm the task file matches your selected stage type.\n\n + 1. Before using, please confirm the job file matches your selected stage type.\n\n 2. {key=MainStage}, {key=StoryCollection}, {key=SideStory}: Please start from the screen where the blue "Start" button appears in the bottom right corner.\n\n 3. {key=SSS}: Multiple built-in files are in the resource/copilot folder. First create your squad manually, then start from the screen where the "Start Deployment" button appears in the bottom right. Can be used with 「{key=LoopTimes}」.\n\n 4. {key=ParadoxSimulation}: After selecting skills, start from the skill selection screen where the "Start Simulation" button appears. For 1/2★ {key=Operator}s (without skills), start from the screen with the "Start Simulation" button in the bottom right. If using 「{key=UseCopilotList}」, please start from the 「Level/Rarity」 tab in the {key=Operator} list.\n\n 5. When using a support unit, turn off 「{key=AutoSquad}」 and 「{key=UseCopilotList}」, manually select the {key=Operator}, then start from the squad screen where the "Mission Start" button appears in the bottom right.\n\n 6. {key=Operator}s marked as "Favorite" may affect 「{key=AutoSquad}」 recognition and selection. When using 「{key=AutoSquad}」, consider removing favorites, or if errors occur, turn off 「{key=AutoSquad}」 and manually supplement the missing {key=Operator}s as prompted.\n\n - 7. In the Copilot workstation, you can paste the mysterious code by clicking the paste button on the right side of the input box:\n - - click the second button = Add a single job.\n - - click the third button = Add a job set.\n\n + 7. In the Copilot workstation, you can paste codes by clicking the paste button on the right side of the input box:\n + ● Second button = Add a single job.\n + ● Third button = Add a job set.\n\n 8. {key=UseCopilotList}:\n - - After selecting a task, check that the stage name below is correct (e.g.: CV-EX-1).\n - - Add: Left-click for normal difficulty, right-click for challenge mode.\n - - Clear: Left-click to clear all, right-click to remove only inactive tasks.\n - - Start from a screen where the target stage name is visible. Cross-chapter navigation is not supported.\n - - Operation will stop automatically when encountering insufficient sanity, battle failure, or failure to achieve 3-star clear. + ● After selecting a job, verify the stage name below is correct (e.g., CV-EX-1).\n + ● Add: Left-click for Normal difficulty, right-click for Challenge difficulty.\n + ● Clear: Left-click to clear all jobs, right-click to remove only inactive jobs.\n + ● Start from a screen where the target stage name is visible. Cross-chapter navigation is not supported.\n + ● Execution will stop automatically when sanity is insufficient, battle fails, or fails to achieve a 3-star clear. Failed to read file! No corresponding file found! diff --git a/src/MaaWpfGui/Res/Localizations/ja-jp.xaml b/src/MaaWpfGui/Res/Localizations/ja-jp.xaml index fa951fe788..7fb43e6373 100644 --- a/src/MaaWpfGui/Res/Localizations/ja-jp.xaml +++ b/src/MaaWpfGui/Res/Localizations/ja-jp.xaml @@ -842,14 +842,21 @@ C:\\leidian\\LDPlayer9 感謝と評価!\nウェブページには既にコメント欄が開放されており、あなたのコメントを心待ちにしています! 自動編成 自動編成では、一時的に「戦術要員」{key=Operator}を認識できません - 支援要員借用戦略 + 戦友サポート借用 + 1つ足りないくらいならまだ使えますが、2つ以上欠けている場合は別の攻略に替えたほうがいいでしょう。 借用しない 補完のみ ランダム 信頼度低の{key=Operator}追加 {key=Operator}要件を無視 + 一部の攻略は特定のモジュールなどを前提条件とします +この項目を選択するとこれらのチェックをスキップしますが、タスクが正常に動作しなくなる可能性があります。 カスタム{key=Operator}追加 「;」はセパレータ、「,」で{key=Operator}の名前とスキルを区切ります。例: Surtr, 3; Eyjafjalla, 1 + {key=Operator}名 + スキル + 操作 + 追加 ループ回数 バトルリスト 以下のモードのみ対応: @@ -859,7 +866,7 @@ C:\\leidian\\LDPlayer9 4. {key=ParadoxSimulation}: {key=Operator}リストから起動 対応する画面から開始してください。章間のナビゲーションはサポートされていません。 -- 「{key=UseCopilotList}」を有効にすると、個々のジョブが自動的に「{key=UseCopilotList}」に追加されます。 +「{key=UseCopilotList}」を有効にすると、個々のジョブが自動的に「{key=UseCopilotList}」に追加されます。 (強襲) 左クリックで通常難易度を追加 右クリックしてレイドの難易度を追加します @@ -901,14 +908,14 @@ C:\\leidian\\LDPlayer9 5. サポートを使うときは、「{key=AutoSquad}」と「{key=UseCopilotList}」をオフにし、手動で {key=Operator} を選択した後、編成画面の右下に「行動開始」ボタン画面で開始してください。\n\n 6. 「戦術要員」機能を有効にすると、「{key=AutoSquad}」の{key=Operator}選択に影響を与える可能性があります。「{key=AutoSquad}」を 使用する際は「戦術要員」をオフにするか、不足している{key=Operator}を手動で補充して編成を完成させることをお勧めします。\n\n 7. Copilot作業ステーションでは、入力欄の右側にある貼り付けボタンをクリックして、謎のコードを貼り付けることができます:\n - - 2番目のボタンをクリック = 割り当てを追加。\n - - 3番目のボタンをクリック = 割り当てセットを追加。\n\n + ● 2番目のボタンをクリック = 割り当てを追加。\n + ● 3番目のボタンをクリック = 割り当てセットを追加。\n\n 8. {key=UseCopilotList}:\n - - 攻略ファイルを選択した後、リストの下のステージ名が正しいことを確認してください(例: CV-EX-1)。 \n - - 追加: 左クリックして通常難易度を追加し、右クリックして強襲難易度を追加します。\n - - クリア: 左クリックするとリストがクリアされ、右クリックすると非アクティブなタスクのみが削除されます。\n - - ターゲットのステージ名が見える画面で起動してください。章間のナビゲーションはサポートされていません。\n - - 理性不足の場合、戦闘が失敗した場合、または 3 つ星を解決できない場合、アクションは終了します。 + ● 攻略ファイルを選択した後、リストの下のステージ名が正しいことを確認してください(例: CV-EX-1)。 \n + ● 追加: 左クリックして通常難易度を追加し、右クリックして強襲難易度を追加します。\n + ● クリア: 左クリックするとリストがクリアされ、右クリックすると非アクティブなタスクのみが削除されます。\n + ● ターゲットのステージ名が見える画面で起動してください。章間のナビゲーションはサポートされていません。\n + ● 理性不足の場合、戦闘が失敗した場合、または 3 つ星を解決できない場合、アクションは終了します。 ファイルの読み取りに失敗しました! 対応する攻略ファイルが見つかりません! diff --git a/src/MaaWpfGui/Res/Localizations/ko-kr.xaml b/src/MaaWpfGui/Res/Localizations/ko-kr.xaml index 14e1e2e55e..e598a2966b 100644 --- a/src/MaaWpfGui/Res/Localizations/ko-kr.xaml +++ b/src/MaaWpfGui/Res/Localizations/ko-kr.xaml @@ -843,14 +843,21 @@ C:\\leidian\\LDPlayer9 감사합니다!\n웹사이트에 댓글을 남길 수도 있으니 의견을 남겨 주세요! 자동 편성 자동 편성은 「선호」 설정 된 {key=Operator}를 인식할 수 없습니다 - 지원 유닛 모집 전략 + 지원 유닛 모집 + 하나 정도 부족한 것은 문제가 없지만, 둘 이상 부족하면 다른 작업을 사용하는 것을 권장합니다. 모집안함 보완용 랜덤 신뢰도가 낮은 {key=Operator} 추가 {key=Operator} 요구 사항 무시 + 일부 작업은 특정 모듈이 필요합니다 +이 옵션을 선택하면 검사 과정을 건너뛰지만 작업이 정상적으로 동작하지 않을 수 있습니다. 커스텀 {key=Operator} 추가 「;」 로 {key=Operator}를 구분하고, 「,」 로 스킬을 구분합니다. 예시: W, 3; Ash, 2 + {key=Operator} 이름 + 스킬 + 작업 + 추가 반복 횟수 작업 목록 다음 모드에서만 지원됩니다: @@ -860,7 +867,7 @@ C:\\leidian\\LDPlayer9 4. {key=ParadoxSimulation}: {key=Operator} 목록에서 시작 해당 화면에서 시작하세요. 챕터 간 이동은 지원되지 않습니다. -- 「{key=UseCopilotList}」 가 활성화되면 개별 작업이 자동으로 「{key=UseCopilotList}」 에 추가됩니다. +「{key=UseCopilotList}」 가 활성화되면 개별 작업이 자동으로 「{key=UseCopilotList}」 에 추가됩니다. (하드) 좌클릭으로 일반 난이도 추가 하드 모드를 추가하려면 마우스 오른쪽 버튼을 클릭하세요 @@ -902,14 +909,14 @@ C:\\leidian\\LDPlayer9 5. 지원 유닛을 사용할 때는 「{key=AutoSquad}」 및 「{key=UseCopilotList}」를 끄고, 수동으로 오퍼레이터를 선택한 다음, 우측 하단에 "작전 개시" 버튼이 나타나는 분대 화면에서 시작하세요.\n\n 6. "선호"로 표시된 오퍼레이터는 「{key=AutoSquad}」 인식 및 선택에 영향을 줄 수 있습니다. 「{key=AutoSquad}」 사용 시, 선호 표시를 해제하거나 오류 발생 시 「{key=AutoSquad}」를 끄고 안내에 따라 누락된 오퍼레이터를 수동으로 편성하는 것을 고려하세요.\n\n 7. 자동지휘 작업 공간에서, 입력 상자 오른쪽의 붙여넣기 버튼을 클릭하여 코드를 붙여넣을 수 있습니다:\n - - 두 번째 버튼을 왼쪽 클릭 = 작업 추가.\n - - 세 번째 버튼을 클릭 = 작업 목록 추가.\n\n + ● 두 번째 버튼을 왼쪽 클릭 = 작업 추가.\n + ● 세 번째 버튼을 클릭 = 작업 목록 추가.\n\n 8. {key=UseCopilotList}:\n - - 작업 선택 후, 아래의 스테이지 이름이 올바른지 확인하세요 (예: CV-EX-1).\n - - 추가: 좌클릭은 일반, 우클릭은 하드 모드입니다.\n - - 지우기: 좌클릭은 전체 삭제, 우클릭은 비활성 작업만 삭제합니다.\n - - 목표 스테이지 이름이 보이는 화면에서 시작하세요. 챕터 간 이동은 지원되지 않습니다.\n - - 이성 부족, 작전 실패, 또는 3성 클리어 실패 시 작전이 자동으로 중지됩니다. + ● 작업 선택 후, 아래의 스테이지 이름이 올바른지 확인하세요 (예: CV-EX-1).\n + ● 추가: 좌클릭은 일반, 우클릭은 하드 모드입니다.\n + ● 지우기: 좌클릭은 전체 삭제, 우클릭은 비활성 작업만 삭제합니다.\n + ● 목표 스테이지 이름이 보이는 화면에서 시작하세요. 챕터 간 이동은 지원되지 않습니다.\n + ● 이성 부족, 작전 실패, 또는 3성 클리어 실패 시 작전이 자동으로 중지됩니다. 파일을 읽지 못했습니다! 해당 파일을 찾을 수 없습니다! diff --git a/src/MaaWpfGui/Res/Localizations/zh-cn.xaml b/src/MaaWpfGui/Res/Localizations/zh-cn.xaml index fad304b46a..ad8fb02ec9 100644 --- a/src/MaaWpfGui/Res/Localizations/zh-cn.xaml +++ b/src/MaaWpfGui/Res/Localizations/zh-cn.xaml @@ -842,14 +842,21 @@ C:\\leidian\\LDPlayer9。\n 感谢评价!\n网页已经开放评论区,欢迎前往留下你的评论! 自动编队 自动编队可能无法识别带有 「特别关注」 标记的{key=Operator} - 助战干员借用策略 + 借助战 + 缺一个还能用用,缺两个以上还是换份作业吧 不借用 仅用于补漏 随机 补充低信赖{key=Operator} 忽略{key=Operator}属性要求 + 某些作业需要特定模组等作为前置条件 +勾选此项将跳过这些检查,但可能导致作业无法正常运行 追加自定{key=Operator} 以英文 「;」 为分隔符,英文 「,」 分隔{key=Operator}名与技能,例: 史尔特尔,3;艾雅法拉,1 + {key=Operator}名 + 技能 + 操作 + 添加 循环次数 战斗列表 仅支持以下模式: @@ -859,7 +866,7 @@ C:\\leidian\\LDPlayer9。\n 4. {key=ParadoxSimulation}: 从{key=Operator}列表启动 请在对应界面启动,不支持跨章节导航 -- 当 「{key=UseCopilotList}」 启用后, 选择单个作业时会自动添加到 「{key=UseCopilotList}」 +当 「{key=UseCopilotList}」 启用后, 选择单个作业时会自动添加到 「{key=UseCopilotList}」 (突袭) 左键添加普通难度 右键添加突袭难度 @@ -901,14 +908,14 @@ C:\\leidian\\LDPlayer9。\n 5. 使用好友助战时,请关闭 「{key=AutoSquad}」 和 「{key=UseCopilotList}」,手动选择{key=Operator}后,在编队界面右下角存在 「开始行动」 按钮界面启动。\n\n 6. {key=Operator}若被标记为 「特别关注」,可能影响 「{key=AutoSquad}」 的识别与选择。建议使用 「{key=AutoSquad}」 时移除关注,或在报错后关闭 「{key=AutoSquad}」,根据提示手动补充缺失的{key=Operator}。\n\n 7. Copilot 作业站的神秘代码可通过输入框右侧的粘贴按钮粘贴:\n - - 单击第 2 个按钮 = 添加作业。\n - - 单击第 3 个按钮 = 添加作业集。\n\n + ● 单击第 2 个按钮 = 添加作业。\n + ● 单击第 3 个按钮 = 添加作业集。\n\n 8. {key=UseCopilotList}:\n - - 选择作业后,检查下方关卡名是否正确 (例: CV-EX-1)。\n - - 添加: 左键 = 普通难度,右键 = 突袭难度。\n - - 清除: 左键 = 全部清空,右键 = 仅移除未激活任务。\n - - 请在能看到目标关卡名的界面启动,不支持跨章节导航。\n - - 遇到理智不足、战斗失败、未能三星结算时将自动中止。 + ● 选择作业后,检查下方关卡名是否正确 (例: CV-EX-1)。\n + ● 添加: 左键 = 普通难度,右键 = 突袭难度。\n + ● 清除: 左键 = 全部清空,右键 = 仅移除未激活任务。\n + ● 请在能看到目标关卡名的界面启动,不支持跨章节导航。\n + ● 遇到理智不足、战斗失败、未能三星结算时将自动中止。 读取文件失败! 未找到对应作业! diff --git a/src/MaaWpfGui/Res/Localizations/zh-tw.xaml b/src/MaaWpfGui/Res/Localizations/zh-tw.xaml index 1bc00314ed..e1cfe81f9e 100644 --- a/src/MaaWpfGui/Res/Localizations/zh-tw.xaml +++ b/src/MaaWpfGui/Res/Localizations/zh-tw.xaml @@ -843,14 +843,21 @@ C:\\leidian\\LDPlayer9。\n 感謝評價!\n網頁已經開放評論區,歡迎前往留下你的評論! 自動編隊 自動編隊可能無法辨識 「特別關注」 的{key=Operator} - 助戰幹員借用策略 + 借助戰 + 缺一個還能用用,缺兩個以上還是換份作業吧 不借用 僅用於補漏 隨機 補充低信賴{key=Operator} 忽略{key=Operator}屬性要求 + 某些作業需要特定模組等作為前置條件 +勾選此項將跳過這些檢查,但可能導致作業無法正常執行 追加自定{key=Operator} 使用 「;」 作為分隔符,用 「,」 分隔{key=Operator}姓名和技能,例:史爾特爾,3;艾雅法拉,1 + {key=Operator}名 + 技能 + 操作 + 添加 戰鬥列表 僅支援以下模式: 1. {key=MainStage}: 同一章節內導航 @@ -859,7 +866,7 @@ C:\\leidian\\LDPlayer9。\n 4. {key=ParadoxSimulation}: 從{key=Operator}列表啟動 請在對應介面啟動,不支援跨章節導航 -- 當 「{key=UseCopilotList}」 啟用後, 選擇單一作業時會自動新增至 「{key=UseCopilotList}」 +當 「{key=UseCopilotList}」 啟用後, 選擇單一作業時會自動新增至 「{key=UseCopilotList}」 (突襲) 左鍵加入普通難度 右鍵加入突襲難度 @@ -902,14 +909,14 @@ C:\\leidian\\LDPlayer9。\n 5. 使用好友助戰時,請關閉 「{key=AutoSquad}」 和 「{key=UseCopilotList}」,手動選擇{key=Operator}後,在編隊介面右下角存在 「開始行動」 按鈕的介面啟動。\n\n 6. {key=Operator}若被標記為 「特別關注」,可能影響 「{key=AutoSquad}」 的辨識與選擇。建議使用 「{key=AutoSquad}」 時移除關注,或在報錯後關閉 「{key=AutoSquad}」,依據提示手動補充缺少的{key=Operator}。\n\n 7. Copilot 作業站的神秘代碼可透過輸入框右側的貼上按鈕貼上:\n - - 點選第 2 個按鈕 = 新增作業。\n - - 點選第3個按鈕 = 新增作業集。\n\n + ● 點選第 2 個按鈕 = 新增作業。\n + ● 點選第 3 個按鈕 = 新增作業集。\n\n 8. {key=UseCopilotList}:\n - - 選擇作業後,檢查下方關卡名是否正確 (例: CV-EX-1)。\n - - 加入: 左鍵 = 普通難度,右鍵 = 突襲難度。\n - - 清除: 左鍵 = 全部清空,右鍵 = 僅移除未啟用任務。\n - - 請在能看到目標關卡名的介面啟動,不支援跨章節導航。\n - - 遇到理智不足、戰鬥失敗、未能三星結算時將會自動中止。 + ● 選擇作業後,檢查下方關卡名是否正確 (例: CV-EX-1)。\n + ● 加入: 左鍵 = 普通難度,右鍵 = 突襲難度。\n + ● 清除: 左鍵 = 全部清空,右鍵 = 僅移除未啟用任務。\n + ● 請在能看到目標關卡名的介面啟動,不支援跨章節導航。\n + ● 遇到理智不足、戰鬥失敗、未能三星結算時將會自動中止。 讀取檔案失敗! 未找到對應作業! diff --git a/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs b/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs index 962d434310..fc74bfcbff 100644 --- a/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs @@ -317,7 +317,7 @@ public partial class CopilotViewModel : Screen } } - private string _userAdditional = ConfigurationHelper.GetValue(ConfigurationKeys.CopilotUserAdditional, string.Empty).Replace(",", ",").Replace(";", ";").Trim(); + private string _userAdditional = ConfigurationHelper.GetValue(ConfigurationKeys.CopilotUserAdditional, string.Empty).Trim(); /// /// Gets or sets a value indicating whether to use auto-formation. @@ -327,12 +327,239 @@ public partial class CopilotViewModel : Screen get => _userAdditional; set { - value = value.Replace(",", ",").Replace(";", ";").Trim(); + value = value.Trim(); SetAndNotify(ref _userAdditional, value); ConfigurationHelper.SetValue(ConfigurationKeys.CopilotUserAdditional, value); } } + private bool _isUserAdditionalPopupOpen; + + /// + /// Gets or sets a value indicating whether the UserAdditional popup is open. + /// + public bool IsUserAdditionalPopupOpen + { + get => _isUserAdditionalPopupOpen; + set => SetAndNotify(ref _isUserAdditionalPopupOpen, value); + } + + /// + /// Gets the view models of UserAdditional items. + /// + public ObservableCollection UserAdditionalItems { get; } = []; + + /// + /// Opens the UserAdditional popup for editing. + /// + public void OpenUserAdditionalPopup() + { + // 清空列表 + UserAdditionalItems.Clear(); + + // 优先按 JSON 反序列化 + if (!string.IsNullOrWhiteSpace(UserAdditional)) + { + try + { + var list = JsonConvert.DeserializeObject>(UserAdditional) ?? []; + foreach (var op in list) + { + if (string.IsNullOrWhiteSpace(op.Name)) + { + continue; + } + + int skill = op.Skill; + if (skill < 1) + { + skill = 1; + } + else if (skill > 3) + { + skill = 3; + } + + var item = new UserAdditionalItemViewModel + { + Name = op.Name, + Skill = skill, + Module = op.Module, + }; + UserAdditionalItems.Add(item); + } + } + catch + { + // 兼容旧格式:以 ; 和 , 解析 "name,skill;name,skill" 形式 + try + { + Regex regex = new(@"(?<=;)(?[^,;]+)(?:, *(?\d))?(?=;)", RegexOptions.Compiled); + var matches = regex.Matches(";" + UserAdditional + ";"); + foreach (Match match in matches) + { + var name = match.Groups["name"].Value.Trim(); + var skillStr = match.Groups["skill"].Value; + int skill = string.IsNullOrEmpty(skillStr) ? 1 : int.Parse(skillStr); + if (skill < 1) + { + skill = 1; + } + else if (skill > 3) + { + skill = 3; + } + + var item = new UserAdditionalItemViewModel + { + Name = name, + Skill = skill, + Module = 0, + }; + UserAdditionalItems.Add(item); + } + } + catch + { + // ignore + } + } + } + + // 如果列表为空,添加一行空行 + if (UserAdditionalItems.Count == 0) + { + var newItem = new UserAdditionalItemViewModel { Name = string.Empty, Skill = 1, Module = 0 }; + UserAdditionalItems.Add(newItem); + } + + IsUserAdditionalPopupOpen = true; + } + + /// + /// Saves the UserAdditional value from the popup. + /// + public void SaveUserAdditional() + { + // 将列表转换为 UserAdditional 并序列化为 JSON + var list = new List(); + foreach (var item in UserAdditionalItems) + { + if (string.IsNullOrWhiteSpace(item.Name)) + { + continue; // 跳过空行 + } + + int skill = item.Skill; + if (skill < 1) + { + skill = 1; + } + else if (skill > 3) + { + skill = 3; + } + + list.Add(new UserAdditional + { + Name = item.Name.Trim(), + Skill = skill, + Module = item.Module, + }); + } + + UserAdditional = list.Count > 0 + ? JsonConvert.SerializeObject(list, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore }) + : string.Empty; + + IsUserAdditionalPopupOpen = false; + } + + /// + /// Cancels editing UserAdditional and closes the popup. + /// + public void CancelUserAdditionalEdit() + { + IsUserAdditionalPopupOpen = false; + } + + /// + /// Gets a value indicating whether can add a new UserAdditional item. + /// + public bool CanAddUserAdditionalItem => UserAdditionalItems.All(item => !string.IsNullOrWhiteSpace(item.Name)); + + /// + /// Adds a new UserAdditional item. + /// + public void AddUserAdditionalItem() + { + if (!CanAddUserAdditionalItem) + { + return; + } + + var newItem = new UserAdditionalItemViewModel { Name = string.Empty, Skill = 1, Module = 0 }; + UserAdditionalItems.Add(newItem); + } + + /// + /// Removes a UserAdditional item. + /// + /// The item to remove. + public void RemoveUserAdditionalItem(UserAdditionalItemViewModel item) + { + if (item != null && UserAdditionalItems.Contains(item)) + { + UserAdditionalItems.Remove(item); + } + } + + public static Dictionary ModuleMapping { get; } = new() + { + { LocalizationHelper.GetString("CopilotWithoutModule"), 0 }, + { "χ", 1 }, + { "γ", 2 }, + { "α", 3 }, + { "Δ", 4 }, + }; + + public class UserAdditionalItemViewModel : PropertyChangedBase + { + private string _name = string.Empty; + + /// + /// Gets or sets the operator name. + /// + public string Name + { + get => _name; + set => SetAndNotify(ref _name, value); + } + + private int _skill; + + /// + /// Gets or sets the skill number. + /// + public int Skill + { + get => _skill; + set => SetAndNotify(ref _skill, value); + } + + private int _module; + + /// + /// Gets or sets the module number. + /// -1: 不切换模组 / 无要求, 0: 不使用模组, 1-4: 不同模组 + /// + public int Module + { + get => _module; + set => SetAndNotify(ref _module, value); + } + } + private bool _useFormation; public bool UseFormation @@ -351,7 +578,7 @@ public partial class CopilotViewModel : Screen private int _formationIndex = ConfigurationHelper.GetValue(ConfigurationKeys.CopilotSelectFormation, 1); - public int SelectFormation + public int FormationIndex { get => _formationIndex; set @@ -361,6 +588,14 @@ public partial class CopilotViewModel : Screen } } + private bool _useSupportUnitUsage; + + public bool UseSupportUnitUsage + { + get => _useSupportUnitUsage; + set => SetAndNotify(ref _useSupportUnitUsage, value); + } + private int _supportUnitUsage = ConfigurationHelper.GetValue(ConfigurationKeys.CopilotSupportUnitUsage, 0); public int SupportUnitUsage @@ -375,7 +610,7 @@ public partial class CopilotViewModel : Screen public List> SupportUnitUsageList { get; } = [ - new() { Display = LocalizationHelper.GetString("SupportUnitUsage.None"), Value = 0 }, + /* new() { Display = LocalizationHelper.GetString("SupportUnitUsage.None"), Value = 0 }, */ new() { Display = LocalizationHelper.GetString("SupportUnitUsage.WhenNeeded"), Value = 1 }, new() { Display = LocalizationHelper.GetString("SupportUnitUsage.Random"), Value = 3 }, ]; @@ -1359,13 +1594,70 @@ public partial class CopilotViewModel : Screen return; } - Regex regex = new Regex(@"(?<=;)(?[^,;]+)(?:, *(?\d))?(?=;)", RegexOptions.Compiled); - - var userAdditional = regex.Matches(";" + UserAdditional + ";").ToList().Select(match => new UserAdditional + // 反序列化自定义追加干员列表(JSON),兼容旧格式 + IEnumerable userAdditional = []; + if (!string.IsNullOrWhiteSpace(UserAdditional)) { - Name = match.Groups[1].Value.Trim(), - Skill = string.IsNullOrEmpty(match.Groups[2].Value) ? 0 : int.Parse(match.Groups[2].Value), - }); + try + { + var list = JsonConvert.DeserializeObject>(UserAdditional) ?? []; + foreach (var op in list) + { + if (string.IsNullOrWhiteSpace(op.Name)) + { + continue; + } + + int skill = op.Skill; + if (skill < 1) + { + skill = 1; + } + else if (skill > 3) + { + skill = 3; + } + + op.Skill = skill; + } + + userAdditional = list.Where(op => !string.IsNullOrWhiteSpace(op.Name)); + } + catch + { + // 兼容旧格式:以 ; 和 , 解析 "name,skill;name,skill" 形式 + try + { + Regex regex = new(@"(?<=;)(?[^,;]+)(?:, *(?\d))?(?=;)", RegexOptions.Compiled); + var matches = regex.Matches(";" + UserAdditional + ";").ToList(); + userAdditional = matches.Select(match => + { + var name = match.Groups[1].Value.Trim(); + var skillStr = match.Groups[2].Value; + int skill = string.IsNullOrEmpty(skillStr) ? 1 : int.Parse(skillStr); + if (skill < 1) + { + skill = 1; + } + else if (skill > 3) + { + skill = 3; + } + + return new UserAdditional + { + Name = name, + Skill = skill, + Module = 0, + }; + }).Where(op => !string.IsNullOrWhiteSpace(op.Name)); + } + catch + { + userAdditional = []; + } + } + } bool ret = true; try @@ -1383,13 +1675,13 @@ public partial class CopilotViewModel : Screen var task = new AsstCopilotTask() { MultiTasks = [.. t], - Formation = _form, - SupportUnitUsage = _supportUnitUsage, - AddTrust = _addTrust, - IgnoreRequirements = _ignoreRequirements, - UserAdditionals = AddUserAdditional ? userAdditional.ToList() : [], - UseSanityPotion = _useSanityPotion, - FormationIndex = UseFormation ? _formationIndex : 0, + Formation = Form, + SupportUnitUsage = UseSupportUnitUsage ? SupportUnitUsage : 0, + AddTrust = AddTrust, + IgnoreRequirements = IgnoreRequirements, + UserAdditionals = AddUserAdditional ? [.. userAdditional] : [], + UseSanityPotion = UseSanityPotion, + FormationIndex = UseFormation ? FormationIndex : 0, }; ret = Instances.AsstProxy.AsstAppendTaskWithEncoding(AsstProxy.TaskType.Copilot, task); @@ -1414,14 +1706,14 @@ public partial class CopilotViewModel : Screen var task = new AsstCopilotTask() { FileName = IsDataFromWeb ? TempCopilotFile : Filename, - Formation = _form, - SupportUnitUsage = _supportUnitUsage, - AddTrust = _addTrust, - IgnoreRequirements = _ignoreRequirements, - UserAdditionals = AddUserAdditional ? userAdditional.ToList() : [], + Formation = Form, + SupportUnitUsage = SupportUnitUsage, + AddTrust = AddTrust, + IgnoreRequirements = IgnoreRequirements, + UserAdditionals = AddUserAdditional ? [.. userAdditional] : [], LoopTimes = Loop ? LoopTimes : 1, - UseSanityPotion = _useSanityPotion, - FormationIndex = UseFormation ? _formationIndex : 0, + UseSanityPotion = false, + FormationIndex = UseFormation ? FormationIndex : 0, }; ret = Instances.AsstProxy.AsstAppendTaskWithEncoding(AsstProxy.TaskType.Copilot, _taskType, task.Serialize().Params); ret = ret && Instances.AsstProxy.AsstStart(); diff --git a/src/MaaWpfGui/Views/UI/CopilotView.xaml b/src/MaaWpfGui/Views/UI/CopilotView.xaml index 198d39ee71..08806e8d1d 100644 --- a/src/MaaWpfGui/Views/UI/CopilotView.xaml +++ b/src/MaaWpfGui/Views/UI/CopilotView.xaml @@ -27,15 +27,15 @@ mc:Ignorable="d"> + + - + - - - + - + @@ -147,328 +147,442 @@ - - - - - - + + + + + + + + + + + VerticalAlignment="Bottom"> +