//
// Part of the MaaWpfGui project, maintained by the MaaAssistantArknights team (Maa Team)
// Copyright (C) 2021-2025 MaaAssistantArknights 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.Buffers;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using HandyControl.Controls;
using JetBrains.Annotations;
using MaaWpfGui.Constants;
using MaaWpfGui.Extensions;
using MaaWpfGui.Helper;
using MaaWpfGui.Main;
using MaaWpfGui.Models;
using MaaWpfGui.Models.AsstTasks;
using MaaWpfGui.Services.Notification;
using MaaWpfGui.States;
using MaaWpfGui.Utilities;
using MaaWpfGui.ViewModels.UserControl.Settings;
using MaaWpfGui.ViewModels.UserControl.TaskQueue;
using MaaWpfGui.Views.UI;
using Newtonsoft.Json.Linq;
using Serilog;
using Stylet;
using Application = System.Windows.Application;
using Screen = Stylet.Screen;
using Task = System.Threading.Tasks.Task;
namespace MaaWpfGui.ViewModels.UI;
///
/// The view model of task queue.
///
// 通过 container.Get(); 实例化或获取实例
// ReSharper disable once ClassNeverInstantiated.Global
public class TaskQueueViewModel : Screen
{
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.Instance;
public static SettingsViewModel TaskSettingDataContext => Instances.SettingsViewModel;
///
/// Gets the after action setting.
///
public PostActionSetting PostActionSetting { get; } = PostActionSetting.Instance;
#region 长草任务Model
///
/// Gets 连接任务Model
///
public static StartUpSettingsUserControlModel StartUpTask => StartUpSettingsUserControlModel.Instance;
///
/// Gets 战斗任务Model
///
public static FightSettingsUserControlModel FightTask => FightSettingsUserControlModel.Instance;
///
/// Gets 招募任务Model
///
public static RecruitSettingsUserControlModel RecruitTask => RecruitSettingsUserControlModel.Instance;
///
/// Gets 信用及购物任务Model
///
public static MallSettingsUserControlModel MallTask => MallSettingsUserControlModel.Instance;
///
/// Gets 基建任务Model
///
public static InfrastSettingsUserControlModel InfrastTask => InfrastSettingsUserControlModel.Instance;
///
/// Gets 领取奖励任务
///
public static AwardSettingsUserControlModel AwardTask => AwardSettingsUserControlModel.Instance;
///
/// Gets 肉鸽任务Model
///
public static RoguelikeSettingsUserControlModel RoguelikeTask => RoguelikeSettingsUserControlModel.Instance;
///
/// Gets 生稀盐酸任务Model
///
public static ReclamationSettingsUserControlModel ReclamationTask => ReclamationSettingsUserControlModel.Instance;
///
/// Gets 生稀盐酸任务Model
///
public static CustomSettingsUserControlModel CustomTask => CustomSettingsUserControlModel.Instance;
#endregion 长草任务Model
private static readonly IEnumerable _taskViewModelTypes = InitTaskViewModelList();
///
/// 实时更新任务顺序
///
/// 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;
}
});
}
// a:Action 绑定,不能为 static
public void ChooseOverlayTarget()
{
try
{
var owner = Application.Current.MainWindow;
var picker = new ProcessPickerWindow { Owner = owner };
var ok = picker.ShowDialog();
if (ok == true && picker.SelectedHwnd != IntPtr.Zero)
{
var overlayVm = Instances.OverlayViewModel;
if (overlayVm != null)
{
overlayVm.SetTargetHwnd(picker.SelectedHwnd);
if (overlayVm.IsCreated)
{
overlayVm.Close();
overlayVm.EnsureCreated();
}
}
}
}
catch
{
// ignored
}
}
private bool _isOverlayEnabled;
public bool IsOverlayEnabled
{
get => _isOverlayEnabled;
set => SetAndNotify(ref _isOverlayEnabled, value);
}
public void EnableOverlay()
{
if (IsOverlayEnabled)
{
return;
}
IsOverlayEnabled = true;
Instances.OverlayViewModel?.EnsureCreated();
}
public void DisableOverlay()
{
if (!IsOverlayEnabled)
{
return;
}
IsOverlayEnabled = false;
Instances.OverlayViewModel?.Close();
}
public void ToggleOverlay()
{
if (IsOverlayEnabled)
{
DisableOverlay();
}
else
{
EnableOverlay();
}
}
///
/// Gets or private sets the view models of log items.
///
public ObservableCollection LogItemViewModels { get; private set; } = [];
///
/// Gets the grouped log cards. Each card contains multiple .
///
public ObservableCollection LogCardViewModels { get; private set; } = [];
private bool TryMergeIntoLastCard(string content, string color, string weight, ToolTip? toolTip)
{
// Merge into last existing card when it exists and is not sealed.
if (LogCardViewModels.Count == 0)
{
return false;
}
var lastCard = LogCardViewModels[^1];
var log = new LogItemViewModel(content, color, weight, toolTip: toolTip);
LogItemViewModels.Add(log);
lastCard.Items.Add(log);
return true;
}
///
/// Gets or private sets the single download-related log item.
/// Use a single LogItemViewModel instead of a collection because only one entry is shown.
///
private LogItemViewModel _downloadLogItemViewModel = new(string.Empty);
public LogItemViewModel DownloadLogItemViewModel
{
get => _downloadLogItemViewModel;
private set => SetAndNotify(ref _downloadLogItemViewModel, value);
}
#region LogThumbnails
private readonly SemaphoreSlim _logThumbnailSemaphore = new(1, 1);
private const int LogThumbnailWidth = 640;
private const int LogThumbnailHeight = 360;
private static int MaxLogItemsWithThumbnails => SettingsViewModel.GuiSettings.MaxNumberOfLogThumbnails;
private async Task AttachThumbnailToCardAsync(LogCardViewModel card, bool forceScreencap)
{
if (card is null)
{
_logger.Warning("Cannot attach thumbnail to null log card.");
return;
}
try
{
var thumbnail = await GetOrCaptureLogThumbnailAsync(forceScreencap).ConfigureAwait(false);
if (thumbnail is null)
{
return;
}
await Execute.OnUIThreadAsync(() => {
// 检查卡片是否还在集合中,避免给已清空的卡片赋值
if (!LogCardViewModels.Contains(card))
{
return;
}
card.Thumbnail = thumbnail;
TrimOldThumbnails();
});
}
catch
{
_logger.Warning("Failed to attach thumbnail to log card.");
}
}
private async Task GetOrCaptureLogThumbnailAsync(bool forceScreencap = false)
{
if (!await _logThumbnailSemaphore.WaitAsync(100))
{
return null;
}
try
{
var frameData = await Instances.AsstProxy.AsstGetImageBgrDataAsync(forceScreencap: forceScreencap).ConfigureAwait(false);
if (frameData is null || frameData.Length == 0)
{
return null;
}
try
{
// 只保留小图,避免日志列表长期运行时占用过多内存。
var thumbnail = AsstProxy.CreateBgrBitmapSourceScaled(frameData, LogThumbnailWidth, LogThumbnailHeight);
return thumbnail;
}
finally
{
ArrayPool.Shared.Return(frameData);
}
}
finally
{
_logThumbnailSemaphore.Release();
}
}
private void TrimOldThumbnails()
{
var thumbnailIndices = LogCardViewModels
.Select((vm, index) => new { vm, index })
.Where(x => x.vm.Thumbnail != null)
.Select(x => x.index)
.ToList();
if (thumbnailIndices.Count > MaxLogItemsWithThumbnails)
{
for (int i = 0; i < thumbnailIndices.Count - MaxLogItemsWithThumbnails; i++)
{
LogCardViewModels[thumbnailIndices[i]].Thumbnail = null;
}
}
}
#endregion
#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.Instance.Set("AfterAction", value);
}
}
///
/// Checks after completion.
///
/// Task
public async Task CheckAfterCompleted()
{
await Task.Run(() => SettingsViewModel.GameSettings.RunScript("EndsWithScript"));
var actions = PostActionSetting;
_logger.Information("Post actions: " + actions.ActionDescription);
if (actions.BackToAndroidHome)
{
Instances.AsstProxy.AsstBackToHome();
await Task.Delay(1000);
}
if (actions.ExitArknights)
{
var clientType = SettingsViewModel.GameSettings.ClientType;
if (!Instances.AsstProxy.AsstStartCloseDown(clientType))
{
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}", processesCount);
return processesCount > 1;
}
void DoKillEmulator()
{
if (!EmulatorHelper.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()
{
PowerManagement.Shutdown();
await Execute.OnUIThreadAsync(() => Instances.MainWindowManager?.Show());
if (await TimerCanceledAsync(
LocalizationHelper.GetString("Shutdown"),
LocalizationHelper.GetString("AboutToShutdown"),
LocalizationHelper.GetString("Cancel"),
60))
{
PowerManagement.AbortShutdown();
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.
///
public TaskQueueViewModel()
{
_runningState = RunningState.Instance;
_runningState.StateChanged += (_, e) => {
Idle = e.Idle;
Inited = e.Inited;
Stopping = e.Stopping;
Instances.SettingsViewModel.Idle = e.Idle;
if (!e.Idle)
{
Instances.Data.ClearCache();
}
};
_runningState.TimeoutOccurred += RunningState_TimeOut;
}
private void RunningState_TimeOut(object? sender, string message)
{
Execute.OnUIThread(() => {
AddLog(message, UiLogColor.Warning);
ToastNotification.ShowDirect(message);
if (!SettingsViewModel.ExternalNotificationSettings.ExternalNotificationSendWhenTimeout)
{
return;
}
var lastLogs = LogItemViewModels
.TakeLast(5)
.Aggregate(string.Empty, (current, logItem) => current + $"[{logItem.Time}][{logItem.Color}]{logItem.Content}\n");
ExternalNotificationService.Send(message, lastLogs);
});
}
protected override void OnInitialActivate()
{
base.OnInitialActivate();
DisplayName = LocalizationHelper.GetString("Farming");
LogItemViewModels = [];
InitializeItems();
InitTimer();
_ = UpdateDatePromptAndStagesWeb();
}
///
protected override void OnClose()
{
base.OnClose();
}
/*
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 (SettingsViewModel.VersionUpdateSettings.IsCheckingForUpdates)
{
var result = MessageBoxHelper.Show(
LocalizationHelper.GetString("UpdateConfirmExitText"),
LocalizationHelper.GetString("UpdateConfirmExitTitle"),
MessageBoxButton.YesNo,
MessageBoxImage.Question);
Closing = false;
return result == MessageBoxResult.Yes;
}
if (!Running)
{
// no need to confirm if no running task
return true;
}
var confirmResult = MessageBoxHelper.Show(
LocalizationHelper.GetString("ConfirmExitText"),
LocalizationHelper.GetString("ConfirmExitTitle"),
MessageBoxButton.YesNo,
MessageBoxImage.Question);
Closing = false;
return confirmResult == MessageBoxResult.Yes;
}
public override Task CanCloseAsync()
{
return Task.FromResult(this.ConfirmExit());
}
private void InitTimer()
{
_timer.Interval = 30 * 1000;
_timer.Elapsed += Timer1_Elapsed;
_timer.Start();
}
private DateTime _lastTimerElapsed = DateTime.MinValue;
private async void Timer1_Elapsed(object? sender, EventArgs e)
{
try
{
// 提前记录时间,避免等待超过定时时间
DateTime currentTime = DateTime.Now;
currentTime = new(currentTime.Year, currentTime.Month, currentTime.Day, currentTime.Hour, currentTime.Minute, 0);
if (currentTime <= _lastTimerElapsed)
{
return;
}
_lastTimerElapsed = currentTime;
if ((currentTime.Hour == 3 || currentTime.Hour == 13 || currentTime.Hour == 23) && currentTime.Minute == 25 && !Idle)
{
AchievementTrackerHelper.Instance.Unlock(AchievementIds.Time325);
}
VersionUpdateSettingsUserControlModel.Instance.RefreshMirrorChyanCdkRemaining();
HandleDatePromptUpdate();
HandleCheckForUpdates();
InfrastTask.RefreshCustomInfrastPlanDisplay();
await HandleTimerLogic(currentTime);
}
catch
{
// ignored
}
}
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 (!SettingsViewModel.VersionUpdateSettings.UpdateAutoCheck)
{
return;
}
if (!NeedToCheckForUpdates() || _isCheckingForUpdates)
{
return;
}
_isCheckingForUpdates = true;
var delayTime = CalculateRandomDelay();
_ = Task.Run(async () => {
_logger.Information("waiting for update check: {DelayTime}", delayTime);
await Task.Delay(delayTime);
await Instances.VersionUpdateViewModel.VersionUpdateAndAskToRestartAsync();
await ResourceUpdater.ResourceUpdateAndReloadAsync();
_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 == false)
{
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}", configIndex);
HandleConfigChange(configIndex);
return;
}
if (timeToStart)
{
_logger.Information("Scheduled start: Timer Index: {ConfigIndex}", configIndex);
await HandleScheduledStart(configIndex);
SettingsViewModel.TimerSettings.TimerModels.Timers[configIndex].IsOn ??= false;
}
}
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: {CurrentConfiguration}, Scheduled Configuration: {TimerConfig}",
configIndex,
Instances.SettingsViewModel.CurrentConfiguration,
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")))
{
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);
}
ResetAllTemporaryVariable();
}
await LinkStart();
AchievementTrackerHelper.Instance.AddProgressToGroup(AchievementIds.ScheduleMasterGroup);
}
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);
};
_logger.Information("Timer wait time: {Seconds}", seconds);
await Task.WhenAny(Task.Delay(delay), tcs.Task);
dialog.Close();
_logger.Information("Timer canceled: {Canceled}", canceled);
return canceled;
}
}
///
/// Initializes items.
///
private void InitializeItems()
{
List taskList =
[
"WakeUp",
"Recruiting",
"Base",
"Combat",
"Mall",
"Mission",
"AutoRoguelike",
"Reclamation"
];
if (Instances.VersionUpdateViewModel.IsDebugVersion() || File.Exists("DEBUG") || File.Exists("DEBUG.txt"))
{
taskList.Add("Custom");
CanShowAutoReload = true;
}
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" or "Custom"));
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)
{
if (newVm == null)
{
continue;
}
int i = 0;
while (tempOrderList[i] != null)
{
++i;
}
tempOrderList[i] = newVm;
ConfigurationHelper.SetTaskOrder(newVm.OriginalName, i.ToString());
}
TaskItemViewModels = [.. tempOrderList.OfType()];
TaskItemViewModels.CollectionChanged += TaskItemSelectionChanged;
FightTask.InitDrops();
NeedToUpdateDatePrompt();
UpdateDatePromptAndStagesLocally();
InfrastTask.ParseCustomInfrastPlan();
if (DateTime.UtcNow.ToYjDate().IsAprilFoolsDay())
{
AddLog(LocalizationHelper.GetString("BuyWineOnAprilFoolsDay"), UiLogColor.Info);
}
}
public DayOfWeek CurDayOfWeek { get; private set; }
///
/// Determine whether the specified stage is open
///
/// stage name
/// Whether the specified stage is open
public bool IsStageOpen(string name) => Instances.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();
FightTask.UpdateStageList();
ToolboxViewModel.UpdateMiniGameTaskList();
}
///
/// 访问 api 获取更新后更新日期提示和关卡列表
///
/// 可等待
public async Task UpdateDatePromptAndStagesWeb()
{
await Instances.StageManager.UpdateStageWeb();
UpdateDatePromptAndStagesLocally();
}
private DateOnly _lastPromptDate;
private bool NeedToUpdateDatePrompt()
{
var now = DateTime.UtcNow.ToYjDateTime();
CurDayOfWeek = now.DayOfWeek;
// yj历的 4/16 点
var today = DateOnly.FromDateTime(now);
bool isCriticalTime = now is { Minute: 0, Hour: 0 or 12 };
bool isNewDate = today != _lastPromptDate;
if (!isCriticalTime && !isNewDate)
{
return false;
}
_lastPromptDate = today;
return true;
}
private static bool NeedToCheckForUpdates()
{
var now = DateTime.UtcNow.ToYjDateTime();
// yj历的 4/22 点
return now is { Minute: 0, Hour: 0 or 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 FightTask.Stages)
{
if (stage == null || Instances.StageManager.GetStageInfo(stage).IsActivityClosed() != true)
{
continue;
}
builder.Append(stage).Append(": ").AppendLine(LocalizationHelper.GetString("ClosedStage"));
}
// Open stages today
var openStages = Instances.StageManager.GetStageTips(CurDayOfWeek);
if (!string.IsNullOrEmpty(openStages))
{
builder.Append(openStages);
}
var prompt = builder.ToString();
if (StagesOfToday == prompt)
{
return;
}
StagesOfToday = prompt;
}
private string _stagesOfToday = string.Empty;
///
/// Gets or private sets the stages of today.
///
public string StagesOfToday
{
get => _stagesOfToday;
private set => SetAndNotify(ref _stagesOfToday, value);
}
public enum LogCardSplitMode
{
///
/// 不拆分日志卡片
///
None = 0,
///
/// 插入日志前拆分卡片
///
Before = 1,
///
/// 插入日志后拆分卡片
///
After = 2,
///
/// 插入日志前后都拆分卡片
///
Both = 3,
}
///
/// Adds log.
///
/// The content.
/// The font color.
/// The font weight.
/// The toolTip
/// Whether to update the containing card's image/thumbnail.
/// Whether to force fetching a fresh screenshot instead of using cache.
/// Whether to split cards before/after this log.
public void AddLog(string? content, string color = UiLogColor.Trace, string weight = "Regular", ToolTip? toolTip = null, bool updateCardImage = false, bool fetchLatestImage = false, LogCardSplitMode splitMode = LogCardSplitMode.None)
{
bool isEmpty = string.IsNullOrEmpty(content);
bool needsBeforeSplit = splitMode == LogCardSplitMode.Before || splitMode == LogCardSplitMode.Both;
bool needsAfterSplit = splitMode == LogCardSplitMode.After || splitMode == LogCardSplitMode.Both;
Execute.OnUIThread(() => {
if (needsBeforeSplit)
{
createNewCard();
}
// 确保至少有一个卡片(如果没有内容且不需要切割,也需要确保有卡片才能更新图片)
if (LogCardViewModels.Count <= 0 && (!isEmpty || updateCardImage))
{
createNewCard();
}
if (LogCardViewModels.Count > 0)
{
// 如果有内容,添加到卡片
if (!isEmpty)
{
TryMergeIntoLastCard(content!, color, weight, toolTip);
}
if (updateCardImage)
{
_ = AttachThumbnailToCardAsync(LogCardViewModels[^1], fetchLatestImage);
}
}
if (needsAfterSplit)
{
createNewCard();
}
});
// 记录日志
if (!isEmpty)
{
switch (color)
{
case UiLogColor.Error:
_logger.Error("{Content}", content);
break;
case UiLogColor.Warning:
_logger.Warning("{Content}", content);
break;
default:
_logger.Information("{Content}", content);
break;
}
}
}
private void createNewCard()
{
if (LogCardViewModels.Count > 0 && LogCardViewModels[^1].Items.Count <= 0)
{
return;
}
var card = new LogCardViewModel();
LogCardViewModels.Add(card);
}
///
/// Clears log.
///
private void ClearLog()
{
Execute.OnUIThread(() => {
LogItemViewModels.Clear();
LogCardViewModels.Clear();
DownloadLogItemViewModel = new(string.Empty);
_logger.Information("Main windows log clear.");
_logger.Information("{Empty}", string.Empty);
});
}
///
/// Update the dedicated download log area. Thread-safe and will run on UI thread.
/// Mirrors previous logic which updated the first download log entry.
///
/// The full text to show in download area.
/// Optional tooltip.
public void UpdateDownloadLog(string fullText, string? toolTip = null)
{
Execute.OnUIThread(() => {
// Keep download area limited to a single entry.
if (string.IsNullOrEmpty(fullText))
{
DownloadLogItemViewModel = new(string.Empty);
return;
}
var log = new LogItemViewModel(fullText, UiLogColor.Download, toolTip: toolTip?.CreateTooltip());
DownloadLogItemViewModel = log;
});
}
///
/// Selects all.
/// UI 绑定的方法
///
[UsedImplicitly]
public void SelectedAll()
{
foreach (var item in TaskItemViewModels)
{
switch (item.OriginalName)
{
case "AutoRoguelike":
case "Reclamation":
case "Custom":
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 绑定的方法
///
[UsedImplicitly]
public void ChangeInverseMode()
{
InverseMode = !InverseMode;
}
///
/// Selects inversely.
/// UI 绑定的方法
///
[UsedImplicitly]
public void InverseSelected()
{
if (InverseMode)
{
foreach (var item in TaskItemViewModels)
{
switch (item.OriginalName)
{
case "AutoRoguelike":
case "Reclamation":
case "Custom":
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 = GuiSettingsUserControlModel.Instance.MainTasksInvertNullFunction;
}
}
}
///
/// 还原所有临时变量(右键半选)
///
public void ResetAllTemporaryVariable()
{
FightTask.ResetFightVariables();
RecruitTask.ResetRecruitVariables();
ResetTaskSelection();
}
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 (_runningState.GetStopping())
{
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 (_runningState.GetStopping())
{
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 (_runningState.GetStopping())
{
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 (_runningState.GetStopping())
{
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);
}
}
private bool _canShowAutoReload;
public bool CanShowAutoReload
{
get => _canShowAutoReload;
set => SetAndNotify(ref _canShowAutoReload, value);
}
private bool _enableAutoReload;
public bool EnableAutoReload
{
get => _enableAutoReload;
set => SetAndNotify(ref _enableAutoReload, value);
}
private DateTime? _taskStartTime;
///
/// Starts.
///
/// Task
public async Task LinkStart()
{
AddLog(1.ToString());
return;
if (!_runningState.GetIdle())
{
_logger.Information("Not idle, return.");
return;
}
_taskStartTime = DateTime.Now;
ClearLog();
Instances.OverlayViewModel.LogItemsSource = LogItemViewModels;
var buildDateTimeLong = VersionUpdateSettingsUserControlModel.BuildDateTimeCurrentCultureString;
var resourceDateTimeLong = SettingsViewModel.VersionUpdateSettings.ResourceDateTimeCurrentCultureString;
AddLog($"Build Time:\n{buildDateTimeLong}\nResource Time:\n{resourceDateTimeLong}");
var buildTimeInterval = (DateTime.UtcNow - VersionUpdateSettingsUserControlModel.BuildDateTime).TotalDays;
var resourceTimeInterval = (DateTime.UtcNow - SettingsViewModel.VersionUpdateSettings.ResourceDateTime).TotalDays;
var maxTimeInterval = Math.Max(buildTimeInterval, resourceTimeInterval);
if (maxTimeInterval > 90)
{
AddLog(
string.Format(
LocalizationHelper.GetString("Achievement.Martian.ConditionsTip"),
Math.Round(maxTimeInterval / 30, 1)),
UiLogColor.Error);
}
var uiVersion = VersionUpdateSettingsUserControlModel.UiVersion;
var coreVersion = VersionUpdateSettingsUserControlModel.CoreVersion;
if (!Instances.VersionUpdateViewModel.IsDebugVersion() && uiVersion != coreVersion)
{
AddLog(string.Format(LocalizationHelper.GetString("VersionMismatch"), uiVersion, coreVersion), UiLogColor.Error);
return;
}
MainTasksCompletedCount = 0;
// 所有提前 return 都要放在 _runningState.SetIdle(false) 之前,否则会导致无法再次点击开始
_runningState.SetIdle(false);
// 虽然更改时已经保存过了,不过保险起见在点击开始之后再次保存任务和基建列表
TaskItemSelectionChanged();
InfrastTask.InfrastOrderSelectionChanged();
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 (_runningState.GetStopping())
{
SetStopped();
return;
}
if (!await ConnectToEmulator())
{
return;
}
// 一般是点了“停止”按钮了
if (_runningState.GetStopping())
{
SetStopped();
return;
}
bool taskRet = true;
// 直接遍历TaskItemViewModels里面的内容,是排序后的
int count = 0;
foreach (var item in TaskItemViewModels)
{
if (item.IsChecked == false || (GuiSettingsUserControlModel.Instance.MainTasksInvertNullFunction && item.IsCheckedWithNull == null))
{
continue;
}
++count;
switch (item.OriginalName)
{
case "Base":
taskRet &= AppendInfrast();
break;
case "WakeUp":
taskRet &= Instances.AsstProxy.AsstAppendTaskWithEncoding(AsstProxy.TaskType.StartUp, StartUpTask.Serialize());
break;
case "Combat":
taskRet &= AppendFight();
break;
case "Recruiting":
taskRet &= Instances.AsstProxy.AsstAppendTaskWithEncoding(AsstProxy.TaskType.Recruit, RecruitTask.Serialize());
break;
case "Mall":
taskRet &= Instances.AsstProxy.AsstAppendTaskWithEncoding(AsstProxy.TaskType.Mall, MallTask.Serialize());
break;
case "Mission":
taskRet &= Instances.AsstProxy.AsstAppendTaskWithEncoding(AsstProxy.TaskType.Award, AwardTask.Serialize());
break;
case "AutoRoguelike":
taskRet &= Instances.AsstProxy.AsstAppendTaskWithEncoding(AsstProxy.TaskType.Roguelike, RoguelikeTask.Serialize());
break;
case "Reclamation":
taskRet &= Instances.AsstProxy.AsstAppendTaskWithEncoding(AsstProxy.TaskType.Reclamation, ReclamationTask.Serialize());
break;
case "Custom":
{
var tasks = CustomTask.SerializeMultiTasks();
foreach (var (type, param) in tasks)
{
taskRet &= Instances.AsstProxy.AsstAppendTaskWithEncoding(AsstProxy.TaskType.Custom, type, param);
}
break;
}
default:
--count;
_logger.Error("Unknown task: " + item.OriginalName);
break;
}
if (taskRet)
{
continue;
}
AddLog($"{LocalizationHelper.GetString(item.OriginalName)} task append error", UiLogColor.Error);
taskRet = true;
--count;
}
if (count == 0)
{
AddLog(LocalizationHelper.GetString("UnselectedTask"));
_runningState.SetIdle(true);
Instances.AsstProxy.AsstStop();
SetStopped();
return;
}
AchievementTrackerHelper.Instance.SetProgress(AchievementIds.TaskChainKing, count);
taskRet &= Instances.AsstProxy.AsstStart();
if (taskRet)
{
AddLog(LocalizationHelper.GetString("Running"));
Instances.AsstProxy.StartTaskTime = DateTimeOffset.Now;
}
else
{
AddLog(LocalizationHelper.GetString("UnknownErrorOccurs"));
await Stop();
SetStopped();
}
AchievementTrackerHelper.Instance.MissionStartCountAdd();
AchievementTrackerHelper.Instance.UseDailyAdd();
}
public void ManualStop()
{
if (Stopping || _runningState.GetIdle())
{
_logger.Information("Already stopping or idle, return.");
return;
}
_ = Stop();
AchievementTrackerHelper.Instance.Unlock(AchievementIds.TacticalRetreat);
if (_taskStartTime is null)
{
return;
}
var duration = DateTime.Now - _taskStartTime.Value;
if (duration.TotalSeconds < 5)
{
AchievementTrackerHelper.Instance.Unlock(AchievementIds.TaskStartCancel);
}
}
///
/// 通常要和 一起使用,除非能保证回调消息能收到 `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)
{
_runningState.SetStopping(true);
AddLog(LocalizationHelper.GetString("Stopping"), splitMode: LogCardSplitMode.Both);
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 绑定的方法
[UsedImplicitly]
public async Task WaitAndStop()
{
Waiting = true;
AddLog(LocalizationHelper.GetString("Waiting"));
if (RoguelikeTask.RoguelikeDelayAbortUntilCombatComplete)
{
await WaitUntilRoguelikeCombatComplete();
if (Instances.AsstProxy.AsstRunning() && !_runningState.GetStopping())
{
await Stop();
}
}
}
///
/// 等待肉鸽战斗结束,10分钟强制退出
///
private async Task WaitUntilRoguelikeCombatComplete()
{
int time = 0;
while (RoguelikeTask.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() || _runningState.GetStopping())
{
AddLog(LocalizationHelper.GetString("Stopped"), splitMode: LogCardSplitMode.Both);
}
Waiting = false;
_runningState.SetStopping(false);
_runningState.SetIdle(true);
// 只抑制“本轮任务期间”的自动开启;任务结束后应允许下一轮自动开启 LiveView。
}
// 该函数将于未来被废弃,改用 LinkStart 代替
public async Task QuickSwitchAccount()
{
if (!_runningState.GetIdle())
{
return;
}
_runningState.SetIdle(false);
_taskStartTime = DateTime.Now; // 快速修复
// 虽然更改时已经保存过了,不过保险起见在点击开始之后再次保存任务和基建列表
TaskItemSelectionChanged();
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 (_runningState.GetStopping())
{
SetStopped();
return;
}
if (!await ConnectToEmulator())
{
return;
}
// 一般是点了“停止”按钮了
if (_runningState.GetStopping())
{
SetStopped();
return;
}
bool taskRet = true;
taskRet &= Instances.AsstProxy.AsstAppendTaskWithEncoding(AsstProxy.TaskType.StartUp, StartUpTask.Serialize());
taskRet &= Instances.AsstProxy.AsstStart();
if (taskRet)
{
AddLog(LocalizationHelper.GetString("Running"));
}
else
{
AddLog(LocalizationHelper.GetString("UnknownErrorOccurs"));
await Stop();
SetStopped();
}
}
public bool AppendFight()
{
string curStage = FightTask.Stage;
var (type, mainParam) = FightTask.Serialize();
bool mainFightRet = Instances.AsstProxy.AsstAppendTaskWithEncoding(AsstProxy.TaskType.Fight, type, mainParam);
if (!mainFightRet)
{
AddLog(LocalizationHelper.GetString("UnsupportedStages") + ": " + curStage, UiLogColor.Error);
return false;
}
if ((curStage == "Annihilation") && FightTask.UseAlternateStage)
{
foreach (var stage in FightTask.Stages)
{
if (stage is null || !IsStageOpen(stage) || (stage == curStage))
{
continue;
}
AddLog(LocalizationHelper.GetString("AnnihilationTaskTip"), UiLogColor.Info);
var task = mainParam.ToObject();
if (task != null)
{
task.Stage = stage;
task.Stone = 0;
task.MaxTimes = int.MaxValue;
task.Drops = [];
mainFightRet = Instances.AsstProxy.AsstAppendTaskWithEncoding(AsstProxy.TaskType.FightAnnihilationAlternate, type, task.Serialize().Params);
}
break;
}
}
if (mainFightRet && FightTask.UseRemainingSanityStage && !string.IsNullOrEmpty(FightTask.RemainingSanityStage))
{
var task = new AsstFightTask() {
Stage = FightTask.RemainingSanityStage,
MaxTimes = int.MaxValue,
Series = 0,
IsDrGrandet = FightTask.IsDrGrandet,
ReportToPenguin = SettingsViewModel.GameSettings.EnablePenguin,
ReportToYituliu = SettingsViewModel.GameSettings.EnableYituliu,
PenguinId = SettingsViewModel.GameSettings.PenguinId,
YituliuId = SettingsViewModel.GameSettings.PenguinId,
ServerType = Instances.SettingsViewModel.ServerType,
ClientType = SettingsViewModel.GameSettings.ClientType,
};
return Instances.AsstProxy.AsstAppendTaskWithEncoding(AsstProxy.TaskType.FightRemainingSanity, type, task.Serialize().Params);
}
return mainFightRet;
}
public bool EnableSetFightParams { get; set; } = true;
///
/// Sets parameters.
///
public void SetFightParams()
{
var type = AsstProxy.TaskType.Fight;
var id = Instances.AsstProxy.TasksStatus.FirstOrDefault(t => t.Value.Type == type).Key;
if (!EnableSetFightParams || id == 0)
{
return;
}
var taskParams = FightTask.Serialize().Params;
Instances.AsstProxy.AsstSetTaskParamsEncoded(id, taskParams);
}
public static void SetFightRemainingSanityParams()
{
var type = AsstProxy.TaskType.FightRemainingSanity;
var id = Instances.AsstProxy.TasksStatus.FirstOrDefault(t => t.Value.Type == type).Key;
if (id == 0)
{
return;
}
var task = new AsstFightTask() {
Stage = FightTask.RemainingSanityStage ?? string.Empty,
MaxTimes = int.MaxValue,
Series = 0,
IsDrGrandet = FightTask.IsDrGrandet,
ReportToPenguin = SettingsViewModel.GameSettings.EnablePenguin,
ReportToYituliu = SettingsViewModel.GameSettings.EnableYituliu,
PenguinId = SettingsViewModel.GameSettings.PenguinId,
YituliuId = SettingsViewModel.GameSettings.PenguinId,
ServerType = Instances.SettingsViewModel.ServerType,
ClientType = SettingsViewModel.GameSettings.ClientType,
};
var taskParams = task.Serialize().Params;
Instances.AsstProxy.AsstSetTaskParamsEncoded(id, taskParams);
}
public bool AppendInfrast()
{
if (InfrastTask.InfrastMode == InfrastMode.Custom && (!File.Exists(InfrastTask.CustomInfrastFile) || InfrastTask.CustomInfrastPlanList.Count == 0))
{
AddLog(LocalizationHelper.GetString("CustomizeInfrastSelectionEmpty"), UiLogColor.Error);
return false;
}
try
{
return Instances.AsstProxy.AsstAppendTaskWithEncoding(AsstProxy.TaskType.Infrast, InfrastTask.Serialize());
}
catch (Exception ex)
{
_logger.Error(ex, "AppendInfrast error");
AddLog("Append Infrast Error: " + ex.Message, UiLogColor.Error);
return false;
}
}
private bool _inited = false;
public bool Inited
{
get => _inited;
set => SetAndNotify(ref _inited, value);
}
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;
}
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
[UsedImplicitly]
get => _waiting;
private set => SetAndNotify(ref _waiting, 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 static IEnumerable InitTaskViewModelList()
{
var types = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(t => t is { Namespace: "MaaWpfGui.ViewModels.UserControl.TaskQueue", IsClass: true, IsAbstract: false } && t.IsSubclassOf(typeof(TaskViewModel)));
foreach (var type in types)
{
// 获取 Instance 字段
if (type.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static) is not { } property)
{
continue;
}
// 获取实例
if (property.GetValue(null) is TaskViewModel instance)
{
yield return instance;
}
}
}
public static void InvokeProcSubTaskMsg(AsstMsg msg, JObject details)
{
foreach (var instance in _taskViewModelTypes)
{
// 调用 ProcSubTaskMsg 方法
instance.ProcSubTaskMsg(msg, details);
}
}
}