diff --git a/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs b/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs
index 748a7648..8e5ea498 100644
--- a/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs
+++ b/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs
@@ -894,7 +894,7 @@ public class TpTask
if (matchRect == null)
{
Logger.LogWarning("切换区域失败:{Country}", areaName);
- if (areaName == MapTypes.TheChasm.GetDescription() || areaName == MapTypes.Enkanomiya.GetDescription())
+ if (areaName == MapTypes.TheChasm.GetDescription() || areaName == MapTypes.Enkanomiya.GetDescription() || areaName == MapTypes.SeaOfBygoneEras.GetDescription())
{
throw new Exception($"切换独立地图区域[{areaName}]失败");
}
diff --git a/BetterGenshinImpact/GameTask/Common/Map/Maps/Base/DisplayMapTypes.cs b/BetterGenshinImpact/GameTask/Common/Map/Maps/Base/DisplayMapTypes.cs
index d8978d11..98b5cb3f 100644
--- a/BetterGenshinImpact/GameTask/Common/Map/Maps/Base/DisplayMapTypes.cs
+++ b/BetterGenshinImpact/GameTask/Common/Map/Maps/Base/DisplayMapTypes.cs
@@ -13,4 +13,7 @@ public enum DisplayMapTypes
[Description("渊下宫")]
Enkanomiya,
+
+ [Description("旧日之海")]
+ SeaOfBygoneEras,
}
\ No newline at end of file
diff --git a/BetterGenshinImpact/GameTask/Common/Map/Maps/SeaOfBygoneErasMap.cs b/BetterGenshinImpact/GameTask/Common/Map/Maps/SeaOfBygoneErasMap.cs
index 7abd52d6..b1fceb8a 100644
--- a/BetterGenshinImpact/GameTask/Common/Map/Maps/SeaOfBygoneErasMap.cs
+++ b/BetterGenshinImpact/GameTask/Common/Map/Maps/SeaOfBygoneErasMap.cs
@@ -1,26 +1,47 @@
-using BetterGenshinImpact.GameTask.Common.Map.Maps.Base;
+using BetterGenshinImpact.Core.Config;
+using BetterGenshinImpact.GameTask.Common.Map.Maps.Base;
using OpenCvSharp;
+using Microsoft.Extensions.Logging;
+using BetterGenshinImpact.Core.Recognition.OpenCv;
+using System.Collections.Generic;
+using System.Linq;
+using System;
+using Newtonsoft.Json.Linq;
+
namespace BetterGenshinImpact.GameTask.Common.Map.Maps;
///
/// 旧日之海
-/// 从3x4改成了1x2
-/// 大地图都是半黑的,传送可能有问题
///
public class SeaOfBygoneErasMap : SceneBaseMap
{
#region 地图参数
- static readonly int GameMapRows = 1; // 游戏坐标下地图块的行数
- static readonly int GameMapCols = 2; // 游戏坐标下地图块的列数
- static readonly int GameMapUpRows = 0; // 游戏坐标下 左上角离地图原点的行数(注意原点在块的右下角) TODO 没找到
- static readonly int GameMapLeftCols = 0; // 游戏坐标下 左上角离地图原点的列数(注意原点在块的右下角) TODO 没找到
+ static readonly int GameMapRows = 3; // 游戏坐标下地图块的行数
+ static readonly int GameMapCols = 4; // 游戏坐标下地图块的列数
+ static readonly int GameMapUpRows = 2; // 游戏坐标下 左上角离地图原点的行数(注意原点在块的右下角)
+ static readonly int GameMapLeftCols = 5; // 游戏坐标下 左上角离地图原点的列数(注意原点在块的右下角)
#endregion 地图参数
static readonly int SeaOfBygoneErasMapImageBlockWidth = 1024;
+ private static Mat TeleportTemplate;
+ private static Mat TeleportTemplateMask;
+ private List MapTeleports;
+
+ static SeaOfBygoneErasMap()
+ {
+ Mat img = GameTaskManager.LoadAssetImage("QuickTeleport", "TeleportTransparentBackground.png", ImreadModes.Unchanged);
+
+ TeleportTemplate = new Mat();
+ Cv2.CvtColor(img, TeleportTemplate, ColorConversionCodes.BGRA2GRAY);
+
+ Mat[] channels = Cv2.Split(img);
+ TeleportTemplateMask = channels[3];
+ }
+
public SeaOfBygoneErasMap() : base(type: MapTypes.SeaOfBygoneEras,
mapSize: new Size(GameMapCols * SeaOfBygoneErasMapImageBlockWidth, GameMapRows * SeaOfBygoneErasMapImageBlockWidth),
mapOriginInImageCoordinate: new Point2f((GameMapLeftCols + 1) * SeaOfBygoneErasMapImageBlockWidth, (GameMapUpRows + 1) * SeaOfBygoneErasMapImageBlockWidth),
@@ -28,7 +49,222 @@ public class SeaOfBygoneErasMap : SceneBaseMap
splitRow: 0,
splitCol: 0)
{
+ ExtractAndSaveFeature(Global.Absolute("Assets/Map/SeaOfBygoneEras/SeaOfBygoneEras_0_1024.png"));
Layers = BaseMapLayer.LoadLayers(this);
+
+ var mapTeleports = new List();
+ var tpJson = System.IO.File.ReadAllText(Global.Absolute(@"GameTask\AutoTrackPath\Assets\tp.json"));
+
+ JArray j = JArray.Parse(tpJson);
+ foreach (JObject i in j)
+ {
+ var sceneId = i["sceneId"];
+ if (sceneId != null && (int)sceneId == 11)
+ {
+ foreach (var p in i["points"]!)
+ {
+ if ((string)p["type"]! != "Teleport")
+ {
+ continue;
+ }
+ var x = (float)p["position"]![2]!;
+ var y = (float)p["position"]![0]!;
+ var (x1, y1) = ConvertGenshinMapCoordinatesToImageCoordinates(x, y);
+ mapTeleports.Add(new Point(x1, y1));
+ }
+ }
+ }
+ mapTeleports = mapTeleports.OrderBy(i => i.X).ThenBy(i => i.Y).ToList();
+ MapTeleports = mapTeleports;
+ }
+
+ public override Point2f GetBigMapPosition(Mat greyBigMapMat)
+ {
+ var rect = GetBigMapRectByTeleports(greyBigMapMat);
+ if (rect != default)
+ {
+ return rect.GetCenterPoint();
+ }
+
+ return base.GetBigMapPosition(greyBigMapMat);
+ }
+
+ public override Rect GetBigMapRect(Mat greyBigMapMat)
+ {
+ var rect = GetBigMapRectByTeleports(greyBigMapMat);
+ if (rect != default)
+ {
+ return rect;
+ }
+
+ return base.GetBigMapRect(greyBigMapMat);
+ }
+
+ private Rect GetBigMapRectByTeleports(Mat greyBigMapMat)
+ {
+ // It's fine to miss some, but definitely no false positive results.
+ const double threshold = 0.99;
+ using Mat result = new Mat();
+ Cv2.MatchTemplate(greyBigMapMat, TeleportTemplate, result, TemplateMatchModes.CCorrNormed, TeleportTemplateMask);
+
+ var teleportPoints = new List();
+
+ // Step 1: Get all teleport positions from current screenshot
+ for (int i = 1; i < result.Rows - 1; ++i)
+ {
+ for (int j = 1; j < result.Cols - 1; ++j)
+ {
+ float val = result.At(i, j);
+
+ if (val > threshold)
+ {
+ if (val >= result.At(i - 1, j - 1) &&
+ val >= result.At(i - 1, j) &&
+ val >= result.At(i - 1, j + 1) &&
+ val >= result.At(i, j - 1) &&
+ val >= result.At(i, j + 1) &&
+ val >= result.At(i + 1, j - 1) &&
+ val >= result.At(i + 1, j) &&
+ val >= result.At(i + 1, j + 1))
+ {
+ var newPoint = new Point(j, i);
+ bool tooClose = false;
+ foreach (var p in teleportPoints)
+ {
+ if (p.DistanceTo(newPoint) < 50)
+ {
+ tooClose = true;
+ break;
+ }
+ }
+ if (!tooClose)
+ {
+ teleportPoints.Add(newPoint);
+ }
+ }
+ }
+ }
+ }
+
+ if (teleportPoints.Count < 2)
+ {
+ return default;
+ }
+ teleportPoints = teleportPoints.OrderBy(i => i.X).ThenBy(i => i.Y).ToList();
+ /*
+ foreach (var p in teleportPoints) {
+ Logger.LogInformation("Teleport point: {a}", p);
+ }
+ Logger.LogInformation("Total telepoints: {c}", teleportPoints.Count);
+ */
+
+ Func GetAngleOfTwoPoints = (p0, p1) =>
+ {
+ double deltaX = p0.X - p1.X;
+ int deltaY = p0.Y - p1.Y;
+ if (deltaY == 0) { return 90; }
+ var val = Math.Atan(deltaX / deltaY);
+ return val / Math.PI * 180;
+ };
+
+ // Step 2: find a diagonal determined by two of the teleports, let's call these two teleports reference points
+ Point rp0 = new Point();
+ Point rp1 = new Point();
+ double refAngle = 0.0;
+ {
+ double minAngleDiff = 180;
+ for (int i = 0; i < teleportPoints.Count; ++i)
+ {
+ for (int j = i + 1; j < teleportPoints.Count; ++j)
+ {
+ var p0 = teleportPoints[i];
+ var p1 = teleportPoints[j];
+ var angle = GetAngleOfTwoPoints(p0, p1);
+ if (Math.Abs(Math.Abs(angle) - 45) < minAngleDiff)
+ {
+ rp0 = p0;
+ rp1 = p1;
+ minAngleDiff = Math.Abs(Math.Abs(angle) - 45);
+ }
+ }
+ }
+ refAngle = GetAngleOfTwoPoints(rp0, rp1);
+ // Logger.LogInformation("Reference points {a} and {b}, Angle {c}", rp0, rp1, refAngle);
+ }
+
+ {
+ /*
+ var debugMat = new Mat();
+ Cv2.CvtColor(greyBigMapMat, debugMat, ColorConversionCodes.GRAY2BGR);
+ foreach (var p in teleportPoints)
+ {
+ Cv2.DrawMarker(debugMat, p, new Scalar(255, 0, 0), MarkerTypes.Cross, 20, 2);
+ }
+ Cv2.DrawMarker(debugMat, rp0, new Scalar(0, 255, 0), MarkerTypes.TriangleUp, 20, 2);
+ Cv2.DrawMarker(debugMat, rp1, new Scalar(0, 255, 0), MarkerTypes.TriangleUp, 20, 2);
+ Cv2.ImWrite(((DateTimeOffset)DateTime.UtcNow).ToUnixTimeMilliseconds().ToString() + ".png", debugMat);
+ */
+ }
+
+ // Step 3: For all diagonals determined by pairs of teleports on this map.
+ var minDeviation = double.MaxValue;
+ var transformParamScale = 0.0;
+ var transformParamDeltaX = 0.0;
+ var transformParamDeltaY = 0.0;
+ for (int i = 0; i < MapTeleports.Count; ++i)
+ {
+ for (int j = i + 1; j < MapTeleports.Count; ++j)
+ {
+ var mp0 = MapTeleports[i];
+ var mp1 = MapTeleports[j];
+ var angle = GetAngleOfTwoPoints(mp0, mp1);
+ if (Math.Abs(angle - refAngle) < 5)
+ {
+ // Step 4: Assuming this pair corresponds to the reference points
+ var mpDist = mp0.DistanceTo(mp1);
+ var rpDist = rp0.DistanceTo(rp1);
+ var scale = mpDist / rpDist;
+ var deltaX = mp0.X - rp0.X * scale;
+ var deltaY = mp0.Y - rp0.Y * scale;
+
+ Func transformPoint = i =>
+ {
+ return new Point(i.X * scale + deltaX, i.Y * scale + deltaY);
+ };
+
+ var transformedPoints = teleportPoints.Select(i => transformPoint(i)).ToList();
+ // Step 5: Check how close the fit is
+ double totalDeviation = 0;
+ foreach (var p in transformedPoints)
+ {
+ var minDist = MapTeleports.Select(i => i.DistanceTo(p)).Min();
+ totalDeviation += minDist;
+ }
+ if (totalDeviation < minDeviation)
+ {
+ minDeviation = totalDeviation;
+ transformParamScale = scale;
+ transformParamDeltaX = deltaX;
+ transformParamDeltaY = deltaY;
+ }
+ }
+ }
+ }
+ // Logger.LogInformation("Min deviation: {d}", minDeviation);
+ if (minDeviation < 200)
+ {
+ Func transformPoint = i =>
+ {
+ return new Point(i.X * transformParamScale + transformParamDeltaX, i.Y * transformParamScale + transformParamDeltaY);
+ };
+ var pTopLeft = transformPoint(new Point(0, 0));
+ var pBottomRight = transformPoint(new Point(greyBigMapMat.Width, greyBigMapMat.Height));
+ // Logger.LogInformation("Rect: {a}, {b}", pTopLeft, pBottomRight);
+ return new Rect(pTopLeft.X, pTopLeft.Y, pBottomRight.X - pTopLeft.X, pBottomRight.Y - pTopLeft.Y);
+ }
+
+
+ return default;
}
}
\ No newline at end of file
diff --git a/BetterGenshinImpact/GameTask/QuickTeleport/Assets/1920x1080/TeleportTransparentBackground.png b/BetterGenshinImpact/GameTask/QuickTeleport/Assets/1920x1080/TeleportTransparentBackground.png
new file mode 100644
index 00000000..b0bf883d
Binary files /dev/null and b/BetterGenshinImpact/GameTask/QuickTeleport/Assets/1920x1080/TeleportTransparentBackground.png differ
diff --git a/BetterGenshinImpact/ViewModel/Windows/MapViewerViewModel.cs b/BetterGenshinImpact/ViewModel/Windows/MapViewerViewModel.cs
index 20605a68..a5bd8fe6 100644
--- a/BetterGenshinImpact/ViewModel/Windows/MapViewerViewModel.cs
+++ b/BetterGenshinImpact/ViewModel/Windows/MapViewerViewModel.cs
@@ -98,6 +98,10 @@ public partial class MapViewerViewModel : ObservableObject
{
_mapImage = new Mat(Global.Absolute(@"Assets/Map/Enkanomiya/Enkanomiya_0_1024.png"));
}
+ else if (mapName == MapTypes.SeaOfBygoneEras.ToString())
+ {
+ _mapImage = new Mat(Global.Absolute(@"Assets/Map/SeaOfBygoneEras/SeaOfBygoneEras_0_1024.png"));
+ }
else
{
throw new Exception("暂时不支持展示路径的地图类型:" + mapName);