mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-15 17:30:27 +08:00
feat: 重构仓库识别结构,支持根据关卡掉落情况自动更新 (#15358)
* feat: 重构仓库识别结构,支持根据关卡掉落情况自动更新 * feat: 上次同步时间 * chore: 使用 TryParseExact
This commit is contained in:
@@ -524,39 +524,8 @@ Todo
|
||||
|
||||
```json
|
||||
// Corresponding details field example
|
||||
"done": bool, // Whether recognition is complete, false means still in progress (data during process)
|
||||
"arkplanner": { // https://penguin-stats.cn/planner
|
||||
"object": {
|
||||
"items": [
|
||||
{
|
||||
"id": "2004",
|
||||
"have": 4,
|
||||
"name": "高级作战记录" // "Advanced Battle Record"
|
||||
},
|
||||
{
|
||||
"id": "mod_unlock_token",
|
||||
"have": 25,
|
||||
"name": "模组数据块" // "Module Data Block"
|
||||
},
|
||||
{
|
||||
"id": "2003",
|
||||
"have": 20,
|
||||
"name": "中级作战记录" // "Tactical Battle Record"
|
||||
}
|
||||
],
|
||||
"@type": "@penguin-statistics/depot"
|
||||
},
|
||||
"data": "{\"@type\":\"@penguin-statistics/depot\",\"items\":[{\"id\":\"2004\",\"have\":4,\"name\":\"高级作战记录\"},{\"id\":\"mod_unlock_token\",\"have\":25,\"name\":\"模组数据块\"},{\"id\":\"2003\",\"have\":20,\"name\":\"中级作战记录\"}]}"
|
||||
},
|
||||
"lolicon": { // https://arkntools.app/#/material
|
||||
"object": {
|
||||
"2004" : 4,
|
||||
"mod_unlock_token": 25,
|
||||
"2003": 20
|
||||
},
|
||||
"data": "{\"2003\":20,\"2004\": 4,\"mod_unlock_token\": 25}"
|
||||
}
|
||||
// Currently only supports ArkPlanner and Lolicon (Arkntools) formats, may support more websites in the future
|
||||
"done": bool, // Whether recognition is complete, false means still in progress (data during process)
|
||||
"data": "{\"2001\":18000,\"31043\":317}" // JSON string, format: {"itemId": quantity, ...}
|
||||
```
|
||||
|
||||
- `OperBox`
|
||||
|
||||
@@ -535,44 +535,13 @@ Todo
|
||||
}
|
||||
```
|
||||
|
||||
- `DepotInfo`
|
||||
- `Depot`
|
||||
倉庫のアイテムの認識結果
|
||||
|
||||
```json
|
||||
// 対応する詳細フィールドの例
|
||||
"done": bool, // 認識が完了したかどうか,false はまだ進行中かどうか(処理中のデータ)
|
||||
"arkplanner": { // https://penguin-stats.cn/planner
|
||||
"object": {
|
||||
"items": [
|
||||
{
|
||||
"id": "2004",
|
||||
"have": 4,
|
||||
"name": "上級作戦記録"
|
||||
},
|
||||
{
|
||||
"id": "mod_unlock_token",
|
||||
"have": 25,
|
||||
"name": "モジュールデータ"
|
||||
},
|
||||
{
|
||||
"id": "2003",
|
||||
"have": 20,
|
||||
"name": "中級作戦記録"
|
||||
}
|
||||
],
|
||||
"@type": "@penguin-statistics/depot"
|
||||
},
|
||||
"data": "{\"@type\":\"@penguin-statistics/depot\",\"items\":[{\"id\":\"2004\",\"have\":4,\"name\":\"上級作戦記録\"},{\"id\":\"mod_unlock_token\",\"have\":25,\"name\":\"モジュールデータ\"},{\"id\":\"2003\",\"have\":20,\"name\":\"中級作戦記録\"}]}"
|
||||
},
|
||||
"lolicon": { // https://arkntools.app/#/material
|
||||
"object": {
|
||||
"2004" : 4,
|
||||
"mod_unlock_token": 25,
|
||||
"2003": 20
|
||||
},
|
||||
"data": "{\"2003\":20,\"2004\": 4,\"mod_unlock_token\": 25}"
|
||||
}
|
||||
// 現在は ArkPlanner と Lolicon (Arkntools) 形式のみ対応、今後対応するサイトが増える可能性あり
|
||||
"done": bool, // 認識が完了したかどうか,false はまだ進行中かどうか(処理中のデータ)
|
||||
"data": "{\"2001\":18000,\"31043\":317}" // JSON文字列、形式: {"アイテムID": 数量, ...}
|
||||
```
|
||||
|
||||
- `OperBoxInfo`
|
||||
|
||||
@@ -474,8 +474,7 @@ typedef void(ASST_CALL* AsstCallback)(int msg, const char* details, void* custom
|
||||
- `Depot`
|
||||
창고 인식 결과. `details` 필드 구조는 다음과 같습니다:
|
||||
- `done` (boolean, required): 인식 완료 여부, `false`는 아직 인식 중임(진행 중 데이터)을 의미
|
||||
- `arkplanner` (object, required): [ArkPlanner](https://penguin-stats.cn/planner) 포맷 데이터
|
||||
- `lolicon` (object, required): [lolicon](https://arkntools.app/#/material) (Arkntools) 포맷 데이터
|
||||
- `data` (string, required): JSON 문자열, 형식은 `{"아이템ID": 수량, ...}`, 예: `{"2001":18000,"31043":317}`
|
||||
|
||||
- `OperBox`
|
||||
오퍼레이터 보관함 인식 결과. `details` 필드 구조는 다음과 같습니다:
|
||||
|
||||
@@ -474,8 +474,7 @@ typedef void(ASST_CALL* AsstCallback)(int msg, const char* details, void* custom
|
||||
- `Depot`
|
||||
仓库识别结果。`details` 字段结构如下:
|
||||
- `done` (boolean, required): 是否已经识别完了,为 `false` 表示仍在识别中(过程中的数据)。
|
||||
- `arkplanner` (object, required): [ArkPlanner](https://penguin-stats.cn/planner) 格式的数据。
|
||||
- `lolicon` (object, required): [lolicon](https://arkntools.app/#/material) (Arkntools) 格式的数据。
|
||||
- `data` (string, required): JSON 字符串,格式为 `{"物品ID": 数量, ...}`,例如 `{"2001":18000,"31043":317}`。
|
||||
|
||||
- `OperBox`
|
||||
干员识别结果。`details` 字段结构如下:
|
||||
|
||||
@@ -536,39 +536,8 @@ Todo
|
||||
|
||||
```json
|
||||
// 對應的 details 欄位舉例
|
||||
"done": bool, // 是否已經辨識完了,為 false 表示仍在辨識中(過程中的數據)
|
||||
"arkplanner": { // https://penguin-stats.io/planner
|
||||
"object": {
|
||||
"items": [
|
||||
{
|
||||
"id": "2004",
|
||||
"have": 4,
|
||||
"name": "高級作戰記錄"
|
||||
},
|
||||
{
|
||||
"id": "mod_unlock_token",
|
||||
"have": 25,
|
||||
"name": "模組數據塊"
|
||||
},
|
||||
{
|
||||
"id": "2003",
|
||||
"have": 20,
|
||||
"name": "中級作戰記錄"
|
||||
}
|
||||
],
|
||||
"@type": "@penguin-statistics/depot"
|
||||
},
|
||||
"data": "{\"@type\":\"@penguin-statistics/depot\",\"items\":[{\"id\":\"2004\",\"have\":4,\"name\":\"高級作戰記錄\"},{\"id\":\"mod_unlock_token\",\"have\":25,\"name\":\"模組數據塊\"},{\"id\":\"2003\",\"have\":20,\"name\":\"中級作戰記錄\"}]}"
|
||||
},
|
||||
"lolicon": { // https://arkntools.app/#/material
|
||||
"object": {
|
||||
"2004" : 4,
|
||||
"mod_unlock_token": 25,
|
||||
"2003": 20
|
||||
},
|
||||
"data": "{\"2003\":20,\"2004\": 4,\"mod_unlock_token\": 25}"
|
||||
}
|
||||
// 目前只支援 ArkPlanner 和 Lolicon (Arkntools) 的格式,以後可能會兼容更多網站
|
||||
"done": bool, // 是否已經辨識完了,為 false 表示仍在辨識中(過程中的數據)
|
||||
"data": "{\"2001\":18000,\"31043\":317}" // JSON 字串,格式為 {"物品ID": 數量, ...}
|
||||
```
|
||||
|
||||
- `OperBox`
|
||||
|
||||
@@ -36,11 +36,6 @@
|
||||
"recruitUrl": "https://backend.yituliu.cn/maa/upload/recruit",
|
||||
"dropUrl": "https://backend.yituliu.cn/maa/upload/stageDrop"
|
||||
},
|
||||
"depotExportTemplate": {
|
||||
"Doc": "仓库识别导出结果模板",
|
||||
"arkPlanner": "{\"@type\": \"@penguin-statistics/depot\",\"items\": []}",
|
||||
"arkPlanner_Doc": "https://penguin-stats.cn/planner"
|
||||
},
|
||||
"debug": {
|
||||
"cleanFilesFreq": 5,
|
||||
"maxDebugFileNum": 10
|
||||
|
||||
@@ -48,8 +48,6 @@ bool asst::GeneralConfig::parse(const json::value& json)
|
||||
m_options.yituliu_report.drop_url = yituliu_opt->get("dropUrl", std::string());
|
||||
m_options.yituliu_report.recruit_url = yituliu_opt->get("recruitUrl", std::string());
|
||||
}
|
||||
m_options.depot_export_template.ark_planner =
|
||||
options_json.get("depotExportTemplate", "arkPlanner", std::string());
|
||||
m_options.debug.clean_files_freq = options_json.get("debug", "cleanFilesFreq", 50);
|
||||
m_options.debug.max_debug_file_num = options_json.get("debug", "maxDebugFileNum", 100);
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ struct Options
|
||||
int swipe_with_pause_required_distance = 0;
|
||||
std::vector<std::string> minitouch_programs_order;
|
||||
RequestInfo penguin_report; // 企鹅物流汇报:每次到结算界面,汇报掉落数据至企鹅物流 https://penguin-stats.io
|
||||
DepotExportTemplate depot_export_template; // 仓库识别结果导出模板
|
||||
RequestInfo yituliu_report; // 一图流大数据汇报:目前只有公招功能,https://ark.yituliu.cn/survey/maarecruitdata
|
||||
DebugConf debug;
|
||||
};
|
||||
|
||||
@@ -58,38 +58,18 @@ void asst::DepotRecognitionTask::callback_analyze_result(bool done)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
auto& templ = Config.get_options().depot_export_template;
|
||||
json::value info = basic_info_with_what("DepotInfo");
|
||||
auto& details = info["details"];
|
||||
|
||||
// https://penguin-stats.io/planner
|
||||
if (auto arkplanner_template_opt = json::parse(templ.ark_planner)) {
|
||||
auto& arkplanner = details["arkplanner"];
|
||||
auto& arkplanner_obj = arkplanner["object"];
|
||||
arkplanner_obj = arkplanner_template_opt.value();
|
||||
auto& arkplanner_data_items = arkplanner_obj["items"];
|
||||
|
||||
for (const auto& [item_id, item_info] : m_all_items) {
|
||||
arkplanner_data_items.emplace(
|
||||
json::object {
|
||||
{ "id", item_id },
|
||||
{ "have", item_info.quantity },
|
||||
{ "name", item_info.item_name },
|
||||
});
|
||||
}
|
||||
arkplanner["data"] = arkplanner_obj.to_string();
|
||||
// 简化格式:只保留 done 和 data
|
||||
// data 为 {"itemId": count} 格式
|
||||
json::object data_obj;
|
||||
for (const auto& [item_id, item_info] : m_all_items) {
|
||||
data_obj.emplace(item_id, item_info.quantity);
|
||||
}
|
||||
|
||||
// https://arkntools.app/#/material
|
||||
{
|
||||
auto& lolicon = details["lolicon"];
|
||||
auto& lolicon_obj = lolicon["object"];
|
||||
for (const auto& [item_id, item_info] : m_all_items) {
|
||||
lolicon_obj.emplace(item_id, item_info.quantity);
|
||||
}
|
||||
lolicon["data"] = lolicon_obj.to_string();
|
||||
}
|
||||
details["done"] = done;
|
||||
details["data"] = data_obj.to_string();
|
||||
|
||||
callback(AsstMsg::SubTaskExtraInfo, info);
|
||||
}
|
||||
|
||||
@@ -1721,7 +1721,7 @@ public class AsstProxy
|
||||
switch (taskChain)
|
||||
{
|
||||
case "Depot":
|
||||
Instances.ToolboxViewModel.DepotParse((JObject?)subTaskDetails);
|
||||
Instances.ToolboxViewModel.DepotParse((JObject?)subTaskDetails, updateSyncTime: true);
|
||||
break;
|
||||
|
||||
case "OperBox":
|
||||
@@ -1785,6 +1785,9 @@ public class AsstProxy
|
||||
|
||||
AchievementTrackerHelper.Instance.AddProgressToGroup(AchievementIds.SanitySpenderGroup, curTimes > 0 ? curTimes : 1);
|
||||
|
||||
// 联动更新 Depot 数据
|
||||
Instances.ToolboxViewModel.UpdateDepotFromDrops(drops);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -726,7 +726,7 @@ Do not adjust the proxy multiplier setting in the game</system:String>
|
||||
<system:String x:Key="MinimumRequirements" xml:space="preserve">Minimum Requirements: </system:String>
|
||||
<system:String x:Key="DaysLeftOpen" xml:space="preserve">Days left open: </system:String>
|
||||
<system:String x:Key="Inventory">Inventory</system:String>
|
||||
<system:String x:Key="InventoryUpdateTip">Inventory data can be updated via 「{key=Toolbox}-{key=DepotRecognition}」 </system:String>
|
||||
<system:String x:Key="InventoryUpdateTip">Inventory data can be updated via 「{key=Toolbox}-{key=DepotRecognition}」</system:String>
|
||||
<system:String x:Key="LessThanOneDay">Less than 1 day</system:String>
|
||||
<system:String x:Key="1-7">1-7</system:String>
|
||||
<system:String x:Key="CE-6">CE-6</system:String>
|
||||
@@ -809,6 +809,7 @@ Right Click: Select Target Process</system:String>
|
||||
<!-- Depot Recognition -->
|
||||
<system:String x:Key="DepotRecognition">Depot</system:String>
|
||||
<system:String x:Key="Depot">{key=DepotRecognition}</system:String>
|
||||
<system:String x:Key="LastDepotSyncTime">Last Sync Time</system:String>
|
||||
<system:String x:Key="DepotRecognitionTip">This function is still in testing. If you encounter any unexpected results, please zip the 「debug/depot」 folder in the installation path and submit an issue on GitHub</system:String>
|
||||
<system:String x:Key="StartToDepotRecognition">Start to Identify</system:String>
|
||||
<system:String x:Key="ExportToArkplanner">Export to Arkplanner</system:String>
|
||||
|
||||
@@ -810,6 +810,7 @@ C:\\leidian\\LDPlayer9
|
||||
<!-- 倉庫アイテム認識 -->
|
||||
<system:String x:Key="DepotRecognition">倉庫アイテム認識</system:String>
|
||||
<system:String x:Key="Depot">{key=DepotRecognition}</system:String>
|
||||
<system:String x:Key="LastDepotSyncTime">前回同期時間</system:String>
|
||||
<system:String x:Key="DepotRecognitionTip">この機能はまだテスト版です。エラーが発生/統計情報に問題があれば、debug/depotフォルダを圧縮し、issueを提出してください~</system:String>
|
||||
<system:String x:Key="StartToDepotRecognition">認識開始</system:String>
|
||||
<system:String x:Key="ExportToArkplanner">Arkplannerへ出力</system:String>
|
||||
|
||||
@@ -811,6 +811,7 @@ C:\\leidian\\LDPlayer9
|
||||
<!-- 창고 인식 -->
|
||||
<system:String x:Key="DepotRecognition">창고 인식</system:String>
|
||||
<system:String x:Key="Depot">{key=DepotRecognition}</system:String>
|
||||
<system:String x:Key="LastDepotSyncTime">마지막 동기화 시간</system:String>
|
||||
<system:String x:Key="DepotRecognitionTip">이 기능은 현재 테스트 중입니다. 내보내기 전 인식 결과가 맞는지 확인 후 사용하세요\n만약 오류가 있다면 MAA 경로 내 debug 폴더 안의 depot 폴더 전체를 압축해서 Github의 issue로 올려주세요</system:String>
|
||||
<system:String x:Key="StartToDepotRecognition">인식 시작</system:String>
|
||||
<system:String x:Key="ExportToArkplanner">Arkplanner로 내보내기</system:String>
|
||||
|
||||
@@ -810,6 +810,7 @@ C:\\leidian\\LDPlayer9。\n
|
||||
<!-- 仓库识别 -->
|
||||
<system:String x:Key="DepotRecognition">仓库识别</system:String>
|
||||
<system:String x:Key="Depot">{key=DepotRecognition}</system:String>
|
||||
<system:String x:Key="LastDepotSyncTime">上次同步时间</system:String>
|
||||
<system:String x:Key="DepotRecognitionTip">该功能尚处于测试阶段,请检查结果是否准确再行使用。若有误,欢迎打包 debug/depot 文件夹后向我们提交 issue ~</system:String>
|
||||
<system:String x:Key="StartToDepotRecognition">开始识别</system:String>
|
||||
<system:String x:Key="ExportToArkplanner">导出至企鹅物流刷图规划</system:String>
|
||||
|
||||
@@ -810,6 +810,7 @@ C:\\leidian\\LDPlayer9。\n
|
||||
<!-- 倉庫辨識 -->
|
||||
<system:String x:Key="DepotRecognition">倉庫辨識</system:String>
|
||||
<system:String x:Key="Depot">{key=DepotRecognition}</system:String>
|
||||
<system:String x:Key="LastDepotSyncTime">上次同步時間</system:String>
|
||||
<system:String x:Key="DepotRecognitionTip">該功能尚處於測試階段,請檢查結果是否準確再行使用。若有誤,歡迎打包 debug/depot 資料夾後向我們提交 issue ~</system:String>
|
||||
<system:String x:Key="StartToDepotRecognition">開始辨識</system:String>
|
||||
<system:String x:Key="ExportToArkplanner">匯出到企鵝物流刷圖規劃</system:String>
|
||||
|
||||
@@ -689,7 +689,7 @@ public class StageManager
|
||||
if (!string.IsNullOrEmpty(stage.Drop))
|
||||
{
|
||||
var str = $"{stage.Value}: {ItemListHelper.GetItemName(stage.Drop) ?? stage.Drop}";
|
||||
var count = Instances.ToolboxViewModel?.DepotResult?.FirstOrDefault(d => d.Id == stage.Drop)?.Count;
|
||||
var count = Instances.ToolboxViewModel?.DepotResult?.FirstOrDefault(d => d.Id == stage.Drop)?.DisplayCount;
|
||||
if (!string.IsNullOrEmpty(count) && count != "-1")
|
||||
{
|
||||
str += $" ({LocalizationHelper.GetString("Inventory")} {count})";
|
||||
|
||||
@@ -16,6 +16,7 @@ using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -32,6 +33,7 @@ using MaaWpfGui.Main;
|
||||
using MaaWpfGui.Models;
|
||||
using MaaWpfGui.Models.AsstTasks;
|
||||
using MaaWpfGui.States;
|
||||
using MaaWpfGui.Utilities;
|
||||
using MaaWpfGui.Utilities.ValueType;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@@ -54,6 +56,7 @@ public class ToolboxViewModel : Screen
|
||||
/// </summary>
|
||||
public ToolboxViewModel()
|
||||
{
|
||||
PropertyDependsOnUtility.InitializePropertyDependencies(this);
|
||||
DisplayName = LocalizationHelper.GetString("Toolbox");
|
||||
_runningState = RunningState.Instance;
|
||||
_runningState.StateChanged += (__, e) => {
|
||||
@@ -406,6 +409,35 @@ public class ToolboxViewModel : Screen
|
||||
set => SetAndNotify(ref _depotInfo, value);
|
||||
}
|
||||
|
||||
private DateTime? _lastDepotSyncTime;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets 上次仓库同步时间(UTC 时间)
|
||||
/// </summary>
|
||||
public DateTime? LastDepotSyncTime
|
||||
{
|
||||
get => _lastDepotSyncTime;
|
||||
set => SetAndNotify(ref _lastDepotSyncTime, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets 上次仓库同步时间的显示文本(本地时间)
|
||||
/// </summary>
|
||||
[PropertyDependsOn(nameof(LastDepotSyncTime))]
|
||||
public string LastDepotSyncTimeText
|
||||
{
|
||||
get {
|
||||
if (LastDepotSyncTime == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// 将 UTC 时间转换为本地时间显示
|
||||
var localTime = LastDepotSyncTime.Value.ToLocalTime();
|
||||
return localTime.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private ObservableCollection<DepotResultDate> _depotResult = [];
|
||||
|
||||
/// <summary>
|
||||
@@ -414,7 +446,25 @@ public class ToolboxViewModel : Screen
|
||||
public ObservableCollection<DepotResultDate> DepotResult
|
||||
{
|
||||
get => _depotResult;
|
||||
set => SetAndNotify(ref _depotResult, value);
|
||||
set {
|
||||
SetAndNotify(ref _depotResult, value);
|
||||
InvalidateDepotCache();
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存相关字段
|
||||
private bool _depotCacheInvalid = true;
|
||||
private string? _cachedArkPlannerResult;
|
||||
private string? _cachedLoliconResult;
|
||||
|
||||
/// <summary>
|
||||
/// 标记仓库缓存失效
|
||||
/// </summary>
|
||||
private void InvalidateDepotCache()
|
||||
{
|
||||
_depotCacheInvalid = true;
|
||||
_cachedArkPlannerResult = null;
|
||||
_cachedLoliconResult = null;
|
||||
}
|
||||
|
||||
public class DepotResultDate
|
||||
@@ -425,54 +475,135 @@ public class ToolboxViewModel : Screen
|
||||
|
||||
public BitmapSource? Image { get; set; }
|
||||
|
||||
public string? Count { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets 物品数量(原始数值)
|
||||
/// </summary>
|
||||
public int Count { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets 格式化后的显示数量(用于 UI 绑定)
|
||||
/// </summary>
|
||||
public string DisplayCount => Count >= 0 ? Count.FormatNumber(false) : "-1";
|
||||
}
|
||||
|
||||
private void SaveDepotDetails(JObject details)
|
||||
/// <summary>
|
||||
/// 保存仓库详情数据
|
||||
/// </summary>
|
||||
private void SaveDepotDetails()
|
||||
{
|
||||
// var json = details.ToString(Formatting.None);
|
||||
// ConfigurationHelper.SetValue(ConfigurationKeys.DepotResult, json);
|
||||
// 构建简化格式:{"itemId": count}
|
||||
var depotData = new JObject();
|
||||
foreach (var item in DepotResult)
|
||||
{
|
||||
if (item.Count >= 0)
|
||||
{
|
||||
depotData[item.Id] = item.Count;
|
||||
}
|
||||
}
|
||||
|
||||
var details = new JObject {
|
||||
["done"] = true,
|
||||
["data"] = depotData.ToString(Formatting.None),
|
||||
};
|
||||
|
||||
// 保存同步时间为 UTC(如果有)
|
||||
if (LastDepotSyncTime.HasValue)
|
||||
{
|
||||
details["syncTime"] = LastDepotSyncTime.Value.ToString("o"); // ISO 8601 格式
|
||||
}
|
||||
|
||||
JsonDataHelper.Set(JsonDataKey.DepotData, details);
|
||||
}
|
||||
|
||||
private void LoadDepotDetails()
|
||||
{
|
||||
// TODO: 删除老数据节省 gui.json 的大小,后续版本可以删除
|
||||
// var json = ConfigurationHelper.GetValue(ConfigurationKeys.DepotResult, string.Empty);
|
||||
ConfigurationHelper.DeleteValue(ConfigurationKeys.DepotResult);
|
||||
var json = JsonDataHelper.Get(JsonDataKey.DepotData, string.Empty);
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var details = JObject.Parse(json);
|
||||
DepotParse(details);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 兼容老数据或异常时忽略
|
||||
var details = JObject.Parse(json);
|
||||
DepotParse(details);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets 获取 ArkPlanner 导出格式(带缓存)
|
||||
/// </summary>
|
||||
public string ArkPlannerResult
|
||||
{
|
||||
get {
|
||||
if (DepotResult.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// 使用缓存
|
||||
if (!_depotCacheInvalid && _cachedArkPlannerResult != null)
|
||||
{
|
||||
return _cachedArkPlannerResult;
|
||||
}
|
||||
|
||||
// 重新计算
|
||||
var items = DepotResult
|
||||
.Where(item => item.Count >= 0)
|
||||
.Select(item => new JObject {
|
||||
["id"] = item.Id,
|
||||
["have"] = item.Count,
|
||||
["name"] = item.Name ?? string.Empty,
|
||||
});
|
||||
|
||||
var result = new JObject {
|
||||
["@type"] = "@penguin-statistics/depot",
|
||||
["items"] = new JArray(items),
|
||||
};
|
||||
|
||||
_cachedArkPlannerResult = result.ToString(Formatting.None);
|
||||
_depotCacheInvalid = false; // 标记缓存已更新
|
||||
return _cachedArkPlannerResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ArkPlanner result.
|
||||
/// Gets 获取 工具箱 导出格式(带缓存)
|
||||
/// </summary>
|
||||
public string ArkPlannerResult { get; set; } = string.Empty;
|
||||
public string LoliconResult
|
||||
{
|
||||
get {
|
||||
if (DepotResult.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// 使用缓存
|
||||
if (!_depotCacheInvalid && _cachedLoliconResult != null)
|
||||
{
|
||||
return _cachedLoliconResult;
|
||||
}
|
||||
|
||||
// 重新计算
|
||||
var depotData = new JObject();
|
||||
foreach (var item in DepotResult)
|
||||
{
|
||||
if (item.Count >= 0)
|
||||
{
|
||||
depotData[item.Id] = item.Count;
|
||||
}
|
||||
}
|
||||
|
||||
_cachedLoliconResult = depotData.ToString(Formatting.None);
|
||||
_depotCacheInvalid = false; // 标记缓存已更新
|
||||
return _cachedLoliconResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Lolicon result.
|
||||
/// 解析仓库识别结果(兼容新旧格式)
|
||||
/// </summary>
|
||||
public string LoliconResult { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// parse of depot recognition result
|
||||
/// </summary>
|
||||
/// <param name="details">detailed json-style parameters</param>
|
||||
/// <returns>Success or not</returns>
|
||||
public bool DepotParse(JObject? details)
|
||||
/// <param name="details">详细的 JSON 参数</param>
|
||||
/// <param name="updateSyncTime">是否更新同步时间为当前时间(从 Core 获取新数据时为 true,从本地加载时为 false)</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public bool DepotParse(JObject? details, bool updateSyncTime = false)
|
||||
{
|
||||
if (details == null)
|
||||
{
|
||||
@@ -481,38 +612,68 @@ public class ToolboxViewModel : Screen
|
||||
|
||||
DepotResult.Clear();
|
||||
|
||||
var sortedItems = details["arkplanner"]?["object"]?["items"]
|
||||
?.Cast<JObject>()
|
||||
.OrderBy(item => (string?)item["id"])
|
||||
?? Enumerable.Empty<JObject>();
|
||||
Dictionary<string, int> depotItems = [];
|
||||
|
||||
foreach (var item in sortedItems)
|
||||
// 尝试解析新格式
|
||||
var dataStr = details["data"]?.ToString();
|
||||
if (!string.IsNullOrEmpty(dataStr))
|
||||
{
|
||||
var id = (string?)item["id"];
|
||||
if (string.IsNullOrEmpty(id))
|
||||
try
|
||||
{
|
||||
continue;
|
||||
var dataObj = JObject.Parse(dataStr);
|
||||
foreach (var prop in dataObj.Properties())
|
||||
{
|
||||
if (int.TryParse(prop.Value.ToString(), out var count))
|
||||
{
|
||||
depotItems[prop.Name] = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int count = -1;
|
||||
bool hasValidCount = false;
|
||||
|
||||
if (item["have"] != null)
|
||||
catch (Exception ex)
|
||||
{
|
||||
var haveValue = item["have"]?.ToString();
|
||||
hasValidCount = int.TryParse(haveValue, out count);
|
||||
_logger.Warning(ex, "Failed to parse depot data format");
|
||||
}
|
||||
}
|
||||
|
||||
string countStr = hasValidCount ? count.FormatNumber(false) : "-1";
|
||||
// 如果新格式解析失败,尝试旧格式
|
||||
if (depotItems.Count == 0)
|
||||
{
|
||||
if (depotItems.Count == 0)
|
||||
{
|
||||
var arkplannerItems = details["arkplanner"]?["object"]?["items"]
|
||||
?.Cast<JObject>()
|
||||
?? [];
|
||||
|
||||
foreach (var item in arkplannerItems)
|
||||
{
|
||||
var id = (string?)item["id"];
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item["have"] != null && int.TryParse(item["have"]?.ToString(), out var count))
|
||||
{
|
||||
depotItems[id] = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 转换为 DepotResult,按 ID 排序
|
||||
foreach (var kvp in depotItems.OrderBy(x => x.Key))
|
||||
{
|
||||
var id = kvp.Key;
|
||||
var count = kvp.Value;
|
||||
|
||||
DepotResultDate result = new() {
|
||||
Id = id,
|
||||
Name = ItemListHelper.GetItemName(id),
|
||||
Image = ItemListHelper.GetItemImage(id),
|
||||
Count = countStr,
|
||||
Count = count,
|
||||
};
|
||||
|
||||
if (hasValidCount && count > 0 &&
|
||||
if (count > 0 &&
|
||||
count > AchievementTrackerHelper.Instance.GetProgress(AchievementIds.WarehouseMiser))
|
||||
{
|
||||
AchievementTrackerHelper.Instance.SetProgress(AchievementIds.WarehouseMiser, count);
|
||||
@@ -521,8 +682,8 @@ public class ToolboxViewModel : Screen
|
||||
DepotResult.Add(result);
|
||||
}
|
||||
|
||||
ArkPlannerResult = details["arkplanner"]?["data"]?.ToString() ?? string.Empty;
|
||||
LoliconResult = details["lolicon"]?["data"]?.ToString() ?? string.Empty;
|
||||
// 标记缓存失效
|
||||
InvalidateDepotCache();
|
||||
|
||||
bool done = (bool)(details["done"] ?? false);
|
||||
if (!done)
|
||||
@@ -530,8 +691,25 @@ public class ToolboxViewModel : Screen
|
||||
return true;
|
||||
}
|
||||
|
||||
if (updateSyncTime)
|
||||
{
|
||||
// 从 Core 获取新数据,更新为当前 UTC 时间
|
||||
LastDepotSyncTime = DateTime.UtcNow;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从本地加载,读取保存的时间
|
||||
var syncTimeStr = details["syncTime"]?.ToString(Formatting.None)?.Trim('"');
|
||||
if (!string.IsNullOrEmpty(syncTimeStr) &&
|
||||
DateTime.TryParseExact(syncTimeStr, "O", null, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
|
||||
out var lastDepotSyncTime))
|
||||
{
|
||||
LastDepotSyncTime = lastDepotSyncTime;
|
||||
}
|
||||
}
|
||||
|
||||
DepotInfo = LocalizationHelper.GetString("IdentificationCompleted");
|
||||
SaveDepotDetails(details);
|
||||
SaveDepotDetails();
|
||||
Instances.TaskQueueViewModel.UpdateDatePrompt();
|
||||
|
||||
return true;
|
||||
@@ -564,8 +742,120 @@ public class ToolboxViewModel : Screen
|
||||
private void DepotClear()
|
||||
{
|
||||
DepotResult.Clear();
|
||||
ArkPlannerResult = string.Empty;
|
||||
LoliconResult = string.Empty;
|
||||
InvalidateDepotCache();
|
||||
}
|
||||
|
||||
// 需要排除的物品 ID(不统计到仓库)
|
||||
private static readonly HashSet<string> ExcludedItemIds =
|
||||
[
|
||||
"3401", // 家具
|
||||
"3112", "3113", "3114", // 碳
|
||||
"4001", // 龙门币
|
||||
"4003", // 合成玉
|
||||
"4006", // 红票
|
||||
"5001", // 经验
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// 检查物品 ID 是否应该被排除(不统计到仓库)
|
||||
/// </summary>
|
||||
/// <param name="itemId">物品 ID</param>
|
||||
/// <returns>true 表示应该排除</returns>
|
||||
private static bool ShouldExcludeItem(string itemId)
|
||||
{
|
||||
// 排除特定 ID
|
||||
if (ExcludedItemIds.Contains(itemId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 排除非纯数字的 ID
|
||||
if (!int.TryParse(itemId, out _))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据 StageDrops 数据更新仓库
|
||||
/// </summary>
|
||||
/// <param name="drops">关卡掉落数据列表 (ItemId, ItemName, Total, Add)</param>
|
||||
public void UpdateDepotFromDrops(List<(string ItemId, string ItemName, int Total, int Add)> drops)
|
||||
{
|
||||
if (drops == null || drops.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasUpdates = false;
|
||||
|
||||
foreach (var (itemId, _, total, add) in drops)
|
||||
{
|
||||
if (string.IsNullOrEmpty(itemId) || add <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 过滤不需要统计的物品
|
||||
if (ShouldExcludeItem(itemId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 查找现有仓库项
|
||||
var existingItem = DepotResult.FirstOrDefault(x => x.Id == itemId);
|
||||
if (existingItem != null)
|
||||
{
|
||||
// 更新现有物品数量
|
||||
if (existingItem.Count >= 0)
|
||||
{
|
||||
var newCount = existingItem.Count + add;
|
||||
existingItem.Count = newCount;
|
||||
hasUpdates = true;
|
||||
|
||||
// 更新成就进度
|
||||
if (newCount > AchievementTrackerHelper.Instance.GetProgress(AchievementIds.WarehouseMiser))
|
||||
{
|
||||
AchievementTrackerHelper.Instance.SetProgress(AchievementIds.WarehouseMiser, newCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 添加新物品
|
||||
var newItem = new DepotResultDate {
|
||||
Id = itemId,
|
||||
Name = ItemListHelper.GetItemName(itemId),
|
||||
Image = ItemListHelper.GetItemImage(itemId),
|
||||
Count = add,
|
||||
};
|
||||
DepotResult.Add(newItem);
|
||||
hasUpdates = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有更新,重新排序并保存
|
||||
if (hasUpdates)
|
||||
{
|
||||
// 按 ID 排序(与 DepotParse 保持一致)
|
||||
var sortedItems = DepotResult.OrderBy(x => x.Id).ToList();
|
||||
DepotResult.Clear();
|
||||
foreach (var item in sortedItems)
|
||||
{
|
||||
DepotResult.Add(item);
|
||||
}
|
||||
|
||||
// 标记缓存失效
|
||||
InvalidateDepotCache();
|
||||
|
||||
// 保存更新后的数据
|
||||
SaveDepotDetails();
|
||||
Instances.TaskQueueViewModel.UpdateDatePrompt();
|
||||
|
||||
_logger.Information("Depot updated from stage drops, {Count} items processed", drops.Count);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -387,8 +387,15 @@
|
||||
<controls:TooltipBlock
|
||||
HorizontalAlignment="Right"
|
||||
VerticalContentAlignment="Top"
|
||||
NormalOpacity="0.5"
|
||||
TooltipText="{DynamicResource InventoryUpdateTip}" />
|
||||
NormalOpacity="0.5">
|
||||
<controls:TooltipBlock.TooltipText>
|
||||
<MultiBinding StringFormat="{}{0}
{1}: {2}">
|
||||
<Binding Source="{StaticResource InventoryUpdateTip}" />
|
||||
<Binding Source="{StaticResource LastDepotSyncTime}" />
|
||||
<Binding Path="LastDepotSyncTimeText" Source="{x:Static helper:Instances.ToolboxViewModel}" />
|
||||
</MultiBinding>
|
||||
</controls:TooltipBlock.TooltipText>
|
||||
</controls:TooltipBlock>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -304,23 +304,34 @@
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="{DynamicResource DepotRecognition}">
|
||||
<Grid Margin="20,0,20,20" IsEnabled="{Binding Idle}">
|
||||
<Grid Margin="20,0,20,20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="150" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel>
|
||||
<controls:TextBlock
|
||||
Height="36"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Center"
|
||||
d:Text="上次同步时间: 2026/1/1 上午00:00:00"
|
||||
Visibility="{c:Binding 'LastDepotSyncTimeText.Length > 0'}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding StringFormat="{}{0}: {1}">
|
||||
<Binding Source="{StaticResource LastDepotSyncTime}" />
|
||||
<Binding Path="LastDepotSyncTimeText" Source="{x:Static helper:Instances.ToolboxViewModel}" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</controls:TextBlock>
|
||||
<controls:TextBlock
|
||||
Margin="5"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
Focusable="True"
|
||||
FontSize="14"
|
||||
Style="{StaticResource TextBlockDefaultBold}"
|
||||
Text="{Binding DepotInfo}"
|
||||
TextWrapping="Wrap" />
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{c:Binding 'DepotInfo.Length > 0'}" />
|
||||
</StackPanel>
|
||||
<controls:NoAutomationDataGrid
|
||||
Grid.Row="1"
|
||||
@@ -350,19 +361,20 @@
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn Binding="{Binding Name}" Header="Name" />
|
||||
<DataGridTextColumn Binding="{Binding Count}" Header="Count" />
|
||||
<DataGridTextColumn Binding="{Binding DisplayCount}" Header="Count" />
|
||||
</DataGrid.Columns>
|
||||
</controls:NoAutomationDataGrid>
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
Margin="27,0"
|
||||
Margin="0,10,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding Idle}"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Width="180"
|
||||
Height="70"
|
||||
Margin="30"
|
||||
Margin="30,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Command="{s:Action ExportToArkplanner}"
|
||||
@@ -371,7 +383,7 @@
|
||||
<Button
|
||||
Width="180"
|
||||
Height="70"
|
||||
Margin="30"
|
||||
Margin="30,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Command="{s:Action ExportToLolicon}"
|
||||
@@ -380,7 +392,7 @@
|
||||
<Button
|
||||
Width="180"
|
||||
Height="70"
|
||||
Margin="30"
|
||||
Margin="30,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Command="{s:Action StartDepot}"
|
||||
|
||||
Reference in New Issue
Block a user