diff --git a/stardew-access/Features/Other.cs b/stardew-access/Features/Other.cs
index a21698f..826c01d 100644
--- a/stardew-access/Features/Other.cs
+++ b/stardew-access/Features/Other.cs
@@ -41,32 +41,6 @@ namespace stardew_access.Features
MainClass.ScreenReader.Say($"{currentLocation.Name} Entered", true);
}
- public static void SnapMouseToPlayer()
- {
- int x = Game1.player.GetBoundingBox().Center.X - Game1.viewport.X;
- int y = Game1.player.GetBoundingBox().Center.Y - Game1.viewport.Y;
-
- int offset = 64;
-
- switch (Game1.player.FacingDirection)
- {
- case 0:
- y -= offset;
- break;
- case 1:
- x += offset;
- break;
- case 2:
- y += offset;
- break;
- case 3:
- x -= offset;
- break;
- }
-
- Game1.setMousePosition(x, y);
- }
-
public static void narrateHudMessages()
{
try
diff --git a/stardew-access/Features/Radar.cs b/stardew-access/Features/Radar.cs
index 86b5e46..5253de9 100644
--- a/stardew-access/Features/Radar.cs
+++ b/stardew-access/Features/Radar.cs
@@ -1,4 +1,4 @@
-using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework;
using StardewValley;
using StardewValley.Objects;
diff --git a/stardew-access/Features/TileViewer.cs b/stardew-access/Features/TileViewer.cs
new file mode 100644
index 0000000..2b07fde
--- /dev/null
+++ b/stardew-access/Features/TileViewer.cs
@@ -0,0 +1,228 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using xTile;
+using StardewValley;
+using StardewValley.Menus;
+using stardew_access.Features;
+
+namespace stardew_access.Features
+{
+
+ ///
+ /// Allows browsing of the map and snapping mouse to tiles with the arrow keys
+ ///
+ public class TileViewer
+ {
+
+ //None of these positions take viewport into account; other functions are responsible later
+ private Vector2 ViewingOffset = Vector2.Zero;
+ private Vector2 relativeOffsetLockPosition = Vector2.Zero;
+ private Boolean relativeOffsetLock = false;
+ private Vector2 prevPlayerPosition = Vector2.Zero, prevFacing = Vector2.Zero;
+
+ private Vector2 PlayerFacingVector
+ {
+ get
+ {
+ switch (Game1.player.FacingDirection)
+ {
+ case 0:
+ return new Vector2(0, -Game1.tileSize);
+ case 1:
+ return new Vector2(Game1.tileSize, 0);
+ case 2:
+ return new Vector2(0, Game1.tileSize);
+ case 3:
+ return new Vector2(-Game1.tileSize, 0);
+ default:
+ return Vector2.Zero;
+ }
+ }
+ }
+
+ private Vector2 PlayerPosition
+ {
+ get
+ {
+ int x = Game1.player.GetBoundingBox().Center.X;
+ int y = Game1.player.GetBoundingBox().Center.Y;
+ return new Vector2(x, y);
+ }
+ }
+
+ ///
+ /// Return the position of the tile cursor in pixels from the upper-left corner of the map.
+ ///
+ /// Vector2
+ public Vector2 GetTileCursorPosition()
+ {
+ Vector2 target = this.PlayerPosition;
+ if (this.relativeOffsetLock)
+ {
+ target += this.relativeOffsetLockPosition;
+ }
+ else
+ {
+ target += this.PlayerFacingVector + this.ViewingOffset;
+ }
+ return target;
+ }
+
+ ///
+ /// Return the tile at the position of the tile cursor.
+ ///
+ /// Vector2
+ public Vector2 GetViewingTile()
+ {
+ Vector2 position = this.GetTileCursorPosition();
+ return new Vector2((int)position.X / Game1.tileSize, (int)position.Y / Game1.tileSize);
+ }
+
+ ///
+ /// Handle keyboard input related to the tile viewer.
+ ///
+ public void HandleInput()
+ {
+ if (MainClass.Config.ToggleRelativeCursorLockKey.JustPressed())
+ {
+ this.relativeOffsetLock = !this.relativeOffsetLock;
+ if (this.relativeOffsetLock)
+ {
+ this.relativeOffsetLockPosition = this.PlayerFacingVector + this.ViewingOffset;
+ }
+ else
+ {
+ this.relativeOffsetLockPosition = Vector2.Zero;
+ }
+ MainClass.ScreenReader.Say("Relative cursor lock " + (this.relativeOffsetLock ? "enabled" : "disabled") + ".", true);
+ }
+ else if (MainClass.Config.TileCursorPreciseUpKey.JustPressed())
+ {
+ this.cursorMoveInput(new Vector2(0, -MainClass.Config.TileCursorPreciseMovementDistance), true);
+ }
+ else if (MainClass.Config.TileCursorPreciseRightKey.JustPressed())
+ {
+ this.cursorMoveInput(new Vector2(MainClass.Config.TileCursorPreciseMovementDistance, 0), true);
+ }
+ else if (MainClass.Config.TileCursorPreciseDownKey.JustPressed())
+ {
+ this.cursorMoveInput(new Vector2(0, MainClass.Config.TileCursorPreciseMovementDistance), true);
+ }
+ else if (MainClass.Config.TileCursorPreciseLeftKey.JustPressed())
+ {
+ this.cursorMoveInput(new Vector2(-MainClass.Config.TileCursorPreciseMovementDistance, 0), true);
+ }
+ else if (MainClass.Config.TileCursorUpKey.JustPressed())
+ {
+ this.cursorMoveInput(new Vector2(0, -Game1.tileSize));
+ }
+ else if (MainClass.Config.TileCursorRightKey.JustPressed())
+ {
+ this.cursorMoveInput(new Vector2(Game1.tileSize, 0));
+ }
+ else if (MainClass.Config.TileCursorDownKey.JustPressed())
+ {
+ this.cursorMoveInput(new Vector2(0, Game1.tileSize));
+ }
+ else if (MainClass.Config.TileCursorLeftKey.JustPressed())
+ {
+ this.cursorMoveInput(new Vector2(-Game1.tileSize, 0));
+ }
+ }
+
+ private void cursorMoveInput(Vector2 delta, Boolean precise = false)
+ {
+ if (!tryMoveTileView(delta)) return;
+ Vector2 position = this.GetTileCursorPosition();
+ Vector2 tile = this.GetViewingTile();
+ String? name = TileInfo.getNameAtTile(tile);
+ if (name == null)
+ {
+ // Report if a tile is empty or blocked if there is nothing on it
+ if (TileInfo.isCollidingAtTile((int)tile.X, (int)tile.Y))
+ {
+ name = "blocked";
+ }
+ else
+ {
+ name = "empty";
+ }
+ }
+ if (precise)
+ {
+ MainClass.ScreenReader.Say($"{name}, {position.X}, {position.Y}", true);
+ }
+ else
+ {
+ MainClass.ScreenReader.Say($"{name}, {(int)(position.X / Game1.tileSize)}, {(int)(position.Y / Game1.tileSize)}", true);
+ }
+ }
+
+ private bool tryMoveTileView(Vector2 delta)
+ {
+ Vector2 dest = this.GetTileCursorPosition() + delta;
+ if (!isPositionOnMap(dest)) return false;
+ if ((MainClass.Config.LimitTileCursorToScreen && Utility.isOnScreen(dest, 0)) || !MainClass.Config.LimitTileCursorToScreen)
+ {
+ if (this.relativeOffsetLock)
+ {
+ this.relativeOffsetLockPosition += delta;
+ }
+ else
+ {
+ this.ViewingOffset += delta;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void SnapMouseToPlayer()
+ {
+ Vector2 cursorPosition = this.GetTileCursorPosition();
+ if (allowMouseSnap(cursorPosition))
+ // Must account for viewport here
+ Game1.setMousePosition((int)cursorPosition.X - Game1.viewport.X, (int)cursorPosition.Y - Game1.viewport.Y);
+ }
+
+ ///
+ /// Handle tile viewer logic.
+ ///
+ public void update()
+ {
+ //Reset the viewing cursor to the player when they turn or move. This will not reset the locked offset relative cursor position.
+ if (this.prevFacing != this.PlayerFacingVector || this.prevPlayerPosition != this.PlayerPosition)
+ {
+ this.ViewingOffset = Vector2.Zero;
+ }
+ this.prevFacing = this.PlayerFacingVector;
+ this.prevPlayerPosition = this.PlayerPosition;
+ if (MainClass.Config.SnapMouse)
+ this.SnapMouseToPlayer();
+ }
+
+ private static bool allowMouseSnap(Vector2 point)
+ {
+ // Utility.isOnScreen treats a vector as a pixel position, not a tile position
+ if (!Utility.isOnScreen(point, 0)) return false;
+
+ //prevent mousing over the toolbar or any other UI component with the tile cursor
+ foreach (IClickableMenu menu in Game1.onScreenMenus)
+ {
+ //must account for viewport here
+ if (menu.isWithinBounds((int)point.X - Game1.viewport.X, (int)point.Y - Game1.viewport.Y)) return false;
+ }
+ return true;
+ }
+
+ private static bool isPositionOnMap(Vector2 position)
+ {
+ //position does not take viewport into account since the entire map needs to be checked.
+ Map map = Game1.currentLocation.map;
+ if (position.X < 0 || position.X > map.Layers[0].DisplayWidth) return false;
+ if (position.Y < 0 || position.Y > map.Layers[0].DisplayHeight) return false;
+ return true;
+ }
+ }
+}
diff --git a/stardew-access/ModConfig.cs b/stardew-access/ModConfig.cs
index 98c19df..479c25d 100644
--- a/stardew-access/ModConfig.cs
+++ b/stardew-access/ModConfig.cs
@@ -25,7 +25,19 @@ namespace stardew_access
public KeybindList TimeNSeasonKey { get; set; } = KeybindList.Parse("Q");
public KeybindList ReadTileKey { get; set; } = KeybindList.Parse("J");
public KeybindList ReadStandingTileKey { get; set; } = KeybindList.Parse("LeftAlt + J");
+ public bool LimitTileCursorToScreen { get; set; } = false;
+ public int TileCursorPreciseMovementDistance { get; set; } = 8;
+ //Tile viewer keys
+ public KeybindList TileCursorUpKey { get; set; } = KeybindList.Parse("Up");
+ public KeybindList TileCursorRightKey { get; set; } = KeybindList.Parse("Right");
+ public KeybindList TileCursorDownKey { get; set; } = KeybindList.Parse("Down");
+ public KeybindList TileCursorLeftKey { get; set; } = KeybindList.Parse("Left");
+ public KeybindList TileCursorPreciseUpKey { get; set; } = KeybindList.Parse("LeftShift + Up");
+ public KeybindList TileCursorPreciseRightKey { get; set; } = KeybindList.Parse("LeftShift + Right");
+ public KeybindList TileCursorPreciseDownKey { get; set; } = KeybindList.Parse("LeftShift + Down");
+ public KeybindList TileCursorPreciseLeftKey { get; set; } = KeybindList.Parse("LeftShift + Left");
+ public KeybindList ToggleRelativeCursorLockKey { get; set; } = KeybindList.Parse("L");
#endregion
// TODO Add the exclusion and focus list too
diff --git a/stardew-access/ModEntry.cs b/stardew-access/ModEntry.cs
index 59dbb2f..28776c4 100644
--- a/stardew-access/ModEntry.cs
+++ b/stardew-access/ModEntry.cs
@@ -20,6 +20,7 @@ namespace stardew_access
private static StaticTiles? sTiles;
private static IScreenReader? screenReader;
private static IModHelper? modHelper;
+ private static TileViewer? tileViewer;
internal static ModConfig Config { get => config; set => config = value; }
public static IModHelper? ModHelper { get => modHelper; }
@@ -62,6 +63,17 @@ namespace stardew_access
set => screenReader = value;
}
+
+ public static TileViewer TileViewer
+ {
+ get
+ {
+ if (tileViewer == null)
+ tileViewer = new TileViewer();
+ return tileViewer;
+ }
+ }
+
#endregion
/*********
@@ -130,8 +142,8 @@ namespace stardew_access
// Narrate current location's name
Other.narrateCurrentLocation();
- if (Config.SnapMouse)
- Other.SnapMouseToPlayer();
+ //handle TileCursor update logic
+ TileViewer.update();
if (!ReadTile.isReadingTile && Config.ReadTile)
{
@@ -256,6 +268,9 @@ namespace stardew_access
ReadTile.run(manuallyTriggered: true);
return;
}
+
+ // Tile viewing cursor keys
+ TileViewer.HandleInput();
}
public static void ErrorLog(string message)