switching

This commit is contained in:
Boki 2026-02-13 17:36:33 -05:00
parent 490fb8bdba
commit ec1f6274e3
5 changed files with 246 additions and 69 deletions

View file

@ -10,6 +10,10 @@ public class MinimapCapture : IDisposable
private readonly MinimapConfig _config;
private readonly IScreenCapture _backend;
private readonly WallColorTracker _colorTracker;
private MinimapMode _detectedMode = MinimapMode.Overlay;
public MinimapMode DetectedMode => _detectedMode;
public event Action<MinimapMode>? ModeChanged;
public MinimapCapture(MinimapConfig config)
{
@ -49,9 +53,17 @@ public class MinimapCapture : IDisposable
public MinimapFrame? CaptureFrame()
{
var region = _config.CaptureRegion;
using var bgr = _backend.CaptureRegion(region);
// Auto-detect minimap mode every frame (just a 5x5 pixel check, negligible cost)
var detected = DetectMinimapMode();
if (detected != _detectedMode)
{
_detectedMode = detected;
Log.Information("Minimap mode switched to {Mode}", _detectedMode);
ResetAdaptation();
ModeChanged?.Invoke(_detectedMode);
}
using var bgr = CaptureAndNormalize(_detectedMode);
if (bgr == null || bgr.Empty())
return null;
@ -66,19 +78,52 @@ public class MinimapCapture : IDisposable
// Wall mask: target #A2AEE5 blue-lavender structure lines (range adapts per-map)
var wallMask = BuildWallMask(hsv, playerMask, sample: true);
// Fog of war: broad blue range minus walls minus player
using var fogMask = BuildFogMask(hsv, wallMask, playerMask);
// Build classified mat (use actual frame size — differs between overlay and corner)
var frameSize = bgr.Width;
var classified = new Mat(frameSize, frameSize, MatType.CV_8UC1, Scalar.Black);
// Build classified mat (fog first, walls override)
var classified = new Mat(_config.CaptureSize, _config.CaptureSize, MatType.CV_8UC1, Scalar.Black);
classified.SetTo(new Scalar((byte)MapCell.Fog), fogMask);
classified.SetTo(new Scalar((byte)MapCell.Wall), wallMask);
if (_detectedMode == MinimapMode.Corner)
{
// Corner minimap is clean — skip fog detection
classified.SetTo(new Scalar((byte)MapCell.Wall), wallMask);
}
else
{
// Overlay: fog detection needed (broad blue minus walls minus player)
using var fogMask = BuildFogMask(hsv, wallMask, playerMask);
classified.SetTo(new Scalar((byte)MapCell.Fog), fogMask);
classified.SetTo(new Scalar((byte)MapCell.Wall), wallMask);
}
// Gray for correlation tracking (player zeroed)
var grayForCorr = new Mat();
Cv2.CvtColor(bgr, grayForCorr, ColorConversionCodes.BGR2GRAY);
grayForCorr.SetTo(Scalar.Black, playerMask);
// Corner mode: rescale classified + wall mask to match overlay scale
// Uses nearest-neighbor so discrete cell values (0-3) stay crisp
if (_detectedMode == MinimapMode.Corner && Math.Abs(_config.CornerScale - 1.0) > 0.01)
{
var scaledSize = (int)Math.Round(frameSize * _config.CornerScale);
var scaledClassified = new Mat();
Cv2.Resize(classified, scaledClassified, new Size(scaledSize, scaledSize),
interpolation: InterpolationFlags.Nearest);
classified.Dispose();
classified = scaledClassified;
var scaledWalls = new Mat();
Cv2.Resize(wallMask, scaledWalls, new Size(scaledSize, scaledSize),
interpolation: InterpolationFlags.Nearest);
wallMask.Dispose();
wallMask = scaledWalls;
var scaledGray = new Mat();
Cv2.Resize(grayForCorr, scaledGray, new Size(scaledSize, scaledSize),
interpolation: InterpolationFlags.Linear);
grayForCorr.Dispose();
grayForCorr = scaledGray;
}
return new MinimapFrame(
GrayMat: grayForCorr,
WallMask: wallMask,
@ -88,6 +133,43 @@ public class MinimapCapture : IDisposable
);
}
private Mat? CaptureAndNormalize(MinimapMode mode)
{
var region = mode == MinimapMode.Overlay
? _config.OverlayRegion
: _config.CornerRegion;
return _backend.CaptureRegion(region);
}
/// <summary>
/// Detect minimap mode by sampling a small patch at the corner minimap center.
/// If the pixel is close to #DE581B (orange player dot), corner minimap is active.
/// </summary>
private MinimapMode DetectMinimapMode()
{
// Capture a tiny 5x5 region at the corner center
var cx = _config.CornerCenterX;
var cy = _config.CornerCenterY;
var probe = new Poe2Trade.Core.Region(cx - 2, cy - 2, 5, 5);
using var patch = _backend.CaptureRegion(probe);
if (patch == null || patch.Empty())
return _detectedMode; // keep current on failure
// Average the BGR values of the patch
var mean = Cv2.Mean(patch);
var b = mean.Val0;
var g = mean.Val1;
var r = mean.Val2;
// #DE581B → R=222, G=88, B=27 — check if close (tolerance ~60 per channel)
const int tol = 60;
if (Math.Abs(r - 222) < tol && Math.Abs(g - 88) < tol && Math.Abs(b - 27) < tol)
return MinimapMode.Corner;
return MinimapMode.Overlay;
}
private Mat BuildWallMask(Mat hsv, Mat playerMask, bool sample = false)
{
// Use adapted range if available (narrows per-map), otherwise broad default
@ -163,8 +245,7 @@ public class MinimapCapture : IDisposable
/// </summary>
public byte[]? CaptureStage(MinimapDebugStage stage)
{
var region = _config.CaptureRegion;
using var bgr = _backend.CaptureRegion(region);
using var bgr = CaptureAndNormalize(_detectedMode);
if (bgr == null || bgr.Empty()) return null;
if (stage == MinimapDebugStage.Raw) return EncodePng(bgr);
@ -192,7 +273,7 @@ public class MinimapCapture : IDisposable
if (stage == MinimapDebugStage.Fog) return EncodePng(fogMask);
// Classified (walls + fog + player — explored is tracked by WorldMap)
using var classified = new Mat(_config.CaptureSize, _config.CaptureSize, MatType.CV_8UC3, Scalar.Black);
using var classified = new Mat(bgr.Height, bgr.Width, MatType.CV_8UC3, Scalar.Black);
classified.SetTo(new Scalar(180, 140, 70), fogMask); // light blue for fog
classified.SetTo(new Scalar(26, 45, 61), wallMask); // brown for walls
classified.SetTo(new Scalar(0, 165, 255), playerMask); // orange for player
@ -211,8 +292,7 @@ public class MinimapCapture : IDisposable
public void SaveDebugCapture(string dir = "debug-minimap")
{
Directory.CreateDirectory(dir);
var region = _config.CaptureRegion;
using var bgr = _backend.CaptureRegion(region);
using var bgr = CaptureAndNormalize(_detectedMode);
if (bgr == null || bgr.Empty()) return;
using var hsv = new Mat();
@ -224,7 +304,7 @@ public class MinimapCapture : IDisposable
using var wallMask = BuildWallMask(hsv, playerMask);
// Colorized classified (walls + player)
using var classified = new Mat(_config.CaptureSize, _config.CaptureSize, MatType.CV_8UC3, Scalar.Black);
using var classified = new Mat(bgr.Height, bgr.Width, MatType.CV_8UC3, Scalar.Black);
classified.SetTo(new Scalar(26, 45, 61), wallMask); // brown
classified.SetTo(new Scalar(0, 165, 255), playerMask); // orange
@ -249,8 +329,8 @@ public class MinimapCapture : IDisposable
if (moments.M00 < 10) // not enough pixels
return new Point2d(0, 0);
var cx = moments.M10 / moments.M00 - _config.CaptureSize / 2.0;
var cy = moments.M01 / moments.M00 - _config.CaptureSize / 2.0;
var cx = moments.M10 / moments.M00 - mask.Width / 2.0;
var cy = moments.M01 / moments.M00 - mask.Height / 2.0;
return new Point2d(cx, cy);
}