diff --git a/stardew-access/Game/CurrentPlayer.cs b/stardew-access/Game/CurrentPlayer.cs index f825b0e..820d997 100644 --- a/stardew-access/Game/CurrentPlayer.cs +++ b/stardew-access/Game/CurrentPlayer.cs @@ -1,5 +1,6 @@ using StardewValley; using StardewModdingAPI; +using Microsoft.Xna.Framework; namespace stardew_access.Game { @@ -88,5 +89,33 @@ namespace stardew_access.Game return Game1.player.Money; } + + public static Vector2 getNextTile() + { + int x = Game1.player.GetBoundingBox().Center.X; + int y = Game1.player.GetBoundingBox().Center.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; + } + + x /= Game1.tileSize; + y /= Game1.tileSize; + return new Vector2(x, y); + } } } diff --git a/stardew-access/Game/ReadTile.cs b/stardew-access/Game/ReadTile.cs new file mode 100644 index 0000000..0e2f2dc --- /dev/null +++ b/stardew-access/Game/ReadTile.cs @@ -0,0 +1,295 @@ + + +using Microsoft.Xna.Framework; +using StardewModdingAPI; +using StardewValley; +using StardewValley.Locations; +using StardewValley.TerrainFeatures; + +namespace stardew_access.Game +{ + public class ReadTile + { + public static bool isReadingTile = false; + public static Vector2 prevTile; + + public ReadTile() + { + isReadingTile = false; + } + + public static async void run(bool manuallyTriggered = false) + { + isReadingTile = true; + + try + { + #region Get Next Grab Tile + Vector2 gt = CurrentPlayer.getNextTile(); + int x = (int)gt.X; + int y = (int)gt.Y; + #endregion + + if (Context.IsPlayerFree) + { + if (!manuallyTriggered && prevTile != gt) + { + ScreenReader.prevTextTile = " "; + } + + Dictionary> terrainFeature = Game1.currentLocation.terrainFeatures.FieldDict; + string toSpeak = " "; + + #region Get objects, crops, resource clumps, etc. + if (Game1.currentLocation.isCharacterAtTile(gt) != null) + { + NPC npc = Game1.currentLocation.isCharacterAtTile(gt); + toSpeak = npc.displayName; + } + else if (!Game1.currentLocation.isTilePassable(Game1.player.nextPosition(Game1.player.getDirection()), Game1.viewport)) + { + toSpeak = "Colliding"; + } + else if (Game1.currentLocation.isWaterTile(x, y)) + { + toSpeak = "Water"; + } + else if (Game1.currentLocation.getObjectAtTile(x, y) != null) + { + #region Objects at tile (TODO) + + #endregion + } + else if (terrainFeature.ContainsKey(gt)) + { + #region Terrain Feature + Netcode.NetRef terrain = terrainFeature[gt]; + + if (terrain.Get() is HoeDirt) + { + HoeDirt dirt = (HoeDirt)terrain.Get(); + if (dirt.crop != null) + { + string cropName = Game1.objectInformation[dirt.crop.indexOfHarvest].Split('/')[0]; + toSpeak = $"{cropName}"; + + bool isWatered = dirt.state.Value == HoeDirt.watered; + bool isHarvestable = dirt.readyForHarvest(); + bool isFertilized = dirt.fertilizer.Value != HoeDirt.noFertilizer; + + if (isWatered) + toSpeak = "Watered " + toSpeak; + + if (isFertilized) + toSpeak = "Fertilized " + toSpeak; + + if (isHarvestable) + toSpeak = "Harvestable " + toSpeak; + } + else + { + toSpeak = "Soil"; + bool isWatered = dirt.state.Value == HoeDirt.watered; + bool isFertilized = dirt.fertilizer.Value != HoeDirt.noFertilizer; + + if (isWatered) + toSpeak = "Watered " + toSpeak; + + if (isFertilized) + toSpeak = "Fertilized " + toSpeak; + } + } + else if (terrain.Get() is Bush) + { + toSpeak = "Bush"; + } + else if (terrain.Get() is CosmeticPlant) + { + CosmeticPlant cosmeticPlant = (CosmeticPlant)terrain.Get(); + toSpeak = cosmeticPlant.textureName().ToLower(); + + if (toSpeak.Contains("terrain")) + toSpeak.Replace("terrain", ""); + + if (toSpeak.Contains("feature")) + toSpeak.Replace("feature", ""); + } + else if (terrain.Get() is Flooring) + { + Flooring flooring = (Flooring)terrain.Get(); + bool isPathway = flooring.isPathway.Get(); + bool isSteppingStone = flooring.isSteppingStone.Get(); + + toSpeak = "Flooring"; + + if (isPathway) + toSpeak = "Pathway"; + + if (isSteppingStone) + toSpeak = "Stepping Stone"; + } + else if (terrain.Get() is FruitTree) + { + FruitTree fruitTree = (FruitTree)terrain.Get(); + toSpeak = Game1.objectInformation[fruitTree.treeType].Split('/')[0]; + } + else if (terrain.Get() is Grass) + { + Grass grass = (Grass)terrain.Get(); + toSpeak = "Grass"; + } + else if (terrain.Get() is Tree) + { + Tree tree = (Tree)terrain.Get(); + int treeType = tree.treeType; + int stage = tree.growthStage.Value; + + if (Game1.player.getEffectiveSkillLevel(2) >= 1) + { + toSpeak = Game1.objectInformation[308 + (int)treeType].Split('/')[0]; + } + else if (Game1.player.getEffectiveSkillLevel(2) >= 1 && (int)treeType <= 3) + { + toSpeak = Game1.objectInformation[308 + (int)treeType].Split('/')[0]; + } + else if (Game1.player.getEffectiveSkillLevel(2) >= 1 && (int)treeType == 8) + { + toSpeak = Game1.objectInformation[292 + (int)treeType].Split('/')[0]; + } + + toSpeak = $"{toSpeak}, {stage} stage"; + } + else if (terrain.Get() is Quartz) + { + toSpeak = "Quartz"; + } + else if (terrain.Get() is Leaf) + { + toSpeak = "Leaf"; + } + #endregion + } + else if (isDoorAtTile(x, y)) + toSpeak = "Door"; + else if (isMineLadderAtTile(x, y)) + toSpeak = "Ladder"; + else + { + string? resourceClump = getResourceClumpAtTile(x, y); + if (resourceClump!=null) + toSpeak = resourceClump; + } + #endregion + + #region Narrate toSpeak + if (toSpeak != " ") + if (manuallyTriggered) + ScreenReader.say(toSpeak, true); + else + ScreenReader.sayWithTileQuery(toSpeak, x, y, true); + #endregion + + prevTile = gt; + } + + } + catch (Exception e) + { + MainClass.monitor.Log($"Error in Read Tile:\n{e.Message}\n{e.StackTrace}"); + } + + await Task.Delay(100); + isReadingTile = false; + } + + public static string getObjectNameAtTile(int x, int y) + { + string? toReturn = null; + + StardewValley.Object obj = Game1.currentLocation.getObjectAtTile(x, y); + toReturn = obj.DisplayName; + + // TODO add individual stone narration using parentSheetIndex + // monitor.Log(obj.parentSheetIndex.ToString(), LogLevel.Debug); + if (Game1.objectInformation.ContainsKey(obj.ParentSheetIndex) && toReturn.ToLower().Equals("stone")) + { + string info = Game1.objectInformation[obj.parentSheetIndex]; + if (info.ToLower().Contains("copper")) + toReturn = "Copper " + toReturn; + } + return toReturn; + } + + public static bool isMineLadderAtTile(int x, int y) + { + if (Game1.inMine || Game1.currentLocation is Mine) + { + int index = Game1.currentLocation.Map.GetLayer("Buildings").Tiles[x, y].TileIndex; + + if (index == 173 || index == 174) + return true; + } + + return false; + } + + public static bool isDoorAtTile(int x, int y) + { + Point tilePoint = new Point(x, y); + List> doorList = Game1.currentLocation.doors.ToList(); + + for (int i=0; i < doorList.Count; i++) + { + for(int j = 0; j< doorList[i].Keys.Count; j++) + { + if (doorList[i].Keys.Contains(tilePoint)) + return true; + } + } + + return false; + } + + public static string getResourceClumpAtTile(int x, int y) + { + string? toReturn = null; + + for(int i = 0; i < Game1.currentLocation.resourceClumps.Count; i++) + { + if (Game1.currentLocation.resourceClumps[i].occupiesTile(x, y)) + { + int index = Game1.currentLocation.resourceClumps[i].parentSheetIndex; + + switch (index) + { + case 600: + toReturn = "Large Stump"; + break; + case 602: + toReturn = "Hollow Log"; + break; + case 622: + toReturn = "Meteorite"; + break; + case 752: + case 754: + case 756: + case 758: + toReturn = "Mine Rock"; + break; + case 672: + toReturn = "Boulder"; + break; + default: + toReturn = "Unknown"; + break; + } + + return toReturn; + } + } + + return toReturn; + } + } +} diff --git a/stardew-access/ModEntry.cs b/stardew-access/ModEntry.cs index 2064352..151cd76 100644 --- a/stardew-access/ModEntry.cs +++ b/stardew-access/ModEntry.cs @@ -18,8 +18,7 @@ namespace stardew_access public class MainClass : Mod { private Harmony? harmony; - private static bool isReadingTile = false, readTile = true, snapMouse = true; - private static Vector2 prevTile; + private static bool readTile = true, snapMouse = true; public static IMonitor? monitor; AutoHotkeyEngine ahk; @@ -271,8 +270,8 @@ namespace stardew_access if (snapMouse) SnapMouseToPlayer(); - if(!isReadingTile && readTile) - ReadTile(); + if(!ReadTile.isReadingTile && readTile) + ReadTile.run(); } private void OnButtonPressed(object sender, ButtonPressedEventArgs e) @@ -340,271 +339,5 @@ namespace stardew_access Game1.setMousePosition(x, y); } - - private static async void ReadTile(bool manuallyTriggered = false) - { - isReadingTile = true; - - try - { - #region Get Correct Grab Tile - int x = Game1.player.GetBoundingBox().Center.X; - int y = Game1.player.GetBoundingBox().Center.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; - } - - x /= Game1.tileSize; - y /= Game1.tileSize; - Vector2 gt = new Vector2(x, y); - #endregion - - if (Context.IsPlayerFree) - { - if (!manuallyTriggered && prevTile != gt) - { - ScreenReader.prevTextTile = " "; - } - - Dictionary> terrainFeature = Game1.currentLocation.terrainFeatures.FieldDict; - string toSpeak = " "; - - #region Get objects, crops, resource clumps, etc. - if (Game1.currentLocation.isCharacterAtTile(gt) != null) - { - NPC npc = Game1.currentLocation.isCharacterAtTile(gt); - toSpeak = npc.displayName; - } - else if (!Game1.currentLocation.isTilePassable(Game1.player.nextPosition(Game1.player.getDirection()), Game1.viewport)) - { - toSpeak = "Colliding"; - } - else if (Game1.currentLocation.isWaterTile(x, y)) - { - toSpeak = "Water"; - } - else if (Game1.currentLocation.getObjectAtTile(x, y) != null) - { - #region Objects at tile (TODO) - StardewValley.Object obj = Game1.currentLocation.getObjectAtTile(x, y); - toSpeak = obj.DisplayName; - - // TODO add individual stone narration using parentSheetIndex - // monitor.Log(obj.parentSheetIndex.ToString(), LogLevel.Debug); - if (Game1.objectInformation.ContainsKey(obj.ParentSheetIndex) && toSpeak.ToLower().Equals("stone")) - { - string info = Game1.objectInformation[obj.parentSheetIndex]; - if (info.ToLower().Contains("copper")) - toSpeak = "Copper " + toSpeak; - } - #endregion - } - else if (terrainFeature.ContainsKey(gt)) - { - #region Terrain Feature - Netcode.NetRef terrain = terrainFeature[gt]; - - if (terrain.Get() is HoeDirt) - { - HoeDirt dirt = (HoeDirt)terrain.Get(); - if (dirt.crop != null) - { - string cropName = Game1.objectInformation[dirt.crop.indexOfHarvest].Split('/')[0]; - toSpeak = $"{cropName}"; - - bool isWatered = dirt.state.Value == HoeDirt.watered; - bool isHarvestable = dirt.readyForHarvest(); - bool isFertilized = dirt.fertilizer.Value != HoeDirt.noFertilizer; - - if (isWatered) - toSpeak = "Watered " + toSpeak; - - if (isFertilized) - toSpeak = "Fertilized " + toSpeak; - - if (isHarvestable) - toSpeak = "Harvestable " + toSpeak; - } - else - { - toSpeak = "Soil"; - bool isWatered = dirt.state.Value == HoeDirt.watered; - bool isFertilized = dirt.fertilizer.Value != HoeDirt.noFertilizer; - - if (isWatered) - toSpeak = "Watered " + toSpeak; - - if (isFertilized) - toSpeak = "Fertilized " + toSpeak; - } - } - else if (terrain.Get() is Bush) - { - toSpeak = "Bush"; - } - else if (terrain.Get() is CosmeticPlant) - { - CosmeticPlant cosmeticPlant = (CosmeticPlant)terrain.Get(); - toSpeak = cosmeticPlant.textureName().ToLower(); - - if (toSpeak.Contains("terrain")) - toSpeak.Replace("terrain", ""); - - if (toSpeak.Contains("feature")) - toSpeak.Replace("feature", ""); - } - else if (terrain.Get() is Flooring) - { - Flooring flooring = (Flooring)terrain.Get(); - bool isPathway = flooring.isPathway.Get(); - bool isSteppingStone = flooring.isSteppingStone.Get(); - - toSpeak = "Flooring"; - - if (isPathway) - toSpeak = "Pathway"; - - if (isSteppingStone) - toSpeak = "Stepping Stone"; - } - else if (terrain.Get() is FruitTree) - { - FruitTree fruitTree = (FruitTree)terrain.Get(); - toSpeak = Game1.objectInformation[fruitTree.treeType].Split('/')[0]; - } - else if (terrain.Get() is Grass) - { - Grass grass = (Grass)terrain.Get(); - toSpeak = "Grass"; - } - else if (terrain.Get() is Tree) - { - Tree tree = (Tree)terrain.Get(); - int treeType = tree.treeType; - int stage = tree.growthStage.Value; - - if (Game1.player.getEffectiveSkillLevel(2) >= 1) - { - toSpeak = Game1.objectInformation[308 + (int)treeType].Split('/')[0]; - } - else if (Game1.player.getEffectiveSkillLevel(2) >= 1 && (int)treeType <= 3) - { - toSpeak = Game1.objectInformation[308 + (int)treeType].Split('/')[0]; - } - else if (Game1.player.getEffectiveSkillLevel(2) >= 1 && (int)treeType == 8) - { - toSpeak = Game1.objectInformation[292 + (int)treeType].Split('/')[0]; - } - - toSpeak = $"{toSpeak}, {stage} stage"; - } - else if (terrain.Get() is Quartz) - { - toSpeak = "Quartz"; - } - else if (terrain.Get() is Leaf) - { - toSpeak = "Leaf"; - } - #endregion - } - else - { - #region Resource CLumps - Game1.currentLocation.resourceClumps.ToList().ForEach(clump => - { - if (clump.occupiesTile(x, y)) - { - int index = clump.parentSheetIndex; - - switch (index) - { - case 600: - toSpeak = "Large Stump"; - break; - case 602: - toSpeak = "Hollow Log"; - break; - case 622: - toSpeak = "Meteorite"; - break; - case 752: - case 754: - case 756: - case 758: - toSpeak = "Mine Rock"; - break; - case 672: - toSpeak = "Boulder"; - break; - default: - toSpeak = "Unknown"; - break; - } - return; - } - }); - #endregion - - #region Doors - Game1.currentLocation.doors.ToList().ForEach(item => - { - item.Keys.ToList().ForEach(ydoor => - { - if (Equals(x, ydoor.X) && Equals(y, ydoor.Y)) - { - toSpeak = "Door"; - } - }); - }); - #endregion - - #region Ladder - if (Game1.inMine || Game1.currentLocation is Mine) - { - int index = Game1.currentLocation.Map.GetLayer("Buildings").Tiles[x, y].TileIndex; - - if (index == 173 || index == 174) - toSpeak = "Ladder"; - } - #endregion - } - #endregion - - #region Narrate toSpeak - if (toSpeak != " ") - if (manuallyTriggered) - ScreenReader.say(toSpeak, true); - else - ScreenReader.sayWithTileQuery(toSpeak, x, y, true); - #endregion - - prevTile = gt; - } - - } - catch (Exception e) - { - monitor.Log($"Error in Read Tile:\n{e.Message}\n{e.StackTrace}"); - } - - await Task.Delay(100); - isReadingTile = false; - } } } \ No newline at end of file diff --git a/stardew-access/Patches/GameMenuPatches.cs b/stardew-access/Patches/GameMenuPatches.cs index 5633e61..c78cce8 100644 --- a/stardew-access/Patches/GameMenuPatches.cs +++ b/stardew-access/Patches/GameMenuPatches.cs @@ -287,6 +287,73 @@ namespace stardew_access.Patches return; } + if (__instance.organizeButton != null && __instance.organizeButton.containsPoint(x, y)) + { + string toSpeak = "Organize Button"; + if (itemGrabMenuQueryKey != toSpeak) + { + itemGrabMenuQueryKey = toSpeak; + gameMenuQueryKey = ""; + hoveredItemQueryKey = ""; + ScreenReader.say(toSpeak, true); + } + return; + } + + if (__instance.fillStacksButton != null && __instance.fillStacksButton.containsPoint(x, y)) + { + string toSpeak = "Add to existing stacks button"; + if (itemGrabMenuQueryKey != toSpeak) + { + itemGrabMenuQueryKey = toSpeak; + gameMenuQueryKey = ""; + hoveredItemQueryKey = ""; + ScreenReader.say(toSpeak, true); + } + return; + } + + if (__instance.specialButton != null && __instance.specialButton.containsPoint(x, y)) + { + string toSpeak = "Special Button"; + if (itemGrabMenuQueryKey != toSpeak) + { + itemGrabMenuQueryKey = toSpeak; + gameMenuQueryKey = ""; + hoveredItemQueryKey = ""; + ScreenReader.say(toSpeak, true); + } + return; + } + + if (__instance.colorPickerToggleButton != null && __instance.colorPickerToggleButton.containsPoint(x, y)) + { + + string toSpeak = "Color Picker: " + (__instance.chestColorPicker.visible?"Enabled":"Disabled"); + if (itemGrabMenuQueryKey != toSpeak) + { + itemGrabMenuQueryKey = toSpeak; + gameMenuQueryKey = ""; + hoveredItemQueryKey = ""; + ScreenReader.say(toSpeak, true); + } + return; + } + + if (__instance.junimoNoteIcon != null && __instance.junimoNoteIcon.containsPoint(x, y)) + { + + string toSpeak = "Community Center Button"; + if (itemGrabMenuQueryKey != toSpeak) + { + itemGrabMenuQueryKey = toSpeak; + gameMenuQueryKey = ""; + hoveredItemQueryKey = ""; + ScreenReader.say(toSpeak, true); + } + return; + } + if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y)) { string toSpeak = "Drop Item"; @@ -300,6 +367,27 @@ namespace stardew_access.Patches } return; } + + // FIXME + /*if (__instance.discreteColorPickerCC.Count > 0) { + for (int i = 0; i < __instance.discreteColorPickerCC.Count; i++) + { + if (__instance.discreteColorPickerCC[i].containsPoint(x, y)) + { + MainClass.monitor.Log(i.ToString(), LogLevel.Debug); + string toSpeak = getChestColorName(i); + if (itemGrabMenuQueryKey != toSpeak) + { + itemGrabMenuQueryKey = toSpeak; + gameMenuQueryKey = ""; + hoveredItemQueryKey = ""; + ScreenReader.say(toSpeak, true); + Game1.playSound("sa_drop_item"); + } + return; + } + } + }*/ #endregion #region Narrate the last shipped item if in the shipping bin @@ -345,6 +433,79 @@ namespace stardew_access.Patches } } + // TODO Add color names + private static string getChestColorName(int i) + { + string toReturn = ""; + switch (i) + { + case 0: + toReturn = "Default chest color"; + break; + case 1: + toReturn = "Default chest color"; + break; + case 2: + toReturn = "Default chest color"; + break; + case 3: + toReturn = "Default chest color"; + break; + case 4: + toReturn = "Default chest color"; + break; + case 5: + toReturn = "Default chest color"; + break; + case 6: + toReturn = "Default chest color"; + break; + case 7: + toReturn = "Default chest color"; + break; + case 8: + toReturn = "Default chest color"; + break; + case 9: + toReturn = "Default chest color"; + break; + case 10: + toReturn = "Default chest color"; + break; + case 11: + toReturn = "Default chest color"; + break; + case 12: + toReturn = "Default chest color"; + break; + case 13: + toReturn = "Default chest color"; + break; + case 14: + toReturn = "Default chest color"; + break; + case 15: + toReturn = "Default chest color"; + break; + case 16: + toReturn = "Default chest color"; + break; + case 17: + toReturn = "Default chest color"; + break; + case 18: + toReturn = "Default chest color"; + break; + case 19: + toReturn = "Default chest color"; + break; + case 20: + toReturn = "Default chest color"; + break; + } + return toReturn; + } + internal static void CraftingPagePatch(CraftingPage __instance, CraftingRecipe ___hoverRecipe, int ___currentCraftingPage) { try @@ -591,7 +752,34 @@ namespace stardew_access.Patches ScreenReader.say(toSpeak, true); } return; - } + } + + if (__instance.organizeButton != null && __instance.organizeButton.containsPoint(x, y)) + { + string toSpeak = "Organize Button"; + if (itemGrabMenuQueryKey != toSpeak) + { + itemGrabMenuQueryKey = toSpeak; + gameMenuQueryKey = ""; + hoveredItemQueryKey = ""; + ScreenReader.say(toSpeak, true); + } + return; + } + + if (__instance.junimoNoteIcon != null && __instance.junimoNoteIcon.containsPoint(x, y)) + { + + string toSpeak = "Community Center Button"; + if (itemGrabMenuQueryKey != toSpeak) + { + itemGrabMenuQueryKey = toSpeak; + gameMenuQueryKey = ""; + hoveredItemQueryKey = ""; + ScreenReader.say(toSpeak, true); + } + return; + } #endregion #region Narrate equipment slots