//
// 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
//
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
using System.Windows.Forms;
using MaaWpfGui.Constants;
using MaaWpfGui.Extensions;
using MaaWpfGui.Helper;
using MaaWpfGui.Main;
using MaaWpfGui.Models;
using MaaWpfGui.Services;
using MaaWpfGui.States;
using MaaWpfGui.Utilities;
using MaaWpfGui.Utilities.ValueType;
using MaaWpfGui.ViewModels.UserControl.Settings;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Serilog;
using Stylet;
using StyletIoC;
using static System.Windows.Forms.AxHost;
using Application = System.Windows.Application;
using Screen = Stylet.Screen;
namespace MaaWpfGui.ViewModels.UI
{
///
/// The view model of task queue.
///
// 通过 container.Get(); 实例化或获取实例
// ReSharper disable once ClassNeverInstantiated.Global
public class TaskQueueViewModel : Screen
{
private readonly IContainer _container;
private StageManager _stageManager;
private readonly RunningState _runningState;
private static readonly ILogger _logger = Log.ForContext();
///
/// Gets or private sets the view models of task items.
///
public ObservableCollection TaskItemViewModels { get; private set; }
///
/// Gets the visibility of task setting views.
///
public static TaskSettingVisibilityInfo TaskSettingVisibilities => TaskSettingVisibilityInfo.Current;
public static SettingsViewModel TaskSettingDataContext => Instances.SettingsViewModel;
///
/// 实时更新任务顺序
///
/// ignored object
/// ignored NotifyCollectionChangedEventArgs
public void TaskItemSelectionChanged(object sender = null, NotifyCollectionChangedEventArgs e = null)
{
_ = (sender, e);
Execute.OnUIThread(() =>
{
int index = 0;
foreach (var item in TaskItemViewModels)
{
ConfigurationHelper.SetTaskOrder(item.OriginalName, index.ToString());
++index;
}
});
}
///
/// Gets or private sets the view models of log items.
///
public ObservableCollection LogItemViewModels { get; private set; }
#region ActionAfterTasks
private bool _enableAfterActionSetting;
///
/// Gets or sets a value indicating whether to show after task queue actions
///
public bool EnableAfterActionSetting
{
get => _enableAfterActionSetting;
set
{
SetAndNotify(ref _enableAfterActionSetting, value);
TaskSettingVisibilityInfo.Current.Set("AfterAction", value);
}
}
///
/// Checks after completion.
///
public async void CheckAfterCompleted()
{
await Task.Run(() => SettingsViewModel.GameSettings.RunScript("EndsWithScript"));
var actions = TaskSettingDataContext.PostActionSetting;
_logger.Information("Post actions: " + actions.ActionDescription);
if (actions.BackToAndroidHome)
{
Instances.AsstProxy.AsstBackToHome();
await Task.Delay(1000);
}
if (actions.ExitArknights)
{
var mode = SettingsViewModel.GameSettings.ClientType;
if (!Instances.AsstProxy.AsstStartCloseDown(mode))
{
AddLog(LocalizationHelper.GetString("CloseArknightsFailed"), UiLogColor.Error);
}
await Task.Delay(1000);
}
if (actions.ExitEmulator)
{
DoKillEmulator();
await Task.Delay(1000);
}
if (actions.ExitSelf && !(actions.Hibernate || actions.Shutdown || actions.Sleep))
{
Bootstrapper.Shutdown();
}
if (actions.Hibernate)
{
if (actions.IfNoOtherMaa && HasOtherMaa())
{
Bootstrapper.Shutdown();
}
else
{
await DoHibernate();
}
}
if (actions.Shutdown)
{
if (actions.IfNoOtherMaa && HasOtherMaa())
{
Bootstrapper.Shutdown();
}
else
{
await DoShutDown();
}
}
if (actions.Sleep)
{
if (actions.IfNoOtherMaa && HasOtherMaa())
{
Bootstrapper.Shutdown();
}
else
{
await DoSleep();
}
}
if (actions.ExitSelf)
{
Bootstrapper.Shutdown();
}
actions.LoadPostActions();
return;
bool HasOtherMaa()
{
var processesCount = Process.GetProcessesByName("MAA").Length;
_logger.Information($"MAA processes count: {processesCount}");
return processesCount > 1;
}
void DoKillEmulator()
{
if (!KillEmulatorModeSwitcher())
{
AddLog(LocalizationHelper.GetString("ExitEmulatorFailed"), UiLogColor.Error);
}
}
async Task DoHibernate()
{
actions.LoadPostActions();
// 休眠提示
AddLog(LocalizationHelper.GetString("HibernatePrompt"), UiLogColor.Error);
await Task.Delay(10000);
PowerManagement.Hibernate();
}
async Task DoShutDown()
{
_logger.Information("Shutdown in 70 seconds.");
Process.Start("shutdown.exe", "-s -t 70");
await Execute.OnUIThreadAsync(() => Instances.MainWindowManager?.Show());
if (await TimerCanceledAsync(
LocalizationHelper.GetString("Shutdown"),
LocalizationHelper.GetString("AboutToShutdown"),
LocalizationHelper.GetString("Cancel"),
60))
{
_logger.Information("Shutdown canceled.");
Process.Start("shutdown.exe", "-a");
return;
}
_logger.Information("Shutdown not canceled, proceeding to exit application.");
Bootstrapper.Shutdown();
}
async Task DoSleep()
{
actions.LoadPostActions();
// 休眠提示
AddLog(LocalizationHelper.GetString("SleepPrompt"), UiLogColor.Error);
await Task.Delay(10000);
PowerManagement.Sleep();
}
}
#endregion
///
/// Initializes a new instance of the class.
///
/// The IoC container.
public TaskQueueViewModel(IContainer container)
{
_container = container;
_runningState = RunningState.Instance;
_runningState.IdleChanged += RunningState_IdleChanged;
}
private void RunningState_IdleChanged(object sender, bool e)
{
Idle = e;
TaskSettingDataContext.Idle = e;
if (!e)
{
Instances.Data.ClearCache();
}
}
protected override void OnInitialActivate()
{
base.OnInitialActivate();
_stageManager = _container.Get();
DisplayName = LocalizationHelper.GetString("Farming");
LogItemViewModels = [];
InitializeItems();
InitTimer();
_ = UpdateDatePromptAndStagesWeb();
}
/*
public void ShowButton()
{
Visible = Visibility.Visible;
Hibernate = true;
}
private Visibility _visible = Visibility.Collapsed;
public Visibility Visible
{
get => _visible;
set => SetAndNotify(ref _visible, value);
}
*/
public bool Running { get; set; }
public bool Closing { get; set; }
private readonly System.Timers.Timer _timer = new();
public bool ConfirmExit()
{
if (Closing)
{
return false;
}
Closing = true;
if (Application.Current.IsShuttingDown())
{
// allow close if application is shutting down
return true;
}
if (!Running)
{
// no need to confirm if no running task
return true;
}
var result = MessageBoxHelper.Show(
LocalizationHelper.GetString("ConfirmExitText"),
LocalizationHelper.GetString("ConfirmExitTitle"),
MessageBoxButton.YesNo,
MessageBoxImage.Question);
Closing = false;
return result == MessageBoxResult.Yes;
}
public override Task CanCloseAsync()
{
return Task.FromResult(this.ConfirmExit());
}
private void InitTimer()
{
_timer.Interval = 50 * 1000;
_timer.Elapsed += Timer1_Elapsed;
_timer.Start();
}
private DateTime _lastTimerElapsed = DateTime.MinValue;
private async void Timer1_Elapsed(object sender, EventArgs e)
{
// 提前记录时间,避免等待超过定时时间
DateTime currentTime = DateTime.Now;
currentTime = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, currentTime.Hour, currentTime.Minute, 0);
if (currentTime == _lastTimerElapsed)
{
return;
}
_lastTimerElapsed = currentTime;
HandleDatePromptUpdate();
HandleCheckForUpdates();
RefreshCustomInfrastPlanIndexByPeriod();
await HandleTimerLogic(currentTime);
}
private static int CalculateRandomDelay()
{
Random random = new Random();
int delayTime = random.Next(0, 60 * 60 * 1000);
return delayTime;
}
private bool _isUpdatingDatePrompt;
private void HandleDatePromptUpdate()
{
if (!NeedToUpdateDatePrompt() || _isUpdatingDatePrompt)
{
return;
}
_isUpdatingDatePrompt = true;
UpdateDatePromptAndStagesLocally();
var delayTime = CalculateRandomDelay();
_ = Task.Run(async () =>
{
await Task.Delay(delayTime);
await _runningState.UntilIdleAsync(60000);
await UpdateDatePromptAndStagesWeb();
_isUpdatingDatePrompt = false;
});
}
private bool _isCheckingForUpdates;
private void HandleCheckForUpdates()
{
if (!NeedToCheckForUpdates() || _isCheckingForUpdates)
{
return;
}
if (!SettingsViewModel.VersionUpdateSettings.UpdateAutoCheck)
{
return;
}
_isCheckingForUpdates = true;
var delayTime = CalculateRandomDelay();
_ = Task.Run(async () =>
{
await Task.Delay(delayTime);
await SettingsViewModel.VersionUpdateSettings.ManualUpdate();
_isCheckingForUpdates = false;
});
}
private static (bool _timeToStart, bool _timeToChangeConfig, int _configIndex) CheckTimers(DateTime currentTime)
{
bool timeToStart = false;
bool timeToChangeConfig = false;
int configIndex = 0;
for (int i = 0; i < 8; ++i)
{
if (!SettingsViewModel.TimerSettings.TimerModels.Timers[i].IsOn)
{
continue;
}
DateTime startTime = new DateTime(currentTime.Year,
currentTime.Month,
currentTime.Day,
SettingsViewModel.TimerSettings.TimerModels.Timers[i].Hour,
SettingsViewModel.TimerSettings.TimerModels.Timers[i].Min,
0);
DateTime restartDateTime = startTime.AddMinutes(-2);
// 确保0点的定时会在当日的23:58重启
if (restartDateTime.Day != startTime.Day)
{
restartDateTime = restartDateTime.AddDays(1);
}
if (currentTime == restartDateTime &&
Instances.SettingsViewModel.CurrentConfiguration != SettingsViewModel.TimerSettings.TimerModels.Timers[i].TimerConfig)
{
timeToChangeConfig = true;
configIndex = i;
break;
}
// ReSharper disable once InvertIf
if (currentTime == startTime)
{
timeToStart = true;
configIndex = i;
break;
}
}
return (timeToStart, timeToChangeConfig, configIndex);
}
private async Task HandleTimerLogic(DateTime currentTime)
{
if (!_runningState.GetIdle() && !SettingsViewModel.TimerSettings.ForceScheduledStart)
{
return;
}
var (timeToStart, timeToChangeConfig, configIndex) = CheckTimers(currentTime);
if (timeToChangeConfig)
{
_logger.Information($"Scheduled configuration change: Timer Index: {configIndex}");
HandleConfigChange(configIndex);
return;
}
if (timeToStart)
{
_logger.Information($"Scheduled start: Timer Index: {configIndex}");
await HandleScheduledStart(configIndex);
}
}
private void HandleConfigChange(int configIndex)
{
if (SettingsViewModel.TimerSettings.CustomConfig &&
(_runningState.GetIdle() || SettingsViewModel.TimerSettings.ForceScheduledStart))
{
Instances.SettingsViewModel.CurrentConfiguration = SettingsViewModel.TimerSettings.TimerModels.Timers[configIndex].TimerConfig;
}
}
private async Task HandleScheduledStart(int configIndex)
{
if (SettingsViewModel.TimerSettings.ForceScheduledStart)
{
if (SettingsViewModel.TimerSettings.CustomConfig &&
Instances.SettingsViewModel.CurrentConfiguration != SettingsViewModel.TimerSettings.TimerModels.Timers[configIndex].TimerConfig)
{
_logger.Warning($"Scheduled start skipped: Custom configuration is enabled, but the current configuration does not match the scheduled timer configuration (Timer Index: {configIndex}). Current Configuration: {Instances.SettingsViewModel.CurrentConfiguration}, Scheduled Configuration: {SettingsViewModel.TimerSettings.TimerModels.Timers[configIndex].TimerConfig}");
return;
}
if (SettingsViewModel.TimerSettings.ShowWindowBeforeForceScheduledStart)
{
await Execute.OnUIThreadAsync(() => Instances.MainWindowManager?.Show());
}
if (await TimerCanceledAsync(
LocalizationHelper.GetString("ForceScheduledStart"),
LocalizationHelper.GetString("ForceScheduledStartTip"),
LocalizationHelper.GetString("Cancel"),
10))
{
return;
}
if (!_runningState.GetIdle())
{
_logger.Information("Not idle, Stop and CloseDown");
await Stop();
SetStopped();
}
var mode = SettingsViewModel.GameSettings.ClientType;
if (!Instances.AsstProxy.AsstAppendCloseDown(mode))
{
AddLog(LocalizationHelper.GetString("CloseArknightsFailed"), UiLogColor.Error);
}
SettingsViewModel.FightTask.ResetFightVariables();
ResetTaskSelection();
RefreshCustomInfrastPlanIndexByPeriod();
}
LinkStart();
}
private static async Task TimerCanceledAsync(string content = "", string tipContent = "", string buttonContent = "", int seconds = 10)
{
if (Application.Current.Dispatcher.CheckAccess())
{
return await ShowDialogAsync();
}
return await await Application.Current.Dispatcher.InvokeAsync(ShowDialogAsync);
async Task ShowDialogAsync()
{
var canceled = false;
var delay = TimeSpan.FromSeconds(seconds);
var dialogUserControl = new Views.UserControl.TextDialogWithTimerUserControl(
content,
tipContent,
buttonContent,
delay.TotalMilliseconds);
var dialog = HandyControl.Controls.Dialog.Show(dialogUserControl, nameof(Views.UI.RootView));
var tcs = new TaskCompletionSource();
dialogUserControl.Click += (_, _) =>
{
canceled = true;
dialog.Close();
tcs.TrySetResult(true);
};
await Task.WhenAny(Task.Delay(delay), tcs.Task);
dialog.Close();
_logger.Information($"Timer canceled: {canceled}");
return canceled;
}
}
///
/// Initializes items.
///
private void InitializeItems()
{
List taskList =
[
"WakeUp",
"Recruiting",
"Base",
"Combat",
"Mall",
"Mission",
"AutoRoguelike",
];
if (SettingsViewModel.GameSettings.ClientType is not "txwy")
{
taskList.Add("Reclamation");
}
var tempOrderList = new List(new DragItemViewModel[taskList.Count]);
var nonOrderList = new List();
for (int i = 0; i != taskList.Count; ++i)
{
var task = taskList[i];
bool parsed = int.TryParse(ConfigurationHelper.GetTaskOrder(task, "-1"), out var order);
DragItemViewModel vm = new DragItemViewModel(
LocalizationHelper.GetString(task),
task,
"TaskQueue.",
task is not ("AutoRoguelike" or "Reclamation"));
if (task == TaskSettingVisibilityInfo.DefaultVisibleTaskSetting)
{
vm.EnableSetting = true;
}
if (!parsed || order < 0 || order >= tempOrderList.Count || tempOrderList[order] != null)
{
nonOrderList.Add(vm);
}
else
{
tempOrderList[order] = vm;
}
}
foreach (var newVm in nonOrderList)
{
int i = 0;
while (tempOrderList[i] != null)
{
++i;
}
tempOrderList[i] = newVm;
ConfigurationHelper.SetTaskOrder(newVm.OriginalName, i.ToString());
}
TaskItemViewModels = new ObservableCollection(tempOrderList);
TaskItemViewModels.CollectionChanged += TaskItemSelectionChanged;
SettingsViewModel.FightTask.InitDrops();
NeedToUpdateDatePrompt();
UpdateDatePromptAndStagesLocally();
RefreshCustomInfrastPlan();
if (DateTime.UtcNow.ToYjDate().IsAprilFoolsDay())
{
AddLog(LocalizationHelper.GetString("BuyWineOnAprilFoolsDay"), UiLogColor.Info);
}
}
private DayOfWeek _curDayOfWeek;
public DayOfWeek CurDayOfWeek => _curDayOfWeek;
///
/// Determine whether the specified stage is open
///
/// stage name
/// Whether the specified stage is open
public bool IsStageOpen(string name) => _stageManager.IsStageOpen(name, _curDayOfWeek);
///
/// Returns the valid stage if it is open, otherwise returns an empty string.
///
/// The stage to check.
/// The valid stage or an empty string.
public string GetValidStage(string stage) => IsStageOpen(stage) ? stage : string.Empty;
///
/// 更新日期提示和关卡列表
///
public void UpdateDatePromptAndStagesLocally()
{
UpdateDatePrompt();
UpdateStageList();
}
///
/// 访问 api 获取更新后更新日期提示和关卡列表
///
/// 可等待
public async Task UpdateDatePromptAndStagesWeb()
{
await _stageManager.UpdateStageWeb();
UpdateDatePromptAndStagesLocally();
}
///
/// 更新 ObservableCollection,确保不替换原集合,而是增删项
///
/// 原始 ObservableCollection
/// 新的列表
public static void UpdateObservableCollection(ObservableCollection originalCollection, List newList)
{
originalCollection.Clear();
foreach (var item in newList)
{
originalCollection.Add(item);
}
}
private bool NeedToUpdateDatePrompt()
{
var now = DateTime.UtcNow.ToYjDateTime();
var hour = now.Hour;
var min = now.Minute;
// yj历的4/16点
if (min == 0 && hour == 12)
{
return true;
}
if (_curDayOfWeek == now.DayOfWeek)
{
return false;
}
_curDayOfWeek = now.DayOfWeek;
return true;
}
private static bool NeedToCheckForUpdates()
{
var now = DateTime.UtcNow.ToYjDateTime();
var hour = now.Hour;
var min = now.Minute;
// yj历的4/22点
return min == 0 && hour == 18;
}
///
/// Updates date prompt.
///
// FIXME: 被注入对象只能在private函数内使用,只有Model显示之后才会被注入。如果Model还没有触发OnInitialActivate时调用函数会NullPointerException
// 这个函数被列为public可见,意味着他注入对象前被调用
public void UpdateDatePrompt()
{
var builder = new StringBuilder(LocalizationHelper.GetString("TodaysStageTip") + "\n");
// Closed activity stages
foreach (var stage in SettingsViewModel.FightTask.Stages)
{
if (stage == null || _stageManager.GetStageInfo(stage)?.IsActivityClosed() != true)
{
continue;
}
builder.Append(stage).Append(": ").AppendLine(LocalizationHelper.GetString("ClosedStage"));
}
// Open stages today
var openStages = _stageManager.GetStageTips(_curDayOfWeek);
if (!string.IsNullOrEmpty(openStages))
{
builder.Append(openStages);
}
var prompt = builder.ToString();
if (StagesOfToday == prompt)
{
return;
}
StagesOfToday = prompt;
}
///
/// Updates stage list.
/// 使用手动输入时,只更新关卡列表,不更新关卡选择
/// 使用隐藏当日不开放时,更新关卡列表,关卡选择为未开放的关卡时清空
/// 使用备选关卡时,更新关卡列表,关卡选择为未开放的关卡时在关卡列表中添加对应未开放关卡,避免清空导致进入上次关卡
/// 啥都不选时,更新关卡列表,关卡选择为未开放的关卡时在关卡列表中添加对应未开放关卡,避免清空导致进入上次关卡
/// 除手动输入外所有情况下,如果剩余理智为未开放的关卡,会被清空
///
// FIXME: 被注入对象只能在private函数内使用,只有Model显示之后才会被注入。如果Model还没有触发OnInitialActivate时调用函数会NullPointerException
// 这个函数被列为public可见,意味着他注入对象前被调用
public void UpdateStageList()
{
Execute.OnUIThread(() =>
{
var hideUnavailableStage = SettingsViewModel.FightTask.HideUnavailableStage;
Instances.TaskQueueViewModel.EnableSetFightParams = false;
var stage1 = SettingsViewModel.FightTask.Stage1 ?? string.Empty;
var stage2 = SettingsViewModel.FightTask.Stage2 ?? string.Empty;
var stage3 = SettingsViewModel.FightTask.Stage3 ?? string.Empty;
var rss = SettingsViewModel.FightTask.RemainingSanityStage ?? string.Empty;
var tempStageList = hideUnavailableStage
? _stageManager.GetStageList(Instances.TaskQueueViewModel.CurDayOfWeek).ToList()
: _stageManager.GetStageList().ToList();
var tempRemainingSanityStageList = _stageManager.GetStageList().ToList();
if (SettingsViewModel.FightTask.CustomStageCode)
{
// 7%
// 使用自定义的时候不做处理
}
else if (hideUnavailableStage)
{
// 15%
stage1 = Instances.TaskQueueViewModel.GetValidStage(stage1);
stage2 = Instances.TaskQueueViewModel.GetValidStage(stage2);
stage3 = Instances.TaskQueueViewModel.GetValidStage(stage3);
}
else if (SettingsViewModel.FightTask.UseAlternateStage)
{
// 11%
AddStagesIfNotExist([stage1, stage2, stage3], tempStageList);
}
else
{
// 啥都没选
AddStageIfNotExist(stage1, tempStageList);
// 避免关闭了使用备用关卡后,始终添加备用关卡中的未开放关卡
stage2 = Instances.TaskQueueViewModel.GetValidStage(stage2);
stage3 = Instances.TaskQueueViewModel.GetValidStage(stage3);
}
// rss 如果结束后还选择了不开放的关卡,刷理智任务会报错
rss = Instances.TaskQueueViewModel.IsStageOpen(rss) ? rss : string.Empty;
if (tempRemainingSanityStageList.Any(item => item.Value == string.Empty))
{
var itemToRemove = tempRemainingSanityStageList.First(item => item.Value == string.Empty);
tempRemainingSanityStageList.Remove(itemToRemove);
}
tempRemainingSanityStageList.Insert(0, new CombinedData { Display = LocalizationHelper.GetString("NoUse"), Value = string.Empty });
UpdateObservableCollection(SettingsViewModel.FightTask.StageList, tempStageList);
UpdateObservableCollection(SettingsViewModel.FightTask.RemainingSanityStageList, tempRemainingSanityStageList);
SettingsViewModel.FightTask._stage1Fallback = stage1;
SettingsViewModel.FightTask.Stage1 = stage1;
SettingsViewModel.FightTask.Stage2 = stage2;
SettingsViewModel.FightTask.Stage3 = stage3;
SettingsViewModel.FightTask.RemainingSanityStage = rss;
Instances.TaskQueueViewModel.EnableSetFightParams = true;
});
}
private void AddStagesIfNotExist(IEnumerable stages, List stageList)
{
foreach (var stage in stages)
{
AddStageIfNotExist(stage, stageList);
}
}
private void AddStageIfNotExist(string stage, List stageList)
{
if (stageList.Any(x => x.Value == stage))
{
return;
}
var stageInfo = _stageManager.GetStageInfo(stage);
stageList.Add(stageInfo);
}
private string _stagesOfToday = string.Empty;
///
/// Gets or private sets the stages of today.
///
public string StagesOfToday
{
get => _stagesOfToday;
private set => SetAndNotify(ref _stagesOfToday, value);
}
///
/// Adds log.
///
/// The content.
/// The font color.
/// The font weight.
public void AddLog(string content, string color = UiLogColor.Trace, string weight = "Regular")
{
Execute.OnUIThread(() =>
{
var log = new LogItemViewModel(content, color, weight);
LogItemViewModels.Add(log);
_logger.Information(content);
});
}
///
/// Clears log.
///
private void ClearLog()
{
Execute.OnUIThread(() =>
{
LogItemViewModels.Clear();
_logger.Information("Main windows log clear.");
_logger.Information(string.Empty);
});
}
///
/// Selects all.
///
// UI 绑定的方法
// ReSharper disable once UnusedMember.Global
public void SelectedAll()
{
foreach (var item in TaskItemViewModels)
{
switch (item.OriginalName)
{
case "AutoRoguelike":
case "Reclamation":
continue;
}
item.IsChecked = true;
}
}
private bool _inverseMode = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.MainFunctionInverseMode, bool.FalseString));
///
/// Gets or sets a value indicating whether to use inverse mode.
///
public bool InverseMode
{
get => _inverseMode;
set
{
SetAndNotify(ref _inverseMode, value);
InverseShowText = value ? LocalizationHelper.GetString("Inverse") : LocalizationHelper.GetString("Clear");
InverseMenuText = value ? LocalizationHelper.GetString("Clear") : LocalizationHelper.GetString("Inverse");
ConfigurationHelper.SetValue(ConfigurationKeys.MainFunctionInverseMode, value.ToString());
}
}
///
/// The width of "Select All" when both.
///
public const int SelectedAllWidthWhenBoth = 80;
private int _selectedAllWidth =
ConfigurationHelper.GetGlobalValue(ConfigurationKeys.InverseClearMode, "Clear") == "ClearInverse" ? SelectedAllWidthWhenBoth : 85;
///
/// Gets or sets the width of "Select All".
///
public int SelectedAllWidth
{
get => _selectedAllWidth;
set => SetAndNotify(ref _selectedAllWidth, value);
}
private bool _showInverse = ConfigurationHelper.GetGlobalValue(ConfigurationKeys.InverseClearMode, "Clear") == "ClearInverse";
///
/// Gets or sets a value indicating whether "Select inversely" is visible.
///
public bool ShowInverse
{
get => _showInverse;
set => SetAndNotify(ref _showInverse, value);
}
private string _inverseShowText = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.MainFunctionInverseMode, bool.FalseString))
? LocalizationHelper.GetString("Inverse")
: LocalizationHelper.GetString("Clear");
///
/// Gets or private Sets the text to be displayed for "Select inversely".
///
public string InverseShowText
{
get => _inverseShowText;
private set => SetAndNotify(ref _inverseShowText, value);
}
private string _inverseMenuText = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.MainFunctionInverseMode, bool.FalseString))
? LocalizationHelper.GetString("Clear")
: LocalizationHelper.GetString("Inverse");
///
/// Gets or private sets the text of inversion menu.
///
public string InverseMenuText
{
get => _inverseMenuText;
private set => SetAndNotify(ref _inverseMenuText, value);
}
///
/// Changes inversion mode.
///
// UI 绑定的方法
// ReSharper disable once UnusedMember.Global
public void ChangeInverseMode()
{
InverseMode = !InverseMode;
}
///
/// Selects inversely.
///
// UI 绑定的方法
// ReSharper disable once UnusedMember.Global
public void InverseSelected()
{
if (InverseMode)
{
foreach (var item in TaskItemViewModels)
{
switch (item.OriginalName)
{
case "AutoRoguelike":
case "Reclamation":
item.IsChecked = false;
continue;
}
item.IsChecked = !item.IsChecked;
}
}
else
{
foreach (var item in TaskItemViewModels)
{
item.IsChecked = false;
}
}
}
///
/// Reset unsaved task selection.
///
public void ResetTaskSelection()
{
foreach (var item in TaskItemViewModels)
{
if (item.IsCheckedWithNull == null)
{
item.IsChecked = false;
}
}
}
private async Task ConnectToEmulator()
{
string errMsg = string.Empty;
bool connected = await Task.Run(() => Instances.AsstProxy.AsstConnect(ref errMsg));
// 尝试启动模拟器
if (!connected && SettingsViewModel.ConnectSettings.RetryOnDisconnected)
{
AddLog(LocalizationHelper.GetString("ConnectFailed") + "\n" + LocalizationHelper.GetString("TryToStartEmulator"));
await Task.Run(() => SettingsViewModel.StartSettings.TryToStartEmulator());
if (Stopping)
{
SetStopped();
return false;
}
connected = await Task.Run(() => Instances.AsstProxy.AsstConnect(ref errMsg));
}
// 尝试断开连接, 然后重新连接
if (!connected)
{
AddLog(LocalizationHelper.GetString("ConnectFailed") + "\n" + LocalizationHelper.GetString("TryToReconnectByAdb"));
await Task.Run(() => SettingsViewModel.StartSettings.ReconnectByAdb());
if (Stopping)
{
SetStopped();
return false;
}
Instances.AsstProxy.Connected = false;
connected = await Task.Run(() => Instances.AsstProxy.AsstConnect(ref errMsg));
}
// 尝试重启 ADB
if (!connected && SettingsViewModel.ConnectSettings.AllowAdbRestart)
{
AddLog(LocalizationHelper.GetString("ConnectFailed") + "\n" + LocalizationHelper.GetString("RestartAdb"));
await Task.Run(() => SettingsViewModel.StartSettings.RestartAdb());
if (Stopping)
{
SetStopped();
return false;
}
connected = await Task.Run(() => Instances.AsstProxy.AsstConnect(ref errMsg));
}
// 尝试杀掉 ADB 进程
if (!connected && SettingsViewModel.ConnectSettings.AllowAdbHardRestart)
{
AddLog(LocalizationHelper.GetString("ConnectFailed") + "\n" + LocalizationHelper.GetString("HardRestartAdb"));
await Task.Run(() => SettingsViewModel.StartSettings.HardRestartAdb());
if (Stopping)
{
SetStopped();
return false;
}
connected = await Task.Run(() => Instances.AsstProxy.AsstConnect(ref errMsg));
}
if (connected)
{
return true;
}
AddLog(errMsg, UiLogColor.Error);
_runningState.SetIdle(true);
SetStopped();
return false;
}
public int MainTasksCompletedCount { get; set; }
public int MainTasksSelectedCount => TaskItemViewModels.Count(x => x.IsChecked);
///
/// updates the main tasks progress.
///
/// 已完成任务数,留空则代表 +1
public void UpdateMainTasksProgress(int? completedCount = null)
{
var rvm = (RootViewModel)this.Parent;
if (MainTasksSelectedCount == 0)
{
rvm.TaskProgress = null;
return;
}
MainTasksCompletedCount = completedCount ?? ++MainTasksCompletedCount;
if (MainTasksCompletedCount >= MainTasksSelectedCount)
{
rvm.TaskProgress = null;
}
else
{
rvm.TaskProgress = (MainTasksCompletedCount, MainTasksSelectedCount);
}
}
///
/// Starts.
///
public async void LinkStart()
{
if (!_runningState.GetIdle())
{
_logger.Information("Not idle, return.");
return;
}
ClearLog();
var buildDateTimeLong = VersionUpdateSettingsUserControlModel.BuildDateTimeCurrentCultureString;
var resourceDateTimeLong = SettingsViewModel.VersionUpdateSettings.ResourceDateTimeCurrentCultureString;
AddLog($"Build Time:\n{buildDateTimeLong}\nResource Time:\n{resourceDateTimeLong}");
var uiVersion = VersionUpdateSettingsUserControlModel.UiVersion;
var coreVersion = VersionUpdateSettingsUserControlModel.CoreVersion;
if (uiVersion != coreVersion &&
Instances.VersionUpdateViewModel.IsStdVersion(uiVersion) &&
Instances.VersionUpdateViewModel.IsStdVersion(coreVersion))
{
AddLog(string.Format(LocalizationHelper.GetString("VersionMismatch"), uiVersion, coreVersion), UiLogColor.Error);
return;
}
MainTasksCompletedCount = 0;
// 所有提前 return 都要放在 _runningState.SetIdle(false) 之前,否则会导致无法再次点击开始
_runningState.SetIdle(false);
// 虽然更改时已经保存过了,不过保险起见在点击开始之后再次保存任务和基建列表
TaskItemSelectionChanged();
SettingsViewModel.InfrastTask.InfrastOrderSelectionChanged();
InfrastTaskRunning = true;
await Task.Run(() => SettingsViewModel.GameSettings.RunScript("StartsWithScript"));
AddLog(LocalizationHelper.GetString("ConnectingToEmulator"));
/*
// 现在的主流模拟器都已经更新过自带的 adb 了,不再需要替换
if (!Instances.SettingsViewModel.AdbReplaced && !Instances.SettingsViewModel.IsAdbTouchMode())
{
AddLog(LocalizationHelper.GetString("AdbReplacementTips"), UiLogColor.Info);
}
*/
// 一般是点了“停止”按钮了
if (Stopping)
{
SetStopped();
return;
}
if (!await ConnectToEmulator())
{
return;
}
// 一般是点了“停止”按钮了
if (Stopping)
{
SetStopped();
return;
}
bool taskRet = true;
// 直接遍历TaskItemViewModels里面的内容,是排序后的
int count = 0;
foreach (var item in TaskItemViewModels)
{
if (item.IsChecked == false)
{
continue;
}
++count;
switch (item.OriginalName)
{
case "Base":
taskRet &= AppendInfrast();
break;
case "WakeUp":
taskRet &= AppendStart();
break;
case "Combat":
taskRet &= AppendFight();
break;
case "Recruiting":
taskRet &= AppendRecruit();
break;
case "Mall":
taskRet &= AppendMall();
break;
case "Mission":
taskRet &= AppendAward();
break;
case "AutoRoguelike":
taskRet &= AppendRoguelike();
break;
case "Reclamation":
taskRet &= AppendReclamation();
break;
default:
--count;
_logger.Error("Unknown task: " + item.OriginalName);
break;
}
if (taskRet)
{
continue;
}
AddLog(item.OriginalName + "Error", UiLogColor.Error);
taskRet = true;
--count;
}
if (count == 0)
{
AddLog(LocalizationHelper.GetString("UnselectedTask"));
_runningState.SetIdle(true);
Instances.AsstProxy.AsstStop();
SetStopped();
return;
}
taskRet &= Instances.AsstProxy.AsstStart();
if (taskRet)
{
AddLog(LocalizationHelper.GetString("Running"));
Instances.AsstProxy.StartTaskTime = DateTimeOffset.Now;
}
else
{
AddLog(LocalizationHelper.GetString("UnknownErrorOccurs"));
await Stop();
SetStopped();
}
}
///
/// 通常要和 一起使用,除非能保证回调消息能收到 `AsstMsg.TaskChainStopped`
/// This is usually done with Unless you are guaranteed to receive the callback message `AsstMsg.TaskChainStopped`
///
/// Timeout millisecond
/// A
/// 尝试等待 core 成功停止运行,默认超时时间一分钟
/// Try to wait for the core to stop running, the default timeout is one minute
///
public async Task Stop(int timeout = 60 * 1000)
{
Stopping = true;
AddLog(LocalizationHelper.GetString("Stopping"));
await Task.Run(() =>
{
if (!Instances.AsstProxy.AsstStop())
{
_logger.Warning("Failed to stop Asst");
}
});
int count = 0;
while (Instances.AsstProxy.AsstRunning() && count <= timeout / 100)
{
await Task.Delay(100);
count++;
}
return !Instances.AsstProxy.AsstRunning();
}
// UI 绑定的方法
// ReSharper disable once UnusedMember.Global
public async void WaitAndStop()
{
Waiting = true;
AddLog(LocalizationHelper.GetString("Waiting"));
if (SettingsViewModel.GameSettings.RoguelikeDelayAbortUntilCombatComplete)
{
await WaitUntilRoguelikeCombatComplete();
if (Instances.AsstProxy.AsstRunning() && !Stopping)
{
await Stop();
}
}
}
///
/// 等待肉鸽战斗结束,10分钟强制退出
///
private async Task WaitUntilRoguelikeCombatComplete()
{
int time = 0;
while (SettingsViewModel.GameSettings.RoguelikeDelayAbortUntilCombatComplete && RoguelikeInCombatAndShowWait && time < 600 && !Stopping)
{
await Task.Delay(1000);
++time;
}
}
private bool _roguelikeInCombatAndShowWait;
public bool RoguelikeInCombatAndShowWait
{
get => _roguelikeInCombatAndShowWait;
set => SetAndNotify(ref _roguelikeInCombatAndShowWait, value);
}
public void SetStopped()
{
SleepManagement.AllowSleep();
if (SettingsViewModel.GameSettings.ManualStopWithScript)
{
Task.Run(() => SettingsViewModel.GameSettings.RunScript("EndsWithScript"));
}
if (!_runningState.GetIdle() || Stopping)
{
AddLog(LocalizationHelper.GetString("Stopped"));
}
Waiting = false;
Stopping = false;
_runningState.SetIdle(true);
}
public async void QuickSwitchAccount()
{
if (!_runningState.GetIdle())
{
return;
}
_runningState.SetIdle(false);
// 虽然更改时已经保存过了,不过保险起见在点击开始之后再次保存任务和基建列表
TaskItemSelectionChanged();
SettingsViewModel.InfrastTask.InfrastOrderSelectionChanged();
ClearLog();
await Task.Run(() => SettingsViewModel.GameSettings.RunScript("StartsWithScript"));
AddLog(LocalizationHelper.GetString("ConnectingToEmulator"));
/*
// 现在的主流模拟器都已经更新过自带的 adb 了,不再需要替换
if (!Instances.SettingsViewModel.AdbReplaced && !Instances.SettingsViewModel.IsAdbTouchMode())
{
AddLog(LocalizationHelper.GetString("AdbReplacementTips"), UiLogColor.Info);
}
*/
// 一般是点了“停止”按钮了
if (Stopping)
{
SetStopped();
return;
}
if (!await ConnectToEmulator())
{
return;
}
// 一般是点了“停止”按钮了
if (Stopping)
{
SetStopped();
return;
}
bool taskRet = true;
taskRet &= AppendStart();
taskRet &= Instances.AsstProxy.AsstStart();
if (taskRet)
{
AddLog(LocalizationHelper.GetString("Running"));
}
else
{
AddLog(LocalizationHelper.GetString("UnknownErrorOccurs"));
await Stop();
SetStopped();
}
}
private static bool AppendStart()
{
var mode = SettingsViewModel.GameSettings.ClientType;
var enable = mode.Length != 0;
Instances.SettingsViewModel.AccountName = Instances.SettingsViewModel.AccountName.Trim();
var accountName = Instances.SettingsViewModel.AccountName;
return Instances.AsstProxy.AsstAppendStartUp(mode, enable, accountName);
}
private bool AppendFight()
{
int medicine = 0;
if (SettingsViewModel.FightTask.UseMedicine)
{
if (!int.TryParse(SettingsViewModel.FightTask.MedicineNumber, out medicine))
{
medicine = 0;
}
}
int stone = 0;
if (SettingsViewModel.FightTask.UseStone)
{
if (!int.TryParse(SettingsViewModel.FightTask.StoneNumber, out stone))
{
stone = 0;
}
}
int times = int.MaxValue;
if (SettingsViewModel.FightTask.HasTimesLimited)
{
if (!int.TryParse(SettingsViewModel.FightTask.MaxTimes, out times))
{
times = 0;
}
}
if (!int.TryParse(SettingsViewModel.FightTask.Series, out var series))
{
series = 1;
}
int dropsQuantity = 0;
if (SettingsViewModel.FightTask.IsSpecifiedDrops)
{
if (!int.TryParse(SettingsViewModel.FightTask.DropsQuantity, out dropsQuantity))
{
dropsQuantity = 0;
}
}
string curStage = SettingsViewModel.FightTask.Stage;
bool mainFightRet = Instances.AsstProxy.AsstAppendFight(curStage, medicine, stone, times, series, SettingsViewModel.FightTask.DropsItemId, dropsQuantity);
if (!mainFightRet)
{
AddLog(LocalizationHelper.GetString("UnsupportedStages") + ": " + curStage, UiLogColor.Error);
return false;
}
if ((curStage == "Annihilation") && SettingsViewModel.FightTask.UseAlternateStage)
{
foreach (var stage in SettingsViewModel.FightTask.Stages)
{
if (!IsStageOpen(stage) || (stage == curStage))
{
continue;
}
AddLog(LocalizationHelper.GetString("AnnihilationTaskTip"), UiLogColor.Info);
mainFightRet = Instances.AsstProxy.AsstAppendFight(stage, medicine, 0, int.MaxValue, series, string.Empty, 0);
break;
}
}
if (mainFightRet && SettingsViewModel.FightTask.UseRemainingSanityStage && !string.IsNullOrEmpty(SettingsViewModel.FightTask.RemainingSanityStage))
{
return Instances.AsstProxy.AsstAppendFight(SettingsViewModel.FightTask.RemainingSanityStage, 0, 0, int.MaxValue, 1, string.Empty, 0, false);
}
return mainFightRet;
}
public bool EnableSetFightParams { get; set; } = true;
///
/// Sets parameters.
///
public void SetFightParams()
{
if (!EnableSetFightParams || !Instances.AsstProxy.ContainsTask(AsstProxy.TaskType.Fight))
{
return;
}
int medicine = 0;
if (SettingsViewModel.FightTask.UseMedicine)
{
if (!int.TryParse(SettingsViewModel.FightTask.MedicineNumber, out medicine))
{
medicine = 0;
}
}
int stone = 0;
if (SettingsViewModel.FightTask.UseStone)
{
if (!int.TryParse(SettingsViewModel.FightTask.StoneNumber, out stone))
{
stone = 0;
}
}
int times = int.MaxValue;
if (SettingsViewModel.FightTask.HasTimesLimited)
{
if (!int.TryParse(SettingsViewModel.FightTask.MaxTimes, out times))
{
times = 0;
}
}
if (!int.TryParse(SettingsViewModel.FightTask.Series, out var series))
{
series = 1;
}
int dropsQuantity = 0;
if (SettingsViewModel.FightTask.IsSpecifiedDrops)
{
if (!int.TryParse(SettingsViewModel.FightTask.DropsQuantity, out dropsQuantity))
{
dropsQuantity = 0;
}
}
Instances.AsstProxy.AsstSetFightTaskParams(SettingsViewModel.FightTask.Stage, medicine, stone, times, series, SettingsViewModel.FightTask.DropsItemId, dropsQuantity);
}
public void SetFightRemainingSanityParams()
{
if (!Instances.AsstProxy.ContainsTask(AsstProxy.TaskType.FightRemainingSanity))
{
return;
}
Instances.AsstProxy.AsstSetFightTaskParams(SettingsViewModel.FightTask.RemainingSanityStage, 0, 0, int.MaxValue, 1, string.Empty, 0, false);
}
private void SetInfrastParams()
{
if (!Instances.AsstProxy.ContainsTask(AsstProxy.TaskType.Infrast))
{
return;
}
var order = SettingsViewModel.InfrastTask.GetInfrastOrderList();
Instances.AsstProxy.AsstSetInfrastTaskParams(
order,
SettingsViewModel.InfrastTask.UsesOfDrones,
SettingsViewModel.InfrastTask.ContinueTraining,
SettingsViewModel.InfrastTask.DormThreshold / 100.0,
SettingsViewModel.InfrastTask.DormFilterNotStationedEnabled,
SettingsViewModel.InfrastTask.DormTrustEnabled,
SettingsViewModel.InfrastTask.OriginiumShardAutoReplenishment,
SettingsViewModel.InfrastTask.CustomInfrastEnabled,
SettingsViewModel.InfrastTask.CustomInfrastFile,
CustomInfrastPlanIndex);
}
private bool AppendInfrast()
{
if (SettingsViewModel.InfrastTask.CustomInfrastEnabled && (!File.Exists(SettingsViewModel.InfrastTask.CustomInfrastFile) || CustomInfrastPlanInfoList.Count == 0))
{
AddLog(LocalizationHelper.GetString("CustomizeInfrastSelectionEmpty"), UiLogColor.Error);
return false;
}
var order = SettingsViewModel.InfrastTask.GetInfrastOrderList();
return Instances.AsstProxy.AsstAppendInfrast(
order,
SettingsViewModel.InfrastTask.UsesOfDrones,
SettingsViewModel.InfrastTask.ContinueTraining,
SettingsViewModel.InfrastTask.DormThreshold / 100.0,
SettingsViewModel.InfrastTask.DormFilterNotStationedEnabled,
SettingsViewModel.InfrastTask.DormTrustEnabled,
SettingsViewModel.InfrastTask.OriginiumShardAutoReplenishment,
SettingsViewModel.InfrastTask.CustomInfrastEnabled,
SettingsViewModel.InfrastTask.CustomInfrastFile,
CustomInfrastPlanIndex);
}
private readonly Dictionary> _blackCharacterListMapping = new()
{
{ string.Empty, ["讯使", "嘉维尔", "坚雷"] },
{ "Official", ["讯使", "嘉维尔", "坚雷"] },
{ "Bilibili", ["讯使", "嘉维尔", "坚雷"] },
{ "YoStarEN", ["Courier", "Gavial", "Dur-nar"] },
{ "YoStarJP", ["クーリエ", "ガヴィル", "ジュナー"] },
{ "YoStarKR", ["쿠리어", "가비알", "듀나"] },
{ "txwy", ["訊使", "嘉維爾", "堅雷"] },
};
private bool AppendMall()
{
var buyFirst = SettingsViewModel.MallTask.CreditFirstList.Split(';', ';')
.Select(s => s.Trim());
var blackList = SettingsViewModel.MallTask.CreditBlackList.Split(';', ';')
.Select(s => s.Trim());
blackList = blackList.Union(_blackCharacterListMapping[SettingsViewModel.GameSettings.ClientType]);
return Instances.AsstProxy.AsstAppendMall(
!string.IsNullOrEmpty(SettingsViewModel.FightTask.Stage) && SettingsViewModel.MallTask.CreditFightTaskEnabled,
SettingsViewModel.MallTask.CreditFightSelectFormation,
SettingsViewModel.MallTask.CreditVisitFriendsEnabled,
SettingsViewModel.MallTask.CreditShopping,
buyFirst.ToArray(),
blackList.ToArray(),
SettingsViewModel.MallTask.CreditForceShoppingIfCreditFull,
SettingsViewModel.MallTask.CreditOnlyBuyDiscount,
SettingsViewModel.MallTask.CreditReserveMaxCredit);
}
private static bool AppendAward()
{
var receiveAward = SettingsViewModel.AwardTask.ReceiveAward;
var receiveMail = SettingsViewModel.AwardTask.ReceiveMail;
var receiveFreeRecruit = SettingsViewModel.AwardTask.ReceiveFreeRecruit;
var receiveOrundum = SettingsViewModel.AwardTask.ReceiveOrundum;
var receiveMining = SettingsViewModel.AwardTask.ReceiveMining;
var receiveSpecialAccess = SettingsViewModel.AwardTask.ReceiveSpecialAccess;
return Instances.AsstProxy.AsstAppendAward(receiveAward, receiveMail, receiveFreeRecruit, receiveOrundum, receiveMining, receiveSpecialAccess);
}
private static bool AppendRecruit()
{
// for debug
if (!int.TryParse(SettingsViewModel.RecruitTask.RecruitMaxTimes, out var maxTimes))
{
maxTimes = 0;
}
var firstList = SettingsViewModel.RecruitTask.AutoRecruitFirstList;
var reqList = new List();
var cfmList = new List();
if (SettingsViewModel.RecruitTask.ChooseLevel3)
{
cfmList.Add(3);
}
if (SettingsViewModel.RecruitTask.ChooseLevel4)
{
reqList.Add(4);
cfmList.Add(4);
}
// ReSharper disable once InvertIf
if (SettingsViewModel.RecruitTask.ChooseLevel5)
{
reqList.Add(5);
cfmList.Add(5);
}
_ = int.TryParse(SettingsViewModel.RecruitTask.SelectExtraTags, out var selectExtra);
return Instances.AsstProxy.AsstAppendRecruit(
maxTimes,
firstList.Cast().Select(i => i.Value).ToArray(),
[.. reqList],
[.. cfmList],
SettingsViewModel.RecruitTask.RefreshLevel3,
SettingsViewModel.RecruitTask.ForceRefresh,
SettingsViewModel.RecruitTask.UseExpedited,
selectExtra,
SettingsViewModel.RecruitTask.NotChooseLevel1,
SettingsViewModel.RecruitTask.ChooseLevel3Time,
SettingsViewModel.RecruitTask.ChooseLevel4Time,
SettingsViewModel.RecruitTask.ChooseLevel5Time);
}
private static bool AppendRoguelike()
{
_ = int.TryParse(SettingsViewModel.RoguelikeTask.RoguelikeMode, out var mode);
return Instances.AsstProxy.AsstAppendRoguelike(
mode,
SettingsViewModel.RoguelikeTask.RoguelikeDifficulty,
SettingsViewModel.RoguelikeTask.RoguelikeStartsCount,
SettingsViewModel.RoguelikeTask.RoguelikeInvestmentEnabled,
SettingsViewModel.RoguelikeTask.RoguelikeInvestmentWithMoreScore,
SettingsViewModel.RoguelikeTask.RoguelikeInvestsCount,
SettingsViewModel.RoguelikeTask.RoguelikeStopWhenInvestmentFull,
SettingsViewModel.RoguelikeTask.RoguelikeSquad,
SettingsViewModel.RoguelikeTask.RoguelikeRoles,
DataHelper.GetCharacterByNameOrAlias(SettingsViewModel.RoguelikeTask.RoguelikeCoreChar)?.Name ?? SettingsViewModel.RoguelikeTask.RoguelikeCoreChar,
SettingsViewModel.RoguelikeTask.RoguelikeStartWithEliteTwo,
SettingsViewModel.RoguelikeTask.RoguelikeOnlyStartWithEliteTwo,
SettingsViewModel.RoguelikeTask.Roguelike3FirstFloorFoldartal,
SettingsViewModel.RoguelikeTask.Roguelike3StartFloorFoldartal,
SettingsViewModel.RoguelikeTask.Roguelike3NewSquad2StartingFoldartal,
SettingsViewModel.RoguelikeTask.Roguelike3NewSquad2StartingFoldartals,
SettingsViewModel.RoguelikeTask.RoguelikeExpectedCollapsalParadigms,
SettingsViewModel.RoguelikeTask.RoguelikeUseSupportUnit,
SettingsViewModel.RoguelikeTask.RoguelikeEnableNonfriendSupport,
SettingsViewModel.RoguelikeTask.RoguelikeTheme,
SettingsViewModel.RoguelikeTask.RoguelikeRefreshTraderWithDice,
SettingsViewModel.RoguelikeTask.RoguelikeStopAtFinalBoss,
SettingsViewModel.RoguelikeTask.RoguelikeStopAtMaxLevel,
SettingsViewModel.RoguelikeTask.RoguelikeStartWithSeed);
}
private static bool AppendReclamation()
{
var toolToCraft = SettingsViewModel.ReclamationTask.ReclamationToolToCraft.Split(';', ';').Select(s => s.Trim());
_ = int.TryParse(SettingsViewModel.ReclamationTask.ReclamationMode, out var mode);
return Instances.AsstProxy.AsstAppendReclamation(
toolToCraft.ToArray(),
SettingsViewModel.ReclamationTask.ReclamationTheme,
mode,
SettingsViewModel.ReclamationTask.ReclamationIncrementMode,
SettingsViewModel.ReclamationTask.ReclamationMaxCraftCountPerRound);
}
[DllImport("User32.dll", EntryPoint = "FindWindow")]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
private static extern int GetWindowThreadProcessId(IntPtr hwnd, out int id);
///
/// 一个根据连接配置判断使用关闭模拟器的方式的方法
///
/// 是否关闭成功
private static bool KillEmulatorModeSwitcher()
{
try
{
string emulatorMode = SettingsViewModel.ConnectSettings.ConnectConfig;
Instances.AsstProxy.Connected = false;
return emulatorMode switch
{
"Nox" => KillEmulatorNox(),
"LDPlayer" => KillEmulatorLdPlayer(),
"XYAZ" => KillEmulatorXyaz(),
"BlueStacks" => KillEmulatorBlueStacks(),
"MuMuEmulator12" => KillEmulatorMuMuEmulator12(),
_ => KillEmulatorByWindow(),
};
}
catch (Exception e)
{
_logger.Error("Failed to close emulator: " + e.Message);
return false;
}
}
///
/// 一个用于调用 MuMu12 模拟器控制台关闭 MuMu12 的方法
///
/// 是否关闭成功
private static bool KillEmulatorMuMuEmulator12()
{
string address = SettingsViewModel.ConnectSettings.ConnectAddress;
int emuIndex;
if (address == "127.0.0.1:16384")
{
emuIndex = 0;
}
else
{
string portStr = address.Split(':')[1];
int port = int.Parse(portStr);
emuIndex = (port - 16384) / 32;
}
Process[] processes = Process.GetProcessesByName("MuMuPlayer");
if (processes.Length <= 0)
{
return false;
}
ProcessModule processModule;
try
{
processModule = processes[0].MainModule;
}
catch (Exception e)
{
_logger.Error("Error: Failed to get the main module of the emulator process.");
_logger.Error(e.Message);
return false;
}
if (processModule == null)
{
return false;
}
string emuLocation = processModule.FileName;
emuLocation = Path.GetDirectoryName(emuLocation);
if (emuLocation == null)
{
return false;
}
string consolePath = Path.Combine(emuLocation, "MuMuManager.exe");
if (File.Exists(consolePath))
{
ProcessStartInfo startInfo = new ProcessStartInfo(consolePath)
{
Arguments = $"api -v {emuIndex} shutdown_player",
CreateNoWindow = true,
UseShellExecute = false,
};
var process = Process.Start(startInfo);
if (process != null && process.WaitForExit(5000))
{
_logger.Information($"Emulator at index {emuIndex} closed through console. Console path: {consolePath}");
return true;
}
_logger.Warning($"Console process at index {emuIndex} did not exit within the specified timeout. Killing emulator by window. Console path: {consolePath}");
return KillEmulatorByWindow();
}
_logger.Error($"Error: `{consolePath}` not found, try to kill emulator by window.");
return KillEmulatorByWindow();
}
///
/// 一个用于调用雷电模拟器控制台关闭雷电模拟器的方法
///
/// 是否关闭成功
private static bool KillEmulatorLdPlayer()
{
string address = SettingsViewModel.ConnectSettings.ConnectAddress;
int emuIndex;
if (address.Contains(':'))
{
string portStr = address.Split(':')[1];
int port = int.Parse(portStr);
emuIndex = (port - 5555) / 2;
}
else
{
string portStr = address.Split('-')[1];
int port = int.Parse(portStr);
emuIndex = (port - 5554) / 2;
}
Process[] processes = Process.GetProcessesByName("dnplayer");
if (processes.Length <= 0)
{
return false;
}
ProcessModule processModule;
try
{
processModule = processes[0].MainModule;
}
catch (Exception e)
{
_logger.Error("Error: Failed to get the main module of the emulator process.");
_logger.Error(e.Message);
return false;
}
if (processModule == null)
{
return false;
}
string emuLocation = processModule.FileName;
emuLocation = Path.GetDirectoryName(emuLocation);
if (emuLocation == null)
{
return false;
}
string consolePath = Path.Combine(emuLocation, "ldconsole.exe");
if (File.Exists(consolePath))
{
ProcessStartInfo startInfo = new ProcessStartInfo(consolePath)
{
Arguments = $"quit --index {emuIndex}",
CreateNoWindow = true,
UseShellExecute = false,
};
var process = Process.Start(startInfo);
if (process != null && process.WaitForExit(5000))
{
_logger.Information($"Emulator at index {emuIndex} closed through console. Console path: {consolePath}");
return true;
}
_logger.Warning($"Console process at index {emuIndex} did not exit within the specified timeout. Killing emulator by window. Console path: {consolePath}");
return KillEmulatorByWindow();
}
_logger.Information($"Error: `{consolePath}` not found, try to kill emulator by window.");
return KillEmulatorByWindow();
}
///
/// 一个用于调用夜神模拟器控制台关闭夜神模拟器的方法
///
/// 是否关闭成功
private static bool KillEmulatorNox()
{
string address = SettingsViewModel.ConnectSettings.ConnectAddress;
int emuIndex;
if (address == "127.0.0.1:62001")
{
emuIndex = 0;
}
else
{
string portStr = address.Split(':')[1];
int port = int.Parse(portStr);
emuIndex = port - 62024;
}
Process[] processes = Process.GetProcessesByName("Nox");
if (processes.Length <= 0)
{
return false;
}
ProcessModule processModule;
try
{
processModule = processes[0].MainModule;
}
catch (Exception e)
{
_logger.Error("Error: Failed to get the main module of the emulator process.");
_logger.Error(e.Message);
return false;
}
if (processModule == null)
{
return false;
}
string emuLocation = processModule.FileName;
emuLocation = Path.GetDirectoryName(emuLocation);
if (emuLocation == null)
{
return false;
}
string consolePath = Path.Combine(emuLocation, "NoxConsole.exe");
if (File.Exists(consolePath))
{
ProcessStartInfo startInfo = new ProcessStartInfo(consolePath)
{
Arguments = $"quit -index:{emuIndex}",
CreateNoWindow = true,
UseShellExecute = false,
};
var process = Process.Start(startInfo);
if (process != null && process.WaitForExit(5000))
{
_logger.Information($"Emulator at index {emuIndex} closed through console. Console path: {consolePath}");
return true;
}
_logger.Warning($"Console process at index {emuIndex} did not exit within the specified timeout. Killing emulator by window. Console path: {consolePath}");
return KillEmulatorByWindow();
}
_logger.Information($"Error: `{consolePath}` not found, try to kill emulator by window.");
return KillEmulatorByWindow();
}
///
/// 一个用于调用逍遥模拟器控制台关闭逍遥模拟器的方法
///
/// 是否关闭成功
private static bool KillEmulatorXyaz()
{
string address = SettingsViewModel.ConnectSettings.ConnectAddress;
string portStr = address.Split(':')[1];
int port = int.Parse(portStr);
var emuIndex = (port - 21503) / 10;
Process[] processes = Process.GetProcessesByName("MEmu");
if (processes.Length <= 0)
{
return false;
}
ProcessModule processModule;
try
{
processModule = processes[0].MainModule;
}
catch (Exception e)
{
_logger.Error("Error: Failed to get the main module of the emulator process.");
_logger.Error(e.Message);
return false;
}
if (processModule == null)
{
return false;
}
string emuLocation = processModule.FileName;
emuLocation = Path.GetDirectoryName(emuLocation);
if (emuLocation == null)
{
return false;
}
string consolePath = Path.Combine(emuLocation, "memuc.exe");
if (File.Exists(consolePath))
{
ProcessStartInfo startInfo = new ProcessStartInfo(consolePath)
{
Arguments = $"stop -i {emuIndex}",
CreateNoWindow = true,
UseShellExecute = false,
};
var process = Process.Start(startInfo);
if (process != null && process.WaitForExit(5000))
{
_logger.Information($"Emulator at index {emuIndex} closed through console. Console path: {consolePath}");
return true;
}
_logger.Warning($"Console process at index {emuIndex} did not exit within the specified timeout. Killing emulator by window. Console path: {consolePath}");
return KillEmulatorByWindow();
}
_logger.Information($"Error: `{consolePath}` not found, try to kill emulator by window.");
return KillEmulatorByWindow();
}
///
/// 一个用于关闭蓝叠模拟器的方法
///
/// 是否关闭成功
private static bool KillEmulatorBlueStacks()
{
Process[] processes = Process.GetProcessesByName("HD-Player");
if (processes.Length <= 0)
{
return false;
}
ProcessModule processModule;
try
{
processModule = processes[0].MainModule;
}
catch (Exception e)
{
_logger.Error("Error: Failed to get the main module of the emulator process.");
_logger.Error(e.Message);
return false;
}
if (processModule == null)
{
return false;
}
string emuLocation = processModule.FileName;
emuLocation = Path.GetDirectoryName(emuLocation);
if (emuLocation == null)
{
return false;
}
string consolePath = Path.Combine(emuLocation, "bsconsole.exe");
if (File.Exists(consolePath))
{
_logger.Information($"Info: `{consolePath}` has been found. This may be the BlueStacks China emulator, try to kill the emulator by window.");
return KillEmulatorByWindow();
}
_logger.Information($"Info: `{consolePath}` not found. This may be the BlueStacks International emulator, try to kill the emulator by the port.");
if (KillEmulator())
{
return true;
}
_logger.Information("Info: Failed to kill emulator by the port, try to kill emulator process with PID.");
if (processes.Length > 1)
{
_logger.Warning("Warning: The number of elements in processes exceeds one, abort closing the emulator");
return false;
}
try
{
processes[0].Kill();
return processes[0].WaitForExit(20000);
}
catch (Exception ex)
{
_logger.Error($"Error: Failed to kill emulator process with PID {processes[0].Id}. Exception: {ex.Message}");
}
return false;
}
///
/// Kills emulator by Window hwnd.
///
/// Whether the operation is successful.
private static bool KillEmulatorByWindow()
{
int pid = 0;
var windowName = new[]
{
"明日方舟",
"明日方舟 - MuMu模拟器",
"BlueStacks App Player",
"BlueStacks",
};
foreach (string i in windowName)
{
var hwnd = FindWindow(null, i);
if (hwnd == IntPtr.Zero)
{
continue;
}
GetWindowThreadProcessId(hwnd, out pid);
break;
}
if (pid == 0)
{
return KillEmulator();
}
try
{
var emulator = Process.GetProcessById(pid);
emulator.CloseMainWindow();
if (!emulator.WaitForExit(5000))
{
emulator.Kill();
if (emulator.WaitForExit(5000))
{
_logger.Information($"Emulator with process ID {pid} killed successfully.");
KillEmulator();
return true;
}
_logger.Error($"Failed to kill emulator with process ID {pid}.");
return false;
}
// 尽管已经成功 CloseMainWindow(),再次尝试 killEmulator()
// Refer to https://github.com/MaaAssistantArknights/MaaAssistantArknights/pull/1878
KillEmulator();
// 已经成功 CloseMainWindow(),所以不管 killEmulator() 的结果如何,都返回 true
return true;
}
catch
{
_logger.Error("Kill emulator by window error");
}
return KillEmulator();
}
///
/// Kills emulator.
///
/// Whether the operation is successful.
public static bool KillEmulator()
{
int pid = 0;
string address = ConfigurationHelper.GetValue(ConfigurationKeys.ConnectAddress, string.Empty);
var port = address.StartsWith("127") ? address.Substring(10) : "5555";
_logger.Information($"address: {address}, port: {port}");
string portCmd = "netstat -ano|findstr \"" + port + "\"";
Process checkCmd = new Process
{
StartInfo =
{
FileName = "cmd.exe",
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
},
};
try
{
checkCmd.Start();
}
catch
{
_logger.Error("Failed to start cmd.exe");
checkCmd.Close();
return false;
}
checkCmd.StandardInput.WriteLine(portCmd);
checkCmd.StandardInput.WriteLine("exit");
Regex reg = new Regex("\\s+", RegexOptions.Compiled);
while (true)
{
var line = checkCmd.StandardOutput.ReadLine();
line = line?.Trim();
if (line == null)
{
break;
}
if (!line.StartsWith("TCP", StringComparison.OrdinalIgnoreCase))
{
continue;
}
line = reg.Replace(line, ",");
try
{
string[] arr = line.Split(',');
if (arr.Length >= 2
&& Convert.ToBoolean(string.Compare(arr[1], address, StringComparison.Ordinal)))
{
continue;
}
pid = int.Parse(arr[4]);
break;
}
catch (Exception e)
{
_logger.Error("Failed to parse cmd.exe output: " + e.Message);
}
}
if (pid == 0)
{
_logger.Error("Failed to get emulator PID");
return false;
}
try
{
Process emulator = Process.GetProcessById(pid);
emulator.Kill();
}
catch
{
return false;
}
finally
{
checkCmd.Close();
}
return true;
}
///
/// Gets a value indicating whether it is initialized.
///
public bool Inited { get; private set; }
///
/// Sets it initialized.
///
public void SetInited()
{
Inited = true;
NotifyOfPropertyChange(nameof(Inited));
}
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;
}
FightTaskRunning = false;
InfrastTaskRunning = false;
UpdateMainTasksProgress(0);
}
}
private bool _stopping;
///
/// Gets a value indicating whether `stop` is awaiting.
///
public bool Stopping
{
get => _stopping;
private set => SetAndNotify(ref _stopping, value);
}
private bool _waiting;
///
/// Gets a value indicating whether waiting for roguelike combat complete.
///
public bool Waiting
{
// UI 会根据这个值来改变 Visibility
// ReSharper disable once UnusedMember.Global
get => _waiting;
private set => SetAndNotify(ref _waiting, value);
}
private bool _fightTaskRunning;
///
/// Gets or sets a value indicating whether the battle task is running.
///
public bool FightTaskRunning
{
get => _fightTaskRunning;
set => SetAndNotify(ref _fightTaskRunning, value);
}
private bool _infrastTaskRunning;
public bool InfrastTaskRunning
{
get => _infrastTaskRunning;
set => SetAndNotify(ref _infrastTaskRunning, value);
}
/*
private bool _shutdown = false;
public bool Shutdown
{
get => return _shutdown;
set
{
SetAndNotify(ref _shutdown, value);
if (value)
{
Hibernate = false;
Suspend = false;
}
}
}
private bool _hibernate = false; // 休眠
public bool Hibernate
{
get => return _hibernate;
set
{
SetAndNotify(ref _hibernate, value);
if (value)
{
Shutdown = false;
Suspend = false;
}
}
}
private bool _suspend = false; // 待机
public bool Suspend
{
get => return _suspend;
set
{
SetAndNotify(ref _suspend, value);
if (value)
{
Shutdown = false;
Hibernate = false;
}
}
}
*/
private bool _customInfrastEnabled = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.CustomInfrastEnabled, bool.FalseString));
public bool CustomInfrastEnabled
{
get => _customInfrastEnabled;
set
{
SetAndNotify(ref _customInfrastEnabled, value);
RefreshCustomInfrastPlan();
}
}
public bool NeedAddCustomInfrastPlanInfo { get; set; } = true;
private int _customInfrastPlanIndex = Convert.ToInt32(ConfigurationHelper.GetValue(ConfigurationKeys.CustomInfrastPlanIndex, "0"));
public int CustomInfrastPlanIndex
{
get
{
if (CustomInfrastPlanInfoList.Count == 0)
{
return 0;
}
if (_customInfrastPlanIndex >= CustomInfrastPlanInfoList.Count || _customInfrastPlanIndex < 0)
{
CustomInfrastPlanIndex = _customInfrastPlanIndex;
}
return _customInfrastPlanIndex;
}
set
{
if (CustomInfrastPlanInfoList.Count == 0)
{
return;
}
if (value >= CustomInfrastPlanInfoList.Count || value < 0)
{
var count = CustomInfrastPlanInfoList.Count;
value = ((value % count) + count) % count;
_logger.Warning($"CustomInfrastPlanIndex out of range, reset to Index % Count: {value}");
}
if (value != _customInfrastPlanIndex && NeedAddCustomInfrastPlanInfo)
{
var plan = CustomInfrastPlanInfoList[value];
AddLog(plan.Name, UiLogColor.Message);
foreach (var period in plan.PeriodList)
{
AddLog($"[ {period.BeginHour:D2}:{period.BeginMinute:D2} - {period.EndHour:D2}:{period.EndMinute:D2} ]");
}
if (plan.Description != string.Empty)
{
AddLog(plan.Description);
}
}
SetAndNotify(ref _customInfrastPlanIndex, value);
SetInfrastParams();
ConfigurationHelper.SetValue(ConfigurationKeys.CustomInfrastPlanIndex, value.ToString());
}
}
public ObservableCollection> CustomInfrastPlanList { get; } = new();
public struct CustomInfrastPlanInfo
{
// ReSharper disable InconsistentNaming
public int Index;
public string Name;
public string Description;
public string DescriptionPost;
// 有效时间段
public struct Period
{
public int BeginHour;
public int BeginMinute;
public int EndHour;
public int EndMinute;
}
public List PeriodList;
// ReSharper restore InconsistentNaming
}
private List CustomInfrastPlanInfoList { get; } = new();
private bool _customInfrastPlanHasPeriod;
private bool _customInfrastInfoOutput;
public void RefreshCustomInfrastPlan()
{
CustomInfrastPlanInfoList.Clear();
CustomInfrastPlanList.Clear();
_customInfrastPlanHasPeriod = false;
if (!CustomInfrastEnabled)
{
return;
}
if (!File.Exists(SettingsViewModel.InfrastTask.CustomInfrastFile))
{
return;
}
try
{
string jsonStr = File.ReadAllText(SettingsViewModel.InfrastTask.CustomInfrastFile);
var root = (JObject)JsonConvert.DeserializeObject(jsonStr);
if (root != null && _customInfrastInfoOutput && root.TryGetValue("title", out var title))
{
AddLog(LocalizationHelper.GetString("CustomInfrastTitle"), UiLogColor.Message);
AddLog($"title: {title}", UiLogColor.Info);
if (root.TryGetValue("description", out var value))
{
AddLog($"description: {value}", UiLogColor.Info);
}
}
var planList = (JArray)root?["plans"];
if (planList != null)
{
for (int i = 0; i < planList.Count; ++i)
{
var plan = (JObject)planList[i];
string display = plan.TryGetValue("name", out var name) ? name.ToString() : ("Plan " + ((char)('A' + i)));
CustomInfrastPlanList.Add(new GenericCombinedData { Display = display, Value = i });
string desc = plan.TryGetValue("description", out var description) ? description.ToString() : string.Empty;
string descPost = plan.TryGetValue("description_post", out var descriptionPost) ? descriptionPost.ToString() : string.Empty;
if (_customInfrastInfoOutput)
{
AddLog(display, UiLogColor.Message);
}
var periodList = new List();
if (plan.TryGetValue("period", out var token))
{
var periodArray = (JArray)token;
foreach (var periodJson in periodArray)
{
var period = default(CustomInfrastPlanInfo.Period);
string beginTime = periodJson[0]?.ToString();
if (beginTime != null)
{
var beginSplit = beginTime.Split(':');
period.BeginHour = int.Parse(beginSplit[0]);
period.BeginMinute = int.Parse(beginSplit[1]);
}
string endTime = periodJson[1]?.ToString();
if (endTime != null)
{
var endSplit = endTime.Split(':');
period.EndHour = int.Parse(endSplit[0]);
period.EndMinute = int.Parse(endSplit[1]);
}
periodList.Add(period);
if (_customInfrastInfoOutput)
{
AddLog($"[ {period.BeginHour:D2}:{period.BeginMinute:D2} - {period.EndHour:D2}:{period.EndMinute:D2} ]");
}
}
if (periodList.Count != 0)
{
_customInfrastPlanHasPeriod = true;
}
}
if (_customInfrastInfoOutput && desc != string.Empty)
{
AddLog(desc);
}
if (_customInfrastInfoOutput && descPost != string.Empty)
{
AddLog(descPost);
}
CustomInfrastPlanInfoList.Add(new CustomInfrastPlanInfo
{
Index = i,
Name = display,
Description = desc,
DescriptionPost = descPost,
PeriodList = periodList,
});
}
}
_customInfrastInfoOutput = true;
}
catch (Exception)
{
_customInfrastInfoOutput = true;
AddLog(LocalizationHelper.GetString("CustomInfrastFileParseFailed"), UiLogColor.Error);
return;
}
RefreshCustomInfrastPlanIndexByPeriod();
}
public void RefreshCustomInfrastPlanIndexByPeriod()
{
if (!CustomInfrastEnabled || !_customInfrastPlanHasPeriod || InfrastTaskRunning)
{
return;
}
var now = DateTime.Now;
foreach (var plan in CustomInfrastPlanInfoList.Where(
plan => plan.PeriodList.Any(
period => TimeLess(period.BeginHour, period.BeginMinute, now.Hour, now.Minute)
&& TimeLess(now.Hour, now.Minute, period.EndHour, period.EndMinute))))
{
CustomInfrastPlanIndex = plan.Index;
return;
}
if (CustomInfrastPlanIndex >= CustomInfrastPlanList.Count || CustomInfrastPlanList.Count < 0)
{
CustomInfrastPlanIndex = 0;
}
return;
static bool TimeLess(int lHour, int lMin, int rHour, int rMin) => (lHour != rHour) ? (lHour < rHour) : (lMin <= rMin);
}
public void IncreaseCustomInfrastPlanIndex()
{
if (!CustomInfrastEnabled || _customInfrastPlanHasPeriod || CustomInfrastPlanInfoList.Count == 0)
{
return;
}
AddLog(LocalizationHelper.GetString("CustomInfrastPlanIndexAutoSwitch"), UiLogColor.Message);
var prePlanPostDesc = CustomInfrastPlanInfoList[CustomInfrastPlanIndex].DescriptionPost;
if (prePlanPostDesc != string.Empty)
{
AddLog(prePlanPostDesc);
}
++CustomInfrastPlanIndex;
}
}
}