rft: Wpf自动战斗重构 (#11977)

* chore: 自动战斗一般作业model

* chore: 自动战斗保全作业model

* chore: 作业model增加输出提示

* rft: 拆分IO

* rft: 重构作业解析、作业集添加

perf: 简化CouldLikeWebJson

rft: 单个作业添加到作业集

perf: 优化作业添加,启动时校验地图是否存在

perf: 优化作业集加载时输出

rft: 移除废弃参数

fix: 作业集作业缓存写入加锁

chore: 简单挪个位置

rft: 整理一下函数

rft: 战斗列表序列化重构优化

chore: 修改log输出位置

chore: func rename & const declare

fix: 批量导入未标注难度的作业时,将按照普通难度追加

fix: 浏览列表中作业时重复添加

chore: 移除未使用变量

fix: 移除重复显示作业详情

fix: 自动战斗作业保存重复导致覆盖

fix: 修复加载作业时解析返回值错误

* rft: 更改战斗列表中任务的缓存路径
This commit is contained in:
status102
2025-02-24 19:29:33 +08:00
committed by GitHub
parent 4154184fb4
commit 94cb4f8f7f
4 changed files with 1226 additions and 360 deletions

View File

@@ -0,0 +1,229 @@
// <copyright file="CopilotHelper.cs" company="MaaAssistantArknights">
// MaaWpfGui - A part of the MaaCoreArknights project
// Copyright (C) 2021 MistEO and Contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License v3.0 only as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
// </copyright>
#nullable enable
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MaaWpfGui.Constants;
using MaaWpfGui.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace MaaWpfGui.Helper;
public static class CopilotHelper
{
public static async Task<(PrtsStatus Status, PrtsCopilotModel? Copilot)> RequestCopilotAsync(int copilotId)
{
var jsonResponse = await Instances.HttpService.GetStringAsync(new Uri(MaaUrls.PrtsPlusCopilotGet + copilotId)) ?? string.Empty;
if (jsonResponse is null)
{
return (PrtsStatus.NetworkError, null);
}
var json = JsonConvert.DeserializeObject<PrtsCopilotModel>(jsonResponse);
if (json != null && json.StatusCode == 200)
{
return (PrtsStatus.Success, json);
}
return (PrtsStatus.NotFound, null);
}
public static async Task<(PrtsStatus Status, PrtsCopilotSetModel? CopilotSet)> RequestCopilotSetAsync(int copilotId)
{
var jsonResponse = await Instances.HttpService.GetStringAsync(new Uri(MaaUrls.PrtsPlusCopilotSetGet + copilotId)) ?? string.Empty;
if (jsonResponse is null)
{
return (PrtsStatus.NetworkError, null);
}
var json = JsonConvert.DeserializeObject<PrtsCopilotSetModel>(jsonResponse);
if (json != null && json.StatusCode == 200)
{
return (PrtsStatus.Success, json);
}
return (PrtsStatus.NotFound, null);
}
public static async Task<PrtsStatus> RateWebJsonAsync(int copilotId, string rating)
{
var response = await Instances.HttpService.PostAsJsonAsync(new Uri(MaaUrls.PrtsPlusCopilotRating), new { id = copilotId, rating });
if (response == null)
{
return PrtsStatus.NetworkError;
}
return PrtsStatus.Success;
}
public enum PrtsStatus
{
/// <summary>
/// 成功
/// </summary>
Success,
/// <summary>
/// 未找到
/// </summary>
NotFound,
/// <summary>
/// 网络错误
/// </summary>
NetworkError,
}
public class PrtsCopilotModel
{
[JsonProperty("status_code")]
public int StatusCode { get; set; }
[JsonProperty("data")]
public CopilotData? Data { get; set; } = new();
public class CopilotData
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("upload_time")]
public DateTime UploadTime { get; set; }
[JsonProperty("uploader_id")]
public string UploaderId { get; set; } = string.Empty;
[JsonProperty("uploader")]
public string Uploader { get; set; } = string.Empty;
[JsonProperty("views")]
public int Views { get; set; }
[JsonProperty("hot_score")]
public double HotScore { get; set; }
[JsonProperty("available")]
public bool Available { get; set; }
[JsonProperty("rating_level")]
public int RatingLevel { get; set; }
[JsonProperty("not_enough_rating")]
public bool NotEnoughRating { get; set; }
[JsonProperty("rating_ratio")]
public double RatingRatio { get; set; }
[JsonProperty("rating_type")]
public int RatingType { get; set; }
[JsonProperty("comments_count")]
public int CommentsCount { get; set; }
[JsonProperty("like")]
public int Like { get; set; }
[JsonProperty("dislike")]
public int Dislike { get; set; }
[JsonProperty("content")]
[JsonConverter(typeof(CopilotContentConverter))]
public object? Content { get; set; } = new();
}
}
public class PrtsCopilotSetModel
{
[JsonProperty("status_code")]
public int StatusCode { get; set; }
/// <summary>
/// Gets or sets 输出提示code 200时不存在该项
/// <para>e.g.: code=400, msg=作业不存在</para>
/// </summary>
[JsonProperty("message")]
public string? Message { get; set; }
[JsonProperty("data")]
public CopilotSetData? Data { get; set; } = new();
public class CopilotSetData
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; } = string.Empty;
[JsonProperty("description")]
public string Description { get; set; } = string.Empty;
[JsonProperty("copilot_ids")]
public List<int> CopilotIds { get; set; } = [];
[JsonProperty("creator_id")]
public string CreatorId { get; set; } = string.Empty;
[JsonProperty("creator")]
public string Creator { get; set; } = string.Empty;
[JsonProperty("create_time")]
public DateTime CreatedTime { get; set; }
[JsonProperty("update_time")]
public DateTime UpdatedTime { get; set; }
[JsonProperty("status")]
public string Status { get; set; } = string.Empty;
}
}
public class CopilotContentConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(object);
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
// 处理字符串或对象两种输入形式
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.String)
{
// 如果 Content 是字符串,解析为嵌套 JSON
string jsonString = token.Value<string>() ?? string.Empty;
token = JToken.Parse(jsonString);
}
if (token is JObject obj)
{
// 根据 "type" 字段判断类型
if (obj.TryGetValue("type", StringComparison.OrdinalIgnoreCase, out var typeToken) && typeToken.ToString() == "SSS")
{
return obj.ToObject<SSSCopilotModel>(serializer);
}
else
{
return obj.ToObject<CopilotModel>(serializer);
}
}
throw new JsonSerializationException("Unsupported JSON structure for Content");
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
writer.WriteValue(JsonConvert.SerializeObject(value));
}
}
}

View File

@@ -0,0 +1,334 @@
// <copyright file="CopilotModel.cs" company="MaaAssistantArknights">
// MaaWpfGui - A part of the MaaCoreArknights project
// Copyright (C) 2021 MistEO and Contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License v3.0 only as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
// </copyright>
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using MaaWpfGui.Constants;
using MaaWpfGui.Helper;
using Newtonsoft.Json;
namespace MaaWpfGui.Models;
public class CopilotModel
{
/// <summary>
/// Gets or sets 关卡名必选。关卡中文名、code、stageId、levelId等只要能保证唯一均可。
/// </summary>
[JsonProperty("stage_name")]
public string StageName { get; set; } = string.Empty;
[JsonProperty("opers")]
public List<Oper> Opers { get; set; } = [];
[JsonProperty("groups")]
public List<Group> Groups { get; set; } = [];
/// <summary>
/// Gets or sets 战斗中的操作列表。有序,执行完前一个才会去执行下一个。必选
/// </summary>
[JsonProperty("actions")]
public List<Action> Actions { get; set; } = [];
/// <summary>
/// Gets or sets 最低要求 maa 版本号,必选。保留字段,暂未实现。
/// </summary>
[JsonProperty("minimum_required")]
public string? MinimumRequired { get; set; }
/// <summary>
/// Gets or sets 描述信息。
/// </summary>
[JsonProperty("doc")]
public Doc? Documentation { get; set; }
/// <summary>
/// Gets or sets 难度。可选,默认为 0
/// </summary>
[JsonProperty("difficulty")]
public DifficultyFlags Difficulty { get; set; }
public List<(string Output, string? Color)> Output()
{
var output = new List<(string, string?)>();
if (Documentation is not null)
{
var title = Documentation.Title;
if (!string.IsNullOrEmpty(title))
{
output.Add((title, Documentation.TitleColor ?? UiLogColor.Message));
}
var details = Documentation.Details;
if (!string.IsNullOrEmpty(details))
{
output.Add((details, Documentation.DetailsColor ?? UiLogColor.Message));
}
}
output.Add((string.Empty, null));
output.Add(("------------------------------------------------", null));
output.Add((string.Empty, null));
int count = 0;
foreach (var oper in Opers)
{
count++;
var localizedName = DataHelper.GetLocalizedCharacterName(oper.Name);
output.Add(($"{localizedName}, {LocalizationHelper.GetString("CopilotSkill")} {oper.Skill}", UiLogColor.Message));
}
foreach (var group in Groups)
{
count++;
string groupName = group.Name + ": ";
var operInfos = group.Opers.Select(oper => $"{DataHelper.GetLocalizedCharacterName(oper.Name)} {oper.Skill}").ToList();
output.Add((groupName + string.Join(" / ", operInfos), UiLogColor.Message));
}
output.Add((string.Format(LocalizationHelper.GetString("TotalOperatorsCount"), count), UiLogColor.Message));
return output;
}
public class Oper
{
/// <summary>
/// Gets or sets 干员名,必选。
/// </summary>
[JsonProperty("name")]
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets or sets 技能序号可选默认为1取值范围 [1, 3]
/// </summary>
[JsonProperty("skill")]
public int Skill { get; set; } = 1;
/// <summary>
/// Gets or sets 技能用法。可选,默认为 0
/// <list type="bullet">
/// <item>0 - 不自动使用(依赖 "actions" 字段)</item>
/// <item>1 - 好了就用,有多少次用多少次(例如干员 棘刺 3 技能、桃金娘 1 技能等)</item>
/// <item>2 - 使用 X 次(例如干员 山 2 技能用 1 次、重岳 3 技能用 5 次,通过 "skill_times" 字段设置)</item>
/// <item>3 - 自动判断使用时机(画饼.jpg</item>
/// </list>
/// </summary>
[JsonProperty("skill_usage")]
public int SkillUsage { get; set; }
/// <summary>
/// Gets or sets 技能使用次数。可选,默认为 1
/// </summary>
[JsonProperty("skill_times")]
public int SkillTimes { get; set; } = 1;
/// <summary>
/// Gets or sets 练度要求。保留接口,暂未实现。可选,默认为空
/// </summary>
[JsonProperty("requirements")]
public Requirements? Requirements { get; set; }
}
public class Group
{
/// <summary>
/// Gets or sets 群组名,必选
/// </summary>
[JsonProperty("name")]
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets or sets 干员列表。
/// </summary>
[JsonProperty("opers")]
public List<Oper> Opers { get; set; } = [];
}
public class Action
{
/// <summary>
/// Gets or sets 操作类型,可选,默认为 "Deploy"。
/// <list type="bullet">
/// <item>"Deploy" - 部署</item>
/// <item>"Skill" - 技能</item>
/// <item>"Retreat" - 撤退</item>
/// <item>"SpeedUp" - 二倍速</item>
/// <item>"BulletTime" - 子弹时间</item>
/// <item>"SkillUsage" - 技能用法</item>
/// <item>"Output" - 打印</item>
/// <item>"SkillDaemon" - 摆完挂机</item>
/// <item>"MoveCamera" - 移动镜头</item>
/// </list>
/// </summary>
[JsonProperty("type")]
public string Type { get; set; } = "Deploy";
/// <summary>
/// Gets or sets 击杀数条件,如果没达到就一直等待。可选,默认为 0直接执行。
/// </summary>
[JsonProperty("kills")]
public int Kills { get; set; }
/// <summary>
/// Gets or sets 费用条件,如果没达到就一直等待。可选,默认为 0直接执行。
/// </summary>
[JsonProperty("costs")]
public int Costs { get; set; }
/// <summary>
/// Gets or sets 费用变化量条件,如果没达到就一直等待。可选,默认为 0直接执行。
/// </summary>
[JsonProperty("cost_changes")]
public int CostChanges { get; set; }
/// <summary>
/// Gets or sets CD 中干员数量条件,如果没达到就一直等待。可选,默认为 -1不识别。
/// </summary>
[JsonProperty("cooling")]
public int Cooling { get; set; } = -1;
/// <summary>
/// Gets or sets 干员名 或 群组名, type 为 "部署" 时必选,为 "技能" | "撤退" 时可选。
/// </summary>
[JsonProperty("name")]
public string? Name { get; set; }
/// <summary>
/// Gets or sets 部署干员的位置。type 为 "部署" 时必选。type 为 "技能" | "撤退" 时可选。
/// </summary>
[JsonProperty("location")]
public List<int>? Location { get; set; }
/// <summary>
/// Gets or sets 部署干员的干员朝向。 type 为 "部署" 时必选。
/// <list type="bullet">
/// <item>"Left" - 左</item>
/// <item>"Right" - 右</item>
/// <item>"Up" - 上</item>
/// <item>"Down" - 下</item>
/// <item>"None" - 无</item>
/// </list>
/// </summary>
[JsonProperty("direction")]
public string? Direction { get; set; }
/// <summary>
/// Gets or sets 修改技能用法。当 type 为 "技能用法" 时必选。
/// </summary>
[JsonProperty("skill_usage")]
public int SkillUsage { get; set; }
/// <summary>
/// Gets or sets 技能使用次数。可选,默认为 1。
/// </summary>
[JsonProperty("skill_times")]
public int SkillTimes { get; set; } = 1;
/// <summary>
/// Gets or sets 前置延时。可选,默认为 0, 单位毫秒。
/// </summary>
[JsonProperty("pre_delay")]
public int PreDelay { get; set; }
/// <summary>
/// Gets or sets 后置延时。可选,默认为 0, 单位毫秒。
/// </summary>
[JsonProperty("post_delay")]
public int PostDelay { get; set; }
/// <summary>
/// Gets or sets 移动镜头的距离。type 为 "移动镜头" 时必选。
/// </summary>
[JsonProperty("distance")]
public List<double>? Distance { get; set; }
/// <summary>
/// Gets or sets 描述,可选。会显示在界面上,没有实际作用
/// </summary>
[JsonProperty("doc")]
public string? Doc { get; set; }
/// <summary>
/// Gets or sets 描述颜色,可选,默认灰色。会显示在界面上,没有实际作用
/// </summary>
[JsonProperty("doc_color")]
public string? DocColor { get; set; }
}
public class Requirements
{
/// <summary>
/// Gets or sets 精英化等级。可选,默认为 0, 不要求精英化等级。
/// </summary>
[JsonProperty("elite")]
public int Elite { get; set; }
/// <summary>
/// Gets or sets 干员等级。可选,默认为 0。
/// </summary>
[JsonProperty("level")]
public int Level { get; set; }
/// <summary>
/// Gets or sets 技能等级。可选,默认为 0。
/// </summary>
[JsonProperty("skill_level")]
public int SkillLevel { get; set; }
/// <summary>
/// Gets or sets 模组编号。可选,默认为 0。
/// </summary>
[JsonProperty("module")]
public int Module { get; set; }
/// <summary>
/// Gets or sets 潜能要求。可选,默认为 0。
/// </summary>
[JsonProperty("potentiality")]
public int Potentiality { get; set; }
}
public class Doc
{
[JsonProperty("title")]
public string? Title { get; set; }
[JsonProperty("title_color")]
public string? TitleColor { get; set; }
[JsonProperty("details")]
public string? Details { get; set; }
[JsonProperty("details_color")]
public string? DetailsColor { get; set; }
}
[Flags]
public enum DifficultyFlags
{
/// <summary>
/// 缺省,未设置
/// </summary>
None = 0,
/// <summary>
/// 普通
/// </summary>
Normal = 1,
/// <summary>
/// 突袭
/// </summary>
Raid = 2,
}
}

View File

@@ -0,0 +1,231 @@
// <copyright file="SSSCopilotModel.cs" company="MaaAssistantArknights">
// MaaWpfGui - A part of the MaaCoreArknights project
// Copyright (C) 2021 MistEO and Contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License v3.0 only as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
// </copyright>
#nullable enable
using System.Collections.Generic;
using System.Linq;
using MaaWpfGui.Constants;
using MaaWpfGui.Helper;
using Newtonsoft.Json;
namespace MaaWpfGui.Models;
public class SSSCopilotModel
{
/// <summary>
/// Gets 协议类型SSS 表示保全派驻,必选,不可修改
/// </summary>
[JsonProperty("type")]
public string Type { get; } = "SSS";
/// <summary>
/// Gets or sets 保全派驻地图名,必选,例:多索雷斯在建地块
/// </summary>
[JsonProperty("stage_name")]
public string StageName { get; set; } = string.Empty;
/// <summary>
/// Gets or sets 最低要求 maa 版本号,必选
/// </summary>
[JsonProperty("minimum_required")]
public string MinimumRequired { get; set; } = string.Empty;
/// <summary>
/// Gets or sets 描述,可选
/// </summary>
[JsonProperty("doc")]
public Doc? Documentation { get; set; }
/// <summary>
/// Gets or sets 开局导能元件选择,可选
/// </summary>
[JsonProperty("buff")]
public string? Buff { get; set; }
/// <summary>
/// Gets or sets 开局装备选择,横着数,可选。当前版本暂未实现,只会在界面上显示一下
/// </summary>
[JsonProperty("equipment")]
public List<string>? Equipment { get; set; }
/// <summary>
/// Gets or sets 优选策略,可选。当前版本暂未实现,只会在界面上显示一下
/// </summary>
[JsonProperty("strategy")]
public string? Strategy { get; set; }
/// <summary>
/// Gets or sets 指定干员,可选
/// </summary>
[JsonProperty("opers")]
public List<Oper>? Opers { get; set; }
/// <summary>
/// Gets or sets 剩余所需各职业人数,按费用排序随便拿,可选
/// </summary>
[JsonProperty("tool_men")]
public Dictionary<string, int>? ToolMen { get; set; }
/// <summary>
/// Gets or sets 战斗开始时和战斗中途,招募干员、获取装备优先级
/// </summary>
[JsonProperty("drops")]
public List<string>? Drops { get; set; }
/// <summary>
/// Gets or sets 黑名单,可选。在 drops 里不会选这些人。
/// </summary>
[JsonProperty("blacklist")]
public List<string>? Blacklist { get; set; }
/// <summary>
/// Gets or sets 关卡信息
/// </summary>
[JsonProperty("stages")]
public List<Stage>? Stages { get; set; }
public List<(string Output, string? Color)> Output()
{
var output = new List<(string, string?)>();
if (Documentation is not null)
{
var title = Documentation.Title;
if (!string.IsNullOrEmpty(title))
{
output.Add((title, Documentation.TitleColor ));
}
var details = Documentation.Details;
if (!string.IsNullOrEmpty(details))
{
output.Add((details, Documentation.DetailsColor ));
}
}
output.Add((string.Empty, null));
output.Add(("------------------------------------------------", null));
output.Add((string.Empty, null));
int count = 0;
foreach (var oper in Opers ?? [])
{
count++;
var localizedName = DataHelper.GetLocalizedCharacterName(oper.Name);
output.Add(($"{localizedName}, {LocalizationHelper.GetString("CopilotSkill")} {oper.Skill}", null));
}
output.Add((string.Format(LocalizationHelper.GetString("TotalOperatorsCount"), count), null));
if (Buff is not null)
{
string buffLog = LocalizationHelper.GetString("DirectiveECTerm");
var localizedBuffName = DataHelper.GetLocalizedCharacterName(Buff);
output.Add((buffLog + (string.IsNullOrEmpty(localizedBuffName) ? Buff : localizedBuffName), null));
}
if (ToolMen is not null)
{
string toolMenLog = LocalizationHelper.GetString("OtherOperators");
output.Add((toolMenLog + JsonConvert.SerializeObject(ToolMen), null));
}
if (Equipment is not null)
{
string equipmentLog = LocalizationHelper.GetString("InitialEquipmentHorizontal") + '\n';
output.Add((equipmentLog + string.Join('\n', Equipment.Chunk(4).Select(i => string.Join(",", i))), null));
}
if (Strategy is not null)
{
string strategyLog = LocalizationHelper.GetString("InitialStrategy");
output.Add((strategyLog + Strategy, null));
}
return output;
}
public class Doc
{
[JsonProperty("title")]
public string? Title { get; set; }
[JsonProperty("title_color")]
public string? TitleColor { get; set; }
[JsonProperty("details")]
public string? Details { get; set; }
[JsonProperty("details_color")]
public string? DetailsColor { get; set; }
}
public class Oper
{
[JsonProperty("name")]
public string Name { get; set; } = string.Empty;
[JsonProperty("skill")]
public int Skill { get; set; }
[JsonProperty("skill_usage")]
public int SkillUsage { get; set; }
}
public class Stage
{
[JsonProperty("stage_name")]
public string StageName { get; set; } = string.Empty;
[JsonProperty("strategies")]
public List<Strategy>? Strategies { get; set; }
[JsonProperty("draw_as_possible")]
public bool? DrawAsPossible { get; set; }
[JsonProperty("actions")]
public List<Action>? Actions { get; set; }
[JsonProperty("retry_times")]
public int? RetryTimes { get; set; }
public class Strategy
{
[JsonProperty("core")]
public string? Core { get; set; }
[JsonProperty("tool_men")]
public Dictionary<string, int>? ToolMen { get; set; }
[JsonProperty("location")]
public List<int>? Location { get; set; }
[JsonProperty("direction")]
public string? Direction { get; set; }
}
public class Action
{
[JsonProperty("type")]
public string Type { get; set; } = string.Empty;
[JsonProperty("name")]
public string? Name { get; set; }
[JsonProperty("location")]
public List<int>? Location { get; set; }
[JsonProperty("kills")]
public int? Kills { get; set; }
}
}
}

File diff suppressed because it is too large Load Diff