using System;
using System.Threading;
using System.Threading.Tasks;
using BetterGenshinImpact.Core.Recognition.OpenCv;
using BetterGenshinImpact.GameTask.AutoPathing;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using BetterGenshinImpact.GameTask.Common.Map.Maps;
using BetterGenshinImpact.GameTask.Common.Map.Maps.Base;
using BetterGenshinImpact.GameTask.Common.Map.Maps.Layer;
using BetterGenshinImpact.GameTask.Model.Area;
using BetterGenshinImpact.Helpers;
using BetterGenshinImpact.View;
using BetterGenshinImpact.ViewModel;
using Microsoft.Extensions.Logging;
using OpenCvSharp;
using Rect = System.Windows.Rect;
namespace BetterGenshinImpact.GameTask.MapMask;
///
/// 地图遮罩触发器
///
public class MapMaskTrigger : ITaskTrigger
{
private readonly ILogger _logger = App.GetLogger();
public string Name => "地图遮罩";
public bool IsEnabled { get; set; }
public int Priority => 1; // 低优先级
public bool IsExclusive => false;
public GameUiCategory SupportedGameUiCategory => GameUiCategory.Unknown;
private readonly MapMaskConfig _config = TaskContext.Instance().Config.MapMaskConfig;
private readonly string _mapMatchingMethod = TaskContext.Instance().Config.PathingConditionConfig.MapMatchingMethod;
private readonly TemplateMatchStabilityDetector _detector = new();
private DateTime _prevExecute = DateTime.MinValue;
// 图像连续稳定次数
private int _stableCount = 0;
private ISceneMap _teyvatMap => MapManager.GetMap(MapTypes.Teyvat, _mapMatchingMethod);
private OpenCvSharp.Rect _prevRect = default;
private readonly object _prevRectLock = new();
private const int RectDebounceThreshold = 3;
private readonly NavigationInstance _navigationInstance = new();
private sealed class PendingUiUpdate
{
public bool? IsInBigMapUi { get; init; }
public Rect? BigMapViewport { get; init; }
public Rect? MiniMapViewport { get; init; }
}
private PendingUiUpdate? _pendingUiUpdate;
private int _uiApplyScheduled;
private sealed class ComputeWorkItem : IDisposable
{
public required string MapMatchingMethod { get; init; }
public Mat? Mat { get; set; }
public void Dispose()
{
Mat?.Dispose();
Mat = null;
}
}
private ComputeWorkItem? _pendingBigMapCompute;
private int _bigMapWorkerRunning;
private ComputeWorkItem? _pendingMiniMapCompute;
private int _miniMapWorkerRunning;
///
/// 初始化触发器状态,并在关闭时同步隐藏遮罩UI
///
public void Init()
{
IsEnabled = _config.Enabled;
// 关闭时隐藏UI
if (!IsEnabled)
{
UIDispatcherHelper.BeginInvoke(() =>
{
if (MaskWindow.InstanceNullable() != null)
{
if (MaskWindow.Instance().DataContext is MaskWindowViewModel vm)
{
vm.IsInBigMapUi = false;
}
}
});
}
}
///
/// 接收每帧截图内容并驱动大地图/小地图的异步定位与UI更新
///
/// 捕获到的画面内容
public void OnCapture(CaptureContent content)
{
if ((DateTime.Now - _prevExecute).TotalMilliseconds <= 50)
{
return;
}
_prevExecute = DateTime.Now;
try
{
var region = content.CaptureRectArea;
var inBigMapUi = content.CurrentGameUiCategory == GameUiCategory.BigMap || Bv.IsInBigMapUi(region);
var mapMatchingMethod = TaskContext.Instance().Config.PathingConditionConfig.MapMatchingMethod;
PendingUiUpdate? update = null;
if (inBigMapUi)
{
if (_detector.IsStable(region.CacheGreyMat))
{
_stableCount++;
if (_stableCount >= 20)
{
_stableCount = 0;
}
}
else
{
_stableCount = 0;
}
if (_stableCount == 0)
{
var greyMat = region.CacheGreyMat.Clone();
EnqueueBigMapCompute(new ComputeWorkItem
{
MapMatchingMethod = mapMatchingMethod,
Mat = greyMat
});
}
}
else
{
// 主界面上展示小地图
if (_config.MiniMapMaskEnabled)
{
if (Bv.IsInMainUi(region))
{
var srcMat = region.SrcMat.Clone();
EnqueueMiniMapCompute(new ComputeWorkItem
{
MapMatchingMethod = mapMatchingMethod,
Mat = srcMat
});
// 自动记录路径
if (_config.PathAutoRecordEnabled)
{
// ...
}
}
else
{
update = new PendingUiUpdate { MiniMapViewport = new Rect(0, 0, 0, 0) };
}
}
lock (_prevRectLock)
{
_prevRect = default;
}
}
update = update == null
? new PendingUiUpdate { IsInBigMapUi = inBigMapUi }
: new PendingUiUpdate
{
IsInBigMapUi = inBigMapUi,
BigMapViewport = update.BigMapViewport,
MiniMapViewport = update.MiniMapViewport
};
QueueUiUpdate(update);
}
catch (Exception e)
{
_logger.LogDebug(e, "实时地图定位时发生异常");
}
}
///
/// 入队大地图定位计算,仅保留正在执行与最新任务
///
/// 计算任务
private void EnqueueBigMapCompute(ComputeWorkItem workItem)
{
var previous = Interlocked.Exchange(ref _pendingBigMapCompute, workItem);
previous?.Dispose();
if (Interlocked.Exchange(ref _bigMapWorkerRunning, 1) == 0)
{
_ = Task.Run(BigMapWorkerLoop);
}
}
///
/// 入队小地图定位计算,仅保留正在执行与最新任务
///
/// 计算任务
private void EnqueueMiniMapCompute(ComputeWorkItem workItem)
{
var previous = Interlocked.Exchange(ref _pendingMiniMapCompute, workItem);
previous?.Dispose();
if (Interlocked.Exchange(ref _miniMapWorkerRunning, 1) == 0)
{
_ = Task.Run(MiniMapWorkerLoop);
}
}
///
/// 大地图计算工作线程循环
///
private void BigMapWorkerLoop()
{
while (true)
{
var workItem = Interlocked.Exchange(ref _pendingBigMapCompute, null);
if (workItem == null)
{
Interlocked.Exchange(ref _bigMapWorkerRunning, 0);
if (Volatile.Read(ref _pendingBigMapCompute) != null && Interlocked.Exchange(ref _bigMapWorkerRunning, 1) == 0)
{
continue;
}
return;
}
try
{
ProcessBigMapCompute(workItem);
}
catch (Exception e)
{
_logger.LogDebug(e, "地图遮罩异步计算时发生异常");
}
finally
{
workItem.Dispose();
}
}
}
///
/// 小地图计算工作线程循环
///
private void MiniMapWorkerLoop()
{
while (true)
{
var workItem = Interlocked.Exchange(ref _pendingMiniMapCompute, null);
if (workItem == null)
{
Interlocked.Exchange(ref _miniMapWorkerRunning, 0);
if (Volatile.Read(ref _pendingMiniMapCompute) != null && Interlocked.Exchange(ref _miniMapWorkerRunning, 1) == 0)
{
continue;
}
return;
}
try
{
ProcessMiniMapCompute(workItem);
}
catch (Exception e)
{
_logger.LogDebug(e, "地图遮罩异步计算时发生异常");
}
finally
{
workItem.Dispose();
}
}
}
///
/// 执行大地图定位计算并产出UI更新
///
/// 计算任务
private void ProcessBigMapCompute(ComputeWorkItem workItem)
{
if (workItem.Mat == null)
{
return;
}
OpenCvSharp.Rect prevRect;
lock (_prevRectLock)
{
prevRect = _prevRect;
}
var sceneMap = (SceneBaseMap)MapManager.GetMap(MapTypes.Teyvat, workItem.MapMatchingMethod);
var rect256 = BigMapTeyvat256Layer.GetInstance(sceneMap).GetBigMapRect(workItem.Mat, prevRect);
if (rect256 != default)
{
if (rect256 is { Width: < 50, Height: < 40 } || rect256 is { Width: > 3000, Height: > 1800 })
{
lock (_prevRectLock)
{
_prevRect = default;
}
return;
}
lock (_prevRectLock)
{
_prevRect = rect256;
}
}
const int s = TeyvatMap.BigMap256ScaleTo2048;
var rect2048 = new Rect(rect256.X * s, rect256.Y * s, rect256.Width * s, rect256.Height * s);
QueueUiUpdate(new PendingUiUpdate { BigMapViewport = rect2048 });
}
///
/// 执行小地图定位计算并产出UI更新
///
/// 计算任务
private void ProcessMiniMapCompute(ComputeWorkItem workItem)
{
if (workItem.Mat == null)
{
return;
}
using var imageRegion = new ImageRegion(workItem.Mat, 0, 0);
workItem.Mat = null;
var miniPoint = _navigationInstance.GetPositionStable(imageRegion, nameof(MapTypes.Teyvat), workItem.MapMatchingMethod);
if (miniPoint != default)
{
double viewportSize = MapAssets.MimiMapRect1080P.Width / 3.0 * 10;
QueueUiUpdate(new PendingUiUpdate
{
MiniMapViewport = new Rect(
miniPoint.X - viewportSize / 2.0,
miniPoint.Y - viewportSize / 2.0,
viewportSize,
viewportSize)
});
}
else
{
QueueUiUpdate(new PendingUiUpdate { MiniMapViewport = new Rect(0, 0, 0, 0) });
}
}
///
/// 合并并异步投递UI更新
///
/// 待应用的UI更新
private void QueueUiUpdate(PendingUiUpdate update)
{
Interlocked.Exchange(ref _pendingUiUpdate, update);
TryScheduleUiApply();
}
///
/// 确保仅有一个UI更新调度在队列中
///
private void TryScheduleUiApply()
{
if (Interlocked.Exchange(ref _uiApplyScheduled, 1) == 0)
{
UIDispatcherHelper.BeginInvoke(ApplyPendingUiUpdate);
}
}
///
/// 在UI线程应用合并后的更新
///
private void ApplyPendingUiUpdate()
{
var update = Interlocked.Exchange(ref _pendingUiUpdate, null);
if (update != null)
{
var window = MaskWindow.Instance();
if (update.IsInBigMapUi is { } isInBigMapUi && window.DataContext is MaskWindowViewModel vm)
{
vm.IsInBigMapUi = isInBigMapUi;
}
if (update.BigMapViewport is { } bigMapViewport)
{
window.PointsCanvasControl.UpdateViewport(bigMapViewport.X, bigMapViewport.Y, bigMapViewport.Width, bigMapViewport.Height);
}
if (update.MiniMapViewport is { } miniMapViewport)
{
window.MiniMapPointsCanvasControl.UpdateViewport(miniMapViewport.X, miniMapViewport.Y, miniMapViewport.Width, miniMapViewport.Height);
}
}
Interlocked.Exchange(ref _uiApplyScheduled, 0);
if (Volatile.Read(ref _pendingUiUpdate) != null)
{
TryScheduleUiApply();
}
}
}