Merge remote-tracking branch 'sda/master' into update

master
erion 2023-01-20 14:26:52 +01:00
commit 4af19aeb7a
61 changed files with 7983 additions and 2532 deletions

5
.gitignore vendored
View File

@ -3,7 +3,12 @@
## ##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
*.dll
stardew-access/assets/custom-tiles.json
.vscode/* .vscode/*
.git-old/
bin/
# User-specific files # User-specific files
*.rsuser *.rsuser

View File

@ -16,13 +16,13 @@ namespace stardew_access.ScreenReader
/// <param name="center">The starting point.</param> /// <param name="center">The starting point.</param>
/// <param name="limit">The limiting factor or simply radius of the search area.</param> /// <param name="limit">The limiting factor or simply radius of the search area.</param>
/// <returns>A dictionary with all the detected tiles along with the name of the object on it and it's category.</returns> /// <returns>A dictionary with all the detected tiles along with the name of the object on it and it's category.</returns>
public Dictionary<Vector2, (string, string)> SearchNearbyTiles(Vector2 center, int limit) public Dictionary<Vector2, (string name, string category)> SearchNearbyTiles(Vector2 center, int limit)
{ {
/* /*
* How to use the Dictionary to get the name and category of a tile:- * How to use the Dictionary to get the name and category of a tile:-
* *
* string? objectName = detectedTiles.GetValueOrDefault(center).Item1; * string tileName = detectedTiles.GetValueOrDefault(tilePosition).name;
* string? objectCategory = detectedTiles.GetValueOrDefault(center).Item2; * string tileCategory = detectedTiles.GetValueOrDefault(tilePosition).category;
* *
* Here detectedTiles is the Dictionary returned by this method * Here detectedTiles is the Dictionary returned by this method
*/ */
@ -30,14 +30,32 @@ namespace stardew_access.ScreenReader
return new Radar().SearchNearbyTiles(center, limit, false); return new Radar().SearchNearbyTiles(center, limit, false);
} }
/// <summary>
/// Search the entire location using Breadth First Search algorithm(BFS).
/// </summary>
/// <returns>A dictionary with all the detected tiles along with the name of the object on it and it's category.</returns>
public Dictionary<Vector2, (string name, string category)> SearchLocation()
{
/*
* How to use the Dictionary to get the name and category of a tile:-
*
* string tileName = detectedTiles.GetValueOrDefault(tilePosition).name;
* string tileCategory = detectedTiles.GetValueOrDefault(tilePosition).category;
*
* Here detectedTiles is the Dictionary returned by this method
*/
return new Radar().SearchLocation();
}
/// <summary> /// <summary>
/// Check the tile for any object /// Check the tile for any object
/// </summary> /// </summary>
/// <param name="tile">The tile where we want to check the name and category of object if any</param> /// <param name="tile">The tile where we want to check the name and category of object if any</param>
/// <returns>Name of the object as the first item (Item1) and category as the second item (Item2). Returns null if no object found.</returns> /// <returns>Name of the object as the first item (name) and category as the second item (category). Returns null if no object found.</returns>
public (string?, string?) GetNameWithCategoryNameAtTile(Vector2 tile) public (string? name, string? category) GetNameWithCategoryNameAtTile(Vector2 tile)
{ {
return ReadTile.getNameWithCategoryNameAtTile(tile); return TileInfo.getNameWithCategoryNameAtTile(tile);
} }
/// <summary> /// <summary>
@ -47,7 +65,7 @@ namespace stardew_access.ScreenReader
/// <returns>Name of the object. Returns null if no object found.</returns> /// <returns>Name of the object. Returns null if no object found.</returns>
public string? GetNameAtTile(Vector2 tile) public string? GetNameAtTile(Vector2 tile)
{ {
return ReadTile.getNameAtTile(tile); return TileInfo.getNameAtTile(tile);
} }
/// <summary>Speaks the text via the loaded screen reader (if any).</summary> /// <summary>Speaks the text via the loaded screen reader (if any).</summary>
@ -55,10 +73,10 @@ namespace stardew_access.ScreenReader
/// <param name="interrupt">Whether to skip the currently speaking text or not.</param> /// <param name="interrupt">Whether to skip the currently speaking text or not.</param>
public void Say(String text, Boolean interrupt) public void Say(String text, Boolean interrupt)
{ {
if (MainClass.GetScreenReader() == null) if (MainClass.ScreenReader == null)
return; return;
MainClass.GetScreenReader().Say(text, interrupt); MainClass.ScreenReader.Say(text, interrupt);
} }
/// <summary>Speaks the text via the loaded screen reader (if any). /// <summary>Speaks the text via the loaded screen reader (if any).
@ -67,10 +85,10 @@ namespace stardew_access.ScreenReader
/// <param name="interrupt">Whether to skip the currently speaking text or not.</param> /// <param name="interrupt">Whether to skip the currently speaking text or not.</param>
public void SayWithChecker(String text, Boolean interrupt) public void SayWithChecker(String text, Boolean interrupt)
{ {
if (MainClass.GetScreenReader() == null) if (MainClass.ScreenReader == null)
return; return;
MainClass.GetScreenReader().SayWithChecker(text, interrupt); MainClass.ScreenReader.SayWithChecker(text, interrupt);
} }
/// <summary>Speaks the text via the loaded screen reader (if any). /// <summary>Speaks the text via the loaded screen reader (if any).
@ -80,10 +98,10 @@ namespace stardew_access.ScreenReader
/// <param name="interrupt">Whether to skip the currently speaking text or not.</param> /// <param name="interrupt">Whether to skip the currently speaking text or not.</param>
public void SayWithMenuChecker(String text, Boolean interrupt) public void SayWithMenuChecker(String text, Boolean interrupt)
{ {
if (MainClass.GetScreenReader() == null) if (MainClass.ScreenReader == null)
return; return;
MainClass.GetScreenReader().SayWithMenuChecker(text, interrupt); MainClass.ScreenReader.SayWithMenuChecker(text, interrupt);
} }
/// <summary>Speaks the text via the loaded screen reader (if any). /// <summary>Speaks the text via the loaded screen reader (if any).
@ -93,10 +111,10 @@ namespace stardew_access.ScreenReader
/// <param name="interrupt">Whether to skip the currently speaking text or not.</param> /// <param name="interrupt">Whether to skip the currently speaking text or not.</param>
public void SayWithChatChecker(String text, Boolean interrupt) public void SayWithChatChecker(String text, Boolean interrupt)
{ {
if (MainClass.GetScreenReader() == null) if (MainClass.ScreenReader == null)
return; return;
MainClass.GetScreenReader().SayWithChatChecker(text, interrupt); MainClass.ScreenReader.SayWithChatChecker(text, interrupt);
} }
/// <summary>Speaks the text via the loaded screen reader (if any). /// <summary>Speaks the text via the loaded screen reader (if any).
@ -108,10 +126,10 @@ namespace stardew_access.ScreenReader
/// <param name="interrupt">Whether to skip the currently speaking text or not.</param> /// <param name="interrupt">Whether to skip the currently speaking text or not.</param>
public void SayWithTileQuery(String text, int x, int y, Boolean interrupt) public void SayWithTileQuery(String text, int x, int y, Boolean interrupt)
{ {
if (MainClass.GetScreenReader() == null) if (MainClass.ScreenReader == null)
return; return;
MainClass.GetScreenReader().SayWithTileQuery(text, x, y, interrupt); MainClass.ScreenReader.SayWithTileQuery(text, x, y, interrupt);
} }
} }

View File

@ -11,49 +11,66 @@ namespace stardew_access
{ {
internal static void Initialize() internal static void Initialize()
{ {
IModHelper helper = MainClass.ModHelper; //TODO organise this, create separate method for all commands
IModHelper? helper = MainClass.ModHelper;
if (helper == null)
return;
#region Read Tile
helper.ConsoleCommands.Add("readtile", "Toggle read tile feature.", (string commmand, string[] args) => helper.ConsoleCommands.Add("readtile", "Toggle read tile feature.", (string commmand, string[] args) =>
{ {
MainClass.readTile = !MainClass.readTile; MainClass.Config.ReadTile = !MainClass.Config.ReadTile;
helper.WriteConfig(MainClass.Config);
MainClass.GetMonitor().Log("Read Tile is " + (MainClass.readTile ? "on" : "off"), LogLevel.Info); MainClass.InfoLog("Read Tile is " + (MainClass.Config.ReadTile ? "on" : "off"));
});
helper.ConsoleCommands.Add("flooring", "Toggle flooring in read tile.", (string commmand, string[] args) =>
{
MainClass.Config.ReadFlooring = !MainClass.Config.ReadFlooring;
helper.WriteConfig(MainClass.Config);
MainClass.InfoLog("Flooring is " + (MainClass.Config.ReadFlooring ? "on" : "off"));
}); });
helper.ConsoleCommands.Add("snapmouse", "Toggle snap mouse feature.", (string commmand, string[] args) => helper.ConsoleCommands.Add("watered", "Toggle speaking watered or unwatered for crops.", (string commmand, string[] args) =>
{ {
MainClass.snapMouse = !MainClass.snapMouse; MainClass.Config.WateredToggle = !MainClass.Config.WateredToggle;
helper.WriteConfig(MainClass.Config);
MainClass.GetMonitor().Log("Snap Mouse is " + (MainClass.snapMouse ? "on" : "off"), LogLevel.Info); MainClass.InfoLog("Watered toggle is " + (MainClass.Config.WateredToggle ? "on" : "off"));
});
helper.ConsoleCommands.Add("radar", "Toggle radar feature.", (string commmand, string[] args) =>
{
MainClass.radar = !MainClass.radar;
MainClass.GetMonitor().Log("Radar " + (MainClass.radar ? "on" : "off"), LogLevel.Info);
}); });
#endregion
#region Radar Feature #region Radar Feature
helper.ConsoleCommands.Add("radar", "Toggle radar feature.", (string commmand, string[] args) =>
{
MainClass.Config.Radar = !MainClass.Config.Radar;
helper.WriteConfig(MainClass.Config);
MainClass.InfoLog("Radar " + (MainClass.Config.Radar ? "on" : "off"));
});
helper.ConsoleCommands.Add("rdebug", "Toggle debugging in radar feature.", (string commmand, string[] args) => helper.ConsoleCommands.Add("rdebug", "Toggle debugging in radar feature.", (string commmand, string[] args) =>
{ {
MainClass.radarDebug = !MainClass.radarDebug; MainClass.radarDebug = !MainClass.radarDebug;
MainClass.GetMonitor().Log("Radar debugging " + (MainClass.radarDebug ? "on" : "off"), LogLevel.Info); MainClass.InfoLog("Radar debugging " + (MainClass.radarDebug ? "on" : "off"));
}); });
helper.ConsoleCommands.Add("rstereo", "Toggle stereo sound in radar feature.", (string commmand, string[] args) => helper.ConsoleCommands.Add("rstereo", "Toggle stereo sound in radar feature.", (string commmand, string[] args) =>
{ {
MainClass.radarStereoSound = !MainClass.radarStereoSound; MainClass.Config.RadarStereoSound = !MainClass.Config.RadarStereoSound;
helper.WriteConfig(MainClass.Config);
MainClass.GetMonitor().Log("Stereo sound is " + (MainClass.radarStereoSound ? "on" : "off"), LogLevel.Info); MainClass.InfoLog("Stereo sound is " + (MainClass.Config.RadarStereoSound ? "on" : "off"));
}); });
helper.ConsoleCommands.Add("rfocus", "Toggle focus mode in radar feature.", (string commmand, string[] args) => helper.ConsoleCommands.Add("rfocus", "Toggle focus mode in radar feature.", (string commmand, string[] args) =>
{ {
bool focus = MainClass.RadarFeature.ToggleFocus(); bool focus = MainClass.RadarFeature.ToggleFocus();
MainClass.GetMonitor().Log("Focus mode is " + (focus ? "on" : "off"), LogLevel.Info); MainClass.InfoLog("Focus mode is " + (focus ? "on" : "off"));
}); });
helper.ConsoleCommands.Add("rdelay", "Set the delay of radar feature in milliseconds.", (string commmand, string[] args) => helper.ConsoleCommands.Add("rdelay", "Set the delay of radar feature in milliseconds.", (string commmand, string[] args) =>
@ -72,19 +89,19 @@ namespace stardew_access
{ {
MainClass.RadarFeature.delay = delay; MainClass.RadarFeature.delay = delay;
if (delay >= 1000) if (delay >= 1000)
MainClass.GetMonitor().Log($"Delay set to {MainClass.RadarFeature.delay} milliseconds.", LogLevel.Info); MainClass.InfoLog($"Delay set to {MainClass.RadarFeature.delay} milliseconds.");
else else
MainClass.GetMonitor().Log($"Delay should be atleast 1 second or 1000 millisecond long.", LogLevel.Info); MainClass.InfoLog($"Delay should be atleast 1 second or 1000 millisecond long.");
} }
else else
{ {
MainClass.GetMonitor().Log("Invalid delay amount, it can only be in numeric form.", LogLevel.Info); MainClass.InfoLog("Invalid delay amount, it can only be in numeric form.");
} }
} }
else else
{ {
MainClass.GetMonitor().Log("Enter the delay amount (in milliseconds)!", LogLevel.Info); MainClass.InfoLog("Enter the delay amount (in milliseconds)!");
} }
}); });
@ -105,19 +122,19 @@ namespace stardew_access
{ {
MainClass.RadarFeature.range = range; MainClass.RadarFeature.range = range;
if (range >= 2 && range <= 10) if (range >= 2 && range <= 10)
MainClass.GetMonitor().Log($"Range set to {MainClass.RadarFeature.range}.", LogLevel.Info); MainClass.InfoLog($"Range set to {MainClass.RadarFeature.range}.");
else else
MainClass.GetMonitor().Log($"Range should be atleast 2 and maximum 10.", LogLevel.Info); MainClass.InfoLog($"Range should be atleast 2 and maximum 10.");
} }
else else
{ {
MainClass.GetMonitor().Log("Invalid range amount, it can only be in numeric form.", LogLevel.Info); MainClass.InfoLog("Invalid range amount, it can only be in numeric form.");
} }
} }
else else
{ {
MainClass.GetMonitor().Log("Enter the range amount!", LogLevel.Info); MainClass.InfoLog("Enter the range amount!");
} }
}); });
@ -136,16 +153,16 @@ namespace stardew_access
if (!MainClass.RadarFeature.exclusions.Contains(keyToAdd)) if (!MainClass.RadarFeature.exclusions.Contains(keyToAdd))
{ {
MainClass.RadarFeature.exclusions.Add(keyToAdd); MainClass.RadarFeature.exclusions.Add(keyToAdd);
MainClass.GetMonitor().Log($"Added {keyToAdd} key to exclusions list.", LogLevel.Info); MainClass.InfoLog($"Added {keyToAdd} key to exclusions list.");
} }
else else
{ {
MainClass.GetMonitor().Log($"{keyToAdd} key already present in the list.", LogLevel.Info); MainClass.InfoLog($"{keyToAdd} key already present in the list.");
} }
} }
else else
{ {
MainClass.GetMonitor().Log("Unable to add the key to exclusions list.", LogLevel.Info); MainClass.InfoLog("Unable to add the key to exclusions list.");
} }
}); });
@ -161,16 +178,16 @@ namespace stardew_access
if (MainClass.RadarFeature.exclusions.Contains(keyToAdd)) if (MainClass.RadarFeature.exclusions.Contains(keyToAdd))
{ {
MainClass.RadarFeature.exclusions.Remove(keyToAdd); MainClass.RadarFeature.exclusions.Remove(keyToAdd);
MainClass.GetMonitor().Log($"Removed {keyToAdd} key from exclusions list.", LogLevel.Info); MainClass.InfoLog($"Removed {keyToAdd} key from exclusions list.");
} }
else else
{ {
MainClass.GetMonitor().Log($"Cannot find {keyToAdd} key in exclusions list.", LogLevel.Info); MainClass.InfoLog($"Cannot find {keyToAdd} key in exclusions list.");
} }
} }
else else
{ {
MainClass.GetMonitor().Log("Unable to remove the key from exclusions list.", LogLevel.Info); MainClass.InfoLog("Unable to remove the key from exclusions list.");
} }
}); });
@ -183,23 +200,23 @@ namespace stardew_access
{ {
toPrint = $"{toPrint}\t{i + 1}: {MainClass.RadarFeature.exclusions[i]}"; toPrint = $"{toPrint}\t{i + 1}: {MainClass.RadarFeature.exclusions[i]}";
} }
MainClass.GetMonitor().Log(toPrint, LogLevel.Info); MainClass.InfoLog(toPrint);
} }
else else
{ {
MainClass.GetMonitor().Log("No exclusions found.", LogLevel.Info); MainClass.InfoLog("No exclusions found.");
} }
}); });
helper.ConsoleCommands.Add("reclear", "Clear the focus exclusions in the radar featrure.", (string commmand, string[] args) => helper.ConsoleCommands.Add("reclear", "Clear the focus exclusions in the radar featrure.", (string commmand, string[] args) =>
{ {
MainClass.RadarFeature.exclusions.Clear(); MainClass.RadarFeature.exclusions.Clear();
MainClass.GetMonitor().Log($"Cleared the focus list in the exclusions feature.", LogLevel.Info); MainClass.InfoLog($"Cleared the focus list in the exclusions feature.");
}); });
helper.ConsoleCommands.Add("recount", "Number of exclusions in the radar feature.", (string commmand, string[] args) => helper.ConsoleCommands.Add("recount", "Number of exclusions in the radar feature.", (string commmand, string[] args) =>
{ {
MainClass.GetMonitor().Log($"There are {MainClass.RadarFeature.exclusions.Count} exclusiond in the radar feature.", LogLevel.Info); MainClass.InfoLog($"There are {MainClass.RadarFeature.exclusions.Count} exclusiond in the radar feature.");
}); });
#endregion #endregion
@ -216,16 +233,16 @@ namespace stardew_access
if (!MainClass.RadarFeature.focus.Contains(keyToAdd)) if (!MainClass.RadarFeature.focus.Contains(keyToAdd))
{ {
MainClass.RadarFeature.focus.Add(keyToAdd); MainClass.RadarFeature.focus.Add(keyToAdd);
MainClass.GetMonitor().Log($"Added {keyToAdd} key to focus list.", LogLevel.Info); MainClass.InfoLog($"Added {keyToAdd} key to focus list.");
} }
else else
{ {
MainClass.GetMonitor().Log($"{keyToAdd} key already present in the list.", LogLevel.Info); MainClass.InfoLog($"{keyToAdd} key already present in the list.");
} }
} }
else else
{ {
MainClass.GetMonitor().Log("Unable to add the key to focus list.", LogLevel.Info); MainClass.InfoLog("Unable to add the key to focus list.");
} }
}); });
@ -241,16 +258,16 @@ namespace stardew_access
if (MainClass.RadarFeature.focus.Contains(keyToAdd)) if (MainClass.RadarFeature.focus.Contains(keyToAdd))
{ {
MainClass.RadarFeature.focus.Remove(keyToAdd); MainClass.RadarFeature.focus.Remove(keyToAdd);
MainClass.GetMonitor().Log($"Removed {keyToAdd} key from focus list.", LogLevel.Info); MainClass.InfoLog($"Removed {keyToAdd} key from focus list.");
} }
else else
{ {
MainClass.GetMonitor().Log($"Cannot find {keyToAdd} key in focus list.", LogLevel.Info); MainClass.InfoLog($"Cannot find {keyToAdd} key in focus list.");
} }
} }
else else
{ {
MainClass.GetMonitor().Log("Unable to remove the key from focus list.", LogLevel.Info); MainClass.InfoLog("Unable to remove the key from focus list.");
} }
}); });
@ -263,23 +280,23 @@ namespace stardew_access
{ {
toPrint = $"{toPrint}\t{i + 1}): {MainClass.RadarFeature.focus[i]}"; toPrint = $"{toPrint}\t{i + 1}): {MainClass.RadarFeature.focus[i]}";
} }
MainClass.GetMonitor().Log(toPrint, LogLevel.Info); MainClass.InfoLog(toPrint);
} }
else else
{ {
MainClass.GetMonitor().Log("No objects found in the focus list.", LogLevel.Info); MainClass.InfoLog("No objects found in the focus list.");
} }
}); });
helper.ConsoleCommands.Add("rfclear", "Clear the focus list in the radar featrure.", (string commmand, string[] args) => helper.ConsoleCommands.Add("rfclear", "Clear the focus list in the radar featrure.", (string commmand, string[] args) =>
{ {
MainClass.RadarFeature.focus.Clear(); MainClass.RadarFeature.focus.Clear();
MainClass.GetMonitor().Log($"Cleared the focus list in the radar feature.", LogLevel.Info); MainClass.InfoLog($"Cleared the focus list in the radar feature.");
}); });
helper.ConsoleCommands.Add("rfcount", "Number of list in the radar feature.", (string commmand, string[] args) => helper.ConsoleCommands.Add("rfcount", "Number of list in the radar feature.", (string commmand, string[] args) =>
{ {
MainClass.GetMonitor().Log($"There are {MainClass.RadarFeature.focus.Count} objects in the focus list in the radar feature.", LogLevel.Info); MainClass.InfoLog($"There are {MainClass.RadarFeature.focus.Count} objects in the focus list in the radar feature.");
}); });
#endregion #endregion
@ -290,14 +307,14 @@ namespace stardew_access
{ {
if (Game1.currentLocation is not Farm) if (Game1.currentLocation is not Farm)
{ {
MainClass.GetMonitor().Log("Can only use this command in the farm", LogLevel.Info); MainClass.InfoLog("Can only use this command in the farm");
return; return;
} }
string? indexInString = args.ElementAtOrDefault(0); string? indexInString = args.ElementAtOrDefault(0);
if (indexInString == null) if (indexInString == null)
{ {
MainClass.GetMonitor().Log("Enter the index too!", LogLevel.Info); MainClass.InfoLog("Enter the index too!");
return; return;
} }
@ -306,12 +323,12 @@ namespace stardew_access
if (!isParsable || !(index >= 0 && index <= 9)) if (!isParsable || !(index >= 0 && index <= 9))
{ {
MainClass.GetMonitor().Log("Index can only be a number and from 0 to 9 only", LogLevel.Info); MainClass.InfoLog("Index can only be a number and from 0 to 9 only");
return; return;
} }
BuildingNAnimalMenuPatches.marked[index] = new Vector2((int)Game1.player.getTileX(), (int)Game1.player.getTileY()); BuildingNAnimalMenuPatches.marked[index] = new Vector2((int)Game1.player.getTileX(), (int)Game1.player.getTileY());
MainClass.GetMonitor().Log($"Location {(int)Game1.player.getTileX()}x {(int)Game1.player.getTileY()}y added at {index} index.", LogLevel.Info); MainClass.InfoLog($"Location {(int)Game1.player.getTileX()}x {(int)Game1.player.getTileY()}y added at {index} index.");
}); });
helper.ConsoleCommands.Add("marklist", "List all marked positions.", (string commmand, string[] args) => helper.ConsoleCommands.Add("marklist", "List all marked positions.", (string commmand, string[] args) =>
@ -326,56 +343,28 @@ namespace stardew_access
} }
if (toPrint == "") if (toPrint == "")
MainClass.GetMonitor().Log("No positions marked!", LogLevel.Info); MainClass.InfoLog("No positions marked!");
else else
MainClass.GetMonitor().Log($"Marked positions:{toPrint}\nOpen command menu and use pageup and pagedown to check the list", LogLevel.Info); MainClass.InfoLog($"Marked positions:{toPrint}\nOpen command menu and use pageup and pagedown to check the list");
}); });
helper.ConsoleCommands.Add("buildlist", "List all buildings for selection for upgrading/demolishing/painting", (string commmand, string[] args) => helper.ConsoleCommands.Add("buildlist", "List all buildings for selection for upgrading/demolishing/painting", (string commmand, string[] args) =>
{ {
if ((Game1.activeClickableMenu is not CarpenterMenu && Game1.activeClickableMenu is not PurchaseAnimalsMenu) || !BuildingNAnimalMenuPatches.isOnFarm) onBuildListCalled();
{
MainClass.GetMonitor().Log($"Cannot list buildings.", LogLevel.Info);
return;
}
string toPrint = "";
Farm farm = (Farm)Game1.getLocationFromName("Farm");
Netcode.NetCollection<Building> buildings = farm.buildings;
int buildingIndex = 0;
for (int i = 0; i < buildings.Count; i++)
{
string? name = buildings[i].nameOfIndoorsWithoutUnique;
name = (name == "null") ? buildings[i].buildingType.Value : name;
BuildingNAnimalMenuPatches.availableBuildings[buildingIndex] = buildings[i];
toPrint = $"{toPrint}\nIndex {buildingIndex}: {name}: At {buildings[i].tileX}x and {buildings[i].tileY}y";
++buildingIndex;
}
if (toPrint == "")
{
MainClass.GetMonitor().Log("No appropriate buildings to list", LogLevel.Info);
}
else
{
MainClass.GetMonitor().Log($"Available buildings:{toPrint}\nOpen command menu and use pageup and pagedown to check the list", LogLevel.Info);
}
}); });
helper.ConsoleCommands.Add("buildsel", "Select the building index which you want to upgrade/demolish/paint", (string commmand, string[] args) => helper.ConsoleCommands.Add("buildsel", "Select the building index which you want to upgrade/demolish/paint", (string commmand, string[] args) =>
{ {
if ((Game1.activeClickableMenu is not CarpenterMenu && Game1.activeClickableMenu is not PurchaseAnimalsMenu) || !BuildingNAnimalMenuPatches.isOnFarm) if ((Game1.activeClickableMenu is not CarpenterMenu && Game1.activeClickableMenu is not PurchaseAnimalsMenu && Game1.activeClickableMenu is not AnimalQueryMenu) || !BuildingNAnimalMenuPatches.isOnFarm)
{ {
MainClass.GetMonitor().Log($"Cannot select building.", LogLevel.Info); MainClass.InfoLog($"Cannot select building.");
return; return;
} }
string? indexInString = args.ElementAtOrDefault(0); string? indexInString = args.ElementAtOrDefault(0);
if (indexInString == null) if (indexInString == null)
{ {
MainClass.GetMonitor().Log("Enter the index of the building too! Use buildlist", LogLevel.Info); MainClass.InfoLog("Enter the index of the building too! Use buildlist");
return; return;
} }
@ -384,7 +373,7 @@ namespace stardew_access
if (!isParsable) if (!isParsable)
{ {
MainClass.GetMonitor().Log("Index can only be a number.", LogLevel.Info); MainClass.InfoLog("Index can only be a number.");
return; return;
} }
@ -398,13 +387,13 @@ namespace stardew_access
{ {
if (BuildingNAnimalMenuPatches.availableBuildings[index] == null) if (BuildingNAnimalMenuPatches.availableBuildings[index] == null)
{ {
MainClass.GetMonitor().Log($"No building found with index {index}. Use buildlist.", LogLevel.Info); MainClass.InfoLog($"No building found with index {index}. Use buildlist.");
return; return;
} }
if (positionIndexInString == null) if (positionIndexInString == null)
{ {
MainClass.GetMonitor().Log("Enter the index of marked place too! Use marklist.", LogLevel.Info); MainClass.InfoLog("Enter the index of marked place too! Use marklist.");
return; return;
} }
@ -412,7 +401,7 @@ namespace stardew_access
if (!isParsable) if (!isParsable)
{ {
MainClass.GetMonitor().Log("Index can only be a number.", LogLevel.Info); MainClass.InfoLog("Index can only be a number.");
return; return;
} }
} }
@ -421,7 +410,7 @@ namespace stardew_access
{ {
if (BuildingNAnimalMenuPatches.marked[index] == Vector2.Zero) if (BuildingNAnimalMenuPatches.marked[index] == Vector2.Zero)
{ {
MainClass.GetMonitor().Log($"No marked position found at {index} index.", LogLevel.Info); MainClass.InfoLog($"No marked position found at {index} index.");
return; return;
} }
} }
@ -429,14 +418,21 @@ namespace stardew_access
{ {
if (BuildingNAnimalMenuPatches.availableBuildings[index] == null) if (BuildingNAnimalMenuPatches.availableBuildings[index] == null)
{ {
MainClass.GetMonitor().Log($"No building found with index {index}. Use buildlist.", LogLevel.Info); MainClass.InfoLog($"No building found with index {index}. Use buildlist.");
return; return;
} }
} }
string? response = null; string? response = null;
if (Game1.activeClickableMenu is PurchaseAnimalsMenu) { BuildingNAnimalMenuPatches.PurchaseAnimal(BuildingNAnimalMenuPatches.availableBuildings[index]); } if (Game1.activeClickableMenu is PurchaseAnimalsMenu)
{
BuildingNAnimalMenuPatches.PurchaseAnimal(BuildingNAnimalMenuPatches.availableBuildings[index]);
}
else if (Game1.activeClickableMenu is AnimalQueryMenu)
{
BuildingNAnimalMenuPatches.MoveAnimal(BuildingNAnimalMenuPatches.availableBuildings[index]);
}
else else
{ {
if (BuildingNAnimalMenuPatches.isConstructing && !BuildingNAnimalMenuPatches.isUpgrading) { response = BuildingNAnimalMenuPatches.Contstruct(BuildingNAnimalMenuPatches.marked[index]); } if (BuildingNAnimalMenuPatches.isConstructing && !BuildingNAnimalMenuPatches.isUpgrading) { response = BuildingNAnimalMenuPatches.Contstruct(BuildingNAnimalMenuPatches.marked[index]); }
@ -448,17 +444,92 @@ namespace stardew_access
if (response != null) if (response != null)
{ {
MainClass.GetMonitor().Log(response, LogLevel.Info); MainClass.InfoLog(response);
} }
}); });
#endregion #endregion
#region Other
helper.ConsoleCommands.Add("refsr", "Refresh screen reader", (string commmand, string[] args) => helper.ConsoleCommands.Add("refsr", "Refresh screen reader", (string commmand, string[] args) =>
{ {
MainClass.GetScreenReader().InitializeScreenReader(); MainClass.ScreenReader.InitializeScreenReader();
MainClass.GetMonitor().Log("Screen Reader refreshed!", LogLevel.Info); MainClass.InfoLog("Screen Reader refreshed!");
});
helper.ConsoleCommands.Add("refmc", "Refresh mod config", (string commmand, string[] args) =>
{
MainClass.Config = helper.ReadConfig<ModConfig>();
MainClass.InfoLog("Mod Config refreshed!");
}); });
helper.ConsoleCommands.Add("refst", "Refresh static tiles", (string commmand, string[] args) =>
{
MainClass.STiles = new Features.StaticTiles();
MainClass.InfoLog("Static tiles refreshed!");
});
helper.ConsoleCommands.Add("hnspercent", "Toggle between speaking in percentage or full health and stamina.", (string commmand, string[] args) =>
{
MainClass.Config.HealthNStaminaInPercentage = !MainClass.Config.HealthNStaminaInPercentage;
helper.WriteConfig(MainClass.Config);
MainClass.InfoLog("Speaking in percentage is " + (MainClass.Config.HealthNStaminaInPercentage ? "on" : "off"));
});
helper.ConsoleCommands.Add("snapmouse", "Toggle snap mouse feature.", (string commmand, string[] args) =>
{
MainClass.Config.SnapMouse = !MainClass.Config.SnapMouse;
helper.WriteConfig(MainClass.Config);
MainClass.InfoLog("Snap Mouse is " + (MainClass.Config.SnapMouse ? "on" : "off"));
});
helper.ConsoleCommands.Add("warning", "Toggle warnings feature.", (string commmand, string[] args) =>
{
MainClass.Config.Warning = !MainClass.Config.Warning;
helper.WriteConfig(MainClass.Config);
MainClass.InfoLog("Warnings is " + (MainClass.Config.Warning ? "on" : "off"));
});
helper.ConsoleCommands.Add("tts", "Toggles the screen reader/tts", (string commmand, string[] args) =>
{
MainClass.Config.TTS = !MainClass.Config.TTS;
helper.WriteConfig(MainClass.Config);
MainClass.InfoLog("TTS is " + (MainClass.Config.TTS ? "on" : "off"));
});
#endregion
}
internal static void onBuildListCalled()
{
string toPrint = "";
Farm farm = (Farm)Game1.getLocationFromName("Farm");
Netcode.NetCollection<Building> buildings = farm.buildings;
int buildingIndex = 0;
for (int i = 0; i < buildings.Count; i++)
{
string? name = buildings[i].nameOfIndoorsWithoutUnique;
name = (name == "null") ? buildings[i].buildingType.Value : name;
BuildingNAnimalMenuPatches.availableBuildings[buildingIndex] = buildings[i];
toPrint = $"{toPrint}\nIndex {buildingIndex}: {name}: At {buildings[i].tileX}x and {buildings[i].tileY}y";
++buildingIndex;
}
if (toPrint == "")
{
MainClass.InfoLog("No appropriate buildings to list");
}
else
{
MainClass.InfoLog($"Available buildings:{toPrint}\nOpen command menu and use pageup and pagedown to check the list");
}
} }
} }
} }

View File

@ -1,5 +1,4 @@
using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Audio;
using StardewModdingAPI;
using StardewValley; using StardewValley;
namespace stardew_access namespace stardew_access
@ -16,6 +15,9 @@ namespace stardew_access
{ {
try try
{ {
if (MainClass.ModHelper == null)
return;
Dictionary<String, TYPE> soundEffects = new Dictionary<String, TYPE>(); Dictionary<String, TYPE> soundEffects = new Dictionary<String, TYPE>();
soundEffects.Add("drop_item", TYPE.Sound); soundEffects.Add("drop_item", TYPE.Sound);
@ -55,7 +57,7 @@ namespace stardew_access
} }
SoundEffect effect; SoundEffect effect;
string filePath = Path.Combine(MainClass.ModHelper.DirectoryPath, "sounds", $"{soundEffect.Key}.wav"); string filePath = Path.Combine(MainClass.ModHelper.DirectoryPath, "assets", "sounds", $"{soundEffect.Key}.wav");
using (FileStream stream = new(filePath, FileMode.Open)) using (FileStream stream = new(filePath, FileMode.Open))
{ {
effect = SoundEffect.FromStream(stream); effect = SoundEffect.FromStream(stream);
@ -71,7 +73,7 @@ namespace stardew_access
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to initialize custom sounds:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to initialize custom sounds:\n{e.Message}\n{e.StackTrace}");
} }
} }
} }

View File

@ -6,115 +6,185 @@ namespace stardew_access.Features
internal class CurrentPlayer internal class CurrentPlayer
{ {
public static int getHealth() /// <summary>
/// Returns the percentage health remaining of player.
/// </summary>
public static int PercentHealth
{ {
if (Game1.player == null) get
return 0;
int maxHealth = Game1.player.maxHealth;
int currentHealth = Game1.player.health;
int healthPercentage = (int)(currentHealth * 100) / maxHealth;
return healthPercentage;
}
public static int getStamina()
{
if (Game1.player == null)
return 0;
int maxStamina = Game1.player.maxStamina;
int currentStamine = (int)Game1.player.stamina;
int staminaPercentage = (int)(currentStamine * 100) / maxStamina;
return staminaPercentage;
}
public static int getPositionX()
{
if (Game1.player == null)
return 0;
int x = (int)Game1.player.getTileLocation().X;
return x;
}
public static int getPositionY()
{
if (Game1.player == null)
return 0;
int y = (int)Game1.player.getTileLocation().Y;
return y;
}
public static string getTimeOfDay()
{
int timeOfDay = Game1.timeOfDay;
int minutes = timeOfDay % 100;
int hours = timeOfDay / 100;
string amOrpm = "A M";
if (hours >= 12)
{ {
amOrpm = "P M"; if (Game1.player == null)
if (hours > 12) return 0;
hours -= 12;
return (CurrentHealth * 100) / Game1.player.maxHealth; ;
} }
return $"{hours}:{minutes} {amOrpm}";
} }
public static string getSeason() /// <summary>
/// Returns the total health player has currently
/// </summary>
public static int CurrentHealth
{ {
return Game1.CurrentSeasonDisplayName; get
}
public static int getDate()
{
return Game1.dayOfMonth;
}
public static string getDay()
{
return Game1.Date.DayOfWeek.ToString();
}
public static int getMoney()
{
if (Game1.player == null)
return -1;
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: if (Game1.player == null)
y -= offset; return 0;
break;
case 1:
x += offset;
break;
case 2:
y += offset;
break;
case 3:
x -= offset;
break;
}
x /= Game1.tileSize; return Game1.player.health;
y /= Game1.tileSize; }
return new Vector2(x, y); }
/// <summary>
/// Returns the percentage stamine/energy remaining of player.
/// </summary>
public static int PercentStamina
{
get
{
if (Game1.player == null)
return 0;
return (CurrentStamina * 100) / Game1.player.maxStamina.Value;
}
}
/// <summary>
/// Returns the total stamina player has currently
/// </summary>
public static int CurrentStamina
{
get
{
if (Game1.player == null)
return 0;
return (int)Game1.player.stamina;
}
}
/// <summary>
/// Returns the tile location of the player
/// </summary>
public static Vector2 Position
{
get
{
if (Game1.player == null)
return Vector2.Zero;
return Game1.player.getTileLocation();
}
}
/// <summary>
/// Returns the X coordinate of the player
/// </summary>
public static int PositionX
{
get
{
if (Game1.player == null)
return 0;
return (int)Position.X;
}
}
/// <summary>
/// Returns the Y coordinate of the player
/// </summary>
public static int PositionY
{
get
{
if (Game1.player == null)
return 0;
return (int)Position.Y;
}
}
/// <summary>
/// Returns the time in the 12 hours format
/// </summary>
public static string TimeOfDay
{
get
{
int timeOfDay = Game1.timeOfDay;
int minutes = timeOfDay % 100;
int hours = timeOfDay / 100;
string amOrpm = hours / 12 == 1 ? "PM" : "AM";
hours = hours % 12;
if (hours == 0) hours = 12;
return $"{hours}:{minutes:00} {amOrpm}";
}
}
/// <summary>
/// Returns the current season
/// </summary>
public static string Season => Game1.CurrentSeasonDisplayName;
/// <summary>
/// Returns the current date of month
/// </summary>
public static int Date => Game1.dayOfMonth;
/// <summary>
/// Returns the current day of week
/// </summary>
/// <returns></returns>
public static string Day => Game1.Date.DayOfWeek.ToString();
/// <summary>
/// Returns the amount of money the player has currently
/// </summary>
public static int Money
{
get
{
if (Game1.player == null)
return -1;
return Game1.player.Money;
}
}
/// <summary>
/// Returns the tile position of the tile the player is facing
/// </summary>
/// <value></value>
public static Vector2 FacingTile
{
get
{
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);
}
} }
} }
} }

View File

@ -23,7 +23,7 @@ namespace stardew_access.Features
return; return;
previousSlotItem = currentSlotItem; previousSlotItem = currentSlotItem;
MainClass.GetScreenReader().Say($"{currentSlotItem.DisplayName} Selected", true); MainClass.ScreenReader.Say($"{currentSlotItem.DisplayName} Selected", true);
} }
// Narrates current location's name // Narrates current location's name
@ -38,38 +38,11 @@ namespace stardew_access.Features
return; return;
previousLocation = currentLocation; previousLocation = currentLocation;
MainClass.GetScreenReader().Say($"{currentLocation.Name} Entered", true); MainClass.ScreenReader.Say($"{currentLocation.Name} Entered", true);
} }
public static void SnapMouseToPlayer() public static void narrateHudMessages()
{ {
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 async void narrateHudMessages()
{
MainClass.isNarratingHudMessage = true;
try try
{ {
if (Game1.hudMessages.Count > 0) if (Game1.hudMessages.Count > 0)
@ -89,18 +62,16 @@ namespace stardew_access.Features
{ {
MainClass.hudMessageQueryKey = searchQuery; MainClass.hudMessageQueryKey = searchQuery;
MainClass.GetScreenReader().Say(toSpeak, true); MainClass.ScreenReader.Say(toSpeak, true);
} }
} }
} }
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate hud messages:\n{e.Message}\n{e.StackTrace}", StardewModdingAPI.LogLevel.Error); MainClass.ErrorLog($"Unable to narrate hud messages:\n{e.Message}\n{e.StackTrace}");
} }
await Task.Delay(300);
MainClass.isNarratingHudMessage = false;
} }
} }
} }

View File

@ -1,48 +1,9 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using StardewValley; using StardewValley;
using StardewValley.Objects; using StardewValley.Objects;
using StardewValley.TerrainFeatures;
namespace stardew_access.Features namespace stardew_access.Features
{ {
/// <summary>
/// This is a custom enum class and contains the name of groups the objects are divided into for the feature
/// </summary>
public class CATEGORY
{
private string _typeKeyWord;
private CATEGORY(string typeKeyWord)
{
_typeKeyWord = typeKeyWord;
}
public override string ToString()
{
return _typeKeyWord;
}
public static CATEGORY Farmers = new CATEGORY("farmer");
public static CATEGORY FarmAnimals = new CATEGORY("animal");
public static CATEGORY NPCs = new CATEGORY("npc");
public static CATEGORY Furnitures = new CATEGORY("furniture");
public static CATEGORY Flooring = new CATEGORY("flooring");
public static CATEGORY Debris = new CATEGORY("debris");
public static CATEGORY Crops = new CATEGORY("crop");
public static CATEGORY Trees = new CATEGORY("tree");
public static CATEGORY Bush = new CATEGORY("bush");
public static CATEGORY Buildings = new CATEGORY("building");
public static CATEGORY MineItems = new CATEGORY("mine item");
public static CATEGORY ResourceClumps = new CATEGORY("resource clump");
public static CATEGORY Chests = new CATEGORY("chest");
public static CATEGORY JunimoBundle = new CATEGORY("bundle");
public static CATEGORY Doors = new CATEGORY("door"); // Also includes ladders and elevators
public static CATEGORY Others = new CATEGORY("other");
public static CATEGORY WaterTiles = new CATEGORY("water");
}
public class Radar public class Radar
{ {
private List<Vector2> closed; private List<Vector2> closed;
@ -80,7 +41,11 @@ namespace stardew_access.Features
exclusions.Add("tree"); exclusions.Add("tree");
exclusions.Add("flooring"); exclusions.Add("flooring");
exclusions.Add("water"); exclusions.Add("water");
exclusions.Add("debris");
exclusions.Add("grass"); exclusions.Add("grass");
exclusions.Add("decoration");
exclusions.Add("bridge");
exclusions.Add("other");
/* Not excluded Categories /* Not excluded Categories
* *
@ -89,19 +54,22 @@ namespace stardew_access.Features
* exclusions.Add("animal"); * exclusions.Add("animal");
* exclusions.Add("npc"); * exclusions.Add("npc");
* exclusions.Add("furniture") * exclusions.Add("furniture")
* exclusions.Add("other");
* exclusions.Add("building"); * exclusions.Add("building");
* exclusions.Add("resource clump");
* exclusions.Add("mine item"); * exclusions.Add("mine item");
* exclusions.Add("chest"); * exclusions.Add("container");
* exclusions.Add("bundle");
* exclusions.Add("door");
* exclusions.Add("machine");
* exclusions.Add("interactable");
*/ */
} }
public async void Run() public void Run()
{ {
if (MainClass.radarDebug) if (MainClass.radarDebug)
MainClass.GetMonitor().Log($"\n\nRead Tile started", StardewModdingAPI.LogLevel.Debug); MainClass.DebugLog($"\n\nRead Tile started");
isRunning = true;
Vector2 currPosition = Game1.player.getTileLocation(); Vector2 currPosition = Game1.player.getTileLocation();
closed.Clear(); closed.Clear();
@ -111,10 +79,7 @@ namespace stardew_access.Features
SearchNearbyTiles(currPosition, range); SearchNearbyTiles(currPosition, range);
if (MainClass.radarDebug) if (MainClass.radarDebug)
MainClass.GetMonitor().Log($"\nRead Tile stopped\n\n", StardewModdingAPI.LogLevel.Debug); MainClass.DebugLog($"\nRead Tile stopped\n\n");
await Task.Delay(delay);
isRunning = false;
} }
/// <summary> /// <summary>
@ -132,7 +97,6 @@ namespace stardew_access.Features
List<Vector2> searched = new List<Vector2>(); List<Vector2> searched = new List<Vector2>();
int[] dirX = { -1, 0, 1, 0 }; int[] dirX = { -1, 0, 1, 0 };
int[] dirY = { 0, 1, 0, -1 }; int[] dirY = { 0, 1, 0, -1 };
int count = 0;
toSearch.Enqueue(center); toSearch.Enqueue(center);
searched.Add(center); searched.Add(center);
@ -151,7 +115,6 @@ namespace stardew_access.Features
detectedTiles.Add(item, (tileInfo.Item2, tileInfo.Item3)); detectedTiles.Add(item, (tileInfo.Item2, tileInfo.Item3));
} }
} }
count++;
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
@ -168,6 +131,52 @@ namespace stardew_access.Features
return detectedTiles; return detectedTiles;
} }
/// <summary>
/// Search the entire location using Breadth First Search algorithm(BFS).
/// </summary>
/// <returns>A dictionary with all the detected tiles along with the name of the object on it and it's category.</returns>
public Dictionary<Vector2, (string, string)> SearchLocation()
{
Dictionary<Vector2, (string, string)> detectedTiles = new Dictionary<Vector2, (string, string)>();
Vector2 position = Vector2.Zero;
(bool, string? name, string category) tileInfo;
Queue<Vector2> toSearch = new Queue<Vector2>();
List<Vector2> searched = new List<Vector2>();
int[] dirX = { -1, 0, 1, 0 };
int[] dirY = { 0, 1, 0, -1 };
int count = 0;
toSearch.Enqueue(Game1.player.getTileLocation());
searched.Add(Game1.player.getTileLocation());
while (toSearch.Count > 0)
{
Vector2 item = toSearch.Dequeue();
tileInfo = CheckTile(item, true);
if (tileInfo.Item1 && tileInfo.name != null)
{
// Add detected tile to the dictionary
detectedTiles.Add(item, (tileInfo.name, tileInfo.category));
}
count++;
for (int i = 0; i < 4; i++)
{
Vector2 dir = new Vector2(item.X + dirX[i], item.Y + dirY[i]);
if (!searched.Contains(dir) && (TileInfo.isWarpPointAtTile((int)dir.X, (int)dir.Y) || Game1.currentLocation.isTileOnMap(dir)))
{
toSearch.Enqueue(dir);
searched.Add(dir);
}
}
}
return detectedTiles;
}
/// <summary> /// <summary>
/// Checks if the provided tile position is within the range/radius and whether the tile has already been checked or not. /// Checks if the provided tile position is within the range/radius and whether the tile has already been checked or not.
/// </summary> /// </summary>
@ -189,16 +198,16 @@ namespace stardew_access.Features
return true; return true;
} }
public (bool, string?, string) CheckTile(Vector2 position) public (bool, string? name, string category) CheckTile(Vector2 position, bool lessInfo = false)
{ {
(string?, CATEGORY?) tileDetail = ReadTile.getNameWithCategoryAtTile(position); (string? name, CATEGORY? category) tileDetail = TileInfo.getNameWithCategoryAtTile(position, lessInfo);
if (tileDetail.Item1 == null) if (tileDetail.name == null)
return (false, null, CATEGORY.Others.ToString()); return (false, null, CATEGORY.Others.ToString());
if (tileDetail.Item2 == null) if (tileDetail.category == null)
tileDetail.Item2 = CATEGORY.Others; tileDetail.category = CATEGORY.Others;
return (true, tileDetail.Item1, tileDetail.Item2.ToString()); return (true, tileDetail.name, tileDetail.category.ToString());
} }
@ -208,9 +217,9 @@ namespace stardew_access.Features
{ {
if (Game1.currentLocation.isObjectAtTile((int)position.X, (int)position.Y)) if (Game1.currentLocation.isObjectAtTile((int)position.X, (int)position.Y))
{ {
(string?, CATEGORY) objDetails = ReadTile.getObjectAtTile((int)position.X, (int)position.Y); (string? name, CATEGORY category) objDetails = TileInfo.getObjectAtTile((int)position.X, (int)position.Y);
string? objectName = objDetails.Item1; string? objectName = objDetails.name;
CATEGORY category = objDetails.Item2; CATEGORY category = objDetails.category;
StardewValley.Object obj = Game1.currentLocation.getObjectAtTile((int)position.X, (int)position.Y); StardewValley.Object obj = Game1.currentLocation.getObjectAtTile((int)position.X, (int)position.Y);
if (objectName != null) if (objectName != null)
@ -232,19 +241,19 @@ namespace stardew_access.Features
} }
else else
{ {
(string?, CATEGORY?) tileDetail = ReadTile.getNameWithCategoryAtTile(position); (string? name, CATEGORY? category) tileDetail = TileInfo.getNameWithCategoryAtTile(position);
if (tileDetail.Item1 != null) if (tileDetail.name != null)
{ {
if (tileDetail.Item2 == null) if (tileDetail.category == null)
tileDetail.Item2 = CATEGORY.Others; tileDetail.category = CATEGORY.Others;
PlaySoundAt(position, tileDetail.Item1, tileDetail.Item2); PlaySoundAt(position, tileDetail.name, tileDetail.category);
} }
} }
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"{e.Message}\n{e.StackTrace}\n{e.Source}", StardewModdingAPI.LogLevel.Error); MainClass.ErrorLog($"{e.Message}\n{e.StackTrace}\n{e.Source}");
} }
} }
@ -253,7 +262,7 @@ namespace stardew_access.Features
#region Check whether to skip the object or not #region Check whether to skip the object or not
// Skip if player is directly looking at the tile // Skip if player is directly looking at the tile
if (CurrentPlayer.getNextTile().Equals(position)) if (CurrentPlayer.FacingTile.Equals(position))
return; return;
if (!radarFocus) if (!radarFocus)
@ -296,7 +305,7 @@ namespace stardew_access.Features
#endregion #endregion
if (MainClass.radarDebug) if (MainClass.radarDebug)
MainClass.GetMonitor().Log($"{radarFocus}\tObject:{searchQuery.ToLower().Trim()}\tPosition: X={position.X} Y={position.Y}", StardewModdingAPI.LogLevel.Debug); MainClass.ErrorLog($"{radarFocus}\tObject:{searchQuery.ToLower().Trim()}\tPosition: X={position.X} Y={position.Y}");
int px = (int)Game1.player.getTileX(); // Player's X postion int px = (int)Game1.player.getTileX(); // Player's X postion
int py = (int)Game1.player.getTileY(); // Player's Y postion int py = (int)Game1.player.getTileY(); // Player's Y postion
@ -330,7 +339,7 @@ namespace stardew_access.Features
{ {
string soundName = $"_{post}"; string soundName = $"_{post}";
if (!MainClass.radarStereoSound) if (!MainClass.Config.RadarStereoSound)
soundName = $"_mono{soundName}"; soundName = $"_mono{soundName}";
if (category == CATEGORY.Farmers) // Villagers and farmers if (category == CATEGORY.Farmers) // Villagers and farmers
@ -353,7 +362,7 @@ namespace stardew_access.Features
soundName = $"obj{soundName}"; soundName = $"obj{soundName}";
else if (category == CATEGORY.MineItems) // Mine items else if (category == CATEGORY.MineItems) // Mine items
soundName = $"obj{soundName}"; soundName = $"obj{soundName}";
else if (category == CATEGORY.Chests) // Chests else if (category == CATEGORY.Containers) // Chests
soundName = $"obj{soundName}"; soundName = $"obj{soundName}";
else if (category == CATEGORY.Debris) // Grass and debris else if (category == CATEGORY.Debris) // Grass and debris
soundName = $"obj{soundName}"; soundName = $"obj{soundName}";

View File

@ -1,54 +1,105 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using StardewModdingAPI; using StardewModdingAPI;
using StardewValley; using StardewValley;
using StardewValley.Buildings;
using StardewValley.Locations;
using StardewValley.Objects;
using StardewValley.TerrainFeatures;
namespace stardew_access.Features namespace stardew_access.Features
{ {
/// <summary>
/// Reads the name and information about a tile.
/// </summary>
public class ReadTile public class ReadTile
{ {
public static bool isReadingTile = false; private bool isBusy; // To pause execution of run method between fixed intervals
public static Vector2 prevTile; private int delay; // Length of each interval (in ms)
private bool shouldPause; // To pause the execution
private Vector2 prevTile;
public ReadTile() public ReadTile()
{ {
isReadingTile = false; isBusy = false;
delay = 100;
} }
public static async void run(bool manuallyTriggered = false) public void update()
{ {
isReadingTile = true; if (this.isBusy)
return;
if (this.shouldPause)
return;
this.isBusy = true;
this.run();
Task.Delay(delay).ContinueWith(_ => { this.isBusy = false; });
}
/// <summary>
/// Pauses the feature for the provided time.
/// </summary>
/// <param name="time">The amount of time we want to pause the execution (in ms).<br/>Default is 2500 (2.5s).</param>
public void pauseUntil(int time = 2500)
{
this.shouldPause = true;
Task.Delay(time).ContinueWith(_ => { this.shouldPause = false; });
}
/// <summary>
/// Pauses the feature
/// </summary>
public void pause()
{
this.shouldPause = true;
}
/// <summary>
/// Resumes the feature
/// </summary>
public void resume()
{
this.shouldPause = false;
}
public void run(bool manuallyTriggered = false, bool playersPosition = false)
{
try try
{ {
#region Get Next Grab Tile Vector2 tile;
Vector2 tile = CurrentPlayer.getNextTile();
int x = (int)tile.X; #region Get Tile
int y = (int)tile.Y; int x, y;
if (!playersPosition)
{
// Grab tile
tile = CurrentPlayer.FacingTile;
}
else
{
// Player's standing tile
tile = CurrentPlayer.Position;
}
x = (int)tile.X;
y = (int)tile.Y;
#endregion #endregion
if (Context.IsPlayerFree) if (Context.IsPlayerFree)
{ {
if (!manuallyTriggered && prevTile != tile) if (!manuallyTriggered && prevTile != tile)
{ {
if (MainClass.GetScreenReader() != null) if (MainClass.ScreenReader != null)
MainClass.GetScreenReader().PrevTextTile = " "; MainClass.ScreenReader.PrevTextTile = " ";
} }
bool isColliding = isCollidingAtTile(x, y); bool isColliding = TileInfo.isCollidingAtTile(x, y);
string? toSpeak = getNameAtTile(tile); (string? name, string? category) info = TileInfo.getNameWithCategoryNameAtTile(tile);
#region Narrate toSpeak #region Narrate toSpeak
if (toSpeak != null) if (info.name != null)
if (MainClass.GetScreenReader() != null) if (MainClass.ScreenReader != null)
if (manuallyTriggered) if (manuallyTriggered)
MainClass.GetScreenReader().Say(toSpeak, true); MainClass.ScreenReader.Say($"{info.name}, Category: {info.category}", true);
else else
MainClass.GetScreenReader().SayWithTileQuery(toSpeak, x, y, true); MainClass.ScreenReader.SayWithTileQuery(info.name, x, y, true);
#endregion #endregion
#region Play colliding sound effect #region Play colliding sound effect
@ -58,865 +109,15 @@ namespace stardew_access.Features
} }
#endregion #endregion
prevTile = tile; if (!manuallyTriggered)
prevTile = tile;
} }
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Error in Read Tile:\n{e.Message}\n{e.StackTrace}", LogLevel.Debug); MainClass.ErrorLog($"Error in Read Tile:\n{e.Message}\n{e.StackTrace}");
} }
await Task.Delay(100);
isReadingTile = false;
}
///<summary>Returns the name of the object at tile alongwith it's category's name</summary>
public static (string?, string?) getNameWithCategoryNameAtTile(Vector2 tile)
{
(string?, CATEGORY?) tileDetail = getNameWithCategoryAtTile(tile);
if (tileDetail.Item2 == null)
tileDetail.Item2 = CATEGORY.Others;
return (tileDetail.Item1, tileDetail.Item2.ToString());
}
///<summary>Returns the name of the object at tile alongwith it's category</summary>
public static (string?, CATEGORY?) getNameWithCategoryAtTile(Vector2 tile)
{
int x = (int)tile.X;
int y = (int)tile.Y;
string? toReturn = "";
CATEGORY? category = CATEGORY.Others;
bool isColliding = isCollidingAtTile(x, y);
Dictionary<Vector2, Netcode.NetRef<TerrainFeature>> terrainFeature = Game1.currentLocation.terrainFeatures.FieldDict;
string? door = getDoorAtTile(x, y);
(CATEGORY?, string?) tileInfo = getTileInfo(x, y);
string? junimoBundle = getJunimoBundleAt(x, y);
string? resourceClump = getResourceClumpAtTile(x, y);
string? farmAnimal = getFarmAnimalAt(Game1.currentLocation, x, y);
if (Game1.currentLocation.isCharacterAtTile(tile) != null)
{
NPC npc = Game1.currentLocation.isCharacterAtTile(tile);
toReturn = npc.displayName;
if (npc.isVillager() || npc.CanSocialize)
category = CATEGORY.Farmers;
else
category = CATEGORY.NPCs;
}
else if (farmAnimal != null)
{
toReturn = farmAnimal;
category = CATEGORY.FarmAnimals;
}
else if (Game1.currentLocation.isWaterTile(x, y) && isColliding)
{
toReturn = "Water";
category = CATEGORY.WaterTiles;
}
else if (Game1.currentLocation.isObjectAtTile(x, y))
{
(string?, CATEGORY?) obj = getObjectAtTile(x, y);
toReturn = obj.Item1;
category = obj.Item2;
}
else if (terrainFeature.ContainsKey(tile))
{
(string?, CATEGORY) tf = getTerrainFeatureAtTile(terrainFeature[tile]);
string? terrain = tf.Item1;
if (terrain != null)
{
toReturn = terrain;
category = tf.Item2;
}
}
else if (Game1.currentLocation.getLargeTerrainFeatureAt(x, y) != null)
{
toReturn = getBushAtTile(x, y);
category = CATEGORY.Bush;
}
else if (resourceClump != null)
{
toReturn = resourceClump;
category = CATEGORY.ResourceClumps;
}
else if (door != null)
{
toReturn = door;
category = CATEGORY.Doors;
}
else if (isMineDownLadderAtTile(x, y))
{
toReturn = "Ladder";
category = CATEGORY.Doors;
}
else if (isMineUpLadderAtTile(x, y))
{
toReturn = "Up Ladder";
category = CATEGORY.Doors;
}
else if (isElevatorAtTile(x, y))
{
toReturn = "Elevator";
category = CATEGORY.Doors;
}
else if (tileInfo.Item2 != null)
{
toReturn = tileInfo.Item2;
category = tileInfo.Item1;
}
else if (junimoBundle != null)
{
toReturn = junimoBundle;
category = CATEGORY.JunimoBundle;
}
if (toReturn == "")
return (null, category);
return (toReturn, category);
}
///<summary>Returns the name of the object at tile</summary>
public static string? getNameAtTile(Vector2 tile)
{
int x = (int)tile.X;
int y = (int)tile.Y;
string? toReturn = "";
bool isColliding = isCollidingAtTile(x, y);
Dictionary<Vector2, Netcode.NetRef<TerrainFeature>> terrainFeature = Game1.currentLocation.terrainFeatures.FieldDict;
string? door = getDoorAtTile(x, y);
(CATEGORY?, string?) tileInfo = getTileInfo(x, y);
string? junimoBundle = getJunimoBundleAt(x, y);
string? resourceClump = getResourceClumpAtTile(x, y);
string? farmAnimal = getFarmAnimalAt(Game1.currentLocation, x, y);
if (Game1.currentLocation.isCharacterAtTile(tile) != null)
{
NPC npc = Game1.currentLocation.isCharacterAtTile(tile);
toReturn = npc.displayName;
}
else if (farmAnimal != null)
{
toReturn = farmAnimal;
}
else if (Game1.currentLocation.isWaterTile(x, y) && isColliding)
{
toReturn = "Water";
}
else if (Game1.currentLocation.isObjectAtTile(x, y))
{
toReturn = getObjectAtTile(x, y).Item1;
}
else if (terrainFeature.ContainsKey(tile))
{
string? terrain = getTerrainFeatureAtTile(terrainFeature[tile]).Item1;
if (terrain != null)
toReturn = terrain;
}
else if (Game1.currentLocation.getLargeTerrainFeatureAt(x, y) != null)
{
toReturn = getBushAtTile(x, y);
}
else if (resourceClump != null)
{
toReturn = resourceClump;
}
else if (door != null)
{
toReturn = door;
}
else if (isMineDownLadderAtTile(x, y))
{
toReturn = "Ladder";
}
else if (isMineUpLadderAtTile(x, y))
{
toReturn = "Up Ladder";
}
else if (isElevatorAtTile(x, y))
{
toReturn = "Elevator";
}
else if (tileInfo.Item2 != null)
{
toReturn = tileInfo.Item2;
}
else if (junimoBundle != null)
{
toReturn = junimoBundle;
}
if (toReturn == "")
return null;
return toReturn;
}
public static string? getBushAtTile(int x, int y)
{
string? toReturn = null;
Bush bush = (Bush)Game1.currentLocation.getLargeTerrainFeatureAt(x, y);
int size = bush.size;
#region Check if bush is harvestable or not
if (!bush.townBush && (int)bush.tileSheetOffset == 1 && bush.inBloom(Game1.GetSeasonForLocation(Game1.currentLocation), Game1.dayOfMonth))
{
// Taken from the game's code
string season = ((int)bush.overrideSeason == -1) ? Game1.GetSeasonForLocation(Game1.currentLocation) : Utility.getSeasonNameFromNumber(bush.overrideSeason);
int shakeOff = -1;
if (!(season == "spring"))
{
if (season == "fall")
{
shakeOff = 410;
}
}
else
{
shakeOff = 296;
}
if ((int)size == 3)
{
shakeOff = 815;
}
if ((int)size == 4)
{
shakeOff = 73;
}
if (shakeOff == -1)
{
return null;
}
toReturn = "Harvestable";
}
#endregion
if (bush.townBush)
toReturn = $"{toReturn} Town Bush";
else if (bush.greenhouseBush)
toReturn = $"{toReturn} Greenhouse Bush";
else
toReturn = $"{toReturn} Bush";
return toReturn;
}
public static string? getJunimoBundleAt(int x, int y)
{
if (Game1.currentLocation is not CommunityCenter)
return null;
CommunityCenter communityCenter = ((CommunityCenter)Game1.currentLocation);
string? name = (x, y) switch
{
(14, 5) => "Pantry",
(14, 23) => "Crafts Room",
(40, 10) => "Fish Tank",
(63, 14) => "Boiler Room",
(55, 6) => "Vault",
(46, 11) => "Bulletin Board",
_ => null,
};
if (name != null && communityCenter.shouldNoteAppearInArea(CommunityCenter.getAreaNumberFromName(name)))
return $"{name} bundle";
else
return null;
}
public static bool isCollidingAtTile(int x, int y)
{
Rectangle rect = new Rectangle(x * 64 + 1, y * 64 + 1, 62, 62);
if (Game1.currentLocation.isCollidingPosition(rect, Game1.viewport, true, 0, glider: false, Game1.player, pathfinding: true))
{
return true;
}
if (Game1.currentLocation is Woods && getStumpsInWoods(x, y) != null)
return true;
return false;
}
public static string? getFarmAnimalAt(GameLocation? location, int x, int y, bool onlyName = false)
{
if (location == null)
return null;
if (location is not Farm && location is not AnimalHouse)
return null;
List<FarmAnimal>? farmAnimals = null;
if (location is Farm)
farmAnimals = ((Farm)location).getAllFarmAnimals();
else if (location is AnimalHouse)
farmAnimals = ((AnimalHouse)location).animals.Values.ToList();
if (farmAnimals == null || farmAnimals.Count <= 0)
return null;
for (int i = 0; i < farmAnimals.Count; i++)
{
int fx = farmAnimals[i].getTileX();
int fy = farmAnimals[i].getTileY();
if (fx.Equals(x) && fy.Equals(y))
{
string name = farmAnimals[i].displayName;
int age = farmAnimals[i].age.Value;
string type = farmAnimals[i].displayType;
if (onlyName)
return name;
return $"{name}, {type}, age {age}";
}
}
return null;
}
/// <summary>
///
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns>Item1: This is the category of the tile. Default to Furnitures.
/// <br/>Item2: This is the name of the tile. Default to null if the tile tile has nothing on it.</returns>
public static (CATEGORY?, string?) getTileInfo(int x, int y)
{
int? index = null;
if (Game1.currentLocation.Map.GetLayer("Buildings").Tiles[x, y] != null)
index = Game1.currentLocation.Map.GetLayer("Buildings").Tiles[x, y].TileIndex;
/* Add More
MainClass.monitor.Log(index.ToString(), LogLevel.Debug);
*/
if (Game1.currentLocation is Farm)
{
Building building = ((Farm)Game1.currentLocation).getBuildingAt(new Vector2(x, y));
if (building != null)
{
return (CATEGORY.Buildings, building.buildingType.Value);
}
}
if (index != null)
{
switch (index)
{
case 1955:
case 41:
return (CATEGORY.Furnitures, "Mail Box");
case 1003:
return (CATEGORY.Furnitures, "Street lamp");
case 78:
return (CATEGORY.Furnitures, "Trash bin");
case 617:
return (CATEGORY.Furnitures, "Daily quest");
case 616:
return (CATEGORY.Furnitures, "Calender");
}
if (Game1.currentLocation is FarmHouse || Game1.currentLocation is IslandFarmHouse)
{
switch (index)
{
case 173:
return (CATEGORY.Furnitures, "Fridge");
case 169:
case 170:
case 171:
case 172:
return (CATEGORY.Furnitures, "Kitchen");
}
}
}
return (null, null);
}
public static (string?, CATEGORY) getTerrainFeatureAtTile(Netcode.NetRef<TerrainFeature> terrain)
{
string? toReturn = null;
CATEGORY category = CATEGORY.Others;
if (terrain.Get() is HoeDirt)
{
category = CATEGORY.Crops;
HoeDirt dirt = (HoeDirt)terrain.Get();
if (dirt.crop != null)
{
string cropName = Game1.objectInformation[dirt.crop.indexOfHarvest].Split('/')[0];
toReturn = $"{cropName}";
bool isWatered = dirt.state.Value == HoeDirt.watered;
bool isHarvestable = dirt.readyForHarvest();
bool isFertilized = dirt.fertilizer.Value != HoeDirt.noFertilizer;
if (isWatered)
toReturn = "Watered " + toReturn;
if (isFertilized)
toReturn = "Fertilized " + toReturn;
if (isHarvestable)
toReturn = "Harvestable " + toReturn;
}
else
{
toReturn = "Soil";
bool isWatered = dirt.state.Value == HoeDirt.watered;
bool isFertilized = dirt.fertilizer.Value != HoeDirt.noFertilizer;
if (isWatered)
toReturn = "Watered " + toReturn;
if (isFertilized)
toReturn = "Fertilized " + toReturn;
}
}
else if (terrain.Get() is GiantCrop)
{
category = CATEGORY.Crops;
int whichCrop = ((GiantCrop)terrain.Get()).which.Value;
switch (whichCrop)
{
case 0:
toReturn = "Cauliflower";
break;
case 1:
toReturn = "Melon";
break;
case 2:
toReturn = "Pumpkin";
break;
}
}
else if (terrain.Get() is CosmeticPlant)
{
category = CATEGORY.Furnitures;
CosmeticPlant cosmeticPlant = (CosmeticPlant)terrain.Get();
toReturn = cosmeticPlant.textureName().ToLower();
if (toReturn.Contains("terrain"))
toReturn.Replace("terrain", "");
if (toReturn.Contains("feature"))
toReturn.Replace("feature", "");
}
else if (terrain.Get() is Flooring)
{
category = CATEGORY.Flooring;
Flooring flooring = (Flooring)terrain.Get();
bool isPathway = flooring.isPathway.Get();
bool isSteppingStone = flooring.isSteppingStone.Get();
toReturn = "Flooring";
if (isPathway)
toReturn = "Pathway";
if (isSteppingStone)
toReturn = "Stepping Stone";
}
else if (terrain.Get() is FruitTree)
{
category = CATEGORY.Trees;
toReturn = getFruitTree((FruitTree)terrain.Get());
}
else if (terrain.Get() is Grass)
{
category = CATEGORY.Debris;
toReturn = "Grass";
}
else if (terrain.Get() is Tree)
{
category = CATEGORY.Trees;
toReturn = getTree((Tree)terrain.Get());
}
else if (terrain.Get() is Quartz)
{
category = CATEGORY.MineItems;
toReturn = "Quartz";
}
return (toReturn, category);
}
public static string getFruitTree(FruitTree fruitTree)
{
int stage = fruitTree.growthStage.Value;
int fruitIndex = fruitTree.indexOfFruit.Get();
string toReturn = Game1.objectInformation[fruitIndex].Split('/')[0];
if (stage == 0)
toReturn = $"{toReturn} seed";
else if (stage == 1)
toReturn = $"{toReturn} sprout";
else if (stage == 2)
toReturn = $"{toReturn} sapling";
else if (stage == 3)
toReturn = $"{toReturn} bush";
else if (stage >= 4)
toReturn = $"{toReturn} tree";
if (fruitTree.fruitsOnTree.Value > 0)
toReturn = $"Harvestable {toReturn}";
return toReturn;
}
public static string getTree(Tree tree)
{
int treeType = tree.treeType.Value;
int treeStage = tree.growthStage.Value;
string treeName = "tree";
string seedName = "";
// Return with the name if it's one of the 3 special trees
switch (treeType)
{
case 4:
case 5:
return "Winter Tree";
case 6:
return "Palm Tree";
case 7:
return "Mushroom Tree";
}
if (treeType <= 3)
seedName = Game1.objectInformation[308 + treeType].Split('/')[0];
else if (treeType == 8)
seedName = Game1.objectInformation[292].Split('/')[0];
if (treeStage >= 1)
{
switch (seedName.ToLower())
{
case "mahogany seed":
treeName = "Mahogany";
break;
case "acorn":
treeName = "Oak";
break;
case "maple seed":
treeName = "Maple";
break;
case "pine cone":
treeName = "Pine";
break;
}
if (treeStage == 1)
treeName = $"{treeName} sprout";
else if (treeStage == 2)
treeName = $"{treeName} sapling";
else if (treeStage == 3 || treeStage == 4)
treeName = $"{treeName} bush";
else if (treeStage >= 5)
treeName = $"{treeName} tree";
return treeName;
}
return seedName;
}
public static (string?, CATEGORY) getObjectAtTile(int x, int y)
{
string? toReturn = null;
StardewValley.Object obj = Game1.currentLocation.getObjectAtTile(x, y);
int index = obj.ParentSheetIndex;
toReturn = obj.DisplayName;
// Get object names based on index
switch (index)
{
case 313:
case 314:
case 315:
case 316:
case 317:
case 318:
case 452:
case 674:
case 675:
case 676:
case 677:
case 678:
case 679:
case 750:
case 784:
case 785:
case 786:
return ("Weed", CATEGORY.Debris);
case 792:
case 793:
case 794:
return ("Fertile weed", CATEGORY.Debris);
case 319:
case 320:
case 321:
return ("Ice crystal", CATEGORY.Debris);
case 75:
return ("Geode", CATEGORY.MineItems);
case 32:
case 34:
case 36:
case 38:
case 40:
case 42:
case 48:
case 50:
case 52:
case 54:
case 56:
case 58:
return ("Coloured stone", CATEGORY.Debris);
case 668:
case 670:
case 845:
case 846:
case 847:
return ("Mine stone", CATEGORY.MineItems);
case 818:
return ("Clay stone", CATEGORY.Debris);
case 816:
case 817:
return ("Fossil stone", CATEGORY.Debris);
case 118:
case 120:
case 122:
case 124:
return ("Barrel", CATEGORY.MineItems);
case 119:
case 121:
case 123:
case 125:
return ("Item box", CATEGORY.MineItems);
}
if (Game1.currentLocation is Mine or MineShaft)
{
switch (index)
{
case 76:
return ("Frozen geode", CATEGORY.MineItems);
case 77:
return ("Magma geode", CATEGORY.MineItems);
case 8:
case 66:
return ("Amethyst node", CATEGORY.MineItems);
case 14:
case 62:
return ("Aquamarine node", CATEGORY.MineItems);
case 843:
case 844:
return ("Cinder shard node", CATEGORY.MineItems);
case 2:
case 72:
return ("Diamond node", CATEGORY.MineItems);
case 12:
case 60:
return ("Emerald node", CATEGORY.MineItems);
case 44:
return ("Gem node", CATEGORY.MineItems);
case 6:
case 70:
return ("Jade node", CATEGORY.MineItems);
case 46:
return ("Mystic stone", CATEGORY.MineItems);
case 74:
return ("Prismatic node", CATEGORY.MineItems);
case 4:
case 64:
return ("Ruby node", CATEGORY.MineItems);
case 10:
case 68:
return ("Topaz node", CATEGORY.MineItems);
case 819:
return ("Omni geode node", CATEGORY.MineItems);
case 751:
case 849:
return ("Copper node", CATEGORY.MineItems);
case 764:
return ("Gold node", CATEGORY.MineItems);
case 765:
return ("Iridium node", CATEGORY.MineItems);
case 290:
case 850:
return ("Iron node", CATEGORY.MineItems);
}
}
if (obj is Chest)
{
Chest chest = (Chest)obj;
return (chest.DisplayName, CATEGORY.Chests);
}
if (obj is Furniture)
return (toReturn, CATEGORY.Furnitures);
if (toReturn != null)
return (toReturn, CATEGORY.Others);
return (null, CATEGORY.Others);
}
public static bool isMineDownLadderAtTile(int x, int y)
{
try
{
if (Game1.currentLocation is Mine or MineShaft)
{
int? index = null;
if (Game1.currentLocation.Map.GetLayer("Buildings").Tiles[x, y] != null)
index = Game1.currentLocation.Map.GetLayer("Buildings").Tiles[x, y].TileIndex;
if (index == 173 || index == 174)
return true;
}
}
catch (Exception) { }
return false;
}
public static bool isMineUpLadderAtTile(int x, int y)
{
try
{
if (Game1.currentLocation is Mine or MineShaft)
{
int? index = null;
if (Game1.currentLocation.Map.GetLayer("Buildings").Tiles[x, y] != null)
index = Game1.currentLocation.Map.GetLayer("Buildings").Tiles[x, y].TileIndex;
if (index == 115)
return true;
}
}
catch (Exception) { }
return false;
}
public static bool isElevatorAtTile(int x, int y)
{
try
{
if (Game1.currentLocation is Mine or MineShaft)
{
int? index = null;
if (Game1.currentLocation.Map.GetLayer("Buildings").Tiles[x, y] != null)
index = Game1.currentLocation.Map.GetLayer("Buildings").Tiles[x, y].TileIndex;
if (index == 112)
return true;
}
}
catch (Exception) { }
return false;
}
public static string? getDoorAtTile(int x, int y)
{
Point tilePoint = new Point(x, y);
StardewValley.Network.NetPointDictionary<string, Netcode.NetString> doorList = Game1.currentLocation.doors;
for (int i = 0; i < doorList.Count(); i++)
{
if (doorList.ContainsKey(tilePoint))
{
string? doorName;
doorList.TryGetValue(tilePoint, out doorName);
if (doorName != null)
return $"{doorName} door";
else
return "door";
}
}
return null;
}
public static string? getResourceClumpAtTile(int x, int y)
{
if (Game1.currentLocation is Woods)
return getStumpsInWoods(x, y);
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:
return "Large Stump";
case 602:
return "Hollow Log";
case 622:
return "Meteorite";
case 752:
case 754:
case 756:
case 758:
return "Mine Rock";
case 672:
return "Boulder";
default:
return "Unknown";
}
}
}
return null;
}
public static string? getStumpsInWoods(int x, int y)
{
if (Game1.currentLocation is not Woods)
return null;
if ((x == 8 || x == 9) && y == 7)
{
return "Old Master Cannoli";
}
Netcode.NetObjectList<ResourceClump> stumps = ((Woods)Game1.currentLocation).stumps;
for (int i = 0; i < stumps.Count; i++)
{
if (stumps[i].occupiesTile(x, y))
{
return "Large Stump";
}
}
return null;
} }
} }
} }

View File

@ -0,0 +1,177 @@
using Newtonsoft.Json.Linq;
using StardewValley;
namespace stardew_access.Features
{
public class StaticTiles
{
private JObject? staticTilesData = null;
private JObject? customTilesData = null;
public StaticTiles()
{
if (MainClass.ModHelper == null)
return;
try
{
using (StreamReader file = new StreamReader(Path.Combine(MainClass.ModHelper.DirectoryPath, "assets", "static-tiles.json")))
{
string json = file.ReadToEnd();
staticTilesData = JObject.Parse(json);
}
MainClass.InfoLog($"Loaded static-tile.json");
}
catch (System.Exception)
{
MainClass.ErrorLog($"static-tiles.json file not found or an error occured while initializing static-tiles.json\nThe path of the file should be:\n\t{Path.Combine(MainClass.ModHelper.DirectoryPath, "assets", "static-tiles.json")}");
}
try
{
using (StreamReader file = new StreamReader(Path.Combine(MainClass.ModHelper.DirectoryPath, "assets", "custom-tiles.json")))
{
string json = file.ReadToEnd();
customTilesData = JObject.Parse(json);
}
MainClass.InfoLog($"Loaded custom-tile.json");
}
catch (System.Exception)
{
MainClass.ErrorLog($"custom-tiles.json file not found or an error occured while initializing custom-tiles.json\nThe path of the file should be:\n\t{Path.Combine(MainClass.ModHelper.DirectoryPath, "assets", "custom-tiles.json")}");
}
}
public bool isAvailable(string locationName)
{
List<JObject> allData = new List<JObject>();
if (customTilesData != null) allData.Add(customTilesData);
if (staticTilesData != null) allData.Add(staticTilesData);
foreach (JObject data in allData)
{
foreach (KeyValuePair<string, JToken?> location in data)
{
if (location.Key.Contains("||") && MainClass.ModHelper != null)
{
string uniqueModID = location.Key.Substring(location.Key.LastIndexOf("||") + 2);
string locationNameInJson = location.Key.Remove(location.Key.LastIndexOf("||"));
bool isLoaded = MainClass.ModHelper.ModRegistry.IsLoaded(uniqueModID);
if (!isLoaded) continue; // Skip if the specified mod is not loaded
if (locationName.ToLower().Equals(locationNameInJson.ToLower())) return true;
}
else if (locationName.ToLower().Equals(location.Key.ToLower()))
return true;
}
}
return false;
}
public string? getStaticTileInfoAt(int x, int y)
{
return getStaticTileInfoAtWithCategory(x, y).name;
}
public (string? name, CATEGORY category) getStaticTileInfoAtWithCategory(int x, int y)
{
List<JObject> allData = new List<JObject>();
if (customTilesData != null) allData.Add(customTilesData);
if (staticTilesData != null) allData.Add(staticTilesData);
foreach (JObject data in allData)
{
foreach (KeyValuePair<string, JToken?> location in data)
{
if (location.Key.Contains("||") && MainClass.ModHelper != null)
{
// Mod Specific Tiles
// We can add tiles that only get detected when the specified mod is loaded.
// Syntax: <location name>||<Mod's unique id, look into the mod's manifest.json for unique id>
// Example: THe following tile will only be detected if Stardew Valley Expanded mod is installed
// {
// .
// .
// .
// "Town||FlashShifter.StardewValleyExpandedCP":{
// "<Tile Name>":{
// "x": [<x location(s)>],
// "y": [<y location(s)>],
// "type": "<Category name>"
// }
// },
// .
// .
// .
// }
string uniqueModID = location.Key.Substring(location.Key.LastIndexOf("||") + 2);
string locationName = location.Key.Remove(location.Key.LastIndexOf("||"));
bool isLoaded = MainClass.ModHelper.ModRegistry.IsLoaded(uniqueModID);
if (!isLoaded) continue; // Skip if the specified mod is not loaded
if (!Game1.currentLocation.Name.ToLower().Equals(locationName.ToLower())) continue;
}
else if (!Game1.currentLocation.Name.ToLower().Equals(location.Key.ToLower())) continue;
if (location.Value != null)
foreach (var tile in ((JObject)location.Value))
{
if (tile.Value == null)
continue;
JToken? tileXArray = tile.Value["x"];
JToken? tileYArray = tile.Value["y"];
JToken? tileType = tile.Value["type"];
if (tileXArray == null || tileYArray == null || tileType == null)
continue;
bool isXPresent = false;
bool isYPresent = false;
foreach (var item in tileXArray)
{
if (short.Parse(item.ToString()) == x)
{
isXPresent = true;
break;
}
}
foreach (var item in tileYArray)
{
if (short.Parse(item.ToString()) == y)
{
isYPresent = true;
break;
}
}
if (isXPresent && isYPresent)
{
string key = tile.Key;
if (key.Contains('[') && key.Contains(']'))
{
int i1 = key.IndexOf('[');
int i2 = key.LastIndexOf(']');
if (i1 < i2)
{
key = key.Remove(i1, ++i2 - i1);
}
}
return (key.Trim(), CATEGORY.FromString(tileType.ToString().ToLower()));
}
}
}
}
return (null, CATEGORY.Others);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,291 @@
using Microsoft.Xna.Framework;
using xTile;
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Features
{
/// <summary>
/// Allows browsing of the map and snapping mouse to tiles with the arrow keys
/// </summary>
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 finalTile = Vector2.Zero;
private Vector2 prevTile = Vector2.Zero;
public Boolean isAutoWalking = false;
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);
}
}
/// <summary>
/// Return the position of the tile cursor in pixels from the upper-left corner of the map.
/// </summary>
/// <returns>Vector2</returns>
public Vector2 GetTileCursorPosition()
{
Vector2 target = this.PlayerPosition;
if (this.relativeOffsetLock)
{
target += this.relativeOffsetLockPosition;
}
else
{
target += this.PlayerFacingVector + this.ViewingOffset;
}
return target;
}
/// <summary>
/// Return the tile at the position of the tile cursor.
/// </summary>
/// <returns>Vector2</returns>
public Vector2 GetViewingTile()
{
Vector2 position = this.GetTileCursorPosition();
return new Vector2((int)position.X / Game1.tileSize, (int)position.Y / Game1.tileSize);
}
/// <summary>
/// Handle keyboard input related to the tile viewer.
/// </summary>
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));
}
else if (MainClass.Config.AutoWalkToTileKey.JustPressed() && StardewModdingAPI.Context.IsPlayerFree)
{
this.startAutoWalking();
}
}
private void startAutoWalking()
{
PathFindController controller = new PathFindController(Game1.player, Game1.currentLocation, this.GetViewingTile().ToPoint(), Game1.player.FacingDirection);
controller.allowPlayerPathingInEvent = true;
if (controller.pathToEndPoint != null && controller.pathToEndPoint.Count > 0)
{
Game1.player.controller = controller;
this.isAutoWalking = true;
this.finalTile = this.GetViewingTile();
MainClass.ReadTileFeature.pause();
MainClass.ScreenReader.Say($"Moving to {this.finalTile.X}x {this.finalTile.Y}y", true);
}
else
{
MainClass.ScreenReader.Say($"Cannot move to {this.finalTile.X}x {this.finalTile.Y}y", true);
}
}
/// <summary>
/// Stop the auto walk controller and reset variables
/// </summary>
/// <param name="wasForced">Narrates a message if set to true.</param>
public void stopAutoWalking(bool wasForced = false)
{
this.finalTile = Vector2.Zero;
this.isAutoWalking = false;
Game1.player.controller = null;
MainClass.ReadTileFeature.resume();
if (wasForced)
MainClass.ScreenReader.Say("Stopped moving", true);
}
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);
// Prepend the player's name if the viewing tile is occupied by the player itself
if (CurrentPlayer.PositionX == tile.X && CurrentPlayer.PositionY == tile.Y)
{
name = $"{Game1.player.displayName}, {name}";
}
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);
}
/// <summary>
/// Handle tile viewer logic.
/// </summary>
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();
if (this.isAutoWalking)
{
if (Vector2.Distance(this.prevTile, CurrentPlayer.Position) >= 2f)
{
prevTile = CurrentPlayer.Position;
Game1.player.checkForFootstep();
}
if (this.finalTile != Vector2.Zero && this.finalTile == CurrentPlayer.Position)
{
MainClass.ScreenReader.Say("Reached destination", true);
this.stopAutoWalking();
}
}
}
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)
{
// Check whether the position is a warp point, if so then return true, sometimes warp points are 1 tile off the map for example in coops and barns
if (TileInfo.isWarpPointAtTile((int)(position.X / Game1.tileSize), (int)(position.Y / Game1.tileSize))) return true;
//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;
}
}
}

View File

@ -0,0 +1,99 @@
namespace stardew_access.Features
{
/// <summary>
/// This is a custom enum class and contains the name of groups the objects are divided into for the feature
/// </summary>
public class CATEGORY
{
private string _typeKeyWord;
private CATEGORY(string typeKeyWord)
{
_typeKeyWord = typeKeyWord;
}
public override string ToString()
{
return _typeKeyWord;
}
public static CATEGORY FromString(string name)
{
if (name == "farmer")
return CATEGORY.Farmers;
else if (name == "animal")
return CATEGORY.FarmAnimals;
else if (name == "npc")
return CATEGORY.NPCs;
else if (name == "furniture")
return CATEGORY.Furnitures;
else if (name == "flooring")
return CATEGORY.Flooring;
else if (name == "debris")
return CATEGORY.Debris;
else if (name == "crop")
return CATEGORY.Crops;
else if (name == "tree")
return CATEGORY.Trees;
else if (name == "bush")
return CATEGORY.Bush;
else if (name == "building")
return CATEGORY.Buildings;
else if (name == "mine item")
return CATEGORY.MineItems;
else if (name == "resource clump")
return CATEGORY.ResourceClumps;
else if (name == "container")
return CATEGORY.Containers;
else if (name == "bundle")
return CATEGORY.JunimoBundle;
else if (name == "door")
return CATEGORY.Doors;
else if (name == "water")
return CATEGORY.WaterTiles;
else if (name == "interactable")
return CATEGORY.Interactables;
else if (name == "decoration")
return CATEGORY.Decor;
else if (name == "machine")
return CATEGORY.Machines;
else if (name == "bridge")
return CATEGORY.Bridges;
else if (name == "dropped item")
return CATEGORY.DroppedItems;
else if (name == "other")
return CATEGORY.Others;
return Others;
}
public static CATEGORY Farmers = new CATEGORY("farmer");
public static CATEGORY FarmAnimals = new CATEGORY("animal");
public static CATEGORY NPCs = new CATEGORY("npc");
public static CATEGORY Furnitures = new CATEGORY("furniture");
public static CATEGORY Flooring = new CATEGORY("flooring");
public static CATEGORY Debris = new CATEGORY("debris");
public static CATEGORY Crops = new CATEGORY("crop");
public static CATEGORY Trees = new CATEGORY("tree");
public static CATEGORY Bush = new CATEGORY("bush");
public static CATEGORY Buildings = new CATEGORY("building");
public static CATEGORY MineItems = new CATEGORY("mine item");
public static CATEGORY ResourceClumps = new CATEGORY("resource clump");
public static CATEGORY Containers = new CATEGORY("container");
public static CATEGORY JunimoBundle = new CATEGORY("bundle");
public static CATEGORY Doors = new CATEGORY("door"); // Also includes ladders and elevators
public static CATEGORY WaterTiles = new CATEGORY("water");
public static CATEGORY Interactables = new CATEGORY("interactable");
public static CATEGORY Decor = new CATEGORY("decoration");
public static CATEGORY Machines = new CATEGORY("machine");
public static CATEGORY Bridges = new CATEGORY("bridge");
public static CATEGORY DroppedItems = new CATEGORY("dropped item");
public static CATEGORY Others = new CATEGORY("other");
}
public enum MachineState
{
Ready, Busy, Waiting
}
}

View File

@ -0,0 +1,90 @@
namespace stardew_access.Features
{
/// <summary>
/// Warns the player when their health or stamina/energy is low. Also warns when its past midnight.
/// </summary>
public class Warnings
{
// Store the previously checked value
private int prevStamina;
private int prevHealth;
private int prevHour;
public Warnings()
{
prevStamina = 100;
prevHealth = 100;
prevHour = 6;
}
public void update()
{
this.checkForHealth();
this.checkForStamina();
this.checkForTimeOfDay();
}
/// <summary>
/// Warns when its past 12:00 am and 1:00 am
/// </summary>
private void checkForTimeOfDay()
{
if (MainClass.ModHelper == null)
return;
int hours = StardewValley.Game1.timeOfDay / 100;
string toSpeak = MainClass.ModHelper.Translation.Get("warnings.time", new { value = CurrentPlayer.TimeOfDay });
if (hours < 1 && prevHour > 2 || hours >= 1 && prevHour < 1)
{
MainClass.ScreenReader.Say(toSpeak, true);
// Pause the read tile feature to prevent interruption in warning message
MainClass.ReadTileFeature.pauseUntil();
}
prevHour = hours;
}
/// <summary>
/// Warns when stamina reaches below 50, 25 and 10.
/// </summary>
public void checkForStamina()
{
if (MainClass.ModHelper == null)
return;
int stamina = CurrentPlayer.PercentStamina;
string toSpeak = MainClass.ModHelper.Translation.Get("warnings.stamina", new { value = stamina });
if ((stamina <= 50 && prevStamina > 50) || (stamina <= 25 && prevStamina > 25) || (stamina <= 10 && prevStamina > 10))
{
MainClass.ScreenReader.Say(toSpeak, true);
// Pause the read tile feature to prevent interruption in warning message
MainClass.ReadTileFeature.pauseUntil();
}
prevStamina = stamina;
}
/// <summary>
/// Warns when health reaches below 50, 25 and 10.
/// </summary>
public void checkForHealth()
{
if (MainClass.ModHelper == null)
return;
int health = CurrentPlayer.PercentHealth;
string toSpeak = MainClass.ModHelper.Translation.Get("warnings.health", new { value = health });
if ((health <= 50 && prevHealth > 50) || (health <= 25 && prevHealth > 25) || (health <= 10 && prevHealth > 10))
{
MainClass.ScreenReader.Say(toSpeak, true);
// Pause the read tile feature to prevent interruption in warning message
MainClass.ReadTileFeature.pauseUntil();
}
prevHealth = health;
}
}
}

View File

@ -1,9 +1,11 @@
using HarmonyLib; using HarmonyLib;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using stardew_access.Patches; using stardew_access.Patches;
using StardewValley; using StardewValley;
using StardewValley.Menus; using StardewValley.Menus;
using StardewValley.Minigames;
namespace stardew_access namespace stardew_access
{ {
@ -26,6 +28,11 @@ namespace stardew_access
original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.drawHoverText), new Type[] { typeof(SpriteBatch), typeof(string), typeof(SpriteFont), typeof(int), typeof(int), typeof(int), typeof(string), typeof(int), typeof(string[]), typeof(Item), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(float), typeof(CraftingRecipe), typeof(IList<Item>) }), original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.drawHoverText), new Type[] { typeof(SpriteBatch), typeof(string), typeof(SpriteFont), typeof(int), typeof(int), typeof(int), typeof(string), typeof(int), typeof(string[]), typeof(Item), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(float), typeof(CraftingRecipe), typeof(IList<Item>) }),
postfix: new HarmonyMethod(typeof(DialoguePatches), nameof(DialoguePatches.HoverTextPatch)) postfix: new HarmonyMethod(typeof(DialoguePatches), nameof(DialoguePatches.HoverTextPatch))
); );
harmony.Patch(
original: AccessTools.Method(typeof(NPC), nameof(NPC.drawAboveAlwaysFrontLayer)),
postfix: new HarmonyMethod(typeof(DialoguePatches), nameof(DialoguePatches.drawAboveAlwaysFrontLayerPatch))
);
#endregion #endregion
#region Title Menu Patches #region Title Menu Patches
@ -41,13 +48,18 @@ namespace stardew_access
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(CharacterCustomization), nameof(CharacterCustomization.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(CharacterCustomization), nameof(CharacterCustomization.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(TitleMenuPatches), nameof(TitleMenuPatches.NewGameMenuPatch)) postfix: new HarmonyMethod(typeof(TitleMenuPatches), nameof(TitleMenuPatches.CharacterCustomizationMenuPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(CoopMenu), nameof(CoopMenu.update), new Type[] { typeof(GameTime) }), original: AccessTools.Method(typeof(CoopMenu), nameof(CoopMenu.update), new Type[] { typeof(GameTime) }),
postfix: new HarmonyMethod(typeof(TitleMenuPatches), nameof(TitleMenuPatches.CoopMenuPatch)) postfix: new HarmonyMethod(typeof(TitleMenuPatches), nameof(TitleMenuPatches.CoopMenuPatch))
); );
harmony.Patch(
original: AccessTools.Method(typeof(AdvancedGameOptions), nameof(AdvancedGameOptions.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(TitleMenuPatches), nameof(TitleMenuPatches.AdvancedGameOptionsPatch))
);
#endregion #endregion
#region Game Menu Patches #region Game Menu Patches
@ -96,16 +108,28 @@ namespace stardew_access
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.SocialPagePatch)) postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.SocialPagePatch))
); );
harmony.Patch(
original: AccessTools.Method(typeof(CollectionsPage), nameof(CollectionsPage.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.CollectionsPagePatch))
);
#endregion
#region Bundle Menu Patches
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(JunimoNoteMenu), nameof(JunimoNoteMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(JunimoNoteMenu), nameof(JunimoNoteMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.JunimoNoteMenuPatch)) postfix: new HarmonyMethod(typeof(BundleMenuPatches), nameof(BundleMenuPatches.JunimoNoteMenuPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(JojaCDMenu), nameof(JojaCDMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(BundleMenuPatches), nameof(BundleMenuPatches.JojaCDMenuPatch))
); );
#endregion #endregion
#region Menu Patches #region Menu Patches
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(LetterViewerMenu), nameof(LetterViewerMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(LetterViewerMenu), nameof(LetterViewerMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.LetterViewerMenuPatch)) postfix: new HarmonyMethod(typeof(DialoguePatches), nameof(DialoguePatches.LetterViewerMenuPatch))
); );
harmony.Patch( harmony.Patch(
@ -124,7 +148,12 @@ namespace stardew_access
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Constructor(typeof(NamingMenu), new Type[] { typeof(NamingMenu.doneNamingBehavior), typeof(string), typeof(string) }), original: AccessTools.Method(typeof(TitleTextInputMenu), nameof(TitleTextInputMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.TitleTextInputMenuPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(NamingMenu), nameof(NamingMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.NamingMenuPatch)) postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.NamingMenuPatch))
); );
@ -137,6 +166,36 @@ namespace stardew_access
original: AccessTools.Method(typeof(LanguageSelectionMenu), nameof(LanguageSelectionMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(LanguageSelectionMenu), nameof(LanguageSelectionMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.LanguageSelectionMenuPatch)) postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.LanguageSelectionMenuPatch))
); );
harmony.Patch(
original: AccessTools.Method(typeof(MuseumMenu), nameof(MuseumMenu.receiveKeyPress), new Type[] { typeof(Keys) }),
prefix: new HarmonyMethod(typeof(DonationMenuPatches), nameof(DonationMenuPatches.MuseumMenuKeyPressPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(ChooseFromListMenu), nameof(ChooseFromListMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.ChooseFromListMenuPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(TailoringMenu), nameof(TailoringMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.TailoringMenuPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(PondQueryMenu), nameof(PondQueryMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.PondQueryMenuPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(ForgeMenu), nameof(ForgeMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.ForgeMenuPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(ItemListMenu), nameof(ItemListMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.ItemListMenuPatch))
);
#endregion #endregion
#region Quest Patches #region Quest Patches
@ -159,7 +218,7 @@ namespace stardew_access
#region Chat Menu Patches #region Chat Menu Patches
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(ChatBox), nameof(ChatBox.update), new Type[] { typeof(GameTime) }), original: AccessTools.Method(typeof(ChatBox), nameof(ChatBox.update), new Type[] { typeof(GameTime) }),
postfix: new HarmonyMethod(typeof(ChatManuPatches), nameof(ChatManuPatches.ChatBoxPatch)) postfix: new HarmonyMethod(typeof(ChatMenuPatches), nameof(ChatMenuPatches.ChatBoxPatch))
); );
#endregion #endregion
@ -186,6 +245,35 @@ namespace stardew_access
prefix: new HarmonyMethod(typeof(BuildingNAnimalMenuPatches), nameof(BuildingNAnimalMenuPatches.PurchaseAnimalsMenuPatch)) prefix: new HarmonyMethod(typeof(BuildingNAnimalMenuPatches), nameof(BuildingNAnimalMenuPatches.PurchaseAnimalsMenuPatch))
); );
harmony.Patch(
original: AccessTools.Method(typeof(AnimalQueryMenu), nameof(AnimalQueryMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(BuildingNAnimalMenuPatches), nameof(BuildingNAnimalMenuPatches.AnimalQueryMenuPatch))
);
#endregion
#region Donation Menus
harmony.Patch(
original: AccessTools.Method(typeof(MuseumMenu), nameof(MuseumMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(DonationMenuPatches), nameof(DonationMenuPatches.MuseumMenuPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(FieldOfficeMenu), nameof(FieldOfficeMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(DonationMenuPatches), nameof(DonationMenuPatches.FieldOfficeMenuPatch))
);
#endregion
#region Mini Games
harmony.Patch(
original: AccessTools.Method(typeof(Intro), nameof(Intro.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MiniGamesPatches), nameof(MiniGamesPatches.IntroPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(GrandpaStory), nameof(GrandpaStory.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MiniGamesPatches), nameof(MiniGamesPatches.GrandpaStoryPatch))
);
#endregion #endregion
harmony.Patch( harmony.Patch(

View File

@ -0,0 +1,88 @@
using StardewModdingAPI.Utilities;
namespace stardew_access
{
internal class ModConfig
{
// https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Input#SButton button key codes
#region Simulate mouse clicks
public KeybindList LeftClickMainKey { get; set; } = KeybindList.Parse("LeftControl + Enter"); // Primary key to simulate mouse left click
public KeybindList RightClickMainKey { get; set; } = KeybindList.Parse("LeftShift + Enter"); // Primary key to simulate mouse right click
public KeybindList LeftClickAlternateKey { get; set; } = KeybindList.Parse("OemOpenBrackets"); // Secondary key to simulate mouse left click
public KeybindList RightClickAlternateKey { get; set; } = KeybindList.Parse("OemCloseBrackets"); // Secondary key to simulate mouse right click
#endregion
#region Chat menu
public KeybindList ChatMenuNextKey { get; set; } = KeybindList.Parse("PageUp"); // Read previous chat message
public KeybindList ChatMenuPreviousKey { get; set; } = KeybindList.Parse("PageDown"); // Read next chat message
#endregion
#region Read tile
public Boolean ReadTile { get; set; } = true; // Toggle this feature.
public KeybindList ReadTileKey { get; set; } = KeybindList.Parse("J"); // Manually trigger read tile for the tile player is *looking at*.
public KeybindList ReadStandingTileKey { get; set; } = KeybindList.Parse("LeftAlt + J"); // Manually trigger read tile for the tile player is *standing on*.
public Boolean ReadFlooring { get; set; } = false; // Toggle reading floorings.
public Boolean WateredToggle { get; set; } = true; // Toggle speaking watered or unwatered for crops.
#endregion
#region Tile viewer
public KeybindList TileCursorUpKey { get; set; } = KeybindList.Parse("Up"); // Move the cursor one tile up
public KeybindList TileCursorRightKey { get; set; } = KeybindList.Parse("Right"); // Move the cursor one tile right
public KeybindList TileCursorDownKey { get; set; } = KeybindList.Parse("Down"); // Move the cursor one tile down
public KeybindList TileCursorLeftKey { get; set; } = KeybindList.Parse("Left"); // Move the cursor one tile left
public KeybindList TileCursorPreciseUpKey { get; set; } = KeybindList.Parse("LeftShift + Up"); // Move the cursor up by precision i.e. pixel by pixel
public KeybindList TileCursorPreciseRightKey { get; set; } = KeybindList.Parse("LeftShift + Right"); // Move the cursor right by precision i.e. pixel by pixel
public KeybindList TileCursorPreciseDownKey { get; set; } = KeybindList.Parse("LeftShift + Down"); // Move the cursor down by precision i.e. pixel by pixel
public KeybindList TileCursorPreciseLeftKey { get; set; } = KeybindList.Parse("LeftShift + Left"); // Move the cursor left by precision i.e. pixel by pixel
public KeybindList ToggleRelativeCursorLockKey { get; set; } = KeybindList.Parse("L"); // Toggles realative cursor lock i.e. if enabled, the cursor will reset when player moves.
public KeybindList AutoWalkToTileKey { get; set; } = KeybindList.Parse("LeftControl + Enter"); // Auto walk to the tile
public bool LimitTileCursorToScreen { get; set; } = false; // #TODO add command for this // Toggle whether to prevent cursor from going out of screen.
public int TileCursorPreciseMovementDistance { get; set; } = 8; // Specifies the number of pixels the cursor should move when using precision movement i.e. with *left shift*.
#endregion
#region Radar
public Boolean Radar { get; set; } = false;
public Boolean RadarStereoSound { get; set; } = true;
#endregion
#region Menu Keys
public KeybindList PrimaryInfoKey { get; set; } = KeybindList.Parse("C");
// Charachter Creatinon menu (new game menu) keys
public KeybindList CharacterCreationMenuNextKey { get; set; } = KeybindList.Parse("Right");
public KeybindList CharacterCreationMenuPreviousKey { get; set; } = KeybindList.Parse("Left");
// Bundle menu keys
public KeybindList BundleMenuIngredientsKey { get; set; } = KeybindList.Parse("I");
public KeybindList BundleMenuInventoryItemsKey { get; set; } = KeybindList.Parse("C");
public KeybindList BundleMenuPurchaseButtonKey { get; set; } = KeybindList.Parse("P");
public KeybindList BundleMenuIngredientsInputSlotKey { get; set; } = KeybindList.Parse("V");
public KeybindList BundleMenuBackButtonKey { get; set; } = KeybindList.Parse("Back");
// Menus with secondary inventory(shop inventory or chest inventory or crafting recipe list)
public KeybindList SnapToFirstInventorySlotKey { get; set; } = KeybindList.Parse("I");
public KeybindList SnapToFirstSecondaryInventorySlotKey { get; set; } = KeybindList.Parse("LeftShift + I");
// Crafting menu
public KeybindList CraftingMenuCycleThroughRecipiesKey { get; set; } = KeybindList.Parse("C");
#endregion
#region Others
public KeybindList HealthNStaminaKey { get; set; } = KeybindList.Parse("H"); // Narrate health and stamina.
public bool HealthNStaminaInPercentage { get; set; } = true;
public KeybindList PositionKey { get; set; } = KeybindList.Parse("K"); // Narrate player position.
public KeybindList LocationKey { get; set; } = KeybindList.Parse("LeftAlt + K"); // Narrate current location name.
public KeybindList MoneyKey { get; set; } = KeybindList.Parse("R"); // Narrate the money the player has currently.
public KeybindList TimeNSeasonKey { get; set; } = KeybindList.Parse("Q"); // Narrate the time of day, day and date and season
public Boolean VerboseCoordinates { get; set; } = true;
public Boolean SnapMouse { get; set; } = true; // Toggles the snap mouse feature
public Boolean Warning { get; set; } = true; // Toggles the warnings feature
public Boolean TTS { get; set; } = true; // Toggles the screen reader/tts.
public Boolean TrackDroppedItems {get; set;} = true; // Toggles detecting the dropped items.
#endregion
// TODO Add the exclusion and focus list too
// public String ExclusionList { get; set; } = "test";
}
}

View File

@ -6,49 +6,102 @@ using HarmonyLib;
using stardew_access.Patches; using stardew_access.Patches;
using stardew_access.ScreenReader; using stardew_access.ScreenReader;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using StardewValley.Menus;
namespace stardew_access namespace stardew_access
{ {
public class MainClass : Mod public class MainClass : Mod
{ {
#region Global Vars & Properties
#pragma warning disable CS8603
private static int prevDate = -99;
private static ModConfig? config;
private Harmony? harmony; private Harmony? harmony;
public static bool readTile = true; private static IMonitor? monitor;
public static bool snapMouse = true; private static Radar? radarFeature;
public static bool isNarratingHudMessage = false; private static StaticTiles? sTiles;
public static bool radar = false;
public static bool radarDebug = false;
public static bool radarStereoSound = true;
private static IMonitor monitor;
public static string hudMessageQueryKey = "";
private static Radar radarFeature;
private static IScreenReader? screenReader; private static IScreenReader? screenReader;
private static IModHelper modHelper; private static IModHelper? modHelper;
private static TileViewer? tileViewer;
private static Warnings? warnings;
private static ReadTile? readTile;
public static IModHelper ModHelper { get => modHelper; } internal static ModConfig Config { get => config; set => config = value; }
public static Radar RadarFeature { get => radarFeature; set => radarFeature = value; } public static IModHelper? ModHelper { get => modHelper; }
public static IScreenReader GetScreenReader() public static StaticTiles STiles
{ {
if (screenReader == null) get
screenReader = new ScreenReaderController().Initialize(); {
return screenReader; if (sTiles == null)
sTiles = new StaticTiles();
return sTiles;
}
set => sTiles = value;
}
public static Radar RadarFeature
{
get
{
if (radarFeature == null)
radarFeature = new Radar();
return radarFeature;
}
set => radarFeature = value;
} }
public static void SetScreenReader(IScreenReader value) public static string hudMessageQueryKey = "";
public static bool isNarratingHudMessage = false;
public static bool radarDebug = false;
public static IScreenReader ScreenReader
{ {
screenReader = value; get
{
if (screenReader == null)
screenReader = new ScreenReaderController().Initialize();
return screenReader;
}
set => screenReader = value;
} }
public static IMonitor GetMonitor() public static TileViewer TileViewerFeature
{ {
return monitor; get
{
if (tileViewer == null)
tileViewer = new TileViewer();
return tileViewer;
}
} }
public static void SetMonitor(IMonitor value) public static ReadTile ReadTileFeature
{ {
monitor = value; get
{
if (readTile == null)
readTile = new ReadTile();
return readTile;
}
} }
public static Warnings WarningsFeature
{
get
{
if (warnings == null)
warnings = new Warnings();
return warnings;
}
}
#pragma warning restore CS8603
#endregion
/********* /*********
** Public methods ** Public methods
*********/ *********/
@ -57,20 +110,20 @@ namespace stardew_access
public override void Entry(IModHelper helper) public override void Entry(IModHelper helper)
{ {
#region Initializations #region Initializations
Config = helper.ReadConfig<ModConfig>();
SetMonitor(base.Monitor); // Inititalize monitor monitor = base.Monitor; // Inititalize monitor
modHelper = helper; modHelper = helper;
Game1.options.setGamepadMode("force_on"); Game1.options.setGamepadMode("force_on");
SetScreenReader(new ScreenReaderController().Initialize()); ScreenReader = new ScreenReaderController().Initialize();
ScreenReader.Say("Initializing Stardew Access", true);
CustomSoundEffects.Initialize(); CustomSoundEffects.Initialize();
CustomCommands.Initialize(); CustomCommands.Initialize();
RadarFeature = new Radar();
harmony = new Harmony(ModManifest.UniqueID); harmony = new Harmony(ModManifest.UniqueID);
HarmonyPatches.Initialize(harmony); HarmonyPatches.Initialize(harmony);
@ -94,9 +147,10 @@ namespace stardew_access
public void OnExit(object? sender, EventArgs? e) public void OnExit(object? sender, EventArgs? e)
{ {
// Don't if this ever gets called or not but, just in case if it does. // This closes the connection with the screen reader, important for linux
if (GetScreenReader() != null) // Don't know if this ever gets called or not but, just in case if it does.
GetScreenReader().CloseScreenReader(); if (ScreenReader != null)
ScreenReader.CloseScreenReader();
} }
/// <summary>Returns the Screen Reader class for other mods to use.</summary> /// <summary>Returns the Screen Reader class for other mods to use.</summary>
@ -110,26 +164,43 @@ namespace stardew_access
if (!Context.IsPlayerFree) if (!Context.IsPlayerFree)
return; return;
// Reset variables // Narrates currently selected inventory slot
MenuPatches.resetGlobalVars();
QuestPatches.resetGlobalVars();
Other.narrateCurrentSlot(); Other.narrateCurrentSlot();
// Narrate current location's name
Other.narrateCurrentLocation(); Other.narrateCurrentLocation();
if (snapMouse) //handle TileCursor update logic
Other.SnapMouseToPlayer(); TileViewerFeature.update();
if (!ReadTile.isReadingTile && readTile) if (Config.Warning)
ReadTile.run(); WarningsFeature.update();
if (!RadarFeature.isRunning && radar) if (Config.ReadTile)
ReadTileFeature.update();
if (!RadarFeature.isRunning && Config.Radar)
{
RadarFeature.isRunning = true;
RadarFeature.Run(); RadarFeature.Run();
Task.Delay(RadarFeature.delay).ContinueWith(_ => { RadarFeature.isRunning = false; });
}
if (!isNarratingHudMessage) if (!isNarratingHudMessage)
{ {
isNarratingHudMessage = true;
Other.narrateHudMessages(); Other.narrateHudMessages();
Task.Delay(300).ContinueWith(_ => { isNarratingHudMessage = false; });
}
if (Game1.player != null)
{
if (Game1.timeOfDay >= 600 && prevDate != CurrentPlayer.Date)
{
prevDate = CurrentPlayer.Date;
DebugLog("Refreshing buildlist...");
CustomCommands.onBuildListCalled();
}
} }
} }
@ -138,74 +209,172 @@ namespace stardew_access
if (e == null) if (e == null)
return; return;
#region Simulate left and right clicks
if (Game1.activeClickableMenu != null) if (Game1.activeClickableMenu != null)
{ {
bool isLeftShiftPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftShift); bool isCustomizingChrachter = Game1.activeClickableMenu is CharacterCustomization || (TitleMenu.subMenu != null && TitleMenu.subMenu is CharacterCustomization);
bool isLeftControlPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftControl);
// Perform Left Click #region Mouse Click Simulation
if (Equals(e.Button, SButton.OemOpenBrackets)) // Main Keybinds
if (Config.LeftClickMainKey.JustPressed())
{ {
Game1.activeClickableMenu.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true)); Game1.activeClickableMenu.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true));
} }
if (isLeftControlPressed && Equals(e.Button, SButton.Enter)) if (Config.RightClickMainKey.JustPressed())
{
Game1.activeClickableMenu.receiveRightClick(Game1.getMouseX(true), Game1.getMouseY(true));
}
// Alternate Keybinds
if (!isCustomizingChrachter && Game1.activeClickableMenu is not AnimalQueryMenu && Config.LeftClickAlternateKey.JustPressed()) // Excluding the character creation menu
{ {
Game1.activeClickableMenu.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true)); Game1.activeClickableMenu.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true));
} }
if (!isCustomizingChrachter && Game1.activeClickableMenu is not AnimalQueryMenu && Config.RightClickAlternateKey.JustPressed()) // Excluding the character creation menu
// Perform Right CLick
if (Equals(e.Button, SButton.OemCloseBrackets))
{
Game1.activeClickableMenu.receiveRightClick(Game1.getMouseX(true), Game1.getMouseY(true));
}
if (isLeftShiftPressed && Equals(e.Button, SButton.Enter))
{ {
Game1.activeClickableMenu.receiveRightClick(Game1.getMouseX(true), Game1.getMouseY(true)); Game1.activeClickableMenu.receiveRightClick(Game1.getMouseX(true), Game1.getMouseY(true));
} }
#endregion
} }
if (Game1.currentMinigame != null)
{
bool isCustomizingChrachter = Game1.activeClickableMenu is CharacterCustomization || (TitleMenu.subMenu != null && TitleMenu.subMenu is CharacterCustomization);
#region Mouse Click Simulation
// Main Keybinds
if (Config.LeftClickMainKey.JustPressed())
{
Game1.currentMinigame.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true));
}
if (Config.RightClickMainKey.JustPressed())
{
Game1.currentMinigame.receiveRightClick(Game1.getMouseX(true), Game1.getMouseY(true));
}
// Alternate Keybinds
if (Config.LeftClickAlternateKey.JustPressed())
{
Game1.currentMinigame.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true));
}
if (Config.RightClickAlternateKey.JustPressed())
{
Game1.currentMinigame.receiveRightClick(Game1.getMouseX(true), Game1.getMouseY(true));
}
#endregion
}
#endregion
if (!Context.IsPlayerFree) if (!Context.IsPlayerFree)
return; return;
// Narrate health and stamina // Stops the auto walk controller if any movement key(WASD) is pressed
if (Equals(e.Button, SButton.H)) if (TileViewerFeature.isAutoWalking &&
(e.Button.Equals(SButtonExtensions.ToSButton(Game1.options.moveUpButton[0]))
|| e.Button.Equals(SButtonExtensions.ToSButton(Game1.options.moveDownButton[0]))
|| e.Button.Equals(SButtonExtensions.ToSButton(Game1.options.moveLeftButton[0]))
|| e.Button.Equals(SButtonExtensions.ToSButton(Game1.options.moveRightButton[0]))))
{ {
string toSpeak = $"Health is {CurrentPlayer.getHealth()} and Stamina is {CurrentPlayer.getStamina()}"; TileViewerFeature.stopAutoWalking(wasForced: true);
MainClass.GetScreenReader().Say(toSpeak, true); }
// Narrate Current Location
if (Config.LocationKey.JustPressed())
{
string toSpeak = $"{Game1.currentLocation.Name}";
MainClass.ScreenReader.Say(toSpeak, true);
return;
} }
// Narrate Position // Narrate Position
if (Equals(e.Button, SButton.K)) if (Config.PositionKey.JustPressed())
{ {
string toSpeak = $"X: {CurrentPlayer.getPositionX()} , Y: {CurrentPlayer.getPositionY()}"; string toSpeak;
MainClass.GetScreenReader().Say(toSpeak, true); if (Config.VerboseCoordinates)
{
toSpeak = $"X: {CurrentPlayer.PositionX}, Y: {CurrentPlayer.PositionY}";
}
else
{
toSpeak = $"{CurrentPlayer.PositionX}, {CurrentPlayer.PositionY}";
}
MainClass.ScreenReader.Say(toSpeak, true);
return;
}
// Narrate health and stamina
if (Config.HealthNStaminaKey.JustPressed())
{
if (ModHelper == null)
return;
string toSpeak;
if (Config.HealthNStaminaInPercentage)
toSpeak = ModHelper.Translation.Get("manuallytriggered.healthnstamina.percent", new { health = CurrentPlayer.PercentHealth, stamina = CurrentPlayer.PercentStamina });
else
toSpeak = ModHelper.Translation.Get("manuallytriggered.healthnstamina.normal", new { health = CurrentPlayer.CurrentHealth, stamina = CurrentPlayer.CurrentStamina });
MainClass.ScreenReader.Say(toSpeak, true);
return;
} }
// Narrate money at hand // Narrate money at hand
if (Equals(e.Button, SButton.R)) if (Config.MoneyKey.JustPressed())
{ {
string toSpeak = $"You have {CurrentPlayer.getMoney()}g"; string toSpeak = $"You have {CurrentPlayer.Money}g";
MainClass.GetScreenReader().Say(toSpeak, true); MainClass.ScreenReader.Say(toSpeak, true);
return;
} }
// Narrate time and season // Narrate time and season
if (Equals(e.Button, SButton.Q)) if (Config.TimeNSeasonKey.JustPressed())
{ {
string toSpeak = $"Time is {CurrentPlayer.getTimeOfDay()} and it is {CurrentPlayer.getDay()} {CurrentPlayer.getDate()} of {CurrentPlayer.getSeason()}"; string toSpeak = $"Time is {CurrentPlayer.TimeOfDay} and it is {CurrentPlayer.Day} {CurrentPlayer.Date} of {CurrentPlayer.Season}";
MainClass.GetScreenReader().Say(toSpeak, true); MainClass.ScreenReader.Say(toSpeak, true);
return;
} }
// Manual read tile // Manual read tile at player's position
if (Equals(e.Button, SButton.J)) if (Config.ReadStandingTileKey.JustPressed())
{ {
ReadTile.run(manuallyTriggered: true); ReadTileFeature.run(manuallyTriggered: true, playersPosition: true);
return;
} }
/*if (Equals(e.Button, SButton.B)) // Manual read tile at looking tile
if (Config.ReadTileKey.JustPressed())
{ {
Game1.player.controller = new PathFindController(Game1.player, Game1.currentLocation, new Point(49,13), 2); ReadTileFeature.run(manuallyTriggered: true);
monitor.Log($"{Game1.player.controller.pathToEndPoint==null}", LogLevel.Debug); // true if path not found return;
}*/ }
// Tile viewing cursor keys
TileViewerFeature.HandleInput();
}
public static void ErrorLog(string message)
{
if (monitor == null)
return;
monitor.Log(message, LogLevel.Error);
}
public static void InfoLog(string message)
{
if (monitor == null)
return;
monitor.Log(message, LogLevel.Info);
}
public static void DebugLog(string message)
{
if (monitor == null)
return;
monitor.Log(message, LogLevel.Debug);
} }
} }
} }

View File

@ -1,5 +1,4 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using StardewModdingAPI;
using StardewValley; using StardewValley;
using StardewValley.Buildings; using StardewValley.Buildings;
using StardewValley.Locations; using StardewValley.Locations;
@ -13,20 +12,100 @@ namespace stardew_access.Patches
internal static Vector2[] marked = new Vector2[10]; internal static Vector2[] marked = new Vector2[10];
internal static Building?[] availableBuildings = new Building[100]; internal static Building?[] availableBuildings = new Building[100];
internal static CarpenterMenu? carpenterMenu = null; internal static CarpenterMenu? carpenterMenu = null;
internal static bool isNarratingAnimalInfo = false;
internal static string animalQueryMenuQuery = " ";
internal static string carpenterMenuQuery = "", purchaseAnimalMenuQuery = ""; internal static string carpenterMenuQuery = "", purchaseAnimalMenuQuery = "";
internal static bool isSayingBlueprintInfo = false; internal static bool isSayingBlueprintInfo = false;
internal static string prevBlueprintInfo = ""; internal static string prevBlueprintInfo = "";
internal static bool isOnFarm = false, isUpgrading = false, isDemolishing = false, isPainting = false, isConstructing = false, isMoving = false, isMagicalConstruction = false; internal static bool isOnFarm = false, isUpgrading = false, isDemolishing = false, isPainting = false, isConstructing = false, isMoving = false, isMagicalConstruction = false;
internal static bool firstTimeInNamingMenu = true; internal static bool firstTimeInNamingMenu = true;
internal static PurchaseAnimalsMenu? purchaseAnimalsMenu; internal static PurchaseAnimalsMenu? purchaseAnimalsMenu;
internal static AnimalQueryMenu? animalQueryMenu;
private static FarmAnimal? animalBeingPurchasedOrMoved = null;
internal static void PurchaseAnimalsMenuPatch(PurchaseAnimalsMenu __instance, bool ___onFarm, bool ___namingAnimal, TextBox ___textBox) internal static void AnimalQueryMenuPatch(AnimalQueryMenu __instance, bool ___confirmingSell, FarmAnimal ___animal, TextBox ___textBox, string ___parentName, bool ___movingAnimal)
{ {
try try
{ {
int x = Game1.getMouseX(), y = Game1.getMouseY(); // Mouse x and y position int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
bool isPrimaryInfoKeyPressed = MainClass.Config.PrimaryInfoKey.JustPressed(); // For narrating animal details
bool isEscPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Escape); // For escaping/unselecting from the animal name text box
string toSpeak = " ", details = " ";
isOnFarm = ___movingAnimal;
animalQueryMenu = __instance;
animalBeingPurchasedOrMoved = ___animal;
if (___textBox.Selected)
{
toSpeak = ___textBox.Text;
if (isEscPressed)
{
___textBox.Selected = false;
}
}
else
{
if (isPrimaryInfoKeyPressed & !isNarratingAnimalInfo)
{
string name = ___animal.displayName;
string type = ___animal.displayType;
int age = (___animal.GetDaysOwned() + 1) / 28 + 1;
string ageText = (age <= 1) ? Game1.content.LoadString("Strings\\UI:AnimalQuery_Age1") : Game1.content.LoadString("Strings\\UI:AnimalQuery_AgeN", age);
string parent = "";
if ((int)___animal.age.Value < (byte)___animal.ageWhenMature.Value)
{
ageText += Game1.content.LoadString("Strings\\UI:AnimalQuery_AgeBaby");
}
if (___parentName != null)
{
parent = Game1.content.LoadString("Strings\\UI:AnimalQuery_Parent", ___parentName);
}
details = $"Name: {name} Type: {type} \n\t Age: {ageText} {parent}";
animalQueryMenuQuery = " ";
isNarratingAnimalInfo = true;
Task.Delay(200).ContinueWith(_ => { isNarratingAnimalInfo = false; });
}
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
toSpeak = "OK button";
else if (__instance.sellButton != null && __instance.sellButton.containsPoint(x, y))
toSpeak = $"Sell for {___animal.getSellPrice()}g button";
else if (___confirmingSell && __instance.yesButton != null && __instance.yesButton.containsPoint(x, y))
toSpeak = "Confirm selling animal";
else if (___confirmingSell && __instance.noButton != null && __instance.noButton.containsPoint(x, y))
toSpeak = "Cancel selling animal";
else if (__instance.moveHomeButton != null && __instance.moveHomeButton.containsPoint(x, y))
toSpeak = "Change home building button";
else if (__instance.allowReproductionButton != null && __instance.allowReproductionButton.containsPoint(x, y))
toSpeak = ((___animal.allowReproduction.Value) ? "Enabled" : "Disabled") + " allow reproduction button";
else if (__instance.textBoxCC != null && __instance.textBoxCC.containsPoint(x, y))
toSpeak = "Animal name text box";
}
if (animalQueryMenuQuery != toSpeak)
{
animalQueryMenuQuery = toSpeak;
MainClass.ScreenReader.Say($"{details} {toSpeak}", true);
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void PurchaseAnimalsMenuPatch(PurchaseAnimalsMenu __instance, bool ___onFarm, bool ___namingAnimal, TextBox ___textBox, FarmAnimal ___animalBeingPurchased)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
purchaseAnimalsMenu = __instance; purchaseAnimalsMenu = __instance;
isOnFarm = ___onFarm; isOnFarm = ___onFarm;
animalBeingPurchasedOrMoved = ___animalBeingPurchased;
if (___onFarm && ___namingAnimal) if (___onFarm && ___namingAnimal)
{ {
@ -61,7 +140,7 @@ namespace stardew_access.Patches
firstTimeInNamingMenu = false; firstTimeInNamingMenu = false;
} }
MainClass.GetScreenReader().Say(toSpeak, true); MainClass.ScreenReader.Say(toSpeak, true);
} }
} }
else if (___onFarm && !___namingAnimal) else if (___onFarm && !___namingAnimal)
@ -90,7 +169,7 @@ namespace stardew_access.Patches
if (purchaseAnimalMenuQuery != toSpeak) if (purchaseAnimalMenuQuery != toSpeak)
{ {
purchaseAnimalMenuQuery = toSpeak; purchaseAnimalMenuQuery = toSpeak;
MainClass.GetScreenReader().Say(toSpeak, true); MainClass.ScreenReader.Say(toSpeak, true);
} }
return; return;
} }
@ -98,7 +177,7 @@ namespace stardew_access.Patches
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
} }
} }
@ -125,8 +204,8 @@ namespace stardew_access.Patches
if (currentBluprint == null) if (currentBluprint == null)
return; return;
int x = Game1.getMouseX(), y = Game1.getMouseY(); // Mouse x and y position int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
bool isBPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.B); bool isPrimaryInfoKeyPressed = MainClass.Config.PrimaryInfoKey.JustPressed();
string ingredients = ""; string ingredients = "";
string name = currentBluprint.displayName; string name = currentBluprint.displayName;
string upgradeName = currentBluprint.nameOfBuildingToUpgrade; string upgradeName = currentBluprint.nameOfBuildingToUpgrade;
@ -143,7 +222,7 @@ namespace stardew_access.Patches
int itemStack = ___ingredients[i].Stack; int itemStack = ___ingredients[i].Stack;
string itemQuality = ""; string itemQuality = "";
int qualityValue = ((StardewValley.Object)___ingredients[i]).quality; int qualityValue = ((StardewValley.Object)___ingredients[i]).Quality;
if (qualityValue == 1) if (qualityValue == 1)
{ {
itemQuality = "Silver quality"; itemQuality = "Silver quality";
@ -163,7 +242,7 @@ namespace stardew_access.Patches
blueprintInfo = $"{name}, Price: {price}, Ingredients: {ingredients}, Dimensions: {width} width and {height} height, Description: {description}"; blueprintInfo = $"{name}, Price: {price}, Ingredients: {ingredients}, Dimensions: {width} width and {height} height, Description: {description}";
if (isBPressed && !isSayingBlueprintInfo) if (isPrimaryInfoKeyPressed && !isSayingBlueprintInfo)
{ {
SayBlueprintInfo(blueprintInfo); SayBlueprintInfo(blueprintInfo);
} }
@ -180,7 +259,7 @@ namespace stardew_access.Patches
if (carpenterMenuQuery != toSpeak) if (carpenterMenuQuery != toSpeak)
{ {
carpenterMenuQuery = toSpeak; carpenterMenuQuery = toSpeak;
MainClass.GetScreenReader().Say(toSpeak, true); MainClass.ScreenReader.Say(toSpeak, true);
} }
return; return;
} }
@ -191,7 +270,7 @@ namespace stardew_access.Patches
if (carpenterMenuQuery != toSpeak) if (carpenterMenuQuery != toSpeak)
{ {
carpenterMenuQuery = toSpeak; carpenterMenuQuery = toSpeak;
MainClass.GetScreenReader().Say(toSpeak, true); MainClass.ScreenReader.Say(toSpeak, true);
} }
return; return;
} }
@ -202,7 +281,7 @@ namespace stardew_access.Patches
if (carpenterMenuQuery != toSpeak) if (carpenterMenuQuery != toSpeak)
{ {
carpenterMenuQuery = toSpeak; carpenterMenuQuery = toSpeak;
MainClass.GetScreenReader().Say(toSpeak, true); MainClass.ScreenReader.Say(toSpeak, true);
} }
return; return;
} }
@ -213,7 +292,7 @@ namespace stardew_access.Patches
if (carpenterMenuQuery != toSpeak) if (carpenterMenuQuery != toSpeak)
{ {
carpenterMenuQuery = toSpeak; carpenterMenuQuery = toSpeak;
MainClass.GetScreenReader().Say(toSpeak, true); MainClass.ScreenReader.Say(toSpeak, true);
} }
return; return;
} }
@ -224,7 +303,7 @@ namespace stardew_access.Patches
if (carpenterMenuQuery != toSpeak) if (carpenterMenuQuery != toSpeak)
{ {
carpenterMenuQuery = toSpeak; carpenterMenuQuery = toSpeak;
MainClass.GetScreenReader().Say(toSpeak, true); MainClass.ScreenReader.Say(toSpeak, true);
} }
return; return;
} }
@ -235,7 +314,7 @@ namespace stardew_access.Patches
if (carpenterMenuQuery != toSpeak) if (carpenterMenuQuery != toSpeak)
{ {
carpenterMenuQuery = toSpeak; carpenterMenuQuery = toSpeak;
MainClass.GetScreenReader().Say(toSpeak, true); MainClass.ScreenReader.Say(toSpeak, true);
} }
return; return;
} }
@ -246,7 +325,7 @@ namespace stardew_access.Patches
if (carpenterMenuQuery != toSpeak) if (carpenterMenuQuery != toSpeak)
{ {
carpenterMenuQuery = toSpeak; carpenterMenuQuery = toSpeak;
MainClass.GetScreenReader().Say(toSpeak, true); MainClass.ScreenReader.Say(toSpeak, true);
} }
return; return;
} }
@ -269,14 +348,14 @@ namespace stardew_access.Patches
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
} }
} }
private static async void SayBlueprintInfo(string info) private static async void SayBlueprintInfo(string info)
{ {
isSayingBlueprintInfo = true; isSayingBlueprintInfo = true;
MainClass.GetScreenReader().Say(info, true); MainClass.ScreenReader.Say(info, true);
await Task.Delay(300); await Task.Delay(300);
isSayingBlueprintInfo = false; isSayingBlueprintInfo = false;
} }
@ -300,7 +379,7 @@ namespace stardew_access.Patches
{ {
if (isDemolishing && toDemolish != null && farm.buildings.Contains(toDemolish)) if (isDemolishing && toDemolish != null && farm.buildings.Contains(toDemolish))
{ {
if ((int)toDemolish.daysOfConstructionLeft > 0 || (int)toDemolish.daysUntilUpgrade > 0) if ((int)toDemolish.daysOfConstructionLeft.Value > 0 || (int)toDemolish.daysUntilUpgrade.Value > 0)
{ {
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantDemolish_DuringConstruction"); response = Game1.content.LoadString("Strings\\UI:Carpenter_CantDemolish_DuringConstruction");
} }
@ -345,8 +424,8 @@ namespace stardew_access.Patches
} }
if (farm.destroyStructure(toDemolish)) if (farm.destroyStructure(toDemolish))
{ {
_ = (int)toDemolish.tileY; _ = (int)toDemolish.tileY.Value;
_ = (int)toDemolish.tilesHigh; _ = (int)toDemolish.tilesHigh.Value;
Game1.flashAlpha = 1f; Game1.flashAlpha = 1f;
toDemolish.showDestroyedAnimation(Game1.getFarm()); toDemolish.showDestroyedAnimation(Game1.getFarm());
Game1.playSound("explosion"); Game1.playSound("explosion");
@ -356,7 +435,7 @@ namespace stardew_access.Patches
// freeze = true; // freeze = true;
if (chest != null) if (chest != null)
{ {
farm.objects[new Vector2((int)toDemolish.tileX + (int)toDemolish.tilesWide / 2, (int)toDemolish.tileY + (int)toDemolish.tilesHigh / 2)] = chest; farm.objects[new Vector2((int)toDemolish.tileX.Value + (int)toDemolish.tilesWide.Value / 2, (int)toDemolish.tileY.Value + (int)toDemolish.tilesHigh.Value / 2)] = chest;
} }
} }
} }
@ -385,7 +464,7 @@ namespace stardew_access.Patches
if (toDemolish != null && toDemolish.indoors.Value is Cabin) if (toDemolish != null && toDemolish.indoors.Value is Cabin)
{ {
Cabin cabin = (Cabin)toDemolish.indoors.Value; Cabin cabin = (Cabin)toDemolish.indoors.Value;
if (cabin.farmhand.Value != null && (bool)cabin.farmhand.Value.isCustomized) if (cabin.farmhand.Value != null && (bool)cabin.farmhand.Value.isCustomized.Value)
{ {
Game1.currentLocation.createQuestionDialogue(Game1.content.LoadString("Strings\\UI:Carpenter_DemolishCabinConfirm", cabin.farmhand.Value.Name), Game1.currentLocation.createYesNoResponses(), delegate (Farmer f, string answer) Game1.currentLocation.createQuestionDialogue(Game1.content.LoadString("Strings\\UI:Carpenter_DemolishCabinConfirm", cabin.farmhand.Value.Name), Game1.currentLocation.createYesNoResponses(), delegate (Farmer f, string answer)
{ {
@ -458,7 +537,7 @@ namespace stardew_access.Patches
Game1.playSound("axe"); Game1.playSound("axe");
DelayedAction.functionAfterDelay(carpenterMenu.returnToCarpentryMenuAfterSuccessfulBuild, 1500); DelayedAction.functionAfterDelay(carpenterMenu.returnToCarpentryMenuAfterSuccessfulBuild, 1500);
// freeze = true; // freeze = true;
// Game1.multiplayer.globalChatInfoMessage("BuildingBuild", Game1.player.Name, Utility.AOrAn(carpenterMenu.CurrentBlueprint.displayName), carpenterMenu.CurrentBlueprint.displayName, Game1.player.farmName); // Game1.multiplayer.globalChatInfoMessage("BuildingBuild", Game1.player.Name, Utility.AOrAn(carpenterMenu.CurrentBlueprint.displayName), carpenterMenu.CurrentBlueprint.displayName, Game1.player.farmName.Value);
} }
else if (toUpgrade != null) else if (toUpgrade != null)
{ {
@ -517,7 +596,7 @@ namespace stardew_access.Patches
string? name = buildingToMove.nameOfIndoorsWithoutUnique; string? name = buildingToMove.nameOfIndoorsWithoutUnique;
name = (name == "null") ? buildingToMove.buildingType.Value : name; name = (name == "null") ? buildingToMove.buildingType.Value : name;
if ((int)buildingToMove.daysOfConstructionLeft > 0) if ((int)buildingToMove.daysOfConstructionLeft.Value > 0)
{ {
buildingToMove = null; buildingToMove = null;
return "Building under construction, cannot move"; return "Building under construction, cannot move";
@ -566,9 +645,70 @@ namespace stardew_access.Patches
if (purchaseAnimalsMenu == null) if (purchaseAnimalsMenu == null)
return; return;
int x = (selection.tileX * Game1.tileSize) - Game1.viewport.X; int x = (selection.tileX.Value * Game1.tileSize) - Game1.viewport.X;
int y = (selection.tileY * Game1.tileSize) - Game1.viewport.Y; int y = (selection.tileY.Value * Game1.tileSize) - Game1.viewport.Y;
if (animalBeingPurchasedOrMoved != null && !selection.buildingType.Value.Contains(animalBeingPurchasedOrMoved.buildingTypeILiveIn.Value))
{
string warn = Game1.content.LoadString("Strings\\StringsFromCSFiles:PurchaseAnimalsMenu.cs.11326", animalBeingPurchasedOrMoved.displayType);
MainClass.ScreenReader.Say(warn, true);
return;
}
if (((AnimalHouse)selection.indoors.Value).isFull())
{
string warn = Game1.content.LoadString("Strings\\StringsFromCSFiles:PurchaseAnimalsMenu.cs.11321");
MainClass.ScreenReader.Say(warn, true);
return;
}
purchaseAnimalsMenu.receiveLeftClick(x, y); purchaseAnimalsMenu.receiveLeftClick(x, y);
} }
public static void MoveAnimal(Building? selection)
{
if (selection == null)
return;
if (animalQueryMenu == null)
return;
if (animalBeingPurchasedOrMoved == null)
return;
// The following code is taken from the game's source code [AnimalQueryMenu.cs::receiveLeftClick]
if (selection.buildingType.Value.Contains(animalBeingPurchasedOrMoved.buildingTypeILiveIn.Value))
{
if (((AnimalHouse)selection.indoors.Value).isFull())
{
string warn = Game1.content.LoadString("Strings\\UI:AnimalQuery_Moving_BuildingFull");
MainClass.ScreenReader.Say(warn, true);
return;
}
if (selection.Equals(animalBeingPurchasedOrMoved.home))
{
string warn = Game1.content.LoadString("Strings\\UI:AnimalQuery_Moving_AlreadyHome");
MainClass.ScreenReader.Say(warn, true);
return;
}
((AnimalHouse)animalBeingPurchasedOrMoved.home.indoors.Value).animalsThatLiveHere.Remove(animalBeingPurchasedOrMoved.myID.Value);
if (((AnimalHouse)animalBeingPurchasedOrMoved.home.indoors.Value).animals.ContainsKey(animalBeingPurchasedOrMoved.myID.Value))
{
((AnimalHouse)selection.indoors.Value).animals.Add(animalBeingPurchasedOrMoved.myID.Value, animalBeingPurchasedOrMoved);
((AnimalHouse)animalBeingPurchasedOrMoved.home.indoors.Value).animals.Remove(animalBeingPurchasedOrMoved.myID.Value);
}
animalBeingPurchasedOrMoved.home = selection;
animalBeingPurchasedOrMoved.homeLocation.Value = new Vector2((int)selection.tileX.Value, (int)selection.tileY.Value);
((AnimalHouse)selection.indoors.Value).animalsThatLiveHere.Add(animalBeingPurchasedOrMoved.myID.Value);
animalBeingPurchasedOrMoved.makeSound();
Game1.globalFadeToBlack(animalQueryMenu.finishedPlacingAnimal);
}
else
{
string warn = Game1.content.LoadString("Strings\\UI:AnimalQuery_Moving_CantLiveThere");
MainClass.ScreenReader.Say(warn, true);
}
return;
}
} }
} }

View File

@ -0,0 +1,360 @@
using StardewValley;
using StardewValley.Locations;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class BundleMenuPatches
{
internal static string junimoNoteMenuQuery = "";
internal static string currentJunimoArea = "";
internal static string jojaCDMenuQuery = "";
internal static bool isUsingCustomButtons = false;
internal static int currentIngredientListItem = -1, currentIngredientInputSlot = -1, currentInventorySlot = -1;
#region Joja Mart Bundle/Quests
internal static void JojaCDMenuPatch(JojaCDMenu __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = "";
for (int i = 0; i < __instance.checkboxes.Count; i++)
{
ClickableComponent c = __instance.checkboxes[i];
if (!c.containsPoint(x, y))
continue;
if (c.name.Equals("complete"))
{
toSpeak = $"Completed {getNameFromIndex(i)}";
}
else
{
toSpeak = $"{getNameFromIndex(i)} Cost: {__instance.getPriceFromButtonNumber(i)}g Description: {__instance.getDescriptionFromButtonNumber(i)}";
}
break;
}
if (jojaCDMenuQuery != toSpeak)
{
jojaCDMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static string getNameFromIndex(int i)
{
string name = i switch
{
0 => "Bus",
1 => "Minecarts",
2 => "Bridge",
3 => "Greenhouse",
4 => "Panning",
_ => "",
};
if (name != "")
return $"{name} Project";
else
return "unkown";
}
#endregion
#region Community Center Bundles
internal static void JunimoNoteMenuPatch(JunimoNoteMenu __instance, bool ___specificBundlePage, int ___whichArea, Bundle ___currentPageBundle)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (!___specificBundlePage)
{
currentIngredientListItem = -1;
isUsingCustomButtons = false;
string areaName = __instance.scrambledText ? CommunityCenter.getAreaEnglishDisplayNameFromNumber(___whichArea) : CommunityCenter.getAreaDisplayNameFromNumber(___whichArea);
string reward = __instance.getRewardNameForArea(___whichArea);
if (__instance.scrambledText)
{
string toSpeak = "Scrambled Text";
if (junimoNoteMenuQuery != toSpeak)
{
junimoNoteMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
if (currentJunimoArea != areaName)
{
currentJunimoArea = areaName;
MainClass.ScreenReader.Say($"Area {areaName}, {reward}", true);
return;
}
for (int i = 0; i < __instance.bundles.Count; i++)
{
if (__instance.bundles[i].containsPoint(x, y))
{
string toSpeak = $"{__instance.bundles[i].name} bundle";
if (junimoNoteMenuQuery != toSpeak)
{
junimoNoteMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
}
if (__instance.presentButton != null && __instance.presentButton.containsPoint(x, y))
{
string toSpeak = "Present Button";
if (junimoNoteMenuQuery != toSpeak)
{
junimoNoteMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
if (__instance.fromGameMenu)
{
if (__instance.areaNextButton.visible && __instance.areaNextButton.containsPoint(x, y))
{
string toSpeak = "Next Area Button";
if (junimoNoteMenuQuery != toSpeak)
{
junimoNoteMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
if (__instance.areaBackButton.visible && __instance.areaBackButton.containsPoint(x, y))
{
string toSpeak = "Previous Area Button";
if (junimoNoteMenuQuery != toSpeak)
{
junimoNoteMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
}
}
else
{
bool isIPressed = MainClass.Config.BundleMenuIngredientsKey.JustPressed(); // For the ingredients
bool isCPressed = MainClass.Config.BundleMenuInventoryItemsKey.JustPressed(); // For the items in inventory
bool isPPressed = MainClass.Config.BundleMenuPurchaseButtonKey.JustPressed(); // For the Purchase Button
bool isVPressed = MainClass.Config.BundleMenuIngredientsInputSlotKey.JustPressed(); // For the ingredient input slots
bool isBackPressed = MainClass.Config.BundleMenuBackButtonKey.JustPressed(); // For the back button
bool isLeftShiftPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftShift);
if (isIPressed && !isUsingCustomButtons)
{
isUsingCustomButtons = true;
JunimoNoteCustomButtons(__instance, ___currentPageBundle, 0, isLeftShiftPressed);
Task.Delay(200).ContinueWith(_ => { isUsingCustomButtons = false; });
}
else if (isVPressed && !isUsingCustomButtons)
{
isUsingCustomButtons = true;
JunimoNoteCustomButtons(__instance, ___currentPageBundle, 1, isLeftShiftPressed);
Task.Delay(200).ContinueWith(_ => { isUsingCustomButtons = false; });
}
else if (isCPressed && !isUsingCustomButtons)
{
isUsingCustomButtons = true;
JunimoNoteCustomButtons(__instance, ___currentPageBundle, 2, isLeftShiftPressed);
Task.Delay(200).ContinueWith(_ => { isUsingCustomButtons = false; });
}
else if (isBackPressed && __instance.backButton != null && !__instance.backButton.containsPoint(x, y))
{
__instance.backButton.snapMouseCursorToCenter();
MainClass.ScreenReader.Say("Back Button", true);
}
else if (isPPressed && __instance.purchaseButton != null && !__instance.purchaseButton.containsPoint(x, y))
{
__instance.purchaseButton.snapMouseCursorToCenter();
MainClass.ScreenReader.Say("Purchase Button", true);
}
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static void JunimoNoteCustomButtons(JunimoNoteMenu __instance, Bundle ___currentPageBundle, int signal, bool isLeftShiftPressed = false)
{
try
{
switch (signal)
{
case 0: // For ingredient list
{
if (___currentPageBundle.ingredients.Count >= 0)
{
currentIngredientListItem = currentIngredientListItem + (isLeftShiftPressed ? -1 : 1);
if (currentIngredientListItem >= ___currentPageBundle.ingredients.Count)
if (isLeftShiftPressed)
currentIngredientListItem = ___currentPageBundle.ingredients.Count - 1;
else
currentIngredientListItem = 0;
if (currentIngredientListItem < 0)
if (isLeftShiftPressed)
currentIngredientListItem = ___currentPageBundle.ingredients.Count - 1;
else
currentIngredientListItem = 0;
ClickableTextureComponent c = __instance.ingredientList[currentIngredientListItem];
BundleIngredientDescription ingredient = ___currentPageBundle.ingredients[currentIngredientListItem];
Item item = new StardewValley.Object(ingredient.index, ingredient.stack, isRecipe: false, -1, ingredient.quality);
bool completed = false;
if (___currentPageBundle != null && ___currentPageBundle.ingredients != null && currentIngredientListItem < ___currentPageBundle.ingredients.Count && ___currentPageBundle.ingredients[currentIngredientListItem].completed)
{
completed = true;
}
string toSpeak = item.DisplayName;
if (!completed)
{
int quality = ingredient.quality;
if (quality == 1)
{
toSpeak = $"Silver quality {toSpeak}";
}
else if (quality == 2 || quality == 3)
{
toSpeak = $"Gold quality {toSpeak}";
}
else if (quality >= 4)
{
toSpeak = $"Iridium quality {toSpeak}";
}
toSpeak = $"{ingredient.stack} {toSpeak}";
}
if (completed)
toSpeak = $"Completed {toSpeak}";
c.snapMouseCursorToCenter();
MainClass.ScreenReader.Say(toSpeak, true);
}
}
break;
case 1: // For input slot list
{
if (__instance.ingredientSlots.Count >= 0)
{
currentIngredientInputSlot = currentIngredientInputSlot + (isLeftShiftPressed ? -1 : 1);
if (currentIngredientInputSlot >= __instance.ingredientSlots.Count)
if (isLeftShiftPressed)
currentIngredientInputSlot = __instance.ingredientSlots.Count - 1;
else
currentIngredientInputSlot = 0;
if (currentIngredientInputSlot < 0)
if (isLeftShiftPressed)
currentIngredientInputSlot = __instance.ingredientSlots.Count - 1;
else
currentIngredientInputSlot = 0;
ClickableTextureComponent c = __instance.ingredientSlots[currentIngredientInputSlot];
Item item = c.item;
string toSpeak;
if (item == null)
{
toSpeak = $"Input Slot {currentIngredientInputSlot + 1}";
}
else
{
toSpeak = item.DisplayName;
}
c.snapMouseCursorToCenter();
MainClass.ScreenReader.Say(toSpeak, true);
}
}
break;
case 2: // For inventory slots
{
if (__instance.inventory != null && __instance.inventory.actualInventory.Count >= 0)
{
int prevSlotIndex = currentInventorySlot;
currentInventorySlot = currentInventorySlot + (isLeftShiftPressed ? -1 : 1);
if (currentInventorySlot >= __instance.inventory.actualInventory.Count)
if (isLeftShiftPressed)
currentInventorySlot = __instance.inventory.actualInventory.Count - 1;
else
currentInventorySlot = 0;
if (currentInventorySlot < 0)
if (isLeftShiftPressed)
currentInventorySlot = __instance.inventory.actualInventory.Count - 1;
else
currentInventorySlot = 0;
Item item = __instance.inventory.actualInventory[currentInventorySlot];
ClickableComponent c = __instance.inventory.inventory[currentInventorySlot];
string toSpeak;
if (item != null)
{
toSpeak = item.DisplayName;
if ((item as StardewValley.Object) != null)
{
int quality = ((StardewValley.Object)item).Quality;
if (quality == 1)
{
toSpeak = $"Silver quality {toSpeak}";
}
else if (quality == 2 || quality == 3)
{
toSpeak = $"Gold quality {toSpeak}";
}
else if (quality >= 4)
{
toSpeak = $"Iridium quality {toSpeak}";
}
}
toSpeak = $"{item.Stack} {toSpeak}";
if (!__instance.inventory.highlightMethod(__instance.inventory.actualInventory[currentInventorySlot]))
{
toSpeak = $"{toSpeak} not usable here";
}
}
else
{
toSpeak = "Empty Slot";
}
c.snapMouseCursorToCenter();
MainClass.ScreenReader.Say(toSpeak, true);
}
}
break;
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
#endregion
}
}

View File

@ -1,10 +1,9 @@
using StardewModdingAPI; using StardewValley;
using StardewValley;
using StardewValley.Menus; using StardewValley.Menus;
namespace stardew_access.Patches namespace stardew_access.Patches
{ {
internal class ChatManuPatches internal class ChatMenuPatches
{ {
private static int currentChatMessageIndex = 0; private static int currentChatMessageIndex = 0;
private static bool isChatRunning = false; private static bool isChatRunning = false;
@ -17,19 +16,23 @@ namespace stardew_access.Patches
if (__instance.chatBox.Selected) if (__instance.chatBox.Selected)
{ {
bool isPrevArrowPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.PageUp); bool isPrevButtonPressed = MainClass.Config.ChatMenuNextKey.JustPressed();
bool isNextArrowPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.PageDown); bool isNextButtonPressed = MainClass.Config.ChatMenuPreviousKey.JustPressed();
if (___messages.Count > 0) if (___messages.Count > 0)
{ {
#region To narrate previous and next chat messages #region To narrate previous and next chat messages
if (isNextArrowPressed && !isChatRunning) if (isNextButtonPressed && !isChatRunning)
{ {
isChatRunning = true;
CycleThroughChatMessages(true, ___messages); CycleThroughChatMessages(true, ___messages);
Task.Delay(200).ContinueWith(_ => { isChatRunning = false; });
} }
else if (isPrevArrowPressed && !isChatRunning) else if (isPrevButtonPressed && !isChatRunning)
{ {
isChatRunning = true;
CycleThroughChatMessages(false, ___messages); CycleThroughChatMessages(false, ___messages);
Task.Delay(200).ContinueWith(_ => { isChatRunning = false; });
} }
#endregion #endregion
} }
@ -42,44 +45,28 @@ namespace stardew_access.Patches
toSpeak += $"{message.message}, "; toSpeak += $"{message.message}, ";
}); });
if (toSpeak != " ") if (toSpeak != " ")
MainClass.GetScreenReader().SayWithChatChecker(toSpeak, false); MainClass.ScreenReader.SayWithChatChecker(toSpeak, false);
#endregion #endregion
} }
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
} }
} }
private static async void CycleThroughChatMessages(bool increase, List<ChatMessage> ___messages) private static void CycleThroughChatMessages(bool increase, List<ChatMessage> ___messages)
{ {
isChatRunning = true;
string toSpeak = " "; string toSpeak = " ";
if (increase)
{ currentChatMessageIndex = (increase) ? (Math.Min(currentChatMessageIndex + 1, ___messages.Count - 1)) : (currentChatMessageIndex = Math.Max(currentChatMessageIndex - 1, 0));
++currentChatMessageIndex;
if (currentChatMessageIndex > ___messages.Count - 1)
{
currentChatMessageIndex = ___messages.Count - 1;
}
}
else
{
--currentChatMessageIndex;
if (currentChatMessageIndex < 0)
{
currentChatMessageIndex = 0;
}
}
___messages[currentChatMessageIndex].message.ForEach(message => ___messages[currentChatMessageIndex].message.ForEach(message =>
{ {
toSpeak += $"{message.message}, "; toSpeak += $"{message.message}, ";
}); });
MainClass.GetScreenReader().Say(toSpeak, true); MainClass.ScreenReader.Say(toSpeak, true);
await Task.Delay(200);
isChatRunning = false;
} }
} }
} }

View File

@ -2,12 +2,12 @@
using StardewModdingAPI; using StardewModdingAPI;
using StardewValley; using StardewValley;
using StardewValley.Menus; using StardewValley.Menus;
using System.Text;
namespace stardew_access.Patches namespace stardew_access.Patches
{ {
internal class DialoguePatches internal class DialoguePatches
{ {
internal static string currentLetterText = " ";
internal static string currentDialogue = " "; internal static string currentDialogue = " ";
internal static bool isDialogueAppearingFirstTime = true; internal static bool isDialogueAppearingFirstTime = true;
@ -56,7 +56,7 @@ namespace stardew_access.Patches
else else
toSpeak = response; toSpeak = response;
MainClass.GetScreenReader().Say(toSpeak, true); MainClass.ScreenReader.Say(toSpeak, true);
} }
} }
else else
@ -64,7 +64,7 @@ namespace stardew_access.Patches
if (currentDialogue != dialogueText) if (currentDialogue != dialogueText)
{ {
currentDialogue = dialogueText; currentDialogue = dialogueText;
MainClass.GetScreenReader().Say(dialogueText, true); MainClass.ScreenReader.Say(dialogueText, true);
} }
} }
} }
@ -105,8 +105,7 @@ namespace stardew_access.Patches
else else
toSpeak = response; toSpeak = response;
MainClass.GetMonitor().Log(toSpeak, LogLevel.Debug); MainClass.ScreenReader.Say(toSpeak, true);
MainClass.GetScreenReader().Say(toSpeak, true);
} }
} }
else else
@ -114,7 +113,7 @@ namespace stardew_access.Patches
if (currentDialogue != dialogueText) if (currentDialogue != dialogueText)
{ {
currentDialogue = dialogueText; currentDialogue = dialogueText;
MainClass.GetScreenReader().Say(dialogueText, true); MainClass.ScreenReader.Say(dialogueText, true);
} }
} }
} }
@ -124,13 +123,13 @@ namespace stardew_access.Patches
if (currentDialogue != __instance.getCurrentString()) if (currentDialogue != __instance.getCurrentString())
{ {
currentDialogue = __instance.getCurrentString(); currentDialogue = __instance.getCurrentString();
MainClass.GetScreenReader().Say(__instance.getCurrentString(), true); MainClass.ScreenReader.Say(__instance.getCurrentString(), true);
} }
} }
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate dialog:\n{e.StackTrace}\n{e.Message}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate dialog:\n{e.StackTrace}\n{e.Message}");
} }
} }
@ -149,81 +148,89 @@ namespace stardew_access.Patches
#region Skip narrating hover text for certain menus #region Skip narrating hover text for certain menus
if (Game1.activeClickableMenu is TitleMenu && !(((TitleMenu)Game1.activeClickableMenu).GetChildMenu() is CharacterCustomization)) if (Game1.activeClickableMenu is TitleMenu && !(((TitleMenu)Game1.activeClickableMenu).GetChildMenu() is CharacterCustomization))
return; return;
else if (Game1.activeClickableMenu is LetterViewerMenu || Game1.activeClickableMenu is QuestLog)
if (Game1.activeClickableMenu is LetterViewerMenu || Game1.activeClickableMenu is QuestLog)
return; return;
else if (Game1.activeClickableMenu is Billboard)
if (Game1.activeClickableMenu is Billboard)
return; return;
else if (Game1.activeClickableMenu is GeodeMenu)
if (Game1.activeClickableMenu is GeodeMenu)
return; return;
else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is InventoryPage)
if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is InventoryPage)
return; return;
else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is CraftingPage)
if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is CraftingPage)
return; return;
else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is OptionsPage)
if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is OptionsPage)
return; return;
else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is ExitPage)
if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is ExitPage)
return; return;
else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is SocialPage)
if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is SocialPage)
return; return;
else if (Game1.activeClickableMenu is ItemGrabMenu)
if (Game1.activeClickableMenu is ItemGrabMenu)
return; return;
else if (Game1.activeClickableMenu is ShopMenu)
if (Game1.activeClickableMenu is ShopMenu)
return; return;
else if (Game1.activeClickableMenu is ConfirmationDialog)
if (Game1.activeClickableMenu is ConfirmationDialog)
return; return;
else if (Game1.activeClickableMenu is JunimoNoteMenu)
if (Game1.activeClickableMenu is JunimoNoteMenu)
return; return;
else if (Game1.activeClickableMenu is CarpenterMenu)
if (Game1.activeClickableMenu is CarpenterMenu)
return; return;
else if (Game1.activeClickableMenu is PurchaseAnimalsMenu)
if (Game1.activeClickableMenu is PurchaseAnimalsMenu) return;
else if (Game1.activeClickableMenu is CraftingPage)
return;
else if (Game1.activeClickableMenu is AnimalQueryMenu)
return;
else if (Game1.activeClickableMenu is ConfirmationDialog)
return;
else if (Game1.activeClickableMenu is ReadyCheckDialog)
return;
else if (Game1.activeClickableMenu is JojaCDMenu)
return;
else if (Game1.activeClickableMenu is TailoringMenu)
return;
else if (Game1.activeClickableMenu is PondQueryMenu)
return;
else if (Game1.activeClickableMenu is ForgeMenu)
return;
else if (Game1.activeClickableMenu is ItemListMenu)
return;
else if (Game1.activeClickableMenu is FieldOfficeMenu)
return;
else if (Game1.activeClickableMenu is MuseumMenu)
return; return;
#endregion #endregion
StringBuilder toSpeak = new StringBuilder(" "); string toSpeak = " ";
#region Add item count before title #region Add item count before title
if (hoveredItem != null && hoveredItem.HasBeenInInventory) if (hoveredItem != null && hoveredItem.HasBeenInInventory)
{ {
int count = hoveredItem.Stack; int count = hoveredItem.Stack;
if (count > 1) if (count > 1)
toSpeak.Append($"{count} "); toSpeak = $"{toSpeak} {count} ";
} }
#endregion #endregion
#region Add title if any #region Add title if any
if (boldTitleText != null) if (boldTitleText != null)
toSpeak.Append($"{boldTitleText}\n"); toSpeak = $"{toSpeak} {boldTitleText}\n";
#endregion #endregion
#region Add quality of item #region Add quality of item
if (hoveredItem is StardewValley.Object && ((StardewValley.Object)hoveredItem).quality > 0) if (hoveredItem is StardewValley.Object && ((StardewValley.Object)hoveredItem).Quality > 0)
{ {
int quality = ((StardewValley.Object)hoveredItem).quality; int quality = ((StardewValley.Object)hoveredItem).Quality;
if (quality == 1) if (quality == 1)
{ {
toSpeak.Append("Silver quality"); toSpeak = $"{toSpeak} Silver quality";
} }
else if (quality == 2 || quality == 3) else if (quality == 2 || quality == 3)
{ {
toSpeak.Append("Gold quality"); toSpeak = $"{toSpeak} Gold quality";
} }
else if (quality >= 4) else if (quality >= 4)
{ {
toSpeak.Append("Iridium quality"); toSpeak = $"{toSpeak} Iridium quality";
} }
} }
#endregion #endregion
@ -234,26 +241,29 @@ namespace stardew_access.Patches
string itemName = Game1.objectInformation[extraItemToShowIndex].Split('/')[0]; string itemName = Game1.objectInformation[extraItemToShowIndex].Split('/')[0];
if (extraItemToShowAmount != -1) if (extraItemToShowAmount != -1)
toSpeak.Append($"Required: {extraItemToShowAmount} {itemName}"); toSpeak = $"{toSpeak} Required: {extraItemToShowAmount} {itemName}";
else else
toSpeak.Append($"Required: {itemName}"); toSpeak = $"{toSpeak} Required: {itemName}";
} }
#endregion #endregion
#region Add money #region Add money
if (moneyAmountToDisplayAtBottom != -1) if (moneyAmountToDisplayAtBottom != -1)
toSpeak.Append($"\nCost: {moneyAmountToDisplayAtBottom}g\n"); toSpeak = $"{toSpeak} \nCost: {moneyAmountToDisplayAtBottom}g\n";
#endregion #endregion
#region Add the base text #region Add the base text
toSpeak.Append(text); if (text == "???")
toSpeak = "unknown";
else
toSpeak = $"{toSpeak} {text}";
#endregion #endregion
#region Add crafting ingredients #region Add crafting ingredients
if (craftingIngredients != null) if (craftingIngredients != null)
{ {
toSpeak.Append($"\n{craftingIngredients.description}"); toSpeak = $"{toSpeak} \n{craftingIngredients.description}";
toSpeak.Append("\nIngredients\n"); toSpeak = $"{toSpeak} \nIngredients\n";
craftingIngredients.recipeList.ToList().ForEach(recipe => craftingIngredients.recipeList.ToList().ForEach(recipe =>
{ {
@ -261,7 +271,7 @@ namespace stardew_access.Patches
int item = recipe.Key; int item = recipe.Key;
string name = craftingIngredients.getNameFromIndex(item); string name = craftingIngredients.getNameFromIndex(item);
toSpeak.Append($" ,{count} {name}"); toSpeak = $"{toSpeak} ,{count} {name}";
}); });
} }
#endregion #endregion
@ -270,11 +280,11 @@ namespace stardew_access.Patches
if (hoveredItem is StardewValley.Object && ((StardewValley.Object)hoveredItem).Edibility != -300) if (hoveredItem is StardewValley.Object && ((StardewValley.Object)hoveredItem).Edibility != -300)
{ {
int stamina_recovery = ((StardewValley.Object)hoveredItem).staminaRecoveredOnConsumption(); int stamina_recovery = ((StardewValley.Object)hoveredItem).staminaRecoveredOnConsumption();
toSpeak.Append($"{stamina_recovery} Energy\n"); toSpeak = $"{toSpeak} {stamina_recovery} Energy\n";
if (stamina_recovery >= 0) if (stamina_recovery >= 0)
{ {
int health_recovery = ((StardewValley.Object)hoveredItem).healthRecoveredOnConsumption(); int health_recovery = ((StardewValley.Object)hoveredItem).healthRecoveredOnConsumption();
toSpeak.Append($"{health_recovery} Health"); toSpeak = $"{toSpeak} {health_recovery} Health";
} }
} }
#endregion #endregion
@ -293,7 +303,7 @@ namespace stardew_access.Patches
{ {
int count = int.Parse(buffName.Substring(0, buffName.IndexOf(' '))); int count = int.Parse(buffName.Substring(0, buffName.IndexOf(' ')));
if (count != 0) if (count != 0)
toSpeak.Append($"{buffName}\n"); toSpeak = $"{toSpeak} {buffName}\n";
} }
catch (Exception) { } catch (Exception) { }
} }
@ -307,15 +317,110 @@ namespace stardew_access.Patches
if (toSpeak.ToString() != " ") if (toSpeak.ToString() != " ")
{ {
if (Context.IsPlayerFree) if (Context.IsPlayerFree)
MainClass.GetScreenReader().SayWithChecker(toSpeak.ToString(), true); // Normal Checker MainClass.ScreenReader.SayWithChecker(toSpeak.ToString(), true); // Normal Checker
else else
MainClass.GetScreenReader().SayWithMenuChecker(toSpeak.ToString(), true); // Menu Checker MainClass.ScreenReader.SayWithMenuChecker(toSpeak.ToString(), true); // Menu Checker
} }
#endregion #endregion
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate dialog:\n{e.StackTrace}\n{e.Message}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate dialog:\n{e.StackTrace}\n{e.Message}");
}
}
internal static void LetterViewerMenuPatch(LetterViewerMenu __instance)
{
try
{
if (!__instance.IsActive())
return;
NarrateLetterContent(__instance);
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void NarrateLetterContent(LetterViewerMenu __instance)
{
int x = Game1.getMousePosition().X, y = Game1.getMousePosition().Y;
#region Texts in the letter
string message = __instance.mailMessage[__instance.page];
string toSpeak = $"{message}";
if (__instance.ShouldShowInteractable())
{
if (__instance.moneyIncluded > 0)
{
string moneyText = Game1.content.LoadString("Strings\\UI:LetterViewer_MoneyIncluded", __instance.moneyIncluded);
toSpeak += $"\t\n\t ,Included money: {moneyText}";
}
else if (__instance.learnedRecipe != null && __instance.learnedRecipe.Length > 0)
{
string recipeText = Game1.content.LoadString("Strings\\UI:LetterViewer_LearnedRecipe", __instance.cookingOrCrafting);
toSpeak += $"\t\n\t ,Learned Recipe: {recipeText}";
}
}
if (currentLetterText != toSpeak)
{
currentLetterText = toSpeak;
// snap mouse to accept quest button
if (__instance.acceptQuestButton != null && __instance.questID != -1)
{
toSpeak += "\t\n Left click to accept quest.";
__instance.acceptQuestButton.snapMouseCursorToCenter();
}
if (__instance.mailMessage.Count > 1)
toSpeak = $"Page {__instance.page + 1} of {__instance.mailMessage.Count}:\n\t{toSpeak}";
MainClass.ScreenReader.Say(toSpeak, true);
}
#endregion
#region Narrate items given in the mail
if (__instance.ShouldShowInteractable())
{
foreach (ClickableComponent c in __instance.itemsToGrab)
{
if (c.item == null)
continue;
string name = c.item.DisplayName;
if (c.containsPoint(x, y))
MainClass.ScreenReader.SayWithChecker($"Left click to collect {name}", false);
}
}
#endregion
#region Narrate buttons
if (__instance.backButton != null && __instance.backButton.visible && __instance.backButton.containsPoint(x, y))
MainClass.ScreenReader.SayWithChecker($"Previous page button", false);
if (__instance.forwardButton != null && __instance.forwardButton.visible && __instance.forwardButton.containsPoint(x, y))
MainClass.ScreenReader.SayWithChecker($"Next page button", false);
#endregion
}
internal static void drawAboveAlwaysFrontLayerPatch(NPC __instance, string ___textAboveHead, int ___textAboveHeadTimer)
{
try
{
if (___textAboveHeadTimer > 2900 && ___textAboveHead != null)
{
MainClass.ScreenReader.SayWithChecker($"{__instance.displayName} says {___textAboveHead}", true);
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Error in patch:NPCShowTextAboveHeadPatch \n{e.Message}\n{e.StackTrace}");
} }
} }
} }

View File

@ -0,0 +1,417 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using StardewValley;
using StardewValley.Locations;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class DonationMenuPatches
{
internal static string museumQueryKey = " ";
internal static string fieldOfficeMenuQuery = " ";
private static bool isMoving = false;
private static (int x, int y)[] donationTiles =
{
(26,5),(26,6),(26,7),(26,8),(26,9),(26,10),(26,11),
(29,5),(30,5),(31,5),(32,5),(33,5),(34,5),(35,5),(36,5),
(28,6),(29,6),(30,6),(31,6),(32,6),(33,6),(34,6),(35,6),(36,6),(37,6),
(28,9),(29,9),(30,9),(31,9),(32,9),(33,9),(34,9),(35,9),(36,9),
(28,10),(29,10),(30,10),(31,10),(32,10),(33,10),(34,10),(35,10),(36,10),
(30,13),(31,13),(32,13),(33,13),(34,13),
(30,14),(31,14),(32,14),(33,14),(34,14),
(28,15),(29,15),(30,15),(31,15),(32,15),(33,15),(34,15),(35,15),(36,15),
(28,16),(29,16),(30,16),(31,16),(32,16),(33,16),(34,16),(35,16),(36,16),
(39,6),(40,6),(41,6),(42,6),(43,6),(44,6),(45,6),(46,6),
(39,7),(40,7),(41,7),(42,7),(43,7),(44,7),(45,7),(46,7),
(48,5),(48,6),(48,7),
(42,15),(43,15),(44,15),(45,15),(46,15),(47,15),
(42,16),(43,16),(44,16),(45,16),(46,16),(47,16),
};
#region Museum
internal static bool MuseumMenuKeyPressPatch()
{
try
{
if (isMoving)
return false;
if (!isMoving)
{
isMoving = true;
Task.Delay(200).ContinueWith(_ => { isMoving = false; });
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
return true;
}
internal static void MuseumMenuPatch(MuseumMenu __instance, bool ___holdingMuseumPiece)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (__instance.heldItem != null)
{
// Museum Inventory
string toSpeak = " ";
int tileX = (int)(Utility.ModifyCoordinateFromUIScale(x) + (float)Game1.viewport.X) / 64;
int tileY = (int)(Utility.ModifyCoordinateFromUIScale(y) + (float)Game1.viewport.Y) / 64;
LibraryMuseum libraryMuseum = (LibraryMuseum)Game1.currentLocation;
if (libraryMuseum.isTileSuitableForMuseumPiece(tileX, tileY))
toSpeak = $"slot {tileX}x {tileY}y";
if (museumQueryKey != toSpeak)
{
museumQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
else
{
// Player Inventory
int i = narrateHoveredItemInInventory(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y);
if (i != -9999)
{
bool isPrimaryInfoKeyPressed = MainClass.Config.PrimaryInfoKey.JustPressed(); // For donating hovered item
if (isPrimaryInfoKeyPressed && __instance.inventory.actualInventory[i] != null)
{
foreach (var tile in donationTiles)
{
#region Manually donates the hovered item (https://github.com/veywrn/StardewValley/blob/3ff171b6e9e6839555d7881a391b624ccd820a83/StardewValley/Menus/MuseumMenu.cs#L206-L247)
int tileX = tile.x;
int tileY = tile.y;
if (((LibraryMuseum)Game1.currentLocation).isTileSuitableForMuseumPiece(tileX, tileY) && ((LibraryMuseum)Game1.currentLocation).isItemSuitableForDonation(__instance.inventory.actualInventory[i]))
{
int objectID = __instance.inventory.actualInventory[i].ParentSheetIndex;
int rewardsCount = ((LibraryMuseum)Game1.currentLocation).getRewardsForPlayer(Game1.player).Count;
((LibraryMuseum)Game1.currentLocation).museumPieces.Add(new Vector2(tileX, tileY), ((StardewValley.Object)__instance.inventory.actualInventory[i]).ParentSheetIndex);
Game1.playSound("stoneStep");
if (((LibraryMuseum)Game1.currentLocation).getRewardsForPlayer(Game1.player).Count > rewardsCount)
{
Game1.playSound("reward");
}
else
{
Game1.playSound("newArtifact");
}
Game1.player.completeQuest(24);
__instance.inventory.actualInventory[i].Stack--;
if (__instance.inventory.actualInventory[i].Stack <= 0)
{
__instance.inventory.actualInventory[i] = null;
}
int pieces = ((LibraryMuseum)Game1.currentLocation).museumPieces.Count();
Game1.stats.checkForArchaeologyAchievements();
switch (pieces)
{
case 95:
globalChatInfoMessage("MuseumComplete", Game1.player.farmName.Value);
break;
case 40:
globalChatInfoMessage("Museum40", Game1.player.farmName.Value);
break;
default:
globalChatInfoMessage("donation", Game1.player.Name, "object:" + objectID);
break;
}
break;
}
#endregion
}
}
}
else if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
if (museumQueryKey != $"ok button")
{
museumQueryKey = $"ok button";
MainClass.ScreenReader.Say("ok button", true);
}
}
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
// Returns the index of the hovered item or -9999
internal static int narrateHoveredItemInInventory(InventoryMenu inventoryMenu, List<ClickableComponent> inventory, IList<Item> actualInventory, int x, int y)
{
#region Narrate hovered item
for (int i = 0; i < inventory.Count; i++)
{
if (inventory[i].containsPoint(x, y))
{
string toSpeak = "";
if ((i + 1) <= actualInventory.Count)
{
if (actualInventory[i] != null)
{
string name = actualInventory[i].DisplayName;
int stack = actualInventory[i].Stack;
string quality = "";
#region Add quality of item
if (actualInventory[i] is StardewValley.Object && ((StardewValley.Object)actualInventory[i]).Quality > 0)
{
int qualityIndex = ((StardewValley.Object)actualInventory[i]).Quality;
if (qualityIndex == 1)
{
quality = "Silver quality";
}
else if (qualityIndex == 2 || qualityIndex == 3)
{
quality = "Gold quality";
}
else if (qualityIndex >= 4)
{
quality = "Iridium quality";
}
}
#endregion
if (inventoryMenu.highlightMethod(inventoryMenu.actualInventory[i]))
name = $"Donatable {name}";
if (stack > 1)
toSpeak = $"{stack} {name} {quality}";
else
toSpeak = $"{name} {quality}";
}
else
{
// For empty slot
toSpeak = "Empty Slot";
}
}
else
{
// For empty slot
toSpeak = "Empty Slot";
}
if (museumQueryKey != $"{toSpeak}:{i}")
{
museumQueryKey = $"{toSpeak}:{i}";
MainClass.ScreenReader.Say(toSpeak, true);
}
return i;
}
}
#endregion
return -9999;
}
#region These methods are taken from the game's source code, https://github.com/veywrn/StardewValley/blob/3ff171b6e9e6839555d7881a391b624ccd820a83/StardewValley/Multiplayer.cs#L1331-L1395
internal static void globalChatInfoMessage(string messageKey, params string[] args)
{
if (Game1.IsMultiplayer || Game1.multiplayerMode != 0)
{
receiveChatInfoMessage(Game1.player, messageKey, args);
sendChatInfoMessage(messageKey, args);
}
}
internal static void sendChatInfoMessage(string messageKey, params string[] args)
{
if (Game1.IsClient)
{
Game1.client.sendMessage(15, messageKey, args);
}
else if (Game1.IsServer)
{
foreach (long id in Game1.otherFarmers.Keys)
{
Game1.server.sendMessage(id, 15, Game1.player, messageKey, args);
}
}
}
internal static void receiveChatInfoMessage(Farmer sourceFarmer, string messageKey, string[] args)
{
if (Game1.chatBox != null)
{
try
{
string[] processedArgs = args.Select(delegate (string arg)
{
if (arg.StartsWith("achievement:"))
{
int key = Convert.ToInt32(arg.Substring("achievement:".Length));
return Game1.content.Load<Dictionary<int, string>>("Data\\Achievements")[key].Split('^')[0];
}
return arg.StartsWith("object:") ? new StardewValley.Object(Convert.ToInt32(arg.Substring("object:".Length)), 1).DisplayName : arg;
}).ToArray();
ChatBox chatBox = Game1.chatBox;
LocalizedContentManager content = Game1.content;
string path = "Strings\\UI:Chat_" + messageKey;
object[] substitutions = processedArgs;
chatBox.addInfoMessage(content.LoadString(path, substitutions));
}
catch (ContentLoadException)
{
}
catch (FormatException)
{
}
catch (OverflowException)
{
}
catch (KeyNotFoundException)
{
}
}
}
#endregion
#endregion
#region Field Office
internal static void FieldOfficeMenuPatch(FieldOfficeMenu __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = " ";
if (__instance.trashCan != null && __instance.trashCan.containsPoint(x, y))
{
toSpeak = "Trashcan";
}
else if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
toSpeak = "ok button";
}
else if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
{
toSpeak = "drop item";
}
else
{
for (int i = 0; i < __instance.inventory.inventory.Count; i++)
{
if (!__instance.inventory.inventory[i].containsPoint(x, y))
continue;
if (__instance.inventory.actualInventory[i] == null)
toSpeak = "Empty slot";
else
{
toSpeak = $"{__instance.inventory.actualInventory[i].Stack} {__instance.inventory.actualInventory[i].DisplayName}";
if (!__instance.inventory.highlightMethod(__instance.inventory.actualInventory[i]))
{
toSpeak = $"{toSpeak} not usable here";
}
}
if (fieldOfficeMenuQuery != $"{toSpeak}:{i}")
{
fieldOfficeMenuQuery = $"{toSpeak}:{i}";
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
for (int i = 0; i < __instance.pieceHolders.Count; i++)
{
if (!__instance.pieceHolders[i].containsPoint(x, y))
continue;
if (__instance.pieceHolders[i].item == null)
toSpeak = i switch
{
0 => "Center skeleton slot",
1 => "Center skeleton slot",
2 => "Center skeleton slot",
3 => "Center skeleton slot",
4 => "Center skeleton slot",
5 => "Center skeleton slot",
6 => "Snake slot",
7 => "Snake slot",
8 => "Snake slot",
9 => "Bat slot",
10 => "Frog slot",
_ => "Donation slot"
};
else
toSpeak = $"Slot {i + 1} finished: {__instance.pieceHolders[i].item.DisplayName}";
if (__instance.heldItem != null && __instance.pieceHolders[i].item == null)
{
int highlight = getPieceIndexForDonationItem(__instance.heldItem.ParentSheetIndex);
if (highlight != -1 && highlight == i)
toSpeak += "Donatable ";
}
if (fieldOfficeMenuQuery != $"{toSpeak}:{i}")
{
fieldOfficeMenuQuery = $"{toSpeak}:{i}";
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
}
if (fieldOfficeMenuQuery != toSpeak)
{
fieldOfficeMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
Game1.playSound("drop_item");
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static int getPieceIndexForDonationItem(int itemIndex)
{
switch (itemIndex)
{
case 820:
return 5;
case 821:
return 4;
case 822:
return 3;
case 823:
return 0;
case 824:
return 1;
case 825:
return 8;
case 826:
return 7;
case 827:
return 9;
case 828:
return 10;
default:
return -1;
}
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,16 +2,366 @@
using stardew_access.Features; using stardew_access.Features;
using StardewModdingAPI; using StardewModdingAPI;
using StardewValley; using StardewValley;
using StardewValley.Buildings;
using StardewValley.Menus; using StardewValley.Menus;
namespace stardew_access.Patches namespace stardew_access.Patches
{ {
internal class MenuPatches internal class MenuPatches
{ {
private static string currentLetterText = " "; internal static string currentLevelUpTitle = " ";
private static string currentLevelUpTitle = " "; internal static bool firstTimeInNamingMenu = true;
internal static bool isNarratingPondInfo = false;
internal static string tailoringMenuQuery = " ";
internal static string pondQueryMenuQuery = " ";
internal static string forgeMenuQuery = " ";
internal static string itemListMenuQuery = " ";
public static Vector2? prevTile = null; public static Vector2? prevTile = null;
internal static void ItemListMenuPatch(ItemListMenu __instance, string ___title, int ___currentTab, int ___totalValueOfItems, List<Item> ___itemsToList)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = " ", currentList = " ";
for (int i = ___currentTab * __instance.itemsPerCategoryPage; i < ___currentTab * __instance.itemsPerCategoryPage + __instance.itemsPerCategoryPage; i++)
{
if (i == 0)
currentList = ___title;
if (___itemsToList.Count > i)
{
if (___itemsToList[i] == null)
{
currentList = $"{currentList}, \n" + Game1.content.LoadString("Strings\\UI:ItemList_ItemsLostValue", ___totalValueOfItems);
continue;
}
currentList = $"{currentList}, \n {___itemsToList[i].Stack} {___itemsToList[i].DisplayName}";
}
}
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
toSpeak = $"Page {___currentTab + 1} of {((int)___itemsToList.Count / __instance.itemsPerCategoryPage) + 1} \n {currentList} \n ok button";
else if (__instance.forwardButton != null && __instance.forwardButton.containsPoint(x, y))
toSpeak = "Next page button";
else if (__instance.backButton != null && __instance.backButton.containsPoint(x, y))
toSpeak = "Previous page button";
if (itemListMenuQuery != toSpeak)
{
itemListMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void ForgeMenuPatch(ForgeMenu __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = " ";
if (__instance.leftIngredientSpot != null && __instance.leftIngredientSpot.containsPoint(x, y))
{
if (__instance.leftIngredientSpot.item == null)
{
toSpeak = "Input weapon or tool here";
}
else
{
Item item = __instance.leftIngredientSpot.item;
toSpeak = $"Weapon slot: {item.Stack} {item.DisplayName}";
}
}
else if (__instance.rightIngredientSpot != null && __instance.rightIngredientSpot.containsPoint(x, y))
{
if (__instance.rightIngredientSpot.item == null)
{
toSpeak = "Input gemstone here";
}
else
{
Item item = __instance.rightIngredientSpot.item;
toSpeak = $"Gemstone slot: {item.Stack} {item.DisplayName}";
}
}
else if (__instance.startTailoringButton != null && __instance.startTailoringButton.containsPoint(x, y))
{
toSpeak = "Star forging button";
}
else if (__instance.unforgeButton != null && __instance.unforgeButton.containsPoint(x, y))
{
toSpeak = "Unforge button";
}
else if (__instance.trashCan != null && __instance.trashCan.containsPoint(x, y))
{
toSpeak = "Trashcan";
}
else if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
toSpeak = "ok button";
}
else if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
{
toSpeak = "drop item";
}
else if (__instance.equipmentIcons.Count > 0 && __instance.equipmentIcons[0].containsPoint(x, y))
{
toSpeak = "Left ring Slot";
if (Game1.player.leftRing.Value != null)
toSpeak = $"{toSpeak}: {Game1.player.leftRing.Value.DisplayName}";
}
else if (__instance.equipmentIcons.Count > 0 && __instance.equipmentIcons[1].containsPoint(x, y))
{
toSpeak = "Right ring Slot";
if (Game1.player.rightRing.Value != null)
toSpeak = $"{toSpeak}: {Game1.player.rightRing.Value.DisplayName}";
}
else
{
for (int i = 0; i < __instance.inventory.inventory.Count; i++)
{
if (!__instance.inventory.inventory[i].containsPoint(x, y))
continue;
if (__instance.inventory.actualInventory[i] == null)
toSpeak = "Empty slot";
else
{
toSpeak = $"{__instance.inventory.actualInventory[i].Stack} {__instance.inventory.actualInventory[i].DisplayName}";
if (!__instance.inventory.highlightMethod(__instance.inventory.actualInventory[i]))
{
toSpeak = $"{toSpeak} not usable here";
}
}
if (forgeMenuQuery != $"{toSpeak}:{i}")
{
forgeMenuQuery = $"{toSpeak}:{i}";
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
}
if (forgeMenuQuery != toSpeak)
{
forgeMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
Game1.playSound("drop_item");
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void PondQueryMenuPatch(PondQueryMenu __instance, StardewValley.Object ____fishItem, FishPond ____pond, string ____statusText, bool ___confirmingEmpty)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
bool isPrimaryInfoKeyPressed = MainClass.Config.PrimaryInfoKey.JustPressed();
string toSpeak = " ", extra = "";
if (___confirmingEmpty)
{
if (__instance.yesButton != null && __instance.yesButton.containsPoint(x, y))
toSpeak = "Confirm button";
else if (__instance.noButton != null && __instance.noButton.containsPoint(x, y))
toSpeak = "Cancel button";
}
else
{
if (isPrimaryInfoKeyPressed && !isNarratingPondInfo)
{
string pond_name_text = Game1.content.LoadString("Strings\\UI:PondQuery_Name", ____fishItem.DisplayName);
string population_text = Game1.content.LoadString("Strings\\UI:PondQuery_Population", string.Concat(____pond.FishCount), ____pond.maxOccupants.Value);
bool has_unresolved_needs = ____pond.neededItem.Value != null && ____pond.HasUnresolvedNeeds() && !____pond.hasCompletedRequest.Value;
string bring_text = "";
if (has_unresolved_needs && ____pond.neededItem.Value != null)
bring_text = Game1.content.LoadString("Strings\\UI:PondQuery_StatusRequest_Bring") + $": {____pond.neededItemCount} {____pond.neededItem.Value.DisplayName}";
extra = $"{pond_name_text} {population_text} {bring_text} Status: {____statusText}";
pondQueryMenuQuery = " ";
isNarratingPondInfo = true;
Task.Delay(200).ContinueWith(_ => { isNarratingPondInfo = false; });
}
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
toSpeak = "Ok button";
else if (__instance.changeNettingButton != null && __instance.changeNettingButton.containsPoint(x, y))
toSpeak = "Change netting button";
else if (__instance.emptyButton != null && __instance.emptyButton.containsPoint(x, y))
toSpeak = "Empty pond button";
}
if (pondQueryMenuQuery != toSpeak)
{
pondQueryMenuQuery = toSpeak;
MainClass.ScreenReader.Say(extra + " \n\t" + toSpeak, true);
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void TailoringMenuPatch(TailoringMenu __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = " ";
if (__instance.leftIngredientSpot != null && __instance.leftIngredientSpot.containsPoint(x, y))
{
if (__instance.leftIngredientSpot.item == null)
{
toSpeak = "Input cloth here";
}
else
{
Item item = __instance.leftIngredientSpot.item;
toSpeak = $"Cloth slot: {item.Stack} {item.DisplayName}";
}
}
else if (__instance.rightIngredientSpot != null && __instance.rightIngredientSpot.containsPoint(x, y))
{
if (__instance.rightIngredientSpot.item == null)
{
toSpeak = "Input ingredient here";
}
else
{
Item item = __instance.rightIngredientSpot.item;
toSpeak = $"Ingredient slot: {item.Stack} {item.DisplayName}";
}
}
else if (__instance.startTailoringButton != null && __instance.startTailoringButton.containsPoint(x, y))
{
toSpeak = "Star tailoring button";
}
else if (__instance.trashCan != null && __instance.trashCan.containsPoint(x, y))
{
toSpeak = "Trashcan";
}
else if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
toSpeak = "ok button";
}
else if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
{
toSpeak = "drop item";
}
else if (__instance.equipmentIcons.Count > 0 && __instance.equipmentIcons[0].containsPoint(x, y))
{
toSpeak = "Hat Slot";
if (Game1.player.hat.Value != null)
toSpeak = $"{toSpeak}: {Game1.player.hat.Value.DisplayName}";
}
else if (__instance.equipmentIcons.Count > 0 && __instance.equipmentIcons[1].containsPoint(x, y))
{
toSpeak = "Shirt Slot";
if (Game1.player.shirtItem.Value != null)
toSpeak = $"{toSpeak}: {Game1.player.shirtItem.Value.DisplayName}";
}
else if (__instance.equipmentIcons.Count > 0 && __instance.equipmentIcons[2].containsPoint(x, y))
{
toSpeak = "Pants Slot";
if (Game1.player.pantsItem.Value != null)
toSpeak = $"{toSpeak}: {Game1.player.pantsItem.Value.DisplayName}";
}
else
{
for (int i = 0; i < __instance.inventory.inventory.Count; i++)
{
if (!__instance.inventory.inventory[i].containsPoint(x, y))
continue;
if (__instance.inventory.actualInventory[i] == null)
toSpeak = "Empty slot";
else
{
toSpeak = $"{__instance.inventory.actualInventory[i].Stack} {__instance.inventory.actualInventory[i].DisplayName}";
if (!__instance.inventory.highlightMethod(__instance.inventory.actualInventory[i]))
{
toSpeak = $"{toSpeak} not usable here";
}
}
if (tailoringMenuQuery != $"{toSpeak}:{i}")
{
tailoringMenuQuery = $"{toSpeak}:{i}";
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
}
if (tailoringMenuQuery != toSpeak)
{
tailoringMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
Game1.playSound("drop_item");
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void ChooseFromListMenuPatch(ChooseFromListMenu __instance, List<string> ___options, int ___index, bool ___isJukebox)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = "";
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
toSpeak = "Select " + (___isJukebox ? Utility.getSongTitleFromCueName(___options[___index]) : ___options[___index]) + " button";
else if (__instance.cancelButton != null && __instance.cancelButton.containsPoint(x, y))
toSpeak = "Cancel button";
else if (__instance.backButton != null && __instance.backButton.containsPoint(x, y))
toSpeak = "Previous option: " + (___isJukebox ? Utility.getSongTitleFromCueName(___options[Math.Max(0, ___index - 1)]) : ___options[Math.Max(0, ___index - 1)]) + " button";
else if (__instance.forwardButton != null && __instance.forwardButton.containsPoint(x, y))
toSpeak = "Next option: " + (___isJukebox ? Utility.getSongTitleFromCueName(___options[Math.Min(___options.Count, ___index + 1)]) : ___options[Math.Min(___options.Count, ___index + 1)]) + " button";
MainClass.ScreenReader.SayWithMenuChecker(toSpeak, true);
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static bool PlaySoundPatch(string cueName) internal static bool PlaySoundPatch(string cueName)
{ {
try try
@ -24,8 +374,8 @@ namespace stardew_access.Patches
if (cueName == "grassyStep" || cueName == "sandyStep" || cueName == "snowyStep" || cueName == "stoneStep" || cueName == "thudStep" || cueName == "woodyStep") if (cueName == "grassyStep" || cueName == "sandyStep" || cueName == "snowyStep" || cueName == "stoneStep" || cueName == "thudStep" || cueName == "woodyStep")
{ {
Vector2 nextTile = CurrentPlayer.getNextTile(); Vector2 nextTile = CurrentPlayer.FacingTile;
if (ReadTile.isCollidingAtTile((int)nextTile.X, (int)nextTile.Y)) if (TileInfo.isCollidingAtTile((int)nextTile.X, (int)nextTile.Y))
{ {
if (prevTile != nextTile) if (prevTile != nextTile)
{ {
@ -38,7 +388,7 @@ namespace stardew_access.Patches
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
} }
return true; return true;
@ -48,17 +398,17 @@ namespace stardew_access.Patches
{ {
try try
{ {
int x = Game1.getMouseX(), y = Game1.getMouseY(); // Mouse x and y position int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (__instance.nextPageButton != null && __instance.nextPageButton.containsPoint(x, y)) if (__instance.nextPageButton != null && __instance.nextPageButton.containsPoint(x, y))
{ {
MainClass.GetScreenReader().SayWithMenuChecker($"Next Page Button", true); MainClass.ScreenReader.SayWithMenuChecker($"Next Page Button", true);
return; return;
} }
if (__instance.previousPageButton != null && __instance.previousPageButton.containsPoint(x, y)) if (__instance.previousPageButton != null && __instance.previousPageButton.containsPoint(x, y))
{ {
MainClass.GetScreenReader().SayWithMenuChecker($"Previous Page Button", true); MainClass.ScreenReader.SayWithMenuChecker($"Previous Page Button", true);
return; return;
} }
@ -66,14 +416,14 @@ namespace stardew_access.Patches
{ {
if (__instance.languages[i].containsPoint(x, y)) if (__instance.languages[i].containsPoint(x, y))
{ {
MainClass.GetScreenReader().SayWithMenuChecker($"{__instance.languageList[i]} Button", true); MainClass.ScreenReader.SayWithMenuChecker($"{__instance.languageList[i]} Button", true);
break; break;
} }
} }
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
} }
} }
@ -81,35 +431,81 @@ namespace stardew_access.Patches
{ {
try try
{ {
int x = Game1.getMouseX(), y = Game1.getMouseY(); // Mouse x and y position int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
for (int i = 0; i < ___elevators.Count; i++) for (int i = 0; i < ___elevators.Count; i++)
{ {
if (___elevators[i].containsPoint(x, y)) if (___elevators[i].containsPoint(x, y))
{ {
MainClass.GetScreenReader().SayWithMenuChecker($"{___elevators[i].name} level", true); MainClass.ScreenReader.SayWithMenuChecker($"{___elevators[i].name} level", true);
break; break;
} }
} }
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
} }
} }
internal static void NamingMenuPatch(NamingMenu __instance, string title, TextBox ___textBox) internal static void TitleTextInputMenuPatch(TitleTextInputMenu __instance)
{ {
try try
{ {
__instance.textBoxCC.snapMouseCursor(); string toSpeak = "";
___textBox.SelectMe(); int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = $"{title}";
MainClass.GetScreenReader().SayWithChecker(toSpeak, true); if (__instance.pasteButton != null && __instance.pasteButton.containsPoint(x, y))
toSpeak = $"Paste button";
if (toSpeak != "")
MainClass.ScreenReader.SayWithChecker(toSpeak, true);
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void NamingMenuPatch(NamingMenu __instance, TextBox ___textBox, string ___title)
{
try
{
string toSpeak = "";
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
bool isEscPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Escape); // For escaping/unselecting from the animal name text box
if (firstTimeInNamingMenu)
{
firstTimeInNamingMenu = false;
___textBox.Selected = false;
}
if (___textBox.Selected)
{
___textBox.Update();
toSpeak = ___textBox.Text;
if (isEscPressed)
{
___textBox.Selected = false;
}
}
else
{
if (__instance.textBoxCC != null && __instance.textBoxCC.containsPoint(x, y))
toSpeak = $"{___title} text box";
else if (__instance.doneNamingButton != null && __instance.doneNamingButton.containsPoint(x, y))
toSpeak = $"Done naming button";
else if (__instance.randomButton != null && __instance.randomButton.containsPoint(x, y))
toSpeak = $"Random button";
}
if (toSpeak != "")
MainClass.ScreenReader.SayWithChecker(toSpeak, true);
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
} }
} }
@ -117,21 +513,23 @@ namespace stardew_access.Patches
{ {
try try
{ {
int x = Game1.getMouseX(), y = Game1.getMouseY(); int x = Game1.getMouseX(true), y = Game1.getMouseY(true);
string toSpeak = ___message;
MainClass.GetScreenReader().SayWithMenuChecker(___message, true);
if (__instance.okButton.containsPoint(x, y)) if (__instance.okButton.containsPoint(x, y))
{ {
MainClass.GetScreenReader().SayWithMenuChecker("Ok Button", false); toSpeak += "\n\tOk Button";
} }
else if (__instance.cancelButton.containsPoint(x, y)) else if (__instance.cancelButton.containsPoint(x, y))
{ {
MainClass.GetScreenReader().SayWithMenuChecker("Cancel Button", false); toSpeak += "\n\tCancel Button";
} }
MainClass.ScreenReader.SayWithMenuChecker(toSpeak, true);
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
} }
} }
@ -139,13 +537,9 @@ namespace stardew_access.Patches
{ {
try try
{ {
int x = Game1.getMouseX(), y = Game1.getMouseY(); int x = Game1.getMouseX(true), y = Game1.getMouseY(true);
string leftProfession = " ", rightProfession = " ", extraInfo = " ", newCraftingRecipe = " ", toSpeak = " "; string leftProfession = " ", rightProfession = " ", extraInfo = " ", newCraftingRecipe = " ", toSpeak = " ";
bool isOpenBracketPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.OemOpenBrackets); // for left click
bool isLeftCtrlPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftControl);
bool isEnterPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Enter);
if (!__instance.informationUp) if (!__instance.informationUp)
{ {
return; return;
@ -167,7 +561,7 @@ namespace stardew_access.Patches
if (__instance.leftProfession.containsPoint(x, y)) if (__instance.leftProfession.containsPoint(x, y))
{ {
if (isOpenBracketPressed || (isLeftCtrlPressed && isEnterPressed && __instance.readyToClose())) if ((MainClass.Config.LeftClickMainKey.JustPressed() || MainClass.Config.LeftClickAlternateKey.JustPressed()) && __instance.readyToClose())
{ {
Game1.player.professions.Add(___professionsToChoose[0]); Game1.player.professions.Add(___professionsToChoose[0]);
__instance.getImmediateProfessionPerk(___professionsToChoose[0]); __instance.getImmediateProfessionPerk(___professionsToChoose[0]);
@ -184,7 +578,7 @@ namespace stardew_access.Patches
if (__instance.rightProfession.containsPoint(x, y)) if (__instance.rightProfession.containsPoint(x, y))
{ {
if (isOpenBracketPressed || (isLeftCtrlPressed && isEnterPressed && __instance.readyToClose())) if ((MainClass.Config.LeftClickMainKey.JustPressed() || MainClass.Config.LeftClickAlternateKey.JustPressed()) && __instance.readyToClose())
{ {
Game1.player.professions.Add(___professionsToChoose[1]); Game1.player.professions.Add(___professionsToChoose[1]);
__instance.getImmediateProfessionPerk(___professionsToChoose[1]); __instance.getImmediateProfessionPerk(___professionsToChoose[1]);
@ -216,23 +610,23 @@ namespace stardew_access.Patches
if (__instance.okButton.containsPoint(x, y)) if (__instance.okButton.containsPoint(x, y))
{ {
if (isOpenBracketPressed || (isLeftCtrlPressed && isEnterPressed)) if (MainClass.Config.LeftClickMainKey.JustPressed() || MainClass.Config.LeftClickAlternateKey.JustPressed())
__instance.okButtonClicked(); __instance.okButtonClicked();
toSpeak = $"{___title} {extraInfo} {newCraftingRecipe}. Left click to close."; toSpeak = $"{___title} {extraInfo} {newCraftingRecipe}. Left click to close.";
} }
if (toSpeak != " ") if (toSpeak != " ")
MainClass.GetScreenReader().SayWithMenuChecker(toSpeak, true); MainClass.ScreenReader.SayWithMenuChecker(toSpeak, true);
else if (__instance.isProfessionChooser && currentLevelUpTitle != $"{___title}. Select a new profession.") else if (__instance.isProfessionChooser && currentLevelUpTitle != $"{___title}. Select a new profession.")
{ {
MainClass.GetScreenReader().SayWithMenuChecker($"{___title}. Select a new profession.", true); MainClass.ScreenReader.SayWithMenuChecker($"{___title}. Select a new profession.", true);
currentLevelUpTitle = $"{___title}. Select a new profession."; currentLevelUpTitle = $"{___title}. Select a new profession.";
} }
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
} }
} }
@ -240,140 +634,47 @@ namespace stardew_access.Patches
{ {
try try
{ {
bool isLeftControlPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftControl);
bool isOpenBracketPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.OemOpenBrackets); // for left click
bool isEnterPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Enter);
if (__instance.currentPage == -1) if (__instance.currentPage == -1)
{ {
int total = ___categoryTotals[5]; int total = ___categoryTotals[5];
string toSpeak; string toSpeak;
if (__instance.okButton.containsPoint(Game1.getMouseX(), Game1.getMouseY())) if (__instance.okButton.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
{ {
// Perform Left Click // Perform Left Click
if (isOpenBracketPressed || (isLeftControlPressed && isEnterPressed)) if (MainClass.Config.LeftClickMainKey.JustPressed() || MainClass.Config.LeftClickAlternateKey.JustPressed())
{ {
Game1.activeClickableMenu.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true)); Game1.activeClickableMenu.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true));
} }
toSpeak = $"{total}g in total. Press left mouse button to save."; toSpeak = $"{total}g in total. Press left mouse button to save.";
MainClass.GetScreenReader().SayWithChecker(toSpeak, true); MainClass.ScreenReader.SayWithChecker(toSpeak, true);
} }
for (int i = 0; i < __instance.categories.Count; i++) for (int i = 0; i < __instance.categories.Count; i++)
{ {
if (__instance.categories[i].containsPoint(Game1.getMouseX(), Game1.getMouseY())) if (__instance.categories[i].containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
{ {
toSpeak = $"Money recieved from {__instance.getCategoryName(i)}: {___categoryTotals[i]}g."; toSpeak = $"Money recieved from {__instance.getCategoryName(i)}: {___categoryTotals[i]}g.";
MainClass.GetScreenReader().SayWithChecker(toSpeak, true); MainClass.ScreenReader.SayWithChecker(toSpeak, true);
} }
} }
} }
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void LetterViewerMenuPatch(LetterViewerMenu __instance)
{
try
{
if (!__instance.IsActive())
return;
#region Texts in the letter
string message = __instance.mailMessage[__instance.page];
string toSpeak = $"{message}";
if (__instance.ShouldShowInteractable())
{
if (__instance.moneyIncluded > 0)
{
string moneyText = Game1.content.LoadString("Strings\\UI:LetterViewer_MoneyIncluded", __instance.moneyIncluded);
toSpeak += $"\t\n\t ,Included money: {moneyText}";
}
else if (__instance.learnedRecipe != null && __instance.learnedRecipe.Length > 0)
{
string recipeText = Game1.content.LoadString("Strings\\UI:LetterViewer_LearnedRecipe", __instance.cookingOrCrafting);
toSpeak += $"\t\n\t ,Learned Recipe: {recipeText}";
}
}
if (currentLetterText != toSpeak)
{
currentLetterText = toSpeak;
// snap mouse to accept quest button
if (__instance.acceptQuestButton != null && __instance.acceptQuestButton.visible)
{
toSpeak += "\t\n Left click to accept quest.";
__instance.acceptQuestButton.snapMouseCursorToCenter();
}
MainClass.GetScreenReader().Say(toSpeak, false);
}
#endregion
#region Narrate items given in the mail
if (__instance.ShouldShowInteractable())
{
foreach (ClickableComponent c in __instance.itemsToGrab)
{
string name = c.name;
string label = c.label;
if (c.containsPoint(Game1.getMousePosition().X, Game1.getMousePosition().Y))
MainClass.GetScreenReader().SayWithChecker($"Grab: {name} \t\n {label}", false);
}
}
#endregion
}
catch (Exception e)
{
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error);
} }
} }
#region Cleanup on exitting a menu
internal static void Game1ExitActiveMenuPatch() internal static void Game1ExitActiveMenuPatch()
{ {
try try
{ {
if (Game1.activeClickableMenu is GameMenu) Cleanup(Game1.activeClickableMenu);
{
GameMenuPatches.gameMenuQueryKey = "";
GameMenuPatches.craftingPageQueryKey = "";
GameMenuPatches.inventoryPageQueryKey = "";
GameMenuPatches.exitPageQueryKey = "";
GameMenuPatches.optionsPageQueryKey = "";
GameMenuPatches.socialPageQuery = "";
GameMenuPatches.currentSelectedCraftingRecipe = -1;
GameMenuPatches.isSelectingRecipe = false;
}
if (Game1.activeClickableMenu is JunimoNoteMenu)
{
GameMenuPatches.currentIngredientListItem = -1;
GameMenuPatches.currentIngredientInputSlot = -1;
GameMenuPatches.currentInventorySlot = -1;
GameMenuPatches.junimoNoteMenuQuery = "";
}
if (Game1.activeClickableMenu is ShopMenu)
{
GameMenuPatches.shopMenuQueryKey = "";
}
if (Game1.activeClickableMenu is ItemGrabMenu)
{
GameMenuPatches.itemGrabMenuQueryKey = "";
}
GameMenuPatches.hoveredItemQueryKey = "";
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
} }
} }
@ -381,62 +682,120 @@ namespace stardew_access.Patches
{ {
try try
{ {
if (__instance is GeodeMenu) Cleanup(__instance);
{
GameMenuPatches.geodeMenuQueryKey = "";
}
if (__instance is ItemGrabMenu)
{
GameMenuPatches.itemGrabMenuQueryKey = "";
}
if (__instance is ShopMenu)
{
GameMenuPatches.shopMenuQueryKey = "";
}
if (__instance is CarpenterMenu)
{
BuildingNAnimalMenuPatches.carpenterMenuQuery = "";
BuildingNAnimalMenuPatches.isUpgrading = false;
BuildingNAnimalMenuPatches.isDemolishing = false;
BuildingNAnimalMenuPatches.isPainting = false;
BuildingNAnimalMenuPatches.isMoving = false;
BuildingNAnimalMenuPatches.isConstructing = false;
BuildingNAnimalMenuPatches.carpenterMenu = null;
}
if (__instance is PurchaseAnimalsMenu)
{
BuildingNAnimalMenuPatches.purchaseAnimalMenuQuery = "";
BuildingNAnimalMenuPatches.firstTimeInNamingMenu = true;
BuildingNAnimalMenuPatches.purchaseAnimalsMenu = null;
}
if (__instance is DialogueBox)
{
DialoguePatches.isDialogueAppearingFirstTime = true;
DialoguePatches.currentDialogue = " ";
}
GameMenuPatches.hoveredItemQueryKey = "";
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
} }
} }
private static void Cleanup(IClickableMenu menu)
{
if (menu is LetterViewerMenu)
{
DialoguePatches.currentLetterText = " ";
}
else if (menu is LevelUpMenu)
{
currentLevelUpTitle = " ";
}
else if (menu is Billboard)
{
QuestPatches.currentDailyQuestText = " ";
}
else if (menu is GameMenu)
{
GameMenuPatches.gameMenuQueryKey = "";
GameMenuPatches.craftingPageQueryKey = "";
GameMenuPatches.inventoryPageQueryKey = "";
GameMenuPatches.exitPageQueryKey = "";
GameMenuPatches.optionsPageQueryKey = "";
GameMenuPatches.socialPageQuery = "";
GameMenuPatches.currentSelectedCraftingRecipe = -1;
GameMenuPatches.isSelectingRecipe = false;
}
else if (menu is JunimoNoteMenu)
{
BundleMenuPatches.currentIngredientListItem = -1;
BundleMenuPatches.currentIngredientInputSlot = -1;
BundleMenuPatches.currentInventorySlot = -1;
BundleMenuPatches.junimoNoteMenuQuery = "";
}
else if (menu is ShopMenu)
{
GameMenuPatches.shopMenuQueryKey = "";
}
else if (menu is ItemGrabMenu)
{
GameMenuPatches.itemGrabMenuQueryKey = "";
}
else if (menu is GeodeMenu)
{
GameMenuPatches.geodeMenuQueryKey = "";
}
else if (menu is CarpenterMenu)
{
BuildingNAnimalMenuPatches.carpenterMenuQuery = "";
BuildingNAnimalMenuPatches.isUpgrading = false;
BuildingNAnimalMenuPatches.isDemolishing = false;
BuildingNAnimalMenuPatches.isPainting = false;
BuildingNAnimalMenuPatches.isMoving = false;
BuildingNAnimalMenuPatches.isConstructing = false;
BuildingNAnimalMenuPatches.carpenterMenu = null;
}
else if (menu is PurchaseAnimalsMenu)
{
BuildingNAnimalMenuPatches.purchaseAnimalMenuQuery = "";
BuildingNAnimalMenuPatches.firstTimeInNamingMenu = true;
BuildingNAnimalMenuPatches.purchaseAnimalsMenu = null;
}
else if (menu is DialogueBox)
{
DialoguePatches.isDialogueAppearingFirstTime = true;
DialoguePatches.currentDialogue = " ";
}
else if (menu is JojaCDMenu)
{
BundleMenuPatches.jojaCDMenuQuery = "";
}
else if (menu is QuestLog)
{
QuestPatches.questLogQuery = " ";
}
else if (menu is TailoringMenu)
{
tailoringMenuQuery = " ";
}
else if (menu is ForgeMenu)
{
forgeMenuQuery = " ";
}
else if (menu is ItemListMenu)
{
itemListMenuQuery = " ";
}
else if (menu is FieldOfficeMenu)
{
DonationMenuPatches.fieldOfficeMenuQuery = " ";
}
else if (menu is MuseumMenu)
{
DonationMenuPatches.museumQueryKey = " ";
}
else if (menu is PondQueryMenu)
{
pondQueryMenuQuery = " ";
}
GameMenuPatches.hoveredItemQueryKey = "";
}
#endregion
internal static void ExitEventPatch() internal static void ExitEventPatch()
{ {
if (MainClass.GetScreenReader() != null) if (MainClass.ScreenReader != null)
MainClass.GetScreenReader().CloseScreenReader(); MainClass.ScreenReader.CloseScreenReader();
}
internal static void resetGlobalVars()
{
currentLetterText = " ";
currentLevelUpTitle = " ";
} }
} }
} }

View File

@ -0,0 +1,120 @@
using Microsoft.Xna.Framework;
using StardewValley;
using StardewValley.Minigames;
namespace stardew_access.Patches
{
public class MiniGamesPatches
{
public static string grandpaStoryQuery = " ";
public static string introQuery = " ";
internal static void IntroPatch(Intro __instance, int ___currentState)
{
try
{
if (MainClass.ModHelper == null)
return;
string toSpeak = " ";
if (___currentState == 3)
{
toSpeak = MainClass.ModHelper.Translation.Get("intro.scene3");
}
else if (___currentState == 4)
{
toSpeak = MainClass.ModHelper.Translation.Get("intro.scene4");
}
if (toSpeak != " " && introQuery != toSpeak)
{
introQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, false);
return;
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void GrandpaStoryPatch(GrandpaStory __instance, StardewValley.Menus.LetterViewerMenu ___letterView, bool ___drawGrandpa, bool ___letterReceived, bool ___mouseActive, Queue<string> ___grandpaSpeech, int ___grandpaSpeechTimer, int ___totalMilliseconds, int ___scene, int ___parallaxPan)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = " ";
if (___letterView != null)
{
DialoguePatches.NarrateLetterContent(___letterView);
}
if (MainClass.ModHelper == null)
return;
if (___scene == 0)
{
toSpeak = MainClass.ModHelper.Translation.Get("grandpastory.scene0");
}
else if (___drawGrandpa)
{
if (___grandpaSpeech.Count > 0 && ___grandpaSpeechTimer > 3000)
{
toSpeak = ___grandpaSpeech.Peek();
}
}
else if (___scene == 3)
{
toSpeak = Game1.content.LoadString("Strings\\StringsFromCSFiles:GrandpaStory.cs.12059");
}
else if (___scene == 4)
{
toSpeak = MainClass.ModHelper.Translation.Get("grandpastory.scene4");
}
else if (___scene == 5)
{
toSpeak = MainClass.ModHelper.Translation.Get("grandpastory.scene5");
}
else if (___scene == 6)
{
if (___grandpaSpeechTimer > 3000)
{
if (clickableGrandpaLetterRect(___parallaxPan, ___grandpaSpeechTimer).Contains(x, y))
{
toSpeak = MainClass.ModHelper.Translation.Get("grandpastory.letteropen");
}
else if (___letterView == null)
{
Point pos = clickableGrandpaLetterRect(___parallaxPan, ___grandpaSpeechTimer).Center;
Game1.setMousePositionRaw((int)((float)pos.X * Game1.options.zoomLevel), (int)((float)pos.Y * Game1.options.zoomLevel));
return;
}
}
else
{
toSpeak = MainClass.ModHelper.Translation.Get("grandpastory.scene6");
}
}
if (toSpeak != " " && grandpaStoryQuery != toSpeak)
{
grandpaStoryQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, false);
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
// This method is taken from the game's source code
private static Rectangle clickableGrandpaLetterRect(int ___parallaxPan, int ___grandpaSpeechTimer)
{
return new Rectangle((int)Utility.getTopLeftPositionForCenteringOnScreen(Game1.viewport, 1294, 730).X + (286 - ___parallaxPan) * 4, (int)Utility.getTopLeftPositionForCenteringOnScreen(Game1.viewport, 1294, 730).Y + 218 + Math.Max(0, Math.Min(60, (___grandpaSpeechTimer - 5000) / 8)), 524, 344);
}
}
}

View File

@ -1,5 +1,4 @@
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI;
using StardewValley; using StardewValley;
using StardewValley.Menus; using StardewValley.Menus;
using StardewValley.Quests; using StardewValley.Quests;
@ -8,14 +7,16 @@ namespace stardew_access.Patches
{ {
internal class QuestPatches internal class QuestPatches
{ {
private static string currentDailyQuestText = " "; internal static string currentDailyQuestText = " ";
internal static string questLogQuery = " ";
internal static bool isNarratingQuestInfo = false, firstTimeInIndividualQuest = true;
#region For Special Orders Board #region For Special Orders Board
internal static void SpecialOrdersBoardPatch(SpecialOrdersBoard __instance) internal static void SpecialOrdersBoardPatch(SpecialOrdersBoard __instance)
{ {
try try
{ {
int x = Game1.getMouseX(), y = Game1.getMouseY(); // Mouse x and y position int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (__instance.acceptLeftQuestButton.visible && __instance.acceptLeftQuestButton.containsPoint(x, y)) if (__instance.acceptLeftQuestButton.visible && __instance.acceptLeftQuestButton.containsPoint(x, y))
{ {
@ -23,7 +24,7 @@ namespace stardew_access.Patches
toSpeak = $"Left Quest:\n\t{toSpeak}\n\tPress left click to accept this quest."; toSpeak = $"Left Quest:\n\t{toSpeak}\n\tPress left click to accept this quest.";
MainClass.GetScreenReader().SayWithMenuChecker(toSpeak, true); MainClass.ScreenReader.SayWithMenuChecker(toSpeak, true);
return; return;
} }
@ -33,13 +34,13 @@ namespace stardew_access.Patches
toSpeak = $"Right Quest:\n\t{toSpeak}\n\tPress left click to accept this quest."; toSpeak = $"Right Quest:\n\t{toSpeak}\n\tPress left click to accept this quest.";
MainClass.GetScreenReader().SayWithMenuChecker(toSpeak, true); MainClass.ScreenReader.SayWithMenuChecker(toSpeak, true);
return; return;
} }
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
} }
} }
@ -83,7 +84,7 @@ namespace stardew_access.Patches
#region Callender #region Callender
for (int i = 0; i < __instance.calendarDays.Count; i++) for (int i = 0; i < __instance.calendarDays.Count; i++)
{ {
if (__instance.calendarDays[i].containsPoint(Game1.getMouseX(), Game1.getMouseY())) if (__instance.calendarDays[i].containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
{ {
string toSpeak = $"Day {i + 1}"; string toSpeak = $"Day {i + 1}";
@ -99,7 +100,7 @@ namespace stardew_access.Patches
if (Game1.dayOfMonth == i + 1) if (Game1.dayOfMonth == i + 1)
toSpeak += $", Current"; toSpeak += $", Current";
MainClass.GetScreenReader().SayWithChecker(toSpeak, true); MainClass.ScreenReader.SayWithChecker(toSpeak, true);
} }
} }
#endregion #endregion
@ -114,7 +115,7 @@ namespace stardew_access.Patches
if (currentDailyQuestText != toSpeak) if (currentDailyQuestText != toSpeak)
{ {
currentDailyQuestText = toSpeak; currentDailyQuestText = toSpeak;
MainClass.GetScreenReader().Say(toSpeak, true); MainClass.ScreenReader.Say(toSpeak, true);
} }
} }
else else
@ -134,7 +135,7 @@ namespace stardew_access.Patches
__instance.acceptQuestButton.snapMouseCursorToCenter(); __instance.acceptQuestButton.snapMouseCursorToCenter();
} }
MainClass.GetScreenReader().Say(toSpeak, true); MainClass.ScreenReader.Say(toSpeak, true);
} }
} }
#endregion #endregion
@ -142,7 +143,7 @@ namespace stardew_access.Patches
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
} }
} }
#endregion #endregion
@ -152,101 +153,135 @@ namespace stardew_access.Patches
{ {
try try
{ {
bool snapMouseToRewardBox = false; bool isPrimaryInfoKeyPressed = MainClass.Config.PrimaryInfoKey.JustPressed();
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = " ", extra = "";
if (___questPage == -1) if (___questPage == -1)
{ {
#region Quest Lists #region Quest Lists
if (!firstTimeInIndividualQuest)
firstTimeInIndividualQuest = true;
for (int i = 0; i < __instance.questLogButtons.Count; i++) for (int i = 0; i < __instance.questLogButtons.Count; i++)
{ {
if (___pages.Count() > 0 && ___pages[___currentPage].Count() > i) if (___pages.Count() > 0 && ___pages[___currentPage].Count() > i)
{ {
if (!__instance.questLogButtons[i].containsPoint(x, y))
continue;
string name = ___pages[___currentPage][i].GetName(); string name = ___pages[___currentPage][i].GetName();
int daysLeft = ___pages[___currentPage][i].GetDaysLeft(); int daysLeft = ___pages[___currentPage][i].GetDaysLeft();
string toSpeak = $"{name} quest"; toSpeak = $"{name}";
if (daysLeft > 0 && ___pages[___currentPage][i].ShouldDisplayAsComplete()) if (daysLeft > 0 && ___pages[___currentPage][i].ShouldDisplayAsComplete())
toSpeak += $"\t\n {daysLeft} days left"; toSpeak += $"\t\n {daysLeft} days left";
toSpeak += ___pages[___currentPage][i].ShouldDisplayAsComplete() ? " completed!" : ""; toSpeak += ___pages[___currentPage][i].ShouldDisplayAsComplete() ? " completed!" : "";
if (__instance.questLogButtons[i].containsPoint(Game1.getOldMouseX(), Game1.getOldMouseY())) break;
{
MainClass.GetScreenReader().SayWithChecker(toSpeak, true);
}
} }
} }
if (__instance.backButton != null && __instance.backButton.visible && __instance.backButton.containsPoint(x, y))
toSpeak = "Previous page button";
else if (__instance.forwardButton != null && __instance.forwardButton.visible && __instance.forwardButton.containsPoint(x, y))
toSpeak = "Next page button";
else if (__instance.upperRightCloseButton != null && __instance.upperRightCloseButton.visible && __instance.upperRightCloseButton.containsPoint(x, y))
toSpeak = "Close menu button";
if (questLogQuery != toSpeak)
{
questLogQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
#endregion #endregion
} }
else else
{ {
#region Individual quest #region Individual quest
bool containsReward = __instance.HasReward() || __instance.HasMoneyReward();
string description = Game1.parseText(____shownQuest.GetDescription(), Game1.dialogueFont, __instance.width - 128); string description = Game1.parseText(____shownQuest.GetDescription(), Game1.dialogueFont, __instance.width - 128);
string title = ____shownQuest.GetName(); string title = ____shownQuest.GetName();
string toSpeak = " ";
if (____shownQuest.ShouldDisplayAsComplete()) if (firstTimeInIndividualQuest || (isPrimaryInfoKeyPressed && !isNarratingQuestInfo))
{ {
#region Quest completed menu if (firstTimeInIndividualQuest)
toSpeak = "Back button";
toSpeak = $"Quest: {title} Completed!"; if (____shownQuest.ShouldDisplayAsComplete())
if (__instance.HasReward())
{ {
snapMouseToRewardBox = true; #region Quest completed menu
extra = $"Quest: {title} Completed!";
if (__instance.HasMoneyReward()) if (__instance.HasMoneyReward())
extra += $"you recieved {____shownQuest.GetMoneyReward()}g";
#endregion
}
else
{
#region Quest in-complete menu
extra = $"Title: {title}. \t\n Description: {description}";
for (int j = 0; j < ____objectiveText.Count; j++)
{ {
toSpeak += $"you recieved {____shownQuest.GetMoneyReward()}g"; string parsed_text = Game1.parseText(____objectiveText[j], width: __instance.width - 192, whichFont: Game1.dialogueFont);
if (____shownQuest != null && ____shownQuest is SpecialOrder)
{
OrderObjective order_objective = ((SpecialOrder)____shownQuest).objectives[j];
if (order_objective.GetMaxCount() > 1 && order_objective.ShouldShowProgress())
parsed_text += "\n\t" + order_objective.GetCount() + " of " + order_objective.GetMaxCount() + " completed";
}
extra += $"\t\nOrder {j + 1}: {parsed_text} \t\n";
} }
toSpeak += "... left click to collect reward";
}
#endregion
}
else
{
#region Quest in-complete menu
toSpeak = $"Title: {title}. \t\n Description: {description}";
for (int j = 0; j < ____objectiveText.Count; j++)
{
if (____shownQuest != null) if (____shownQuest != null)
{ {
_ = ____shownQuest is SpecialOrder; int daysLeft = ____shownQuest.GetDaysLeft();
if (daysLeft > 0)
extra += $"\t\n{daysLeft} days left.";
} }
string parsed_text = Game1.parseText(____objectiveText[j], width: __instance.width - 192, whichFont: Game1.dialogueFont); #endregion
toSpeak += $"\t\nOrder {j + 1}: {parsed_text} \t\n";
} }
if (____shownQuest != null) isNarratingQuestInfo = true;
{ Task.Delay(200).ContinueWith(_ => { isNarratingQuestInfo = false; });
int daysLeft = ____shownQuest.GetDaysLeft(); questLogQuery = " ";
if (daysLeft > 0)
toSpeak += $"\t\n{daysLeft} days left.";
}
#endregion
} }
// Move mouse to reward button if (!firstTimeInIndividualQuest)
if (snapMouseToRewardBox) if (__instance.backButton != null && __instance.backButton.visible && __instance.backButton.containsPoint(x, y))
__instance.rewardBox.snapMouseCursorToCenter(); toSpeak = (___currentPage > 0) ? "Previous page button" : "Back button";
else if (__instance.forwardButton != null && __instance.forwardButton.visible && __instance.forwardButton.containsPoint(x, y))
toSpeak = "Next page button";
else if (__instance.cancelQuestButton != null && __instance.cancelQuestButton.visible && __instance.cancelQuestButton.containsPoint(x, y))
toSpeak = "Cancel quest button";
else if (__instance.upperRightCloseButton != null && __instance.upperRightCloseButton.visible && __instance.upperRightCloseButton.containsPoint(x, y))
toSpeak = "Close menu button";
else if (containsReward && __instance.rewardBox.containsPoint(x, y))
toSpeak = "Left click to collect reward";
if (firstTimeInIndividualQuest || (questLogQuery != toSpeak))
{
questLogQuery = toSpeak;
MainClass.ScreenReader.Say(extra + " \n\t" + toSpeak, true);
if (firstTimeInIndividualQuest)
firstTimeInIndividualQuest = false;
}
MainClass.GetScreenReader().SayWithChecker(toSpeak, true);
#endregion #endregion
} }
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
} }
} }
#endregion #endregion
internal static void resetGlobalVars()
{
currentDailyQuestText = " ";
}
} }
} }

View File

@ -1,7 +1,7 @@
using Microsoft.Xna.Framework; using StardewValley;
using StardewModdingAPI; using StardewValley.Characters;
using StardewValley;
using StardewValley.Menus; using StardewValley.Menus;
using static StardewValley.Menus.CharacterCustomization;
using static StardewValley.Menus.LoadGameMenu; using static StardewValley.Menus.LoadGameMenu;
namespace stardew_access.Patches namespace stardew_access.Patches
@ -10,13 +10,83 @@ namespace stardew_access.Patches
{ {
private static int saveGameIndex = -1; private static int saveGameIndex = -1;
private static bool isRunning = false; private static bool isRunning = false;
private const int MAX_COMPONENTS = 20; public static string characterCreationMenuQueryKey = " ";
public static string advancedGameOptionsQueryKey = " ";
public static string prevPetName = " ";
internal static void AdvancedGameOptionsPatch(AdvancedGameOptions __instance)
{
try
{
int currentItemIndex = Math.Max(0, Math.Min(__instance.options.Count - 7, __instance.currentItemIndex));
int x = Game1.getMouseX(true), y = Game1.getMouseY(true);
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
string toSpeak = "OK Button";
if (advancedGameOptionsQueryKey != toSpeak)
{
advancedGameOptionsQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
for (int i = 0; i < __instance.optionSlots.Count; i++)
{
if (__instance.optionSlots[i].bounds.Contains(x, y) && currentItemIndex + i < __instance.options.Count && __instance.options[currentItemIndex + i].bounds.Contains(x - __instance.optionSlots[i].bounds.X, y - __instance.optionSlots[i].bounds.Y))
{
OptionsElement optionsElement = __instance.options[currentItemIndex + i];
string toSpeak = optionsElement.label;
if (optionsElement is OptionsButton)
toSpeak = $" {toSpeak} Button";
else if (optionsElement is OptionsCheckbox)
toSpeak = (((OptionsCheckbox)optionsElement).isChecked ? "Enabled" : "Disabled") + $" {toSpeak} Checkbox";
else if (optionsElement is OptionsDropDown)
toSpeak = $"{toSpeak} Dropdown, option {((OptionsDropDown)optionsElement).dropDownDisplayOptions[((OptionsDropDown)optionsElement).selectedOption]} selected";
else if (optionsElement is OptionsSlider)
toSpeak = $"{((OptionsSlider)optionsElement).value}% {toSpeak} Slider";
else if (optionsElement is OptionsPlusMinus)
toSpeak = $"{((OptionsPlusMinus)optionsElement).displayOptions[((OptionsPlusMinus)optionsElement).selected]} selected of {toSpeak}";
else if (optionsElement is OptionsInputListener)
{
string buttons = "";
((OptionsInputListener)optionsElement).buttonNames.ForEach(name => { buttons += $", {name}"; });
toSpeak = $"{toSpeak} is bound to {buttons}. Left click to change.";
}
else if (optionsElement is OptionsTextEntry)
{
toSpeak = $"Seed text box";
}
else
{
if (toSpeak.Contains(":"))
toSpeak = toSpeak.Replace(":", "");
toSpeak = $"{toSpeak} Options:";
}
if (advancedGameOptionsQueryKey != toSpeak)
{
advancedGameOptionsQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void CoopMenuPatch(CoopMenu __instance, CoopMenu.Tab ___currentTab) internal static void CoopMenuPatch(CoopMenu __instance, CoopMenu.Tab ___currentTab)
{ {
try try
{ {
int x = Game1.getMouseX(), y = Game1.getMouseY(); int x = Game1.getMouseX(true), y = Game1.getMouseY(true);
string toSpeak = " "; string toSpeak = " ";
#region Join/Host Button (Important! This should be checked before checking other buttons) #region Join/Host Button (Important! This should be checked before checking other buttons)
@ -45,11 +115,11 @@ namespace stardew_access.Patches
#endregion #endregion
if (toSpeak != " ") if (toSpeak != " ")
MainClass.GetScreenReader().SayWithChecker(toSpeak, true); MainClass.ScreenReader.SayWithChecker(toSpeak, true);
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
} }
} }
@ -64,7 +134,7 @@ namespace stardew_access.Patches
__instance.buttons.ForEach(component => __instance.buttons.ForEach(component =>
{ {
if (component.containsPoint(Game1.getMouseX(), Game1.getMouseY())) if (component.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
{ {
string name = component.name; string name = component.name;
string label = component.label; string label = component.label;
@ -72,38 +142,48 @@ namespace stardew_access.Patches
} }
}); });
if (__instance.muteMusicButton.containsPoint(Game1.getMouseX(), Game1.getMouseY())) if (__instance.muteMusicButton.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
{ {
toSpeak = "Mute Music Button"; toSpeak = "Mute Music Button";
} }
if (__instance.aboutButton.containsPoint(Game1.getMouseX(), Game1.getMouseY())) if (__instance.aboutButton.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
{ {
toSpeak = "About Button"; toSpeak = "About Button";
} }
if (__instance.languageButton.containsPoint(Game1.getMouseX(), Game1.getMouseY())) if (__instance.languageButton.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
{ {
toSpeak = "Language Button"; toSpeak = "Language Button";
} }
if (__instance.windowedButton.containsPoint(Game1.getMouseX(), Game1.getMouseY())) if (__instance.windowedButton.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
{ {
toSpeak = "Fullscreen toggle Button"; toSpeak = "Fullscreen: " + ((Game1.isFullscreen) ? "enabled" : "disabled");
} }
if (TitleMenu.subMenu != null && __instance.backButton.containsPoint(Game1.getMouseX(), Game1.getMouseY())) if (TitleMenu.subMenu != null && __instance.backButton.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
{ {
string text = "Back Button"; string text = "Back Button";
MainClass.GetScreenReader().SayWithChecker(text, true); MainClass.ScreenReader.SayWithChecker(text, true);
}
// Fix for back button not working using keyboard
if (TitleMenu.subMenu is CharacterCustomization && ((CharacterCustomization)TitleMenu.subMenu).backButton.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
{
// Perform Left Click
if (MainClass.Config.LeftClickMainKey.JustPressed())
{
__instance.backButtonPressed();
}
} }
if (TitleMenu.subMenu == null && toSpeak != "") if (TitleMenu.subMenu == null && toSpeak != "")
MainClass.GetScreenReader().SayWithChecker(toSpeak, true); MainClass.ScreenReader.SayWithChecker(toSpeak, true);
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
} }
} }
@ -111,7 +191,7 @@ namespace stardew_access.Patches
{ {
try try
{ {
int x = Game1.getMouseX(), y = Game1.getMouseY(); int x = Game1.getMouseX(true), y = Game1.getMouseY(true);
if (___menu.slotButtons[i].containsPoint(x, y)) if (___menu.slotButtons[i].containsPoint(x, y))
{ {
if (__instance.Farmer != null) if (__instance.Farmer != null)
@ -119,7 +199,7 @@ namespace stardew_access.Patches
#region Farms #region Farms
if (___menu.deleteButtons.Count > 0 && ___menu.deleteButtons[i].containsPoint(x, y)) if (___menu.deleteButtons.Count > 0 && ___menu.deleteButtons[i].containsPoint(x, y))
{ {
MainClass.GetScreenReader().SayWithChecker($"Delete {__instance.Farmer.farmName} Farm", true); MainClass.ScreenReader.SayWithChecker($"Delete {__instance.Farmer.farmName.Value} Farm", true);
return; return;
} }
@ -128,523 +208,230 @@ namespace stardew_access.Patches
// Used diff. functions to narrate to prevent it from speaking the message again on selecting another button. // Used diff. functions to narrate to prevent it from speaking the message again on selecting another button.
string message = "Really delete farm?"; string message = "Really delete farm?";
MainClass.GetScreenReader().SayWithChecker(message, true); MainClass.ScreenReader.SayWithChecker(message, true);
if (___menu.okDeleteButton.containsPoint(x, y)) if (___menu.okDeleteButton.containsPoint(x, y))
{ {
MainClass.GetScreenReader().SayWithMenuChecker("Ok Button", false); MainClass.ScreenReader.SayWithMenuChecker("Ok Button", false);
} }
else if (___menu.cancelDeleteButton.containsPoint(x, y)) else if (___menu.cancelDeleteButton.containsPoint(x, y))
{ {
MainClass.GetScreenReader().SayWithMenuChecker("Cancel Button", false); MainClass.ScreenReader.SayWithMenuChecker("Cancel Button", false);
} }
return; return;
} }
String farmerName = __instance.Farmer.displayName; String farmerName = __instance.Farmer.displayName;
String farmName = __instance.Farmer.farmName; String farmName = __instance.Farmer.farmName.Value;
String money = __instance.Farmer.Money.ToString(); String money = __instance.Farmer.Money.ToString();
String hoursPlayed = Utility.getHoursMinutesStringFromMilliseconds(__instance.Farmer.millisecondsPlayed); String hoursPlayed = Utility.getHoursMinutesStringFromMilliseconds(__instance.Farmer.millisecondsPlayed);
string dateStringForSaveGame = ((!__instance.Farmer.dayOfMonthForSaveGame.HasValue || string dateStringForSaveGame = ((!__instance.Farmer.dayOfMonthForSaveGame.HasValue ||
!__instance.Farmer.seasonForSaveGame.HasValue || !__instance.Farmer.seasonForSaveGame.HasValue ||
!__instance.Farmer.yearForSaveGame.HasValue) ? __instance.Farmer.dateStringForSaveGame : Utility.getDateStringFor(__instance.Farmer.dayOfMonthForSaveGame.Value, __instance.Farmer.seasonForSaveGame.Value, __instance.Farmer.yearForSaveGame.Value)); !__instance.Farmer.yearForSaveGame.HasValue) ? __instance.Farmer.dateStringForSaveGame : Utility.getDateStringFor(__instance.Farmer.dayOfMonthForSaveGame.Value, __instance.Farmer.seasonForSaveGame.Value, __instance.Farmer.yearForSaveGame.Value));
string toSpeak = $"{farmName} Farm Selected, \t\n Farmer:{farmerName}, \t\nMoney:{money}, \t\nHours Played:{hoursPlayed}, \t\nDate:{dateStringForSaveGame}"; string toSpeak = $"{farmName} Farm Selected, \t\n Farmer: {farmerName}, \t\nMoney: {money}, \t\nHours Played: {hoursPlayed}, \t\nDate: {dateStringForSaveGame}";
MainClass.GetScreenReader().SayWithChecker(toSpeak, true); MainClass.ScreenReader.SayWithChecker(toSpeak, true);
#endregion #endregion
} }
} }
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
} }
} }
internal static void NewGameMenuPatch(CharacterCustomization __instance, bool ___skipIntro) internal static void CharacterCustomizationMenuPatch(CharacterCustomization __instance, bool ___skipIntro,
ClickableComponent ___startingCabinsLabel, ClickableComponent ___difficultyModifierLabel, TextBox ___nameBox,
TextBox ___farmnameBox, TextBox ___favThingBox)
{ {
try try
{ {
bool isNextArrowPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Right); bool isEscPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Escape); // For escaping/unselecting from the animal name text box
bool isPrevArrowPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Left); string toSpeak = " ";
string currentPetName = getCurrentPetName();
if (isNextArrowPressed && !isRunning) if (___nameBox.Selected)
{ {
_ = CycleThroughItems(true, __instance, ___skipIntro); toSpeak = ___nameBox.Text;
if (isEscPressed)
{
___nameBox.Selected = false;
}
} }
else if (isPrevArrowPressed && !isRunning) else if (___farmnameBox.Selected)
{ {
_ = CycleThroughItems(false, __instance, ___skipIntro); toSpeak = ___farmnameBox.Text;
if (isEscPressed)
{
___farmnameBox.Selected = false;
}
}
else if (___favThingBox.Selected)
{
toSpeak = ___favThingBox.Text;
if (isEscPressed)
{
___favThingBox.Selected = false;
}
}
else if (MainClass.Config.CharacterCreationMenuNextKey.JustPressed() && !isRunning)
{
isRunning = true;
CycleThroughItems(true, __instance, ___skipIntro, ___startingCabinsLabel, ___difficultyModifierLabel);
Task.Delay(200).ContinueWith(_ => { isRunning = false; });
}
else if (MainClass.Config.CharacterCreationMenuPreviousKey.JustPressed() && !isRunning)
{
isRunning = true;
CycleThroughItems(false, __instance, ___skipIntro, ___startingCabinsLabel, ___difficultyModifierLabel);
Task.Delay(200).ContinueWith(_ => { isRunning = false; });
}
if (prevPetName != currentPetName)
{
prevPetName = currentPetName;
toSpeak = $"Current Pet: {currentPetName} \n {toSpeak}";
}
if (characterCreationMenuQueryKey != toSpeak && toSpeak != " ")
{
characterCreationMenuQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
} }
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.GetMonitor().Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
} }
} }
private static async Task CycleThroughItems(bool increase, CharacterCustomization __instance, bool ___skipIntro) private static void CycleThroughItems(bool increase, CharacterCustomization __instance, bool ___skipIntro,
ClickableComponent ___startingCabinsLabel, ClickableComponent ___difficultyModifierLabel)
{ {
isRunning = true;
string toSpeak = " "; string toSpeak = " ";
Dictionary<ClickableComponent, string> buttons = new();
#region Add buttons with their names IF they are available
#region Character related
if (__instance.nameBoxCC != null && __instance.nameBoxCC.visible)
buttons.Add(__instance.nameBoxCC, "Farmer's Name Text box");
if (__instance.farmnameBoxCC != null && __instance.farmnameBoxCC.visible)
buttons.Add(__instance.farmnameBoxCC, "Farm's Name Text box");
if (__instance.favThingBoxCC != null && __instance.favThingBoxCC.visible)
buttons.Add(__instance.favThingBoxCC, "Favourite Thing Text box");
if (__instance.petPortraitBox.HasValue) // Cannot get petButtons like with others
{
ClickableComponent petPrev = __instance.getComponentWithID(511);
buttons.Add(petPrev, "Previous pet button");
ClickableComponent petNext = __instance.getComponentWithID(510);
buttons.Add(petNext, "Next pet button");
}
if (__instance.randomButton != null && __instance.randomButton.visible)
buttons.Add(__instance.randomButton, "Random Skin Button");
if (__instance.genderButtons.Count > 0)
{
buttons.Add(__instance.genderButtons[0], ((Game1.player.IsMale) ? "Selected " : "") + "Gender: Male Button");
buttons.Add(__instance.genderButtons[1], ((!Game1.player.IsMale) ? "Selected " : "") + "Gender: Female Button");
}
#endregion
#region Farm layout related
if (__instance.farmTypeButtons.Count > 0)
{
for (int i = 0; i < __instance.farmTypeButtons.Count; i++)
{
buttons.Add(__instance.farmTypeButtons[i], ((i == Game1.whichFarm) ? "Selected " : "") + getFarmHoverText(__instance.farmTypeButtons[i]));
}
}
if (__instance.farmTypeNextPageButton != null && __instance.farmTypeNextPageButton.visible)
buttons.Add(__instance.farmTypeNextPageButton, "Next Farm Type Page Button");
if (__instance.farmTypePreviousPageButton != null && __instance.farmTypePreviousPageButton.visible)
buttons.Add(__instance.farmTypePreviousPageButton, "Previous Farm Type Page Button");
#endregion
#region Co-op related
if (__instance.source == Source.HostNewFarm)
{
ClickableComponent cabinLeft = __instance.getComponentWithID(621);
if (Game1.startingCabins > 0)
buttons.Add(cabinLeft, "Decrease starting cabins button");
buttons.Add(___startingCabinsLabel, $"Starting cabins: {Game1.startingCabins}");
ClickableComponent cabinRight = __instance.getComponentWithID(622);
if (Game1.startingCabins < 3)
buttons.Add(cabinRight, "Increase starting cabins button");
if (Game1.startingCabins > 0)
{
buttons.Add(__instance.cabinLayoutButtons[0], "Cabin layout to nearby Button");
buttons.Add(__instance.cabinLayoutButtons[1], "Cabin layout to separate Button");
}
ClickableComponent difficultyLeft = __instance.getComponentWithID(627);
buttons.Add(difficultyLeft, "Increase profit margin button");
buttons.Add(___difficultyModifierLabel, "Profit Margin: " + (((Game1.player.difficultyModifier * 100) == 100f) ? "normal" : Game1.player.difficultyModifier.ToString()));
ClickableComponent difficultyRight = __instance.getComponentWithID(628);
buttons.Add(difficultyRight, "Decrease profit margin button");
ClickableComponent walletLeft = __instance.getComponentWithID(631);
buttons.Add(walletLeft, "Money style to " + ((!Game1.player.team.useSeparateWallets.Value) ? "separate wallets" : "shared wallets") + " button");
}
#endregion
if (__instance.skipIntroButton != null && __instance.skipIntroButton.visible)
buttons.Add(__instance.skipIntroButton, (___skipIntro ? "Enabled" : "Disabled") + " Skip Intro Button");
if (__instance.advancedOptionsButton != null && __instance.advancedOptionsButton.visible)
buttons.Add(__instance.advancedOptionsButton, "Advanced Options Button");
if (__instance.okButton != null && __instance.okButton.visible)
buttons.Add(__instance.okButton, "OK Button");
if (__instance.backButton != null && __instance.backButton.visible)
buttons.Add(__instance.backButton, "Back Button");
#endregion
int size = buttons.Count - 1;
if (increase) if (increase)
{ {
saveGameIndex++; saveGameIndex++;
if (saveGameIndex > MAX_COMPONENTS) if (saveGameIndex > size)
saveGameIndex = 1; saveGameIndex = 0;
} }
else else
{ {
saveGameIndex--; saveGameIndex--;
if (saveGameIndex < 1) if (saveGameIndex < 0)
saveGameIndex = MAX_COMPONENTS; saveGameIndex = size;
} }
buttons.ElementAt(saveGameIndex).Key.snapMouseCursor();
switch (saveGameIndex) toSpeak = buttons.ElementAt(saveGameIndex).Value;
{
case 1:
{
#region Skip if button is not available
if (!__instance.nameBoxCC.visible)
{
if (increase)
{
++saveGameIndex;
goto case 2;
}
else
{
--saveGameIndex;
goto case MAX_COMPONENTS;
}
}
#endregion
__instance.nameBoxCC.snapMouseCursorToCenter();
toSpeak = "Enter Farmer's Name";
}
break;
case 2:
{
#region Skip if button is not available
if (!__instance.farmnameBoxCC.visible)
{
if (increase)
{
++saveGameIndex;
goto case 3;
}
else
{
--saveGameIndex;
goto case 1;
}
}
#endregion
__instance.farmnameBoxCC.snapMouseCursorToCenter();
toSpeak = "Enter Farm's Name";
}
break;
case 3:
{
#region Skip if button is not available
if (!__instance.favThingBoxCC.visible)
{
if (increase)
{
++saveGameIndex;
goto case 4;
}
else
{
--saveGameIndex;
goto case 2;
}
}
#endregion
__instance.favThingBoxCC.snapMouseCursorToCenter();
toSpeak = "Enter Favourite Thing";
}
break;
case 4:
{
#region Skip if button is not available
if (!__instance.skipIntroButton.visible)
{
if (increase)
{
++saveGameIndex;
goto case 5;
}
else
{
--saveGameIndex;
goto case 3;
}
}
#endregion
__instance.skipIntroButton.snapMouseCursor();
toSpeak = (___skipIntro ? "Enabled" : "Disabled") + " Skip Intro Button";
}
break;
case 5:
{
#region Skip if button is not available
if (!__instance.randomButton.visible)
{
if (increase)
{
++saveGameIndex;
goto case 6;
}
else
{
--saveGameIndex;
goto case 5;
}
}
#endregion
__instance.randomButton.snapMouseCursor();
toSpeak = "Random Skin Button";
break;
}
case 6:
{
#region Skip if button is not available
if (__instance.genderButtons.Count <= 0)
{
if (increase)
{
++saveGameIndex;
goto case 8;
}
else
{
--saveGameIndex;
goto case 6;
}
}
#endregion
__instance.genderButtons[0].snapMouseCursor();
toSpeak = "Gender Male Button";
break;
}
case 7:
{
#region Skip if button is not available
if (__instance.genderButtons.Count <= 0)
{
if (increase)
{
++saveGameIndex;
goto case 8;
}
else
{
--saveGameIndex;
goto case 6;
}
}
#endregion
__instance.genderButtons[1].snapMouseCursor();
toSpeak = "Gender Female Button";
break;
}
case 8:
{
#region Skip if button is not available
if (__instance.farmTypeButtons.Count <= 0)
{
if (increase)
{
++saveGameIndex;
goto case 9;
}
else
{
--saveGameIndex;
goto case 7;
}
}
#endregion
__instance.farmTypeButtons[0].snapMouseCursor();
toSpeak = getFarmHoverText(__instance.farmTypeButtons[0]);
break;
}
case 9:
{
#region Skip if button is not available
if (__instance.farmTypeButtons.Count <= 0)
{
if (increase)
{
++saveGameIndex;
goto case 10;
}
else
{
--saveGameIndex;
goto case 8;
}
}
#endregion
__instance.farmTypeButtons[1].snapMouseCursor();
toSpeak = getFarmHoverText(__instance.farmTypeButtons[1]);
break;
}
case 10:
{
#region Skip if button is not available
if (__instance.farmTypeButtons.Count <= 0)
{
if (increase)
{
++saveGameIndex;
goto case 11;
}
else
{
--saveGameIndex;
goto case 9;
}
}
#endregion
__instance.farmTypeButtons[2].snapMouseCursor();
toSpeak = getFarmHoverText(__instance.farmTypeButtons[2]);
break;
}
case 11:
{
#region Skip if button is not available
if (__instance.farmTypeButtons.Count <= 0)
{
if (increase)
{
++saveGameIndex;
goto case 12;
}
else
{
--saveGameIndex;
goto case 10;
}
}
#endregion
__instance.farmTypeButtons[3].snapMouseCursor();
toSpeak = getFarmHoverText(__instance.farmTypeButtons[3]);
break;
}
case 12:
{
#region Skip if button is not available
if (__instance.farmTypeButtons.Count <= 0)
{
if (increase)
{
++saveGameIndex;
goto case 13;
}
else
{
--saveGameIndex;
goto case 11;
}
}
#endregion
__instance.farmTypeButtons[4].snapMouseCursor();
toSpeak = getFarmHoverText(__instance.farmTypeButtons[4]);
break;
}
case 13:
{
#region Skip if button is not available
if (__instance.farmTypeButtons.Count <= 0)
{
if (increase)
{
++saveGameIndex;
goto case 14;
}
else
{
--saveGameIndex;
goto case 12;
}
}
#endregion
__instance.farmTypeButtons[5].snapMouseCursor();
toSpeak = getFarmHoverText(__instance.farmTypeButtons[5]);
break;
}
case 14:
{
#region Skip if button is not available
if (__instance.farmTypeButtons.Count <= 0)
{
if (increase)
{
++saveGameIndex;
goto case 15;
}
else
{
--saveGameIndex;
goto case 13;
}
}
#endregion
__instance.farmTypeButtons[6].snapMouseCursor();
toSpeak = getFarmHoverText(__instance.farmTypeButtons[6]);
break;
}
case 15:
{
#region Skip if button is not available
if (__instance.farmTypeNextPageButton == null)
{
if (increase)
{
++saveGameIndex;
goto case 16;
}
else
{
--saveGameIndex;
goto case 14;
}
}
#endregion
__instance.farmTypeNextPageButton.snapMouseCursor();
toSpeak = "Next Farm Type Page Button";
break;
}
case 16:
{
#region Skip if button is not available
if (__instance.farmTypePreviousPageButton == null)
{
if (increase)
{
++saveGameIndex;
goto case 17;
}
else
{
--saveGameIndex;
goto case 15;
}
}
#endregion
__instance.farmTypePreviousPageButton.snapMouseCursor();
toSpeak = "Previous Farm Type Page Button";
break;
}
case 17:
{
#region Skip if button is not available
if (__instance.cabinLayoutButtons.Count <= 0)
{
if (increase)
{
++saveGameIndex;
goto case 18;
}
else
{
--saveGameIndex;
goto case 16;
}
}
#endregion
__instance.cabinLayoutButtons[0].snapMouseCursor();
toSpeak = "Cabin layout nearby";
break;
}
case 18:
{
#region Skip if button is not available
if (__instance.cabinLayoutButtons.Count <= 0)
{
if (increase)
{
++saveGameIndex;
goto case 19;
}
else
{
--saveGameIndex;
goto case 17;
}
}
#endregion
__instance.cabinLayoutButtons[1].snapMouseCursor();
toSpeak = "Cabin layout separate";
break;
}
case 19:
{
#region Skip if button is not available
if (!__instance.okButton.visible)
{
if (increase)
{
++saveGameIndex;
goto case 18;
}
else
{
--saveGameIndex;
goto case 20;
}
}
#endregion
__instance.okButton.snapMouseCursor();
toSpeak = "Ok Button";
}
break;
case 20:
{
#region Exit if button is not available
if (!__instance.backButton.visible)
{
break;
}
#endregion
__instance.backButton.snapMouseCursor();
toSpeak = "Back Button";
}
break;
}
if (toSpeak != " ") if (toSpeak != " ")
{ {
MainClass.GetScreenReader().Say(toSpeak, true); MainClass.ScreenReader.Say(toSpeak, true);
} }
}
await Task.Delay(200); private static string getCurrentPetName()
isRunning = false; {
return ((Game1.player.catPerson) ? "Cat" : "Dog") + " Breed: " + Game1.player.whichPetBreed;
} }
private static string getFarmHoverText(ClickableTextureComponent farm) private static string getFarmHoverText(ClickableTextureComponent farm)

View File

@ -1,3 +1,8 @@
/*
Linux speech dispatcher library used:
https://github.com/shoaib11120/libspeechdwrapper
*/
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace stardew_access.ScreenReader namespace stardew_access.ScreenReader
@ -36,10 +41,16 @@ namespace stardew_access.ScreenReader
public void InitializeScreenReader() public void InitializeScreenReader()
{ {
MainClass.InfoLog("Initializing speech dispatcher...");
int res = Initialize(); int res = Initialize();
if (res == 1) if (res == 1)
{ {
initialized = true; initialized = true;
MainClass.InfoLog("Successfully initialized.");
}
else
{
MainClass.ErrorLog("Unable to initialize.");
} }
} }
@ -57,10 +68,24 @@ namespace stardew_access.ScreenReader
if (text == null) if (text == null)
return; return;
if (initialized) if (!initialized)
return;
if (!MainClass.Config.TTS)
return;
if (text.Contains('^')) text = text.Replace('^', '\n');
GoString str = new GoString(text, text.Length);
int re = Speak(str, interrupt);
if (re == 1)
{ {
GoString str = new GoString(text, text.Length); MainClass.DebugLog($"Speaking(interrupt: {interrupt}) = {text}");
Speak(str, interrupt); }
else
{
MainClass.ErrorLog($"Failed to output text: {text}");
} }
} }

View File

@ -1,10 +1,10 @@
using AccessibleOutput; using DavyKager;
namespace stardew_access.ScreenReader namespace stardew_access.ScreenReader
{ {
public class ScreenReaderWindows : IScreenReader public class ScreenReaderWindows : IScreenReader
{ {
public IAccessibleOutput? screenReader = null; private bool isLoaded = false;
public string prevText = "", prevTextTile = " ", prevChatText = "", prevMenuText = ""; public string prevText = "", prevTextTile = " ", prevChatText = "", prevMenuText = "";
public string PrevTextTile public string PrevTextTile
@ -15,43 +15,31 @@ namespace stardew_access.ScreenReader
public void InitializeScreenReader() public void InitializeScreenReader()
{ {
MainClass.InfoLog("Initializing Tolk...");
Tolk.TrySAPI(true);
Tolk.Load();
NvdaOutput? nvdaOutput = null; MainClass.InfoLog("Querying for the active screen reader driver...");
JawsOutput? jawsOutput = null; string name = Tolk.DetectScreenReader();
SapiOutput? sapiOutput = null; if (name != null)
// Initialize NVDA
try
{ {
nvdaOutput = new NvdaOutput(); MainClass.InfoLog($"The active screen reader driver is: {name}");
isLoaded = true;
} }
catch (Exception) { } else
// Initialize JAWS
try
{ {
jawsOutput = new JawsOutput(); MainClass.ErrorLog("None of the supported screen readers is running");
isLoaded = false;
} }
catch (Exception) { }
// Initialize SAPI
try
{
sapiOutput = new SapiOutput();
}
catch (Exception) { }
if (nvdaOutput != null && nvdaOutput.IsAvailable())
screenReader = nvdaOutput;
else if (jawsOutput != null && jawsOutput.IsAvailable())
screenReader = jawsOutput;
else if (sapiOutput != null && sapiOutput.IsAvailable())
screenReader = sapiOutput;
} }
public void CloseScreenReader() public void CloseScreenReader()
{ {
if (isLoaded)
{
Tolk.Unload();
isLoaded = false;
}
} }
public void Say(string text, bool interrupt) public void Say(string text, bool interrupt)
@ -59,10 +47,22 @@ namespace stardew_access.ScreenReader
if (text == null) if (text == null)
return; return;
if (screenReader == null) if (!isLoaded)
return; return;
screenReader.Speak(text, interrupt); if (!MainClass.Config.TTS)
return;
if (text.Contains('^')) text = text.Replace('^', '\n');
if (Tolk.Output(text, interrupt))
{
MainClass.DebugLog($"Speaking(interrupt: {interrupt}) = {text}");
}
else
{
MainClass.ErrorLog($"Failed to output text: {text}");
}
} }
public void SayWithChecker(string text, bool interrupt) public void SayWithChecker(string text, bool interrupt)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
{
"warnings.health": "Warnung! Die Gesundheit liegt bei {{value}} Prozent!",
"warnings.stamina": "Warnung! Ausdauer beträgt ar {{value}} Prozent!",
"warnings.time": "Warnung! Zeit ist {{value}}",
"grandpastory.scene0":"Opa, auf seinem Sterbebett.",
"grandpastory.scene4":"Mitarbeiter der JoJa corp.",
"grandpastory.scene5":"Mitarbeiter in ihren Kabinen, einige von ihnen sehen erschöpft aus, Sie selbst eingeschlossen.",
"grandpastory.scene6":"Du erreichst deinen Schreibtisch und findest Opas Brief.",
"grandpastory.letteropen":"Linksklick, um Opas Brief zu öffnen",
"intro.scene3":"Fahrt zur Bushaltestelle Stardew Valley",
"intro.scene4":"Stardew Valley 0.5 Meilen entfernt",
"manuallytriggered.healthnstamina.percent":"Gesundheit ist {{health}} % und Ausdauer ist {{stamina}} %",
"manuallytriggered.healthnstamina.normal":"Gesundheit ist {{health}} und Ausdauer ist {{stamina}}",
"readtile.sprinkler.pressurenozzle":"Druck-{{value}}",
"readtile.sprinkler.enricher":"Bereichernd {{value}}"
}

View File

@ -0,0 +1,16 @@
{
"warnings.health": "Warning! Health is at {{value}} percent!",
"warnings.stamina": "Warning! Stamina is at {{value}} percent!",
"warnings.time": "Warning! Time is {{value}}",
"grandpastory.scene0":"Grandpa, on his deathbed.",
"grandpastory.scene4":"Employees working in JoJa corp.",
"grandpastory.scene5":"Employees in their cubicles, some of them look exhausted including yourself.",
"grandpastory.scene6":"You reach your desk finding grandpa's letter.",
"grandpastory.letteropen":"Left click to open grandpa's letter",
"intro.scene3":"Travelling to Stardew Valley bus stop",
"intro.scene4":"Stardew valley 0.5 miles away",
"manuallytriggered.healthnstamina.percent":"Health is {{health}} % and Stamina is {{stamina}} %",
"manuallytriggered.healthnstamina.normal":"Health is {{health}} and Stamina is {{stamina}}",
"readtile.sprinkler.pressurenozzle":"Pressurized {{value}}",
"readtile.sprinkler.enricher":"Enriching {{value}}"
}

View File

@ -0,0 +1,16 @@
{
"warnings.health": "¡Advertencia! ¡La salud está al {{value}} por ciento!",
"warnings.stamina": "¡Advertencia! ¡La resistencia es un {{value}} por ciento!",
"warnings.time": "¡Advertencia! El tiempo es {{valor}}",
"grandpastory.scene0":"Abuelo, en su lecho de muerte.",
"grandpastory.scene4":"Empleados que trabajan en JoJa corp.",
"grandpastory.scene5":"Empleados en sus cubículos, algunos de ellos parecen exhaustos, incluido usted.",
"grandpastory.scene6":"Llegas a tu escritorio y encuentras la carta del abuelo.",
"grandpastory.letteropen":"Haz clic izquierdo para abrir la carta del abuelo.",
"intro.scene3":"Viajando a la parada de autobús de Stardew Valley",
"intro.scene4":"Valle de Stardew a 0.5 millas de distancia",
"manuallytriggered.healthnstamina.percent":"La salud es {{health}} % y la resistencia es {{stamina}} %",
"manuallytriggered.healthnstamina.normal":"La salud es {{health}} y la resistencia es {{stamina}}",
"readtile.sprinkler.pressurenozzle":"presurizada {{value}}",
"readtile.sprinkler.enricher":"Enriquecedora {{value}}"
}

View File

@ -0,0 +1,16 @@
{
"warnings.health": "Avertissement! La santé est à {{value}} pour cent!",
"warnings.stamina": "Avertissement! L'endurance est à {{value}} pour cent!",
"warnings.time": "Avertissement! Le temps est de {{value}}",
"grandpastory.scene0":"Grand-père, sur son lit de mort.",
"grandpastory.scene4":"Les employés travaillant chez JoJa corp.",
"grandpastory.scene5":"Employés dans leurs cabines, certains ont l'air épuisés, y compris vous-même.",
"grandpastory.scene6":"Vous atteignez votre bureau en trouvant la lettre de grand-père.",
"grandpastory.letteropen":"Clic gauche pour ouvrir la lettre de grand-père",
"intro.scene3":"Se rendre à l'arrêt de bus Stardew Valley",
"intro.scene4":"Vallée de Stardew à 0.5 miles",
"manuallytriggered.healthnstamina.percent":"La santé est de {{health}} % et l'endurance est de {{stamina}} %",
"manuallytriggered.healthnstamina.normal":"La santé est {{health}} et l'endurance est {{stamina}}",
"readtile.sprinkler.pressurenozzle":"Sous pression {{value}}",
"readtile.sprinkler.enricher":"Enrichissant {{value}}"
}

View File

@ -0,0 +1,16 @@
{
"warnings.health": "Figyelem! Az egészségi állapot {{érték}} százalék!",
"warnings.stamina": "Figyelem! Az állóképesség {{value}} százalék!",
"warnings.time": "Figyelem! Az idő {{érték}}",
"grandpastory.scene0":"Nagypapa, a halálos ágyán.",
"grandpastory.scene4":"A JoJa corp.-nál dolgozó alkalmazottak",
"grandpastory.scene5":"Alkalmazottak a fülkéiben, néhányuk kimerültnek tűnik, beleértve Önt is.",
"grandpastory.scene6":"Az asztalodhoz érve megtalálod a nagypapa levelét.",
"grandpastory.letteropen":"Kattintson a bal gombbal a nagypapa levelének megnyitásához",
"intro.scene3":"Utazás a Stardew Valley buszmegállóhoz",
"intro.scene4":"Stardew-völgy 0.5 mérföldre van",
"manuallytriggered.healthnstamina.percent":"Az egészségi állapot {{health}} %, az állóképesség pedig {{stamina}} %",
"manuallytriggered.healthnstamina.normal":"Az egészség {{health}}, az állóképesség pedig {{stamina}}",
"readtile.sprinkler.pressurenozzle":"Nyomás alatt {{value}}",
"readtile.sprinkler.enricher":"Gazdagítás {{value}}"
}

View File

@ -0,0 +1,16 @@
{
"warnings.health": "Avvertimento! La salute è al {{value}} percento!",
"warnings.stamina": "Avvertimento! La resistenza è al {{value}} percento!",
"warnings.time": "Avvertimento! L'ora è {{value}}",
"grandpastory.scene0":"Il nonno, sul letto di morte.",
"grandpastory.scene4":"Dipendenti che lavorano in JoJa corp.",
"grandpastory.scene5":"Impiegati nei loro cubicoli, alcuni di loro sembrano esausti, compreso te.",
"grandpastory.scene6":"Raggiungi la tua scrivania e trovi la lettera del nonno.",
"grandpastory.letteropen":"Fare clic con il tasto sinistro per aprire la lettera del nonno",
"intro.scene3":"In viaggio verso la fermata dell'autobus di Stardew Valley",
"intro.scene4":"Stardew Valley 0.5 miglia di distanza",
"manuallytriggered.healthnstamina.percent":"La salute è {{health}} % e la resistenza è {{stamina}} %",
"manuallytriggered.healthnstamina.normal":"La salute è {{health}} e la resistenza è {{stamina}}",
"readtile.sprinkler.pressurenozzle":"Pressurizzato {{value}}",
"readtile.sprinkler.enricher":"Arricchimento {{value}}"
}

View File

@ -0,0 +1,16 @@
{
"warnings.health": "警告!健康状態は{{value}}パーセントです!",
"warnings.stamina": "警告!スタミナは{{value}}パーセントです!",
"warnings.time": "警告!時間は{{value}}です",
"grandpastory.scene0":"おじいちゃん、彼の死の床に。",
"grandpastory.scene4":"JoJacorpで働く従業員。",
"grandpastory.scene5":"彼らのキュービクルの従業員、彼らの何人かはあなた自身を含めて疲れ果てているように見えます。",
"grandpastory.scene6":"おじいちゃんの手紙を見つけて机に着きます。",
"grandpastory.letteropen":"左クリックしておじいちゃんの手紙を開く",
"intro.scene3":"スターデューバレーバス停への移動",
"intro.scene4":"0.5マイル離れたスターデューバレー",
"manuallytriggered.healthnstamina.percent":"体力は {{health}} %、スタミナは {{stamina}} %",
"manuallytriggered.healthnstamina.normal":"体力は{{health}}、スタミナは{{stamina}}です",
"readtile.sprinkler.pressurenozzle":"加圧 {{value}}",
"readtile.sprinkler.enricher":"豊かにする {{value}}"
}

View File

@ -0,0 +1,16 @@
{
"warnings.health": "경고! 건강은 {{value}}퍼센트입니다!",
"warnings.stamina": "경고! 체력은 {{value}}퍼센트입니다!",
"warnings.time": "경고! 시간은 {{value}}입니다",
"grandpastory.scene0":"임종을 앞둔 할아버지.",
"grandpastory.scene4":"(주)조자에서 근무하는 직원들",
"grandpastory.scene5":"칸막이에 있는 직원들, 당신을 포함하여 몇몇은 지쳐 보입니다.",
"grandpastory.scene6":"책상에 다가가 할아버지의 편지를 찾습니다.",
"grandpastory.letteropen":"할아버지의 편지를 열려면 왼쪽 클릭",
"intro.scene3":"스타듀밸리 버스정류장으로 이동",
"intro.scene4":"스타듀 밸리에서 0.8km 떨어짐",
"manuallytriggered.healthnstamina.percent":"체력은 {{health}} %이고 체력은 {{stamina}} %입니다.",
"manuallytriggered.healthnstamina.normal":"체력은 {{health}}이고 체력은 {{stamina}}입니다.",
"readtile.sprinkler.pressurenozzle":"가압 {{value}}",
"readtile.sprinkler.enricher":"풍부하게 하기 {{value}}"
}

View File

@ -0,0 +1,16 @@
{
"warnings.health": "Aviso! A saúde está em {{value}} por cento!",
"warnings.stamina": "Aviso! A resistência está em {{value}} por cento!",
"warnings.time": "Aviso! O tempo é {{value}}",
"grandpastory.scene0":"Vovô, em seu leito de morte.",
"grandpastory.scene4":"Funcionários que trabalham na JoJa corp.",
"grandpastory.scene5":"Funcionários em seus cubículos, alguns deles parecem exaustos, incluindo você.",
"grandpastory.scene6":"Você chega à sua mesa encontrando a carta do vovô.",
"grandpastory.letteropen":"Clique com o botão esquerdo para abrir a carta do vovô",
"intro.scene3":"Viajar para o ponto de ônibus Stardew Valley",
"intro.scene4":"Vale Stardew a 0.5 km de distância",
"manuallytriggered.healthnstamina.percent":"Saúde é {{health}} % e Stamina é {{stamina}} %",
"manuallytriggered.healthnstamina.normal":"Saúde é {{health}} e Stamina é {{stamina}}",
"readtile.sprinkler.pressurenozzle":"Pressurizada {{value}}",
"readtile.sprinkler.enricher":"Enriquecimento {{value}}"
}

View File

@ -0,0 +1,16 @@
{
"warnings.health": "Предупреждение! Здоровье составляет {{value}} процентов!",
"warnings.stamina": "Предупреждение! Выносливость составляет {{value}} процентов!",
"warnings.time": "Предупреждение! Время {{value}}",
"grandpastory.scene0":"Дедушка на смертном одре.",
"grandpastory.scene4":"Сотрудники, работающие в JoJa corp.",
"grandpastory.scene5":"Сотрудники в своих кабинетах, некоторые из них выглядят измученными, в том числе и вы.",
"grandpastory.scene6":"Вы подходите к своему столу и находите дедушкино письмо.",
"grandpastory.letteropen":"Щелкните левой кнопкой мыши, чтобы открыть письмо дедушки",
"intro.scene3":"Поездка на автобусную остановку Stardew Valley",
"intro.scene4":"Долина Стардью: 0.8 км",
"manuallytriggered.healthnstamina.percent":"Здоровье составляет {{health}}%, а выносливость - {{stamina}}%",
"manuallytriggered.healthnstamina.normal":"Здоровье – {{health}}, а выносливость – {{stamina}}.",
"readtile.sprinkler.pressurenozzle":"под давлением {{value}}",
"readtile.sprinkler.enricher":"Обогащение {{value}}"
}

View File

@ -0,0 +1,16 @@
{
"warnings.health": "Uyarı! Sağlık yüzde {{değer}}!",
"warnings.stamina": "uyarı! Dayanıklılık yüzde {{değer}}!",
"warnings.time": "Uyarı! Zaman {{değer}}",
"grandpastory.scene0":"Büyükbaba, ölüm döşeğinde.",
"grandpastory.scene4":"JoJa şirketinde çalışan çalışanlar",
"grandpastory.scene5":"Kabinlerinde çalışanlar, siz de dahil olmak üzere bazıları bitkin görünüyor.",
"grandpastory.scene6":"Dedenizin mektubunu bulmak için masanıza ulaşıyorsunuz.",
"grandpastory.letteropen":"Büyükbabanın mektubunu açmak için sol tıklayın",
"intro.scene3":"Stardew Valley otobüs durağına seyahat",
"intro.scene4":"Stardew vadisi 0.5 mil uzakta",
"manuallytriggered.healthnstamina.percent":"Sağlık %{{health}} ve Dayanıklılık %{{stamina}}",
"manuallytriggered.healthnstamina.normal":"Sağlık {{health}} ve Dayanıklılık {{stamina}}",
"readtile.sprinkler.pressurenozzle":"basınçlı {{value}}",
"readtile.sprinkler.enricher":"zenginleştirici {{value}}"
}

View File

@ -0,0 +1,16 @@
{
"warnings.health": "警告!健康状况为 {{value}} 百分!",
"warnings.stamina": "警告!耐力为 {{value}} 百分!",
"warnings.time": "警告!时间是 {{value}}",
"grandpastory.scene0":"爷爷,去世前。",
"grandpastory.scene4":"在 JoJa corp. 工作的员工",
"grandpastory.scene5":"员工在他们的办公室里,他们看起来很累,这其中也包括你自己。",
"grandpastory.scene6":"你走到办公桌前,找到了爷爷的信。",
"grandpastory.letteropen":"左方括号打开爷爷的信",
"intro.scene3":"前往星露谷物语巴士站",
"intro.scene4":"星露谷物语 0.5 英里外",
"manuallytriggered.healthnstamina.percent":"健康为 {{health}} %,耐力为 {{stamina}} %",
"manuallytriggered.healthnstamina.normal":"健康为 {{health}},耐力为 {{stamina}}",
"readtile.sprinkler.pressurenozzle":"加压 {{value}}",
"readtile.sprinkler.enricher":"丰富 {{value}}"
}

View File

@ -1,7 +1,7 @@
{ {
"Name": "Stardew Access", "Name": "Stardew Access",
"Author": "Mohammad Shoaib", "Author": "Mohammad Shoaib",
"Version": "1.1.0", "Version": "1.3.4",
"Description": "An accessibility mod with screen reader support!", "Description": "An accessibility mod with screen reader support!",
"UniqueID": "shoaib.stardewaccess", "UniqueID": "shoaib.stardewaccess",
"EntryDll": "stardew-access.dll", "EntryDll": "stardew-access.dll",

View File

@ -7,13 +7,14 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<LangVersion>preview</LangVersion> <LangVersion>preview</LangVersion>
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <!-- To generate dlls of nugget packages -->
<!-- <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> -->
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AccessibleOutput" Version="1.0.0" />
<PackageReference Include="Lib.Harmony" Version="2.2.0" /> <PackageReference Include="Lib.Harmony" Version="2.2.0" />
<PackageReference Include="Pathoschild.Stardew.ModBuildConfig" Version="4.0.0" /> <PackageReference Include="Pathoschild.Stardew.ModBuildConfig" Version="4.0.0" />
<Reference Include="./TolkDotNet.dll" />
</ItemGroup> </ItemGroup>
</Project> </Project>