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)