diff --git a/src/TSMapEditor/GameMath/CellMath.cs b/src/TSMapEditor/GameMath/CellMath.cs index faf00c87..ca134a6d 100644 --- a/src/TSMapEditor/GameMath/CellMath.cs +++ b/src/TSMapEditor/GameMath/CellMath.cs @@ -1,4 +1,5 @@ using Microsoft.Xna.Framework; +using System; using TSMapEditor.Models; namespace TSMapEditor.GameMath @@ -75,7 +76,14 @@ public static Point2D CellCoordsFromPixelCoords_2D(Point2D pixelCoords, Map map) return new Point2D(cx, cy); } - public static Point2D CellCoordsFromPixelCoords(Point2D pixelCoords, Map map) + /// + /// Calculates and returns the game-logical coordinates of a cell at given pixel-based coordinates in the world. + /// + /// The pixel-based location. + /// The map. + /// Whether we should "see through" walls in the game world, such as cliffs, + /// allowing us to reach cells that are behind cliffs. + public static Point2D CellCoordsFromPixelCoords(Point2D pixelCoords, Map map, bool seethrough = true) { Point2D coords2D = CellCoordsFromPixelCoords_2D(pixelCoords, map); @@ -85,31 +93,74 @@ public static Point2D CellCoordsFromPixelCoords(Point2D pixelCoords, Map map) Point2D nearestCenterCoords = new Point2D(-1, -1); float nearestDistance = float.MaxValue; + const int threshold = 1; + const int scanSize = 3; + + + // Take isometric perspective into account + Vector2 worldCoordsNonIsometric = new Point2D(pixelCoords.X / 2, pixelCoords.Y).ToXNAVector(); + // Scan all cells that are deemed to be "close enough" to the initial cell // to see if our cursor is on the cell - for (int y = -3; y <= 3; y++) + for (int y = -scanSize; y <= scanSize; y++) { - for (int x = -3; x <= 3; x++) + for (int x = -scanSize; x <= scanSize; x++) { // traverse height - for (int i = 0; i < 14; i++) + for (int i = 0; i <= Constants.MaxMapHeightLevel; i++) { var otherCellCoords = coords2D + new Point2D(x, y) + new Point2D(i, i); var otherCell = map.GetTile(otherCellCoords); if (otherCell != null) { - var tlCoords = CellTopLeftPointFromCellCoords(otherCellCoords, map); var centerCoords = CellCenterPointFromCellCoords_3D(otherCellCoords, map); + // Take isometric perspective into account centerCoords = new Point2D(centerCoords.X / 2, centerCoords.Y); - var twpx = new Point2D(pixelCoords.X / 2, pixelCoords.Y); - float distance = Vector2.Distance(centerCoords.ToXNAVector(), twpx.ToXNAVector()); + float distance = Vector2.Distance(centerCoords.ToXNAVector(), worldCoordsNonIsometric); if (distance <= nearestDistance) { - nearestDistance = distance; - nearestCenterCoords = otherCellCoords; + bool acceptCell = true; + + if (!seethrough) + { + // If seethrough is disabled and the "currently nearest" cell is below the evaluated cell in 2D coords, + // only accept the evaluated cell as nearest if it is significantly closer than the nearest cell. + if (otherCellCoords.X + otherCellCoords.Y < nearestCenterCoords.X + nearestCenterCoords.Y) + { + if (nearestDistance - distance <= threshold) + { + acceptCell = false; + } + } + } + + if (acceptCell) + { + nearestDistance = distance; + nearestCenterCoords = otherCellCoords; + } + } + + // If seethrough is disabled, we need to additionally check if the evaluated cell could be the closest cell at + // any potential height level, if it'd be below the current nearest cell in 2D mode + if (!seethrough && otherCellCoords.X + otherCellCoords.Y > nearestCenterCoords.X + nearestCenterCoords.Y) + { + for (int h = 0; h < otherCell.Level; h++) + { + centerCoords = CellCenterPointFromCellCoords(otherCellCoords, map); + centerCoords = new Point2D(centerCoords.X / 2, centerCoords.Y - Constants.CellHeight * h); + + distance = Vector2.Distance(centerCoords.ToXNAVector(), worldCoordsNonIsometric); + + if (distance <= nearestDistance) + { + nearestDistance = distance; + nearestCenterCoords = otherCellCoords; + } + } } } } diff --git a/src/TSMapEditor/Rendering/MapView.cs b/src/TSMapEditor/Rendering/MapView.cs index d48f628f..fcd4ac44 100644 --- a/src/TSMapEditor/Rendering/MapView.cs +++ b/src/TSMapEditor/Rendering/MapView.cs @@ -1285,7 +1285,7 @@ public override void Update(GameTime gameTime) Point2D cursorMapPoint = GetCursorMapPoint(); Point2D tileCoords = EditorState.Is2DMode ? CellMath.CellCoordsFromPixelCoords_2D(cursorMapPoint, Map) : - CellMath.CellCoordsFromPixelCoords(cursorMapPoint, Map); + CellMath.CellCoordsFromPixelCoords(cursorMapPoint, Map, CursorAction == null || CursorAction.SeeThrough); var tile = Map.GetTile(tileCoords.X, tileCoords.Y); diff --git a/src/TSMapEditor/UI/CursorAction.cs b/src/TSMapEditor/UI/CursorAction.cs index 544736e1..566050a8 100644 --- a/src/TSMapEditor/UI/CursorAction.cs +++ b/src/TSMapEditor/UI/CursorAction.cs @@ -43,6 +43,13 @@ public CursorAction(ICursorActionTarget cursorActionTarget) /// public virtual bool DrawCellCursor => false; + /// + /// Override in derived classes to disable "see-through" cell cursor behaviour. + /// "See-through" behaviour allows the cursor action to reach cells behind "walls" + /// in the game world, such as cliffs. + /// + public virtual bool SeeThrough => true; + public abstract string GetName(); protected Map Map => CursorActionTarget.Map; diff --git a/src/TSMapEditor/UI/CursorActions/HeightActions/FlattenGroundCursorAction.cs b/src/TSMapEditor/UI/CursorActions/HeightActions/FlattenGroundCursorAction.cs index 5e8ad9f3..6798fa02 100644 --- a/src/TSMapEditor/UI/CursorActions/HeightActions/FlattenGroundCursorAction.cs +++ b/src/TSMapEditor/UI/CursorActions/HeightActions/FlattenGroundCursorAction.cs @@ -24,6 +24,8 @@ public FlattenGroundCursorAction(ICursorActionTarget cursorActionTarget) : base( public override bool DrawCellCursor => true; + public override bool SeeThrough => false; + public override void LeftDown(Point2D cellCoords) { var cell = Map.GetTile(cellCoords);