//
// 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
//
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using MaaWpfGui.Constants;
using MaaWpfGui.Helper;
using MaaWpfGui.States;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Stylet;
namespace MaaWpfGui.ViewModels.UI
{
///
/// The view model of recruit.
///
public class RecognizerViewModel : Screen
{
private readonly RunningState _runningState;
///
/// Initializes a new instance of the class.
///
public RecognizerViewModel()
{
DisplayName = LocalizationHelper.GetString("Toolbox");
_runningState = RunningState.Instance;
_runningState.IdleChanged += RunningState_IdleChanged;
}
private void RunningState_IdleChanged(object? sender, bool e)
{
Idle = e;
}
private bool _idle;
///
/// Gets or sets a value indicating whether it is idle.
///
public bool Idle
{
get => _idle;
set
{
SetAndNotify(ref _idle, value);
if (!value)
{
return;
}
// 识别完成、主界面暂停或者连接出错时
GachaDone = true;
DepotDone = true;
OperBoxDone = true;
}
}
private static string PadRightEx(string str, int totalByteCount)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Encoding coding = Encoding.GetEncoding("gb2312");
int count = str.ToCharArray().Count(ch => coding.GetByteCount(ch.ToString()) == 2);
string w = str.PadRight(totalByteCount - count);
return w;
}
#region Recruit
private string _recruitInfo = LocalizationHelper.GetString("RecruitmentRecognitionTip");
///
/// Gets or sets the recruit info.
///
public string RecruitInfo
{
get => _recruitInfo;
set => SetAndNotify(ref _recruitInfo, value);
}
private string _recruitResult;
///
/// Gets or sets the recruit result.
///
public string RecruitResult
{
get => _recruitResult;
set => SetAndNotify(ref _recruitResult, value);
}
private bool _chooseLevel3 = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.ChooseLevel3, bool.FalseString));
///
/// Gets or sets a value indicating whether to choose level 3.
///
public bool ChooseLevel3
{
get => _chooseLevel3;
set
{
SetAndNotify(ref _chooseLevel3, value);
ConfigurationHelper.SetValue(ConfigurationKeys.ChooseLevel3, value.ToString());
}
}
private bool _chooseLevel4 = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.ChooseLevel4, bool.TrueString));
///
/// Gets or sets a value indicating whether to choose level 4.
///
public bool ChooseLevel4
{
get => _chooseLevel4;
set
{
SetAndNotify(ref _chooseLevel4, value);
ConfigurationHelper.SetValue(ConfigurationKeys.ChooseLevel4, value.ToString());
}
}
private bool _chooseLevel5 = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.ChooseLevel5, bool.TrueString));
///
/// Gets or sets a value indicating whether to choose level 5.
///
public bool ChooseLevel5
{
get => _chooseLevel5;
set
{
SetAndNotify(ref _chooseLevel5, value);
ConfigurationHelper.SetValue(ConfigurationKeys.ChooseLevel5, value.ToString());
}
}
private bool _chooseLevel6 = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.ChooseLevel6, bool.TrueString));
///
/// Gets or sets a value indicating whether to choose level 6.
///
public bool ChooseLevel6
{
get => _chooseLevel6;
set
{
SetAndNotify(ref _chooseLevel6, value);
ConfigurationHelper.SetValue(ConfigurationKeys.ChooseLevel6, value.ToString());
}
}
private bool _autoSetTime = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.AutoSetTime, bool.TrueString));
///
/// Gets or sets a value indicating whether to set time automatically.
///
public bool RecruitAutoSetTime
{
get => _autoSetTime;
set
{
SetAndNotify(ref _autoSetTime, value);
ConfigurationHelper.SetValue(ConfigurationKeys.AutoSetTime, value.ToString());
}
}
private bool _isLevel3UseShortTime = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.Level3UseShortTime, bool.FalseString));
///
/// Gets or sets a value indicating whether to shorten the time for level 3.
///
public bool IsLevel3UseShortTime
{
get => _isLevel3UseShortTime;
set
{
if (value)
{
IsLevel3UseShortTime2 = false;
}
SetAndNotify(ref _isLevel3UseShortTime, value);
ConfigurationHelper.SetValue(ConfigurationKeys.Level3UseShortTime, value.ToString());
}
}
private bool _isLevel3UseShortTime2 = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.Level3UseShortTime2, bool.FalseString));
///
/// Gets or sets a value indicating whether to shorten the time for level 3.
///
public bool IsLevel3UseShortTime2
{
get => _isLevel3UseShortTime2;
set
{
if (value)
{
IsLevel3UseShortTime = false;
}
SetAndNotify(ref _isLevel3UseShortTime2, value);
ConfigurationHelper.SetValue(ConfigurationKeys.Level3UseShortTime2, value.ToString());
}
}
private bool _recruitCaught;
///
/// Starts calculation.
///
// UI 绑定的方法
// ReSharper disable once UnusedMember.Global
public async void RecruitStartCalc()
{
string errMsg = string.Empty;
RecruitInfo = LocalizationHelper.GetString("ConnectingToEmulator");
_recruitCaught = await Task.Run(() => Instances.AsstProxy.AsstConnect(ref errMsg));
if (!_recruitCaught)
{
RecruitInfo = errMsg;
return;
}
RecruitInfo = LocalizationHelper.GetString("Identifying");
RecruitResult = string.Empty;
var levelList = new List();
if (ChooseLevel3)
{
levelList.Add(3);
}
if (ChooseLevel4)
{
levelList.Add(4);
}
if (ChooseLevel5)
{
levelList.Add(5);
}
if (ChooseLevel6)
{
levelList.Add(6);
}
Instances.AsstProxy.AsstStartRecruitCalc(levelList.ToArray(), RecruitAutoSetTime);
}
private bool _recruitmentShowPotential = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.RecruitmentShowPotential, bool.TrueString));
public bool RecruitmentShowPotential
{
get => _recruitmentShowPotential;
set
{
SetAndNotify(ref _recruitmentShowPotential, value);
ConfigurationHelper.SetValue(ConfigurationKeys.RecruitmentShowPotential, value.ToString());
}
}
public void ProcRecruitMsg(JObject details)
{
string what = details["what"].ToString();
var subTaskDetails = details["details"];
switch (what)
{
case "RecruitTagsDetected":
{
JArray tags = (JArray)subTaskDetails["tags"];
string infoContent = LocalizationHelper.GetString("RecruitTagsDetected");
tags ??= [];
infoContent = tags.Select(tagName => tagName.ToString()).Aggregate(infoContent, (current, tagStr) => current + (tagStr + " "));
RecruitInfo = infoContent;
}
break;
case "RecruitResult":
{
string resultContent = string.Empty;
JArray resultArray = (JArray)subTaskDetails["result"];
/* int level = (int)subTaskDetails["level"]; */
foreach (var combs in resultArray ?? [])
{
int tagLevel = (int)combs["level"];
resultContent += tagLevel + "★ Tags: ";
resultContent = (((JArray?)combs["tags"]) ?? []).Aggregate(resultContent, (current, tag) => current + (tag + " "));
resultContent += "\n\t";
foreach (var oper in (JArray?)combs["opers"] ?? [])
{
int operLevel = (int)oper["level"];
var operId = oper["id"]?.ToString();
var operName = DataHelper.GetLocalizedCharacterName(oper["name"]?.ToString());
string potential = string.Empty;
if (RecruitmentShowPotential && OperBoxPotential != null && operId != null
&& (tagLevel >= 4 || operLevel == 1))
{
if (OperBoxPotential.ContainsKey(operId))
{
potential = " ( " + OperBoxPotential[operId] + " )";
}
else
{
potential = " ( !!! NEW !!! )";
}
}
resultContent += operLevel + "★ " + operName + potential + " ";
}
resultContent += "\n\n";
}
RecruitResult = resultContent;
}
break;
}
}
#endregion Recruit
#region Depot
private string _depotInfo = LocalizationHelper.GetString("DepotRecognitionTip");
///
/// Gets or sets the depot info.
///
public string DepotInfo
{
get => _depotInfo;
set => SetAndNotify(ref _depotInfo, value);
}
private ObservableCollection _depotResult = new ObservableCollection();
///
/// Gets or sets the depot result.
///
public ObservableCollection DepotResult
{
get => _depotResult;
set => SetAndNotify(ref _depotResult, value);
}
public class DepotResultDate
{
public string Name { get; set; }
public string Id { get; set; }
public BitmapImage Image { get; set; }
public int Count { get; set; }
}
///
/// Gets or sets the ArkPlanner result.
///
public string ArkPlannerResult { get; set; } = string.Empty;
///
/// Gets or sets the Lolicon result.
///
public string LoliconResult { get; set; } = string.Empty;
///
/// parse of depot recognition result
///
/// detailed json-style parameters
/// Success or not
public bool DepotParse(JObject details)
{
if (details == null)
{
return false;
}
DepotResult.Clear();
foreach (var item in details["arkplanner"]?["object"]?["items"]?.Cast()!)
{
var id = (string)item["id"];
DepotResultDate result = new DepotResultDate()
{
Id = id,
Name = ItemListHelper.GetItemName(id),
Image = ItemListHelper.GetItemImage(id),
Count = (int)item["have"],
};
DepotResult.Add(result);
}
ArkPlannerResult = (string)details["arkplanner"]["data"];
LoliconResult = (string)details["lolicon"]["data"];
bool done = (bool)details["done"];
if (!done)
{
return true;
}
DepotInfo = LocalizationHelper.GetString("IdentificationCompleted") + "\n" + LocalizationHelper.GetString("DepotRecognitionTip");
DepotDone = true;
return true;
}
private bool _depotDone = true;
///
/// Gets or sets a value indicating whether depot info is parsed.
///
public bool DepotDone
{
get => _depotDone;
set
{
SetAndNotify(ref _depotDone, value);
if (value)
{
_runningState.SetIdle(true);
}
}
}
///
/// Export depot info to ArkPlanner.
///
// xaml 中用到了
// ReSharper disable once UnusedMember.Global
public void ExportToArkplanner()
{
Clipboard.SetDataObject(ArkPlannerResult);
DepotInfo = LocalizationHelper.GetString("CopiedToClipboard");
}
///
/// Export depot info to Lolicon.
///
// xaml 中用到了
// ReSharper disable once UnusedMember.Global
public void ExportToLolicon()
{
Clipboard.SetDataObject(LoliconResult);
DepotInfo = LocalizationHelper.GetString("CopiedToClipboard");
}
private void DepotClear()
{
DepotResult.Clear();
ArkPlannerResult = string.Empty;
LoliconResult = string.Empty;
DepotDone = false;
}
///
/// Starts depot recognition.
///
// xaml 中用到了
// ReSharper disable once UnusedMember.Global
public async void StartDepot()
{
_runningState.SetIdle(false);
string errMsg = string.Empty;
DepotInfo = LocalizationHelper.GetString("ConnectingToEmulator");
bool caught = await Task.Run(() => Instances.AsstProxy.AsstConnect(ref errMsg));
if (!caught)
{
DepotInfo = errMsg;
_runningState.SetIdle(true);
return;
}
DepotInfo = LocalizationHelper.GetString("Identifying");
DepotClear();
Instances.AsstProxy.AsstStartDepot();
}
#endregion Depot
#region OperBox
///
/// 未实装干员,但在battle_data中,
///
private static readonly HashSet _virtuallyOpers =
[
"char_504_rguard",
"char_505_rcast",
"char_506_rmedic",
"char_507_rsnipe",
"char_514_rdfend",
"char_513_apionr",
"char_511_asnipe",
"char_510_amedic",
"char_509_acast",
"char_508_aguard",
"char_1001_amiya2",
"char_1037_amiya3"
];
private string _operBoxInfo = LocalizationHelper.GetString("OperBoxRecognitionTip");
public string OperBoxInfo
{
get => _operBoxInfo;
set => SetAndNotify(ref _operBoxInfo, value);
}
private string _operBoxResult;
public string OperBoxResult
{
get => _operBoxResult;
set => SetAndNotify(ref _operBoxResult, value);
}
private bool _operBoxDone = true;
///
/// Gets or sets a value indicating whether operBox info is parsed.
///
public bool OperBoxDone
{
get => _operBoxDone;
set
{
SetAndNotify(ref _operBoxDone, value);
if (value)
{
_runningState.SetIdle(true);
}
}
}
public string OperBoxExportData { get; set; } = string.Empty;
private JArray _operBoxDataArray = (JArray)JsonConvert.DeserializeObject(ConfigurationHelper.GetValue(ConfigurationKeys.OperBoxData, string.Empty));
public JArray OperBoxDataArray
{
get => _operBoxDataArray;
set
{
SetAndNotify(ref _operBoxDataArray, value);
ConfigurationHelper.SetValue(ConfigurationKeys.OperBoxData, value.ToString());
_operBoxPotential = null; // reset
}
}
private Dictionary? _operBoxPotential;
public Dictionary? OperBoxPotential
{
get
{
if (_operBoxPotential != null)
{
return _operBoxPotential;
}
_operBoxPotential = new Dictionary();
foreach (JObject operBoxData in OperBoxDataArray.Cast())
{
var id = (string)operBoxData["id"];
var potential = (int)operBoxData["potential"];
if (id != null)
{
_operBoxPotential[id] = potential;
}
}
return _operBoxPotential;
}
}
public bool OperBoxParse(JObject details)
{
var operBoxes = (JArray?)details["all_opers"];
if (operBoxes == null)
{
return false;
}
List> operHave = [];
List> operNotHave = [];
foreach (JObject operBox in operBoxes.Cast())
{
var tuple = new Tuple(DataHelper.GetLocalizedCharacterName((string?)operBox["name"]) ?? "???", (int)(operBox["rarity"] ?? -1));
if (_virtuallyOpers.Contains((string?)operBox["id"]))
{
continue;
}
if ((bool)(operBox["own"] ?? false))
{
/*已拥有干员*/
operHave.Add(tuple);
}
else
{
/*未拥有干员,包含预备干员等*/
operNotHave.Add(tuple);
}
}
operHave.Sort((x, y) => y.Item2.CompareTo(x.Item2));
operNotHave.Sort((x, y) => y.Item2.CompareTo(x.Item2));
int newlineFlag = 0;
string operNotHaveNames = "\t";
foreach (var name in operNotHave.Select(tuple => tuple.Item1))
{
operNotHaveNames += PadRightEx(name, 12) + "\t";
if (newlineFlag++ != 3)
{
continue;
}
operNotHaveNames += "\n\t";
newlineFlag = 0;
}
newlineFlag = 0;
string operHaveNames = "\t";
foreach (var name in operHave.Select(tuple => tuple.Item1))
{
operHaveNames += PadRightEx(name, 12) + "\t";
if (newlineFlag++ != 3)
{
continue;
}
operHaveNames += "\n\t";
newlineFlag = 0;
}
bool done = (bool)(details["done"] ?? false);
if (done)
{
OperBoxInfo = LocalizationHelper.GetString("IdentificationCompleted") + "\n" + LocalizationHelper.GetString("OperBoxRecognitionTip");
OperBoxResult = string.Format(LocalizationHelper.GetString("OperBoxRecognitionResult"), operNotHave.Count, operNotHaveNames, operHave.Count, operHaveNames);
OperBoxExportData = details["own_opers"]?.ToString() ?? string.Empty;
OperBoxDataArray = (JArray)(details["own_opers"] ?? new JArray());
OperBoxDone = true;
}
else
{
OperBoxResult = operHaveNames;
}
return true;
}
///
/// 开始识别干员
///
// xaml 中用到了
// ReSharper disable once UnusedMember.Global
public async void StartOperBox()
{
OperBoxExportData = string.Empty;
OperBoxDone = false;
_runningState.SetIdle(false);
string errMsg = string.Empty;
OperBoxInfo = LocalizationHelper.GetString("ConnectingToEmulator");
bool caught = await Task.Run(() => Instances.AsstProxy.AsstConnect(ref errMsg));
if (!caught)
{
OperBoxInfo = errMsg;
_runningState.SetIdle(true);
return;
}
if (Instances.AsstProxy.AsstStartOperBox())
{
OperBoxInfo = LocalizationHelper.GetString("Identifying");
}
}
// xaml 中用到了
// ReSharper disable once UnusedMember.Global
public void ExportOperBox()
{
if (string.IsNullOrEmpty(OperBoxExportData))
{
return;
}
Clipboard.SetDataObject(OperBoxExportData);
OperBoxInfo = LocalizationHelper.GetString("CopiedToClipboard");
}
#endregion OperBox
#region Gacha
private bool _gachaDone = true;
public bool GachaDone
{
get => _gachaDone;
set
{
bool stop = value && !_gachaDone;
SetAndNotify(ref _gachaDone, value);
if (value)
{
_runningState.SetIdle(true);
}
if (!stop)
{
return;
}
_gachaImageTimer.Stop();
// 强制再刷一下
RefreshGachaImage(null, null);
}
}
private string _gachaInfo = LocalizationHelper.GetString("GachaInitTip");
public string GachaInfo
{
get => _gachaInfo;
set => SetAndNotify(ref _gachaInfo, value);
}
// xaml 中用到了
// ReSharper disable once UnusedMember.Global
public void GachaOnce()
{
StartGacha();
}
// xaml 中用到了
// ReSharper disable once UnusedMember.Global
public void GachaTenTimes()
{
StartGacha(false);
}
public async void StartGacha(bool once = true)
{
GachaDone = false;
GachaImage = null;
_runningState.SetIdle(false);
string errMsg = string.Empty;
GachaInfo = LocalizationHelper.GetString("ConnectingToEmulator");
bool caught = await Task.Run(() => Instances.AsstProxy.AsstConnect(ref errMsg));
if (!caught)
{
GachaInfo = errMsg;
_runningState.SetIdle(true);
return;
}
if (!Instances.AsstProxy.AsstStartGacha(once))
{
return;
}
_gachaImageTimer.Interval = TimeSpan.FromMilliseconds(500);
_gachaImageTimer.Tick += RefreshGachaImage;
_gachaImageTimer.Start();
}
private readonly DispatcherTimer _gachaImageTimer = new DispatcherTimer();
private static readonly object _lock = new object();
private BitmapImage? _gachaImage;
public BitmapImage? GachaImage
{
get => _gachaImage;
set => SetAndNotify(ref _gachaImage, value);
}
private void RefreshGachaImage(object? sender, EventArgs? e)
{
lock (_lock)
{
var image = Instances.AsstProxy.AsstGetImage();
if (GachaImage.IsEqual(image))
{
return;
}
GachaImage = image;
var rd = new Random();
GachaInfo = LocalizationHelper.GetString("GachaTip" + rd.Next(1, 18).ToString());
}
}
// DO NOT CHANGE
// 请勿更改
// 請勿更改
// このコードを変更しないでください
// 변경하지 마십시오
private bool _gachaShowDisclaimer = true; // !Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.ShowDisclaimerNoMore, bool.FalseString));
public bool GachaShowDisclaimer
{
get => _gachaShowDisclaimer;
set
{
SetAndNotify(ref _gachaShowDisclaimer, value);
}
}
private bool _gachaShowDisclaimerNoMore = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.GachaShowDisclaimerNoMore, bool.FalseString));
public bool GachaShowDisclaimerNoMore
{
get => _gachaShowDisclaimerNoMore;
set
{
SetAndNotify(ref _gachaShowDisclaimerNoMore, value);
ConfigurationHelper.SetValue(ConfigurationKeys.GachaShowDisclaimerNoMore, value.ToString());
}
}
// xaml 中用到了
// ReSharper disable once UnusedMember.Global
public void GachaAgreeDisclaimer()
{
var result = MessageBoxHelper.Show(
LocalizationHelper.GetString("GachaWarning"),
LocalizationHelper.GetString("Warning"),
MessageBoxButton.YesNo,
MessageBoxImage.Warning,
no: LocalizationHelper.GetString("Confirm"),
yes: LocalizationHelper.GetString("Cancel"),
iconBrushKey: "DangerBrush");
if (result != MessageBoxResult.No)
{
return;
}
GachaShowDisclaimer = false;
}
#endregion Gacha
}
}