// // 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.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.IO; using System.Linq; using JetBrains.Annotations; using MaaWpfGui.Constants; using MaaWpfGui.Constants.Enums; using MaaWpfGui.Helper; using MaaWpfGui.Main; using MaaWpfGui.Models; using MaaWpfGui.Models.AsstTasks; using MaaWpfGui.Services; using MaaWpfGui.States; using MaaWpfGui.Utilities; using MaaWpfGui.Utilities.ValueType; using Microsoft.Win32; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Serilog; namespace MaaWpfGui.ViewModels.UserControl.TaskQueue; using Mode = InfrastMode; /// /// 基建任务 /// public class InfrastSettingsUserControlModel : TaskViewModel { static InfrastSettingsUserControlModel() { Instance = new(); } public InfrastSettingsUserControlModel() { _runningState = RunningState.Instance; } public static InfrastSettingsUserControlModel Instance { get; } private static readonly ILogger _logger = Log.ForContext(); private readonly RunningState _runningState; /// /// Gets the visibility of task setting views. /// public static TaskSettingVisibilityInfo TaskSettingVisibilities => TaskSettingVisibilityInfo.Instance; public void InitInfrast() { var roomTypes = Enum.GetNames(typeof(InfrastRoomType)); var list = new List>(); var roomList = new List(roomTypes.Length); foreach (var item in roomTypes) { var index = ConfigurationHelper.GetValue("Infrast.Order." + item, -1); list.Add(new KeyValuePair(item, index)); } list.Sort((x, y) => x.Value.CompareTo(y.Value)); for (int i = 0; i < list.Count; ++i) { var item = list[i]; if (item.Value != i) { ConfigurationHelper.SetValue("Infrast.Order." + item.Key, i.ToString()); } roomList.Add(new DragItemViewModel(LocalizationHelper.GetString(item.Key), item.Key, "Infrast.")); } InfrastItemViewModels = new ObservableCollection(roomList); InfrastItemViewModels.CollectionChanged += InfrastOrderSelectionChanged; } /// /// Gets or sets the infrast item view models. /// public ObservableCollection InfrastItemViewModels { get; set; } = []; /// /// Gets the list of uses of drones. /// public List UsesOfDronesList { get; } = [ new() { Display = LocalizationHelper.GetString("DronesNotUse"), Value = "_NotUse" }, new() { Display = LocalizationHelper.GetString("Money"), Value = "Money" }, new() { Display = LocalizationHelper.GetString("SyntheticJade"), Value = "SyntheticJade" }, new() { Display = LocalizationHelper.GetString("CombatRecord"), Value = "CombatRecord" }, new() { Display = LocalizationHelper.GetString("PureGold"), Value = "PureGold" }, new() { Display = LocalizationHelper.GetString("OriginStone"), Value = "OriginStone" }, new() { Display = LocalizationHelper.GetString("Chip"), Value = "Chip" }, ]; /// /// Gets the list of uses of default infrast. /// public List DefaultInfrastList { get; } = [ new() { Display = LocalizationHelper.GetString("UserDefined"), Value = UserDefined }, new() { Display = LocalizationHelper.GetString("153Time3"), Value = "153_layout_3_times_a_day.json" }, new() { Display = LocalizationHelper.GetString("153Time4"), Value = "153_layout_4_times_a_day.json" }, new() { Display = LocalizationHelper.GetString("243Time3"), Value = "243_layout_3_times_a_day.json" }, new() { Display = LocalizationHelper.GetString("243Time4"), Value = "243_layout_4_times_a_day.json" }, new() { Display = LocalizationHelper.GetString("333Time3"), Value = "333_layout_for_Orundum_3_times_a_day.json" }, ]; private int _dormThreshold = Convert.ToInt32(ConfigurationHelper.GetValue(ConfigurationKeys.DormThreshold, "30")); /// /// Gets or sets the threshold to enter dormitory. /// public int DormThreshold { get => _dormThreshold; set { SetAndNotify(ref _dormThreshold, value); ConfigurationHelper.SetValue(ConfigurationKeys.DormThreshold, value.ToString()); } } /// /// Gets infrast order list. /// /// The infrast order list. public List GetInfrastOrderList() { return [.. InfrastItemViewModels.Where(i => i.IsChecked).Select(i => i.OriginalName)]; } // UI 绑定的方法 [UsedImplicitly] public void InfrastItemSelectedAll() { foreach (var item in InfrastItemViewModels) { item.IsChecked = true; } } // UI 绑定的方法 [UsedImplicitly] public void InfrastItemUnselectedAll() { foreach (var item in InfrastItemViewModels) { item.IsChecked = false; } } /// /// 实时更新基建换班顺序 /// /// ignored object /// ignored NotifyCollectionChangedEventArgs public void InfrastOrderSelectionChanged(object? sender = null, NotifyCollectionChangedEventArgs? e = null) { _ = (sender, e); int index = 0; foreach (var item in InfrastItemViewModels) { ConfigurationHelper.SetValue("Infrast.Order." + item.OriginalName, index.ToString()); ++index; } } /// /// Gets the list of uses of infrast mode. /// public List> InfrastModeList { get; } = [ new() { Display = LocalizationHelper.GetString("InfrastModeNormal"), Value = Mode.Normal }, new() { Display = LocalizationHelper.GetString("InfrastModeRotation"), Value = Mode.Rotation }, new() { Display = LocalizationHelper.GetString("InfrastModeCustom"), Value = Mode.Custom }, ]; // 5.16.0-b1,后续版本直接使用 ConfigurationHelper.GetValue(ConfigurationKeys.InfrastMode, Mode.Normal); private Mode _infrastMode = GetInfrastMode(); private static Mode GetInfrastMode() { if (ConfigurationHelper.ContainsKey(ConfigurationKeys.CustomInfrastEnabled) && ConfigurationHelper.DeleteValue(ConfigurationKeys.CustomInfrastEnabled, out string outStr) && bool.TryParse(outStr, out bool enable) && enable) { ConfigurationHelper.SetValue(ConfigurationKeys.InfrastMode, Mode.Custom.ToString()); return Mode.Custom; } return ConfigurationHelper.GetValue(ConfigurationKeys.InfrastMode, Mode.Normal); } /// /// Gets or sets the infrast mode. /// public Mode InfrastMode { get => _infrastMode; set { if (!SetAndNotify(ref _infrastMode, value)) { return; } ConfigurationHelper.SetValue(ConfigurationKeys.InfrastMode, value.ToString()); RefreshCustomInfrastPlan(); NotifyOfPropertyChange(nameof(CustomInfrastPlanIndex)); } } private string _usesOfDrones = ConfigurationHelper.GetValue(ConfigurationKeys.UsesOfDrones, "Money"); /// /// Gets or sets the uses of drones. /// public string UsesOfDrones { get => _usesOfDrones; set { SetAndNotify(ref _usesOfDrones, value); ConfigurationHelper.SetValue(ConfigurationKeys.UsesOfDrones, value); } } private bool _receptionMessageBoardReceive = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.InfrastReceptionMessageBoardReceive, bool.TrueString)); public bool ReceptionMessageBoardReceive { get => _receptionMessageBoardReceive; set { SetAndNotify(ref _receptionMessageBoardReceive, value); ConfigurationHelper.SetValue(ConfigurationKeys.InfrastReceptionMessageBoardReceive, value.ToString()); } } private bool _receptionClueExchange = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.InfrastReceptionClueExchange, bool.TrueString)); public bool ReceptionClueExchange { get => _receptionClueExchange; set { SetAndNotify(ref _receptionClueExchange, value); ConfigurationHelper.SetValue(ConfigurationKeys.InfrastReceptionClueExchange, value.ToString()); } } private bool _continueTraining = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.ContinueTraining, bool.FalseString)); /// /// Gets or sets a value indicating whether to continue training after current training completed. /// public bool ContinueTraining { get => _continueTraining; set { SetAndNotify(ref _continueTraining, value); ConfigurationHelper.SetValue(ConfigurationKeys.ContinueTraining, value.ToString()); } } private string _defaultInfrast = ConfigurationHelper.GetValue(ConfigurationKeys.DefaultInfrast, UserDefined); public const string UserDefined = "user_defined"; /// /// Gets or sets the uses of drones. /// public string DefaultInfrast { get => _defaultInfrast; set { SetAndNotify(ref _defaultInfrast, value); if (_defaultInfrast != UserDefined) { CustomInfrastFile = Path.Combine(PathsHelper.ResourceDir, "custom_infrast", value); } ConfigurationHelper.SetValue(ConfigurationKeys.DefaultInfrast, value); } } [PropertyDependsOn(nameof(DefaultInfrast))] public bool IsCustomInfrastFileReadOnly => _defaultInfrast != UserDefined; private bool _dormFilterNotStationedEnabled = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.DormFilterNotStationedEnabled, bool.TrueString)); /// /// Gets or sets a value indicating whether the not stationed filter in dorm is enabled. /// public bool DormFilterNotStationedEnabled { get => _dormFilterNotStationedEnabled; set { SetAndNotify(ref _dormFilterNotStationedEnabled, value); ConfigurationHelper.SetValue(ConfigurationKeys.DormFilterNotStationedEnabled, value.ToString()); } } private bool _dormTrustEnabled = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.DormTrustEnabled, bool.FalseString)); /// /// Gets or sets a value indicating whether trust in dorm is enabled. /// public bool DormTrustEnabled { get => _dormTrustEnabled; set { SetAndNotify(ref _dormTrustEnabled, value); ConfigurationHelper.SetValue(ConfigurationKeys.DormTrustEnabled, value.ToString()); } } private bool _originiumShardAutoReplenishment = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.OriginiumShardAutoReplenishment, bool.TrueString)); /// /// Gets or sets a value indicating whether Originium shard auto replenishment is enabled. /// public bool OriginiumShardAutoReplenishment { get => _originiumShardAutoReplenishment; set { SetAndNotify(ref _originiumShardAutoReplenishment, value); ConfigurationHelper.SetValue(ConfigurationKeys.OriginiumShardAutoReplenishment, value.ToString()); } } /// /// Selects infrast config file. /// UI 绑定的方法 /// [UsedImplicitly] public void SelectCustomInfrastFile() { var dialog = new OpenFileDialog { Filter = LocalizationHelper.GetString("CustomInfrastFile") + "|*.json", }; if (dialog.ShowDialog() == true) { CustomInfrastFile = dialog.FileName; } DefaultInfrast = UserDefined; } private string _customInfrastFile = ConfigurationHelper.GetValue(ConfigurationKeys.CustomInfrastFile, string.Empty); public string CustomInfrastFile { get => _customInfrastFile; set { SetAndNotify(ref _customInfrastFile, value); ConfigurationHelper.SetValue(ConfigurationKeys.CustomInfrastFile, value); RefreshCustomInfrastPlan(); // SetAndNotify 在值没有变化时不会触发 PropertyChanged 事件,所以这里手动触发一下 NeedAddCustomInfrastPlanInfo = false; NotifyOfPropertyChange(nameof(CustomInfrastPlanIndex)); NeedAddCustomInfrastPlanInfo = true; } } 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}", value); } if (value != _customInfrastPlanIndex && NeedAddCustomInfrastPlanInfo) { var plan = CustomInfrastPlanInfoList[value]; Instances.TaskQueueViewModel.AddLog(plan.Name, UiLogColor.Message); foreach (var period in plan.PeriodList) { Instances.TaskQueueViewModel.AddLog($"[ {period.BeginHour:D2}:{period.BeginMinute:D2} - {period.EndHour:D2}:{period.EndMinute:D2} ]"); } if (plan.Description != string.Empty) { Instances.TaskQueueViewModel.AddLog(plan.Description); } } SetAndNotify(ref _customInfrastPlanIndex, value); ConfigurationHelper.SetValue(ConfigurationKeys.CustomInfrastPlanIndex, value.ToString()); } } public ObservableCollection> CustomInfrastPlanList { get; } = []; 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 } public List CustomInfrastPlanInfoList { get; } = []; private bool _customInfrastPlanHasPeriod; private bool _customInfrastInfoOutput; public void RefreshCustomInfrastPlan() { CustomInfrastPlanInfoList.Clear(); CustomInfrastPlanList.Clear(); _customInfrastPlanHasPeriod = false; if (InfrastMode != Mode.Custom) { return; } if (!File.Exists(CustomInfrastFile)) { return; } try { string jsonStr = File.ReadAllText(CustomInfrastFile); var root = (JObject?)JsonConvert.DeserializeObject(jsonStr); if (root != null && _customInfrastInfoOutput && root.TryGetValue("title", out var title)) { Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetString("CustomInfrastTitle"), UiLogColor.Message); Instances.TaskQueueViewModel.AddLog($"title: {title}", UiLogColor.Info); if (root.TryGetValue("description", out var value)) { Instances.TaskQueueViewModel.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) { Instances.TaskQueueViewModel.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); var beginTime = periodJson[0]?.ToString(); if (beginTime != null) { var beginSplit = beginTime.Split(':'); period.BeginHour = int.Parse(beginSplit[0]); period.BeginMinute = int.Parse(beginSplit[1]); } var 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) { Instances.TaskQueueViewModel.AddLog($"[ {period.BeginHour:D2}:{period.BeginMinute:D2} - {period.EndHour:D2}:{period.EndMinute:D2} ]"); } } if (periodList.Count != 0) { _customInfrastPlanHasPeriod = true; } } if (_customInfrastInfoOutput && desc != string.Empty) { Instances.TaskQueueViewModel.AddLog(desc); } if (_customInfrastInfoOutput && descPost != string.Empty) { Instances.TaskQueueViewModel.AddLog(descPost); } CustomInfrastPlanInfoList.Add(new CustomInfrastPlanInfo { Index = i, Name = display, Description = desc, DescriptionPost = descPost, PeriodList = periodList, }); } } _customInfrastInfoOutput = true; } catch (Exception) { _customInfrastInfoOutput = true; Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetString("CustomInfrastFileParseFailed"), UiLogColor.Error); return; } RefreshCustomInfrastPlanIndexByPeriod(); } public void RefreshCustomInfrastPlanIndexByPeriod() { if (InfrastMode != Mode.Custom || !_customInfrastPlanHasPeriod || CustomInfrastPlanInfoList.Count == 0) { return; } if (!_runningState.GetIdle() && Instances.AsstProxy.TasksStatus.FirstOrDefault(i => i.Value.Type == AsstProxy.TaskType.Infrast).Value.Status != TaskStatus.Completed) { return; } var now = DateTime.Now; if (CustomInfrastPlanIndex >= CustomInfrastPlanInfoList.Count || CustomInfrastPlanIndex < 0) { CustomInfrastPlanIndex = 0; } var currentPlan = CustomInfrastPlanInfoList.First(p => p.Index == CustomInfrastPlanIndex); foreach (var period in currentPlan.PeriodList) { if (TimeLess(period.BeginHour, period.BeginMinute, now.Hour, now.Minute) && TimeLess(now.Hour, now.Minute, period.EndHour, period.EndMinute)) { return; // 当前 index 仍在有效时间内,不需要切换 } } 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; } return; static bool TimeLess(int lHour, int lMin, int rHour, int rMin) => (lHour != rHour) ? (lHour < rHour) : (lMin <= rMin); } public void IncreaseCustomInfrastPlanIndex() { if (InfrastMode != Mode.Custom || _customInfrastPlanHasPeriod || CustomInfrastPlanInfoList.Count == 0) { return; } Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetString("CustomInfrastPlanIndexAutoSwitch"), UiLogColor.Message); var prePlanPostDesc = CustomInfrastPlanInfoList[CustomInfrastPlanIndex].DescriptionPost; if (prePlanPostDesc != string.Empty) { Instances.TaskQueueViewModel.AddLog(prePlanPostDesc); } ++CustomInfrastPlanIndex; } public override (AsstTaskType Type, JObject Params) Serialize() { return new AsstInfrastTask { Mode = InfrastMode, Facilitys = GetInfrastOrderList(), UsesOfDrones = UsesOfDrones, ContinueTraining = ContinueTraining, DormThreshold = DormThreshold / 100.0, DormFilterNotStationedEnabled = DormFilterNotStationedEnabled, DormTrustEnabled = DormTrustEnabled, OriginiumShardAutoReplenishment = OriginiumShardAutoReplenishment, ReceptionMessageBoard = ReceptionMessageBoardReceive, ReceptionClueExchange = ReceptionClueExchange, Filename = CustomInfrastFile, PlanIndex = CustomInfrastPlanIndex, }.Serialize(); } } public enum InfrastMode { /// /// 普通 /// Normal, /// /// 自定义 /// Custom = 10000, /// /// 轮换 /// Rotation = 20000, }