feat: 理智作战支持设定目标材料最大库存 (#16487)

* feat: 理智作战支持设定目标材料最大库存

* chore: 调整翻译绑定,修改运行时 IsEnable,移除无用属性

* chore: 调整描述

* chore: 调整缩进

* perf: 移除不必要的变更判断

* feat: 任务开始时刷新库存

* rft: rename

* chore: 运行时不刷新仓库数据,仅在进入任务时刷新一次

---------

Co-authored-by: status102 <102887808+status102@users.noreply.github.com>
This commit is contained in:
uye
2026-05-27 00:28:56 +08:00
committed by GitHub
parent 753547b5dd
commit 5efdfa1b5c
11 changed files with 587 additions and 93 deletions

View File

@@ -64,6 +64,11 @@ public class FightTask : BaseTask, IJsonOnDeserialized
/// </summary>
public int DropCount { get; set; }
/// <summary>
/// Gets or sets a value indicating whether 指定材料按库存目标计算
/// </summary>
public bool IsInventoryTarget { get; set; }
/// <summary>
/// Gets or sets 是否启用次数限制
/// </summary>

View File

@@ -2873,9 +2873,9 @@ public class AsstProxy
public IReadOnlyDictionary<AsstTaskId, (TaskType Type, TaskStatus Status)> TasksStatus => new Dictionary<AsstTaskId, (TaskType, TaskStatus)>(_tasksStatus);
public delegate void TaskItemStatusDelegate(int taskId, TaskItemStatus status);
public delegate void TaskStatusDelegate(int taskId, TaskItemStatus status);
public event TaskItemStatusDelegate? OnTaskItemStatusChanged;
public event TaskStatusDelegate? OnTaskStatusChanged;
private bool UpdateTaskStatus(AsstTaskId id, TaskStatus status)
{
@@ -2896,7 +2896,7 @@ public class AsstProxy
}
_tasksStatus[id] = (value.Type, status);
OnTaskItemStatusChanged?.Invoke(id, (TaskItemStatus)status);
OnTaskStatusChanged?.Invoke(id, (TaskItemStatus)status);
if (status == TaskStatus.InProgress)
{
TaskSettingVisibilityInfo.Instance.NotifyOfTaskStatus();

View File

@@ -322,7 +322,11 @@ To customize rotation schedules, please use 「{key=InfrastModeCustom}」.</syst
<system:String x:Key="CheckBoxesNotSaved">This option is effective once</system:String>
<system:String x:Key="CheckBoxesNotSavedAsNull">This option only takes effect once when right-clicked</system:String>
<system:String x:Key="CheckBoxesNotSavedAsNullInvert">This option only skips once when right-clicked</system:String>
<system:String x:Key="SpecifiedDropsTip">This feature would not automatically calculate the optimal stage</system:String>
<system:String x:Key="SpecifiedDropsTip" xml:space="preserve">The stage entered is still determined by the options below: 「{key=StageSelect}」 / 「{key=StageSelect2}」.
• {key=Quantity}: uses the selected material's drop count in this combat task as the target, and stops when the set amount is reached.
• {key=TargetInventory}: uses the saved inventory data from 「{key=Toolbox}-{key=DepotRecognition}」 and only farms until the target stock is reached.
• Inventory data is cached and may differ from your real stock after manual farming, crafting, or material use. Sync it with 「{key=UserDataUpdate}」 or 「{key=Toolbox}-{key=DepotRecognition}」.
• When {key=TargetInventory} is enabled, the material and amount cannot be changed during a run.</system:String>
<system:String x:Key="StartupUpdateCheck">Startup Update Check</system:String>
<system:String x:Key="UpdateAutoCheck">Scheduled Update Check</system:String>
<system:String x:Key="UpdateAutoCheckTip">In-game 00:00 / 18:00 (Server local time 04:00 / 22:00)</system:String>
@@ -775,7 +779,9 @@ Right-clicking other options will check/uncheck this option simultaneously</syst
<system:String x:Key="UseOriginitePrime">Use Originium</system:String>
<system:String x:Key="PerformBattles">Perform Battles</system:String>
<system:String x:Key="AssignedMaterial">Material</system:String>
<system:String x:Key="Quantity">Quantity</system:String>
<system:String x:Key="Quantity">Farm</system:String>
<system:String x:Key="KeepInventory">Keep Inventory</system:String>
<system:String x:Key="TargetInventory">Goal</system:String>
<system:String x:Key="Series">Series</system:String>
<system:String x:Key="SeriesTip" xml:space="preserve">• AUTO:
Automatically identify the maximum proxy multiplier of the level, maintain the maximum proxy multiplier and do not overflow sanity after using sanity potion
@@ -805,6 +811,7 @@ Do not adjust the proxy multiplier setting in the game</system:String>
<system:String x:Key="DaysLeftOpen" xml:space="preserve">Days left open:&#160;</system:String>
<system:String x:Key="Inventory">Inventory</system:String>
<system:String x:Key="InventoryUpdateTip">Inventory data can be updated via 「{key=Toolbox}-{key=DepotRecognition}」</system:String>
<system:String x:Key="SpecifiedDropsInventoryUnavailable">Task "{0}" uses {key=TargetInventory}, but no inventory data is currently available, so it was skipped. Update it via 「{key=Toolbox}-{key=DepotRecognition}」.</system:String>
<system:String x:Key="LessThanOneDay">Less than 1 day</system:String>
<system:String x:Key="1-7">1-7</system:String>
<system:String x:Key="CE-6">CE-6</system:String>

View File

@@ -322,7 +322,11 @@
<system:String x:Key="CheckBoxesNotSaved">このオプションは一度有効です</system:String>
<system:String x:Key="CheckBoxesNotSavedAsNull">このオプションは、右クリックで選択した時のみ一度だけ有効です</system:String>
<system:String x:Key="CheckBoxesNotSavedAsNullInvert">このオプションは、右クリックで選択した時のみ一度だけスキップします</system:String>
<system:String x:Key="SpecifiedDropsTip">この機能では最適なステージを自動的に計算しません</system:String>
<system:String x:Key="SpecifiedDropsTip" xml:space="preserve">実際に入るステージは、引き続き下の 「{key=StageSelect}」 / 「{key=StageSelect2}」 で決まります。
• {key=Quantity}: この理性作戦タスク中で、その素材のドロップ数が設定値に達するまで周回します。
• {key=TargetInventory}: 「{key=Toolbox}-{key=DepotRecognition}」 に保存された在庫データを参照し、設定した在庫数になるまで補充します。
• 在庫データはキャッシュのため、手動周回や素材の使用後は実際とずれる場合があります。「{key=UserDataUpdate}」 または 「{key=Toolbox}-{key=DepotRecognition}」 で同期してください。
• {key=TargetInventory} を有効にすると、実行中は素材や数量を変更できません。</system:String>
<system:String x:Key="StartupUpdateCheck">起動時の更新チェック</system:String>
<system:String x:Key="UpdateAutoCheck">定期的な更新チェック</system:String>
<system:String x:Key="UpdateAutoCheckTip">ゲーム内 00:00 / 18:00サーバー現地時間 04:00 / 22:00</system:String>
@@ -777,6 +781,8 @@ C:\\leidian\\LDPlayer9
<system:String x:Key="PerformBattles">周回数指定</system:String>
<system:String x:Key="AssignedMaterial">素材の指定</system:String>
<system:String x:Key="Quantity">ドロップ数</system:String>
<system:String x:Key="KeepInventory">在庫を維持</system:String>
<system:String x:Key="TargetInventory">目標在庫</system:String>
<system:String x:Key="Series">連戦回数</system:String>
<system:String x:Key="SeriesTip" xml:space="preserve">• 自動:
レベルの最大代理乗数を自動で判別し、最大代理乗数を維持し、正気ポーション使用後に正気度オーバーフローが発生しないようにします。
@@ -806,6 +812,7 @@ C:\\leidian\\LDPlayer9
<system:String x:Key="DaysLeftOpen" xml:space="preserve">公開日まで: </system:String>
<system:String x:Key="Inventory">在庫</system:String>
<system:String x:Key="InventoryUpdateTip">在庫データは 「{key=Toolbox}-{key=DepotRecognition}」 で更新できます</system:String>
<system:String x:Key="SpecifiedDropsInventoryUnavailable">タスク「{0}」では {key=TargetInventory} が有効ですが、利用可能な在庫データがないためスキップされました。「{key=Toolbox}-{key=DepotRecognition}」 で在庫を更新してください。</system:String>
<system:String x:Key="LessThanOneDay">1日以内</system:String>
<system:String x:Key="1-7">1-7</system:String>
<system:String x:Key="CE-6">龍門幣-6(CE-6)</system:String>

View File

@@ -322,7 +322,11 @@
<system:String x:Key="CheckBoxesNotSaved">이 옵션은 한 번만 유효합니다</system:String>
<system:String x:Key="CheckBoxesNotSavedAsNull">이 옵션은 우클릭 시 한 번만 적용됩니다</system:String>
<system:String x:Key="CheckBoxesNotSavedAsNullInvert">이 옵션은 우클릭 시 한 번만 건너뜁니다</system:String>
<system:String x:Key="SpecifiedDropsTip">이 기능은 최적의 스테이지를 자동으로 계산하지 않습니다</system:String>
<system:String x:Key="SpecifiedDropsTip" xml:space="preserve">실제로 진입하는 스테이지는 아래의 「{key=StageSelect}」 / 「{key=StageSelect2}」 설정으로 계속 결정됩니다.
• {key=Quantity}: 현재 전투 작업에서 해당 재료의 드롭 수를 목표로 삼아, 설정한 수량에 도달하면 멈춥니다.
• {key=TargetInventory}: 「{key=Toolbox}-{key=DepotRecognition}」 에 저장된 재고 데이터를 참고하여 설정한 재고 수량까지만 보충합니다.
• 재고 데이터는 캐시이므로 수동 파밍, 제작, 재료 사용 후 실제 수량과 달라질 수 있습니다. 「{key=UserDataUpdate}」 또는 「{key=Toolbox}-{key=DepotRecognition}」 에서 동기화해 주세요.
• {key=TargetInventory} 사용 중에는 실행 도중 재료와 수량을 바꿀 수 없습니다.</system:String>
<system:String x:Key="StartupUpdateCheck">시작 시 업데이트 확인</system:String>
<system:String x:Key="UpdateAutoCheck">일정 시간마다 업데이트 확인</system:String>
<system:String x:Key="UpdateAutoCheckTip">게임 내 00:00 / 18:00 (서버 현지 시간 04:00 / 22:00)</system:String>
@@ -778,6 +782,8 @@ C:\\leidian\\LDPlayer9
<system:String x:Key="PerformBattles">횟수 제한</system:String>
<system:String x:Key="AssignedMaterial">드롭 제한</system:String>
<system:String x:Key="Quantity">드롭 수</system:String>
<system:String x:Key="KeepInventory">재고 유지</system:String>
<system:String x:Key="TargetInventory">목표 재고</system:String>
<system:String x:Key="Series">연속 전투</system:String>
<system:String x:Key="SeriesTip" xml:space="preserve">• AUTO:
스테이지의 가능한 최대 대리 지휘 횟수의 배수를 감지하고, 자동으로 최대 대리 지휘 횟수를 유지하며, 이성 회복제 사용 후 이성이 남지 않도록 합니다.
@@ -807,6 +813,7 @@ C:\\leidian\\LDPlayer9
<system:String x:Key="DaysLeftOpen" xml:space="preserve">남은 오픈일 수: </system:String>
<system:String x:Key="Inventory">재고</system:String>
<system:String x:Key="InventoryUpdateTip">재고 데이터는 「{key=Toolbox}-{key=DepotRecognition}」 을 통해 업데이트 가능</system:String>
<system:String x:Key="SpecifiedDropsInventoryUnavailable">작업 "{0}" 에서 {key=TargetInventory} 가 켜져 있지만 현재 사용할 수 있는 재고 데이터가 없어 건너뜁니다. 「{key=Toolbox}-{key=DepotRecognition}」 에서 재고를 업데이트해 주세요.</system:String>
<system:String x:Key="LessThanOneDay">1일 미만</system:String>
<system:String x:Key="1-7">1-7</system:String>
<system:String x:Key="CE-6">CE-6 (용문폐)</system:String>

View File

@@ -322,7 +322,11 @@
<system:String x:Key="CheckBoxesNotSaved">该选项仅生效一次</system:String>
<system:String x:Key="CheckBoxesNotSavedAsNull">该选项右键选中时仅生效一次</system:String>
<system:String x:Key="CheckBoxesNotSavedAsNullInvert">该选项右键选中时仅跳过一次</system:String>
<system:String x:Key="SpecifiedDropsTip">该选项不会自动计算最优关卡</system:String>
<system:String x:Key="SpecifiedDropsTip" xml:space="preserve">进入的关卡仍由下方 「{key=StageSelect}」 / 「{key=StageSelect2}」 决定。
• {key=Quantity}:以当前理智作战任务中该材料的掉落数量为目标,达到设定数量后停止。
• {key=TargetInventory}:会参考 「{key=Toolbox}-{key=DepotRecognition}」 中保存的库存数据,只补到设定库存为止。
• 库存数据是缓存值,手动刷关、合成或消耗材料后可能与实际不一致;可通过 「{key=UserDataUpdate}」 或 「{key=Toolbox}-{key=DepotRecognition}」 同步。
• 使用 {key=TargetInventory} 时,开始后不能临时修改材料和数量。</system:String>
<system:String x:Key="StartupUpdateCheck">启动时检查更新</system:String>
<system:String x:Key="UpdateAutoCheck">定时检查更新</system:String>
<system:String x:Key="UpdateAutoCheckTip">游戏内 00:00 / 18:00对应服务器当地时间 04:00 / 22:00</system:String>
@@ -777,6 +781,8 @@ C:\\leidian\\LDPlayer9。\n
<system:String x:Key="PerformBattles">指定次数</system:String>
<system:String x:Key="AssignedMaterial">指定材料</system:String>
<system:String x:Key="Quantity">刷取数量</system:String>
<system:String x:Key="KeepInventory">保持库存</system:String>
<system:String x:Key="TargetInventory">目标库存</system:String>
<system:String x:Key="Series">代理倍率</system:String>
<system:String x:Key="SeriesTip" xml:space="preserve">• AUTO:
自动识别关卡最大代理倍率, 保持最大代理倍率且使用理智药后理智不溢出
@@ -806,6 +812,7 @@ C:\\leidian\\LDPlayer9。\n
<system:String x:Key="DaysLeftOpen" xml:space="preserve">剩余天数: </system:String>
<system:String x:Key="Inventory">库存</system:String>
<system:String x:Key="InventoryUpdateTip">可通过 「{key=Toolbox}-{key=DepotRecognition}」 更新库存数据</system:String>
<system:String x:Key="SpecifiedDropsInventoryUnavailable">任务「{0}」启用了 {key=TargetInventory},但当前没有可用的库存数据,已跳过。可通过 「{key=Toolbox}-{key=DepotRecognition}」 更新库存。</system:String>
<system:String x:Key="LessThanOneDay">不到 1 天</system:String>
<system:String x:Key="1-7">1-7</system:String>
<system:String x:Key="CE-6">龙门币-6/5</system:String>

View File

@@ -322,7 +322,11 @@
<system:String x:Key="CheckBoxesNotSaved">該選項僅生效一次</system:String>
<system:String x:Key="CheckBoxesNotSavedAsNull">該選項點擊右鍵選取時僅生效一次</system:String>
<system:String x:Key="CheckBoxesNotSavedAsNullInvert">該選項點擊右鍵選取時僅跳過一次</system:String>
<system:String x:Key="SpecifiedDropsTip">此選項不會自動計算最優關卡</system:String>
<system:String x:Key="SpecifiedDropsTip" xml:space="preserve">實際進入的關卡仍由下方 「{key=StageSelect}」 / 「{key=StageSelect2}」 決定。
• {key=Quantity}:以目前理智作戰任務中該材料的掉落數量為目標,達到設定數量後停止。
• {key=TargetInventory}:會參考 「{key=Toolbox}-{key=DepotRecognition}」 中保存的庫存資料,只補到設定庫存為止。
• 庫存資料是快取值,手動刷關、合成或消耗材料後可能與實際不一致;可透過 「{key=UserDataUpdate}」 或 「{key=Toolbox}-{key=DepotRecognition}」 同步。
• 使用 {key=TargetInventory} 時,開始後不能臨時修改材料與數量。</system:String>
<system:String x:Key="StartupUpdateCheck">啟動時檢查更新</system:String>
<system:String x:Key="UpdateAutoCheck">定時檢查更新</system:String>
<system:String x:Key="UpdateAutoCheckTip">遊戲內 00:00 / 18:00對應伺服器當地時間 04:00 / 22:00</system:String>
@@ -777,6 +781,8 @@ C:\\leidian\\LDPlayer9\n
<system:String x:Key="PerformBattles">指定次數</system:String>
<system:String x:Key="AssignedMaterial">指定材料</system:String>
<system:String x:Key="Quantity">刷取數量</system:String>
<system:String x:Key="KeepInventory">保持庫存</system:String>
<system:String x:Key="TargetInventory">目標庫存</system:String>
<system:String x:Key="Series">代理倍率</system:String>
<system:String x:Key="SeriesTip" xml:space="preserve">• AUTO:
自動辨識關卡最大代理倍率,保持最大代理倍率且使用理智藥後理智不溢出
@@ -806,6 +812,7 @@ C:\\leidian\\LDPlayer9\n
<system:String x:Key="DaysLeftOpen" xml:space="preserve">剩餘天數: </system:String>
<system:String x:Key="Inventory">庫存</system:String>
<system:String x:Key="InventoryUpdateTip">可透過 「{key=Toolbox}-{key=DepotRecognition}」 更新庫存數據</system:String>
<system:String x:Key="SpecifiedDropsInventoryUnavailable">任務「{0}」啟用了 {key=TargetInventory},但目前沒有可用的庫存資料,已跳過。可透過 「{key=Toolbox}-{key=DepotRecognition}」 更新庫存。</system:String>
<system:String x:Key="LessThanOneDay">不到 1 天</system:String>
<system:String x:Key="1-7">1-7</system:String>
<system:String x:Key="CE-6">龍門幣-6/5</system:String>

View File

@@ -28,7 +28,7 @@ public class TaskItemViewModel : PropertyChangedBase, IDisposable
{
_name = name;
_isEnable = isCheckedWithNull;
Instances.AsstProxy.OnTaskItemStatusChanged += OnTaskStatusChanged;
Instances.AsstProxy.OnTaskStatusChanged += OnTaskStatusChanged;
}
private string _name;
@@ -130,5 +130,5 @@ public class TaskItemViewModel : PropertyChangedBase, IDisposable
}
}
void IDisposable.Dispose() => Instances.AsstProxy.OnTaskItemStatusChanged -= OnTaskStatusChanged;
void IDisposable.Dispose() => Instances.AsstProxy.OnTaskStatusChanged -= OnTaskStatusChanged;
}

View File

@@ -15,7 +15,9 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using JetBrains.Annotations;
@@ -27,6 +29,7 @@ using MaaWpfGui.Extensions;
using MaaWpfGui.Helper;
using MaaWpfGui.Models;
using MaaWpfGui.Models.AsstTasks;
using MaaWpfGui.States;
using MaaWpfGui.Utilities;
using MaaWpfGui.Utilities.ValueType;
using MaaWpfGui.ViewModels.UI;
@@ -44,6 +47,9 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
{
public const string AnnihilationName = "Annihilation";
private static readonly ILogger _logger = Log.ForContext<FightSettingsUserControlModel>();
private readonly RunningState _runningState;
private readonly Lock _inventoryTargetRuntimeStateLock = new();
private Dictionary<int, InventoryTargetRuntimeState> _inventoryTargetRuntimeStateByTaskId = [];
public static FightTimes? FightReport { get; set; }
@@ -56,6 +62,15 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
public FightSettingsUserControlModel()
{
_runningState = RunningState.Instance;
_runningState.StateChanged += OnRunningStateChanged;
Instances.AsstProxy.OnTaskStatusChanged += OnTaskStatusChanged;
if (Instances.ToolboxViewModel is { } toolboxViewModel)
{
toolboxViewModel.DepotResult.CollectionChanged += OnDepotResultCollectionChanged;
}
foreach (var i in WeeklyScheduleSource)
{
i.PropertyChanged += (_, __) => SaveWeeklySchedule();
@@ -66,15 +81,92 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
InitDrops();
}
/// <summary>
/// 当队列进入或离开空闲状态时,清理按任务保存的目标库存运行时状态。
/// </summary>
private void OnRunningStateChanged(object? sender, RunningState.RunningStateChangedEventArgs e)
{
if (e.NewState.Idle == e.OldState.Idle)
{
return;
}
ResetInventoryTargetRuntimeState();
}
/// <summary>
/// 重置各作战任务启动时捕获的目标库存运行时缓存。
/// </summary>
private void ResetInventoryTargetRuntimeState()
{
lock (_inventoryTargetRuntimeStateLock)
{
_inventoryTargetRuntimeStateByTaskId = [];
}
Execute.OnUIThread(NotifySpecifiedDropsStateChanged);
}
/// <summary>
/// 当作战任务进入进行中状态时,仅捕获一次目标库存值,并在需要时刷新当前选中的面板。
/// </summary>
private void OnTaskStatusChanged(int taskId, TaskItemStatus status)
{
if (status != TaskItemStatus.InProgress || taskId <= 0)
{
return;
}
if (GetFightTaskByTaskId(taskId) is { } startedFight &&
IsInventoryTargetDropEnabled(startedFight) &&
GetInventoryTargetRuntimeState(taskId) == null)
{
SerializeTask(startedFight, taskId);
}
Execute.OnUIThread(() => {
if (TaskSettingVisibilityInfo.CurrentTask is not FightTask currentFight || !IsInventoryTargetDropEnabled(currentFight))
{
return;
}
int currentIndex = TaskSettingVisibilityInfo.Instance.CurrentIndex;
if (currentIndex < 0 || currentIndex >= Instances.TaskQueueViewModel.TaskItemViewModels.Count)
{
return;
}
if (!Instances.TaskQueueViewModel.TaskItemViewModels[currentIndex].TaskIds.Contains(taskId))
{
return;
}
NotifySpecifiedDropsStateChanged();
});
}
/// <summary>
/// 当仓库识别数据变化时,刷新界面显示的目标库存信息。
/// </summary>
private void OnDepotResultCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (!_runningState.Idle)
{
return;
}
Execute.OnUIThread(NotifySpecifiedDropsStateChanged);
}
public static FightSettingsUserControlModel Instance { get; }
/// <summary>
/// Gets or sets a value indicating whether a stage plan item is being dragged.
/// Gets or sets a value indicating whether 关卡规划项正在被拖拽。
/// </summary>
public bool IsDragging { get => field; set => SetAndNotify(ref field, value); }
public bool IsStageItemDragging { get => field; set => SetAndNotify(ref field, value); }
/// <summary>
/// Gets or private sets the list of stages.
/// Gets or private sets a value indicating whether 关卡列表。
/// </summary>
public ObservableCollection<StageSourceItem> StageListSource { get => field; private set => SetAndNotify(ref field, value); } = [];
@@ -123,7 +215,7 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
}
/// <summary>
/// Gets or sets a value indicating whether to use custom stage code.
/// Gets or sets a value indicating whether 使用自定义关卡代码。
/// </summary>
public bool CustomStageCode
{
@@ -148,9 +240,9 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
}
/// <summary>
/// Reset unsaved battle parameters.
/// 重置未保存的作战参数。
/// </summary>
/// <param name="fight">The fight task.</param>
/// <param name="fight">作战任务配置。</param>
public static void ResetFightVariables(FightTask? fight)
{
fight?.UseStone ??= false;
@@ -160,7 +252,7 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
}
/// <summary>
/// Gets or sets a value indicating whether to use medicine with null.
/// Gets or sets a value indicating whether 使用理智药。
/// </summary>
public bool? UseMedicine
{
@@ -181,7 +273,7 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
}
/// <summary>
/// Gets or sets the amount of medicine used.
/// Gets or sets 使用理智药数量。
/// </summary>
public int MedicineNumber
{
@@ -199,7 +291,7 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
public static string UseStoneString => LocalizationHelper.GetString("UseOriginitePrime");
/// <summary>
/// Gets or sets a value indicating whether to use originiums with null.
/// Gets or sets a value indicating whether 使用源石。
/// </summary>
public bool? UseStone
{
@@ -225,7 +317,7 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
}
/// <summary>
/// Gets or sets a value indicating whether to use originiums.
/// Gets or sets a value indicating whether 使用源石 with null
/// </summary>
// ReSharper disable once MemberCanBePrivate.Global
[PropertyDependsOn(nameof(UseStone))]
@@ -236,7 +328,7 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
}
/// <summary>
/// Gets or sets the amount of originiums used.
/// Gets or sets 使用源石数量。
/// </summary>
public int StoneNumber
{
@@ -252,7 +344,7 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
}
/// <summary>
/// Gets or sets a value indicating whether the number of times is limited with null.
/// Gets or sets a value indicating whether 限制次数 with null
/// </summary>
public bool? HasTimesLimited
{
@@ -268,7 +360,7 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
}
/// <summary>
/// Gets or sets the max number of times.
/// Gets or sets 最大次数。
/// </summary>
public int MaxTimes
{
@@ -296,7 +388,7 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
};
/// <summary>
/// Gets or sets the max number of times.
/// Gets or sets 连战次数。
/// </summary>
public int Series
{
@@ -314,12 +406,17 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
#region Drops
/// <summary>
/// Gets or sets a value indicating whether the drops are specified.
/// Gets or sets a value indicating whether 启用指定材料。
/// </summary>
public bool? IsSpecifiedDrops
{
get => GetTaskConfig<FightTask>().EnableTargetDrop;
set {
if (IsSpecifiedInventoryLocked)
{
return;
}
if (!SetTaskConfig<FightTask>(t => t.EnableTargetDrop == value, t => t.EnableTargetDrop = value))
{
return;
@@ -330,7 +427,245 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
}
/// <summary>
/// Gets the list of all drops.
/// Gets or sets a value indicating whether 指定材料按目标库存模式计算。
/// </summary>
public bool UseInventoryTarget
{
get => GetTaskConfig<FightTask>().IsInventoryTarget;
set {
if (!_runningState.Idle)
{
return;
}
if (!SetTaskConfig<FightTask>(t => t.IsInventoryTarget == value, t => t.IsInventoryTarget = value))
{
return;
}
NotifySpecifiedDropsStateChanged();
SetFightParams();
}
}
public bool IsSpecifiedInventoryLocked => UseInventoryTarget && !_runningState.Idle;
public bool UseDropQuantityMode
{
get => !UseInventoryTarget;
set {
if (value)
{
UseInventoryTarget = false;
}
}
}
public bool UseTargetInventoryMode
{
get => UseInventoryTarget;
set {
if (value)
{
UseInventoryTarget = true;
}
}
}
public string CurrentDropsInventoryText => FormatSpecifiedDropsCount(GetSpecifiedDropsInventoryCount(GetTaskConfig<FightTask>(), GetCurrentFightTaskId()));
public string EffectiveDropsQuantityText => FormatSpecifiedDropsCount(GetSpecifiedDropsCoreQuantity(GetTaskConfig<FightTask>(), GetCurrentFightTaskId()));
private static string FormatSpecifiedDropsCount(int? count) => count is int value ? value.FormatNumber(false) : "--";
private static bool IsInventoryTargetDropEnabled(FightTask fight)
{
return fight.EnableTargetDrop != false && !string.IsNullOrEmpty(fight.DropId) && fight.IsInventoryTarget;
}
/// <summary>
/// 读取当前识别到的仓库数量,并转换为查询表。
/// </summary>
private static Dictionary<string, int>? GetCurrentInventoryCounts()
{
var depotResult = Instances.ToolboxViewModel?.DepotResult;
if (depotResult == null || depotResult.Count == 0)
{
return null;
}
var snapshot = depotResult
.Where(item => item.Count >= 0)
.ToDictionary(item => item.Id, item => item.Count);
return snapshot.Count > 0 ? snapshot : null;
}
/// <summary>
/// 获取当前选中作战任务绑定的第一个 core task id。
/// </summary>
private static int? GetCurrentFightTaskId()
{
if (TaskSettingVisibilityInfo.CurrentTask is not FightTask fight)
{
return null;
}
int index = ConfigFactory.CurrentConfig.TaskQueue.IndexOf(fight);
if (index < 0 || index >= Instances.TaskQueueViewModel.TaskItemViewModels.Count)
{
return null;
}
var taskIds = Instances.TaskQueueViewModel.TaskItemViewModels[index].TaskIds;
return taskIds.Count > 0 ? taskIds[0] : null;
}
/// <summary>
/// 根据 core task id 反查对应的作战任务配置。
/// </summary>
private static FightTask? GetFightTaskByTaskId(int taskId)
{
if (taskId <= 0)
{
return null;
}
for (int index = 0; index < Instances.TaskQueueViewModel.TaskItemViewModels.Count; ++index)
{
if (!Instances.TaskQueueViewModel.TaskItemViewModels[index].TaskIds.Contains(taskId))
{
continue;
}
return index < ConfigFactory.CurrentConfig.TaskQueue.Count
? ConfigFactory.CurrentConfig.TaskQueue[index] as FightTask
: null;
}
return null;
}
/// <summary>
/// 检查指定 core 作战任务是否已在运行。
/// </summary>
private static bool IsTaskInProgress(int taskId)
{
return taskId > 0 &&
Instances.AsstProxy.TasksStatus.TryGetValue(taskId, out var taskState) &&
taskState.Status == MaaWpfGui.Main.TaskStatus.InProgress;
}
/// <summary>
/// 获取指定 core 任务缓存的目标库存运行时状态。
/// </summary>
private InventoryTargetRuntimeState? GetInventoryTargetRuntimeState(int? taskId)
{
if (taskId is not int id || id <= 0)
{
return null;
}
lock (_inventoryTargetRuntimeStateLock)
{
return _inventoryTargetRuntimeStateByTaskId.GetValueOrDefault(id);
}
}
/// <summary>
/// 获取指定掉落目标显示的库存数量。任务开始后,该显示值会被冻结。
/// </summary>
private int? GetSpecifiedDropsInventoryCount(FightTask fight, int? taskId = null)
{
if (string.IsNullOrEmpty(fight.DropId))
{
return null;
}
if (GetInventoryTargetRuntimeState(taskId) is { } runtimeState && runtimeState.DropId == fight.DropId)
{
return runtimeState.StartInventory;
}
var currentInventoryCounts = GetCurrentInventoryCounts();
if (currentInventoryCounts == null)
{
return null;
}
return currentInventoryCounts.TryGetValue(fight.DropId, out var count) ? count : 0;
}
/// <summary>
/// 获取指定目标库存任务显示并下发给 core 的实际刷取数量。
/// </summary>
private int? GetSpecifiedDropsCoreQuantity(FightTask fight, int? taskId = null)
{
if (!IsInventoryTargetDropEnabled(fight))
{
return fight.DropCount;
}
if (GetInventoryTargetRuntimeState(taskId) is { } runtimeState)
{
return runtimeState.EffectiveQuantity;
}
int? inventoryCount = GetSpecifiedDropsInventoryCount(fight);
if (inventoryCount is not int currentInventory)
{
return null;
}
return Math.Max(fight.DropCount - currentInventory, 0);
}
/// <summary>
/// 捕获运行中作战任务需要保持固定的目标库存值。
/// </summary>
private static InventoryTargetRuntimeState? CreateInventoryTargetRuntimeState(FightTask fight)
{
var currentInventoryCounts = GetCurrentInventoryCounts();
if (currentInventoryCounts == null)
{
return null;
}
int startInventory = currentInventoryCounts.GetValueOrDefault(fight.DropId);
int effectiveQuantity = Math.Max(fight.DropCount - startInventory, 0);
return new(fight.DropId, startInventory, effectiveQuantity);
}
/// <summary>
/// 在捕获启动时的目标库存值后,保存该 core 作战任务的运行时状态。
/// </summary>
private void RememberInventoryTargetRuntimeState(int taskId, InventoryTargetRuntimeState runtimeState)
{
if (taskId <= 0)
{
return;
}
lock (_inventoryTargetRuntimeStateLock)
{
_inventoryTargetRuntimeStateByTaskId[taskId] = runtimeState;
}
}
/// <summary>
/// 刷新目标库存模式及其显示数量相关的 UI 绑定。
/// </summary>
private void NotifySpecifiedDropsStateChanged()
{
NotifyOfPropertyChange(nameof(IsSpecifiedInventoryLocked));
NotifyOfPropertyChange(nameof(UseDropQuantityMode));
NotifyOfPropertyChange(nameof(UseTargetInventoryMode));
NotifyOfPropertyChange(nameof(CurrentDropsInventoryText));
NotifyOfPropertyChange(nameof(EffectiveDropsQuantityText));
}
/// <summary>
/// Gets 全部掉落材料列表。
/// </summary>
private List<CombinedData> AllDrops { get; } = [];
@@ -389,24 +724,34 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
}
/// <summary>
/// Gets or private sets the list of drops.
/// Gets 获取或私有设置掉落材料列表。
/// </summary>
public ObservableCollection<CombinedData> DropsList { get; private set; } = [];
/// <summary>
/// Gets or sets the item ID of drops.
/// Gets or sets 指定掉落材料 ID。
/// </summary>
public string DropsItemId
{
get => GetTaskConfig<FightTask>().DropId;
set {
SetTaskConfig<FightTask>(t => t.DropId == value, t => t.DropId = value);
if (IsSpecifiedInventoryLocked)
{
return;
}
if (!SetTaskConfig<FightTask>(t => t.DropId == value, t => t.DropId = value))
{
return;
}
NotifySpecifiedDropsStateChanged();
SetFightParams();
}
}
/// <summary>
/// Gets or sets the item Name of drops.
/// Gets or sets 指定掉落材料名称。
/// </summary>
public string DropsItemName { get => field; set => SetAndNotify(ref field, value); } = string.Empty;
@@ -414,6 +759,13 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
[UsedImplicitly]
public void DropsListDropDownClosed()
{
if (IsSpecifiedInventoryLocked)
{
RefreshDropName();
NotifySpecifiedDropsStateChanged();
return;
}
if (DropsList.FirstOrDefault(i => i.Display == DropsItemName) is { } item)
{
DropsItemId = item.Value;
@@ -427,13 +779,23 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
}
/// <summary>
/// Gets or sets the quantity of drops.
/// Gets or sets 指定掉落数量。
/// </summary>
public int DropsQuantity
{
get => GetTaskConfig<FightTask>().DropCount;
set {
SetTaskConfig<FightTask>(t => t.DropCount == value, t => t.DropCount = value);
if (IsSpecifiedInventoryLocked)
{
return;
}
if (!SetTaskConfig<FightTask>(t => t.DropCount == value, t => t.DropCount = value))
{
return;
}
NotifySpecifiedDropsStateChanged();
SetFightParams();
}
}
@@ -487,7 +849,7 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
}
/// <summary>
/// Gets or sets a value indicating whether to use DrGrandet mode.
/// Gets or sets a value indicating whether 启用 DrGrandet 模式。
/// </summary>
public bool IsDrGrandet
{
@@ -496,7 +858,7 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
}
/// <summary>
/// Gets or sets a value indicating whether to use alternate stage.
/// Gets or sets a value indicating whether 使用备选关卡。
/// </summary>
public bool UseAlternateStage
{
@@ -593,7 +955,7 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
public string ActivityInfo { get => field; private set => SetAndNotify(ref field, value); } = string.Empty;
/// <summary>
/// Gets or sets a value indicating whether to hide unavailable stages.
/// Gets or sets a value indicating whether 隐藏未开放关卡。
/// </summary>
public bool HideUnavailableStage
{
@@ -627,7 +989,7 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
}
/// <summary>
/// Gets or sets a value indicating whether to hide series.
/// Gets or sets a value indicating whether 隐藏连战设置。
/// </summary>
public bool HideSeries
{
@@ -636,7 +998,7 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
}
/// <summary>
/// Gets or sets a value indicating whether to use weekly schedule.
/// Gets or sets a value indicating whether 使用周计划。
/// </summary>
public bool UseWeeklySchedule
{
@@ -711,6 +1073,7 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
RefreshCurrentStagePlan();
RefreshWeeklySchedule();
RefreshDropName();
NotifySpecifiedDropsStateChanged();
Refresh();
}
@@ -733,7 +1096,7 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
#region
/// <summary>
/// Updates stage list.
/// 更新关卡列表。
/// 使用手动输入时,只更新关卡列表,不更新关卡选择
/// 使用隐藏当日不开放时,更新关卡列表,关卡选择为未开放的关卡时清空
/// 使用备选关卡时,更新关卡列表,关卡选择为未开放的关卡时在关卡列表中添加对应未开放关卡,避免清空导致进入上次关卡
@@ -741,8 +1104,8 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
/// 除手动输入外所有情况下,如果剩余理智为未开放的关卡,会被清空
/// </summary>
/// <returns>更新任务列表的Task</returns>
// FIXME: 被注入对象只能在private函数内使用只有Model显示之后才会被注入。如果Model还没有触发OnInitialActivate时调用函数会NullPointerException
// 这个函数被列为public可见,意味着注入对象前被调用
// FIXME被注入对象只能在 private 函数内使用,只有 Model 显示之后才会被注入。如果 Model 还没有触发 OnInitialActivate 时调用函数,会导致空引用异常。
// 这个函数被声明为 public意味着它可能会在注入对象前被调用
public Task UpdateStageList()
{
return Execute.OnUIThreadAsync(async () => {
@@ -938,7 +1301,7 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
public bool IsVisible { get => field; set => SetAndNotify(ref field, value); } = true;
/// <summary>
/// Gets or sets a value indicating whether 过期活动关卡, 加删除线
/// Gets or sets a value indicating whether 关卡已过期并显示删除线
/// </summary>
public bool IsOutdated { get; set; } = false;
}
@@ -1028,12 +1391,52 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
var yjTime = DateTimeOffset.Now.ToYjDateTime().ToLocalTime();
var daysUntilEndOfWeek = ((7 - (int)yjTime.DayOfWeek + 7) % 7) + 1; // 距离本周结束的天数, 用鹰历计算
var activityExpireDays = activityExpireIn2Days && fight.UseExpireMedicineForActivity ? daysUntilEndOfWeek : 0;
InventoryTargetRuntimeState? inventoryTargetRuntimeState = null;
bool shouldRememberInventoryTargetRuntimeState = false;
int specifiedDropsQuantity = fight.DropCount;
if (IsInventoryTargetDropEnabled(fight))
{
if (taskId is int existingTaskId and > 0 && Instance.GetInventoryTargetRuntimeState(existingTaskId) is { } existingRuntimeState)
{
inventoryTargetRuntimeState = existingRuntimeState;
}
else
{
inventoryTargetRuntimeState = CreateInventoryTargetRuntimeState(fight);
if (taskId is int startedTaskId and > 0 && inventoryTargetRuntimeState != null && IsTaskInProgress(startedTaskId))
{
shouldRememberInventoryTargetRuntimeState = true;
}
}
if (inventoryTargetRuntimeState == null)
{
Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetStringFormat("SpecifiedDropsInventoryUnavailable", fight.NameDisplay), UiLogColor.Warning);
return (null, []);
}
specifiedDropsQuantity = inventoryTargetRuntimeState.EffectiveQuantity;
if (specifiedDropsQuantity <= 0 && taskId is null)
{
return (null, []);
}
}
var effectiveMaxTimes = fight.EnableTimesLimit != false ? fight.TimesLimit : int.MaxValue;
// 任务运行时如果启用了目标掉落且指定的掉落数量小于等于 0则将最大挑战次数设为 0防止任务继续运行但无法获得目标掉落
if (taskId is int and > 0 && IsInventoryTargetDropEnabled(fight) && specifiedDropsQuantity <= 0)
{
effectiveMaxTimes = 0;
}
var task = new AsstFightTask() {
Stage = stage,
Medicine = fight.UseMedicine != false ? fight.MedicineCount : 0,
Stone = fight.UseStone != false ? fight.StoneCount : 0,
Series = fight.Series,
MaxTimes = fight.EnableTimesLimit != false ? fight.TimesLimit : int.MaxValue,
MaxTimes = effectiveMaxTimes,
MedicineExpireDays = Math.Max(expireDays, activityExpireDays),
IsDrGrandet = fight.IsDrGrandet,
ReportToPenguin = SettingsViewModel.GameSettings.EnablePenguin,
@@ -1049,21 +1452,36 @@ public class FightSettingsUserControlModel : TaskSettingsViewModel, FightSetting
task.Stage = fight.AnnihilationStage;
}
if (fight.EnableTargetDrop != false && !string.IsNullOrEmpty(fight.DropId))
if (fight.EnableTargetDrop != false && !string.IsNullOrEmpty(fight.DropId) && specifiedDropsQuantity > 0)
{
task.Drops.Add(fight.DropId, fight.DropCount);
task.Drops.Add(fight.DropId, specifiedDropsQuantity);
}
if (fight.EnableTimesLimit is not false && fight.Series > 0 && fight.TimesLimit % fight.Series != 0)
if (effectiveMaxTimes > 0 && effectiveMaxTimes < int.MaxValue && fight.Series > 0 && effectiveMaxTimes % fight.Series != 0)
{
Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetStringFormat("FightTimesMayNotExhausted", fight.TimesLimit, fight.Series), UiLogColor.Warning);
Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetStringFormat("FightTimesMayNotExhausted", effectiveMaxTimes, fight.Series), UiLogColor.Warning);
}
return taskId switch {
int id when id > 0 => (Instances.AsstProxy.AsstSetTaskParamsEncoded(id, task), [id]),
null => FromSingle(Instances.AsstProxy.AsstAppendTaskWithEncoding(TaskType.Fight, task)),
_ => (null, []),
};
if (taskId is int id and > 0)
{
bool updated = Instances.AsstProxy.AsstSetTaskParamsEncoded(id, task);
if (updated && shouldRememberInventoryTargetRuntimeState && inventoryTargetRuntimeState != null)
{
Instance.RememberInventoryTargetRuntimeState(id, inventoryTargetRuntimeState);
}
return (updated, [id]);
}
if (taskId is null)
{
var appendResult = Instances.AsstProxy.AsstAppendTaskWithEncoding(TaskType.Fight, task);
return (appendResult.IsSuccess, appendResult.TaskId > 0 ? [appendResult.TaskId] : []);
}
return (null, []);
}
}
private sealed record InventoryTargetRuntimeState(string DropId, int StartInventory, int EffectiveQuantity);
}

View File

@@ -100,45 +100,67 @@
<ColumnDefinition Width="Auto" MinWidth="100" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical">
<Grid Margin="6">
<CheckBox
Height="30"
VerticalContentAlignment="Center"
Content="{DynamicResource AssignedMaterial}"
IsChecked="{Binding IsSpecifiedDrops}"
MouseRightButtonUp="{s:Action ToggleCheckBoxNullOnRightClick,
Target={x:Type helper:CheckBoxHelper}}"
ToolTip="{DynamicResource CheckBoxesNotSavedAsNull}" />
<controls:TooltipBlock
HorizontalAlignment="Right"
VerticalAlignment="Top"
NormalOpacity="0.25"
TooltipText="{DynamicResource SpecifiedDropsTip}" />
</Grid>
<Grid Height="42" Visibility="{c:Binding IsSpecifiedDrops}">
<controls:TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{DynamicResource Quantity}"
TextAlignment="Center" />
</Grid>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Vertical">
<ComboBox
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Margin="6">
<CheckBox
Height="30"
Margin="6"
VerticalContentAlignment="Center"
DisplayMemberPath="Display"
DropDownClosed="{s:Action DropsListDropDownClosed}"
IsEditable="True"
IsTextSearchEnabled="False"
ItemsSource="{Binding DropsList}"
Loaded="{s:Action MakeComboBoxSearchable,
Target={x:Type ui_vms:SettingsViewModel}}"
SelectedValue="{Binding DropsItemId}"
SelectedValuePath="Value"
Text="{Binding DropsItemName}" />
Content="{DynamicResource AssignedMaterial}"
IsChecked="{Binding IsSpecifiedDrops}"
IsEnabled="{c:Binding !IsSpecifiedInventoryLocked}"
MouseRightButtonUp="{s:Action ToggleCheckBoxNullOnRightClick,
Target={x:Type helper:CheckBoxHelper}}"
ToolTip="{DynamicResource CheckBoxesNotSavedAsNull}" />
<controls:TooltipBlock
HorizontalAlignment="Right"
VerticalAlignment="Top"
NormalOpacity="0.25"
TooltipText="{DynamicResource SpecifiedDropsTip}" />
</Grid>
<hc:ButtonGroup
Grid.Row="1"
MaxWidth="88"
Margin="6,6,6,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsEnabled="{c:Binding !IsSpecifiedInventoryLocked}"
Orientation="Vertical"
Visibility="{c:Binding IsSpecifiedDrops}">
<RadioButton
hc:VisualElement.HighlightBackground="{DynamicResource RegionBrushOpacity25}"
Background="{DynamicResource RegionBrushOpacity25}"
Content="{DynamicResource Quantity}"
IsChecked="{Binding UseDropQuantityMode}" />
<RadioButton
hc:VisualElement.HighlightBackground="{DynamicResource RegionBrushOpacity25}"
Background="{DynamicResource RegionBrushOpacity25}"
Content="{DynamicResource TargetInventory}"
IsChecked="{Binding UseTargetInventoryMode}" />
</hc:ButtonGroup>
<ComboBox
Grid.Column="1"
Height="30"
Margin="6"
VerticalContentAlignment="Center"
DisplayMemberPath="Display"
DropDownClosed="{s:Action DropsListDropDownClosed}"
IsEditable="True"
IsEnabled="{c:Binding !IsSpecifiedInventoryLocked}"
IsTextSearchEnabled="False"
ItemsSource="{Binding DropsList}"
Loaded="{s:Action MakeComboBoxSearchable,
Target={x:Type ui_vms:SettingsViewModel}}"
SelectedValue="{Binding DropsItemId}"
SelectedValuePath="Value"
Text="{Binding DropsItemName}" />
<StackPanel
Grid.Row="1"
Grid.Column="1"
VerticalAlignment="Center"
Orientation="Vertical">
<hc:NumericUpDown
Height="30"
MinWidth="60"
@@ -146,10 +168,24 @@
HorizontalAlignment="Left"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
IsEnabled="{c:Binding !IsSpecifiedInventoryLocked}"
Maximum="1145141919"
Minimum="1"
Visibility="{c:Binding IsSpecifiedDrops}"
Value="{Binding DropsQuantity, UpdateSourceTrigger=PropertyChanged}" />
<StackPanel Margin="6,2,6,0" Visibility="{c:Binding 'IsSpecifiedDrops and UseInventoryTarget'}">
<StackPanel Orientation="Horizontal">
<controls:TextBlock Text="{DynamicResource Inventory}" />
<controls:TextBlock Margin="4,0,0,0" Text=":" />
<controls:TextBlock Margin="4,0,0,0" Text="{Binding CurrentDropsInventoryText}" />
<controls:TooltipBlock Margin="4,0,0,0" TooltipText="{DynamicResource InventoryUpdateTip}" />
</StackPanel>
<StackPanel Margin="0,2,0,0" Orientation="Horizontal">
<controls:TextBlock Text="{DynamicResource Quantity}" />
<controls:TextBlock Margin="4,0,0,0" Text=":" />
<controls:TextBlock Margin="4,0,0,0" Text="{Binding EffectiveDropsQuantityText}" />
</StackPanel>
</StackPanel>
</StackPanel>
</Grid>
<Grid Grid.Row="2" Visibility="{c:Binding !HideSeries}">
@@ -310,7 +346,7 @@
<TextBox
Height="30"
hc:InfoElement.Placeholder="{DynamicResource DefaultStage}"
IsEnabled="{c:Binding !IsDragging,
IsEnabled="{c:Binding !IsStageItemDragging,
Source={x:Static ui_vms:TaskQueueViewModel.FightTask}}"
Opacity="{c:Binding 'IsOpen ? 1 : 0.5'}"
Text="{Binding Stage, UpdateSourceTrigger=PropertyChanged}"

View File

@@ -32,7 +32,7 @@ public partial class FightSettingsUserControl : System.Windows.Controls.UserCont
private void DragHandle_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
TaskQueueViewModel.FightTask.IsDragging = true;
TaskQueueViewModel.FightTask.IsStageItemDragging = true;
// 拖拽松开时可能不经过 Button在 Window 层兜底恢复
var window = Window.GetWindow(this);
@@ -41,12 +41,12 @@ public partial class FightSettingsUserControl : System.Windows.Controls.UserCont
private void DragHandle_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
TaskQueueViewModel.FightTask.IsDragging = false;
TaskQueueViewModel.FightTask.IsStageItemDragging = false;
}
private void Window_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
TaskQueueViewModel.FightTask.IsDragging = false;
TaskQueueViewModel.FightTask.IsStageItemDragging = false;
if (sender is Window window)
{
window.PreviewMouseLeftButtonUp -= Window_PreviewMouseLeftButtonUp;