From a513a14ca0888084973c31c8dae174acaf50817a Mon Sep 17 00:00:00 2001 From: shoaib11120 Date: Fri, 10 Dec 2021 21:17:50 +0530 Subject: [PATCH] Made Load Game Menu accessible, organised code --- stardew-access/Game/CurrentPlayer.cs | 20 +- stardew-access/ModEntry.cs | 242 +++--------------------- stardew-access/Patches/DialoguePatch.cs | 115 +++++++++++ stardew-access/Patches/MenuPatch.cs | 91 +++++++++ stardew-access/ScreenReader.cs | 77 ++++++++ 5 files changed, 326 insertions(+), 219 deletions(-) create mode 100644 stardew-access/Patches/DialoguePatch.cs create mode 100644 stardew-access/Patches/MenuPatch.cs create mode 100644 stardew-access/ScreenReader.cs diff --git a/stardew-access/Game/CurrentPlayer.cs b/stardew-access/Game/CurrentPlayer.cs index c5d563f..27b5848 100644 --- a/stardew-access/Game/CurrentPlayer.cs +++ b/stardew-access/Game/CurrentPlayer.cs @@ -5,7 +5,7 @@ namespace stardew_access.Game { internal class CurrentPlayer { - private static Farmer player = null; + private static Farmer? player = null; CurrentPlayer() { @@ -40,5 +40,23 @@ namespace stardew_access.Game return staminaPercentage; } + + public static int getPositionX() + { + if (player == null) + initPlayer(); + + int x = (int)player.getTileLocation().X; + return x; + } + + public static int getPositionY() + { + if (player == null) + initPlayer(); + + int y = (int)player.getTileLocation().Y; + return y; + } } } diff --git a/stardew-access/ModEntry.cs b/stardew-access/ModEntry.cs index e204d76..1e348fe 100644 --- a/stardew-access/ModEntry.cs +++ b/stardew-access/ModEntry.cs @@ -1,26 +1,20 @@ -using AccessibleOutput; -using stardew_access.Game; +using stardew_access.Game; using StardewModdingAPI; using StardewModdingAPI.Events; using StardewValley; using HarmonyLib; using StardewValley.Menus; using Microsoft.Xna.Framework.Graphics; -using System.Text; -using StardewValley.BellsAndWhistles; -using Microsoft.Xna.Framework; +using stardew_access.Patches; namespace stardew_access { - /// The mod entry point. + public class MainClass : Mod { - public static IAccessibleOutput screenReader; - Harmony harmony; - public static IMonitor monitor; - private static string prevText = ""; - private static DialogueBox? dialogueBox = null; - private int index = 0; + private Harmony? harmony; + public static IMonitor? monitor; + /********* ** Public methods *********/ @@ -32,7 +26,7 @@ namespace stardew_access monitor = Monitor; // Initialize the screen reader - initializeScreenReader(); + ScreenReader.initializeScreenReader(); // Init harmony harmony = new Harmony(ModManifest.UniqueID); @@ -40,225 +34,29 @@ namespace stardew_access // Add patches harmony.Patch( original: AccessTools.Method(typeof(DialogueBox), nameof(DialogueBox.draw), new Type[] {typeof(SpriteBatch)}), - postfix: new HarmonyMethod(typeof(MainClass), nameof(MainClass.DialoguePatch)) + postfix: new HarmonyMethod(typeof(DialoguePatch), nameof(DialoguePatch.CharachterDialoguePatch)) ); harmony.Patch( 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(MainClass), nameof(MainClass.HoverTextPatch)) + postfix: new HarmonyMethod(typeof(DialoguePatch), nameof(DialoguePatch.HoverTextPatch)) ); harmony.Patch( original: AccessTools.Method(typeof(TitleMenu), nameof(TitleMenu.draw) , new Type[] {typeof(SpriteBatch)}), - postfix: new HarmonyMethod(typeof(MainClass), nameof(TitleMenuPatch)) + postfix: new HarmonyMethod(typeof(MenuPatch), nameof(MenuPatch.TitleMenuPatch)) + ); + + harmony.Patch( + original: AccessTools.Method(typeof(LoadGameMenu.SaveFileSlot), nameof(LoadGameMenu.SaveFileSlot.Draw), new Type[] { typeof(SpriteBatch), typeof(int) }), + postfix: new HarmonyMethod(typeof(MenuPatch), nameof(MenuPatch.LoadGameMenuPatch)) ); helper.Events.Input.ButtonPressed += this.OnButtonPressed; - //helper.Events.GameLoop.OneSecondUpdateTicked += this.OnOneSecondUpdateTicked; - } - - private static void TitleMenuPatch(TitleMenu __instance, SpriteBatch b) - { - try - { - __instance.allClickableComponents.ForEach(component => - { - if(component.containsPoint(Game1.getMousePosition(true).X, Game1.getMousePosition(true).Y)) - { - string name = component.name; - string label = component.label; - string toSpeak = $"{name} {label}"; - - if(prevText != toSpeak) - { - prevText = toSpeak; - screenReader.Speak(toSpeak, true); - } - } - }); - } - catch (Exception) - { - } - } - - private static void DialoguePatch(DialogueBox __instance, SpriteBatch b) - { - try - { - Dialogue dialogue = __instance.characterDialogue; - string speakerName = dialogue.speaker.Name; - List dialogues = dialogue.dialogues; - int dialogueIndex = dialogue.currentDialogueIndex; - monitor.Log("" + dialogue.isCurrentStringContinuedOnNextScreen, LogLevel.Debug); - - if (prevText != $"{speakerName} said, {dialogues[dialogueIndex]}") - { - prevText = $"{speakerName} said, {dialogues[dialogueIndex]}"; - screenReader.Speak($"{speakerName} said, {dialogues[dialogueIndex]}", false); - } - } - catch (Exception e) - { - monitor.Log($"Unable to narrate dialog:\n{e.StackTrace}", LogLevel.Error); - } - - } - - private void OnOneSecondUpdateTicked(object sender, OneSecondUpdateTickedEventArgs e) - { - if (Game1.activeClickableMenu != null) - { - string name = Game1.activeClickableMenu.getCurrentlySnappedComponent().name; - string label = Game1.activeClickableMenu.getCurrentlySnappedComponent().label; - string toSpeeak = $"{name} {label}"; - - if (prevText != toSpeeak) - { - prevText = toSpeeak; - screenReader.Speak(toSpeeak, true); - } - } - } - - private static void HoverTextPatch(SpriteBatch b, string text, SpriteFont font, int xOffset = 0, int yOffset = 0, int moneyAmountToDisplayAtBottom = -1, string boldTitleText = null, int healAmountToDisplay = -1, string[] buffIconsToDisplay = null, Item hoveredItem = null, int currencySymbol = 0, int extraItemToShowIndex = -1, int extraItemToShowAmount = -1, int overrideX = -1, int overrideY = -1, float alpha = 1f, CraftingRecipe craftingIngredients = null, IList additional_craft_materials = null) - { - try - { - StringBuilder toSpeak = new StringBuilder(); - - #region Add title if any - if (boldTitleText != null) - toSpeak.Append($"{boldTitleText}.\n"); - #endregion - - #region Add the base text - toSpeak.Append(text); - #endregion - - #region Add crafting ingredients - if (craftingIngredients != null) - { - - toSpeak.Append($"\n{craftingIngredients.description}"); - toSpeak.Append("\nIngredients\n"); - - craftingIngredients.recipeList.ToList().ForEach(recipe => - { - int count = recipe.Value; - int item = recipe.Key; - string name = craftingIngredients.getNameFromIndex(item); - - toSpeak.Append($" ,{count} {name}"); - }); - } - #endregion - - #region Add health & stamina - if (hoveredItem is StardewValley.Object && (hoveredItem as StardewValley.Object).Edibility != -300) - { - int stamina_recovery = (hoveredItem as StardewValley.Object).staminaRecoveredOnConsumption(); - toSpeak.Append($"{stamina_recovery} Energy\n"); - if (stamina_recovery >= 0) - { - int health_recovery = (hoveredItem as StardewValley.Object).healthRecoveredOnConsumption(); - toSpeak.Append($"{health_recovery} Health"); - } - } - #endregion - - #region Add buff items (effects like +1 walking speed) - if (buffIconsToDisplay != null) - { - for (int i = 0; i < buffIconsToDisplay.Length; i++) - { - string buffName = ((Convert.ToInt32(buffIconsToDisplay[i]) > 0) ? "+" : "") + buffIconsToDisplay[i] + " "; - if (i <= 11) - { - buffName = Game1.content.LoadString("Strings\\UI:ItemHover_Buff" + i, buffName); - } - try - { - int count = int.Parse(buffName.Substring(0, buffName.IndexOf(' '))); - monitor.Log("" + count); - if (count != 0) - toSpeak.Append($"{buffName}\n"); - } - catch (Exception) { } - } - } - #endregion - - #region Add money - if (moneyAmountToDisplayAtBottom != -1) - toSpeak.Append($"\nValue: {moneyAmountToDisplayAtBottom} coins\n"); - #endregion - - #region Narrate toSpeak - if (prevText != toSpeak.ToString()) - { - prevText = toSpeak.ToString(); - screenReader.Speak(toSpeak.ToString(), true); - } - #endregion - } - catch (Exception e) - { - monitor.Log($"Unable to narrate dialog:\n{e.StackTrace}", LogLevel.Error); - } - } - - private void initializeScreenReader() - { - NvdaOutput nvdaOutput = null; - JawsOutput jawsOutput = null; - SapiOutput sapiOutput = null; - - // Initialize NVDA - try{ - nvdaOutput = new NvdaOutput(); - }catch(Exception ex){ - Monitor.Log($"Error initializing NVDA:\n{ex.StackTrace}", LogLevel.Error); - } - - // Initialize JAWS - try - { - jawsOutput = new JawsOutput(); - }catch (Exception ex){ - Monitor.Log($"Error initializing JAWS:\n{ex.StackTrace}", LogLevel.Error); - } - - // Initialize SAPI - try - { - sapiOutput = new SapiOutput(); - }catch (Exception ex){ - Monitor.Log($"Error initializing SAPI:\n{ex.StackTrace}", LogLevel.Error); - } - - if (nvdaOutput != null && nvdaOutput.IsAvailable()) - screenReader = nvdaOutput; - - if(jawsOutput != null && jawsOutput.IsAvailable()) - screenReader = jawsOutput; - - if (sapiOutput != null && sapiOutput.IsAvailable()) - screenReader = sapiOutput; } private void OnButtonPressed(object sender, ButtonPressedEventArgs e) { - if (Equals(e.Button, SButton.K)) - { - if (Game1.activeClickableMenu != null) - { - string name = Game1.activeClickableMenu.getCurrentlySnappedComponent().label; - screenReader.Speak(name, true); - } - } - - // ignore if player hasn't loaded a save yet if (!Context.IsWorldReady) return; @@ -266,7 +64,15 @@ namespace stardew_access // Narrate Health And Energy if (Equals(e.Button, SButton.I)) { - screenReader.Speak($"Health is {CurrentPlayer.getHealth()} and Stamina is {CurrentPlayer.getStamina()}"); + string toSpeak = $"Health is {CurrentPlayer.getHealth()} and Stamina is {CurrentPlayer.getStamina()}"; + ScreenReader.say(toSpeak, true); + } + + // Narrate Position + if (Equals(e.Button, SButton.K)) + { + string toSpeak = $"X: {CurrentPlayer.getPositionX()} , Y: {CurrentPlayer.getPositionX()}"; + ScreenReader.say(toSpeak, true); } } diff --git a/stardew-access/Patches/DialoguePatch.cs b/stardew-access/Patches/DialoguePatch.cs new file mode 100644 index 0000000..3ebe4aa --- /dev/null +++ b/stardew-access/Patches/DialoguePatch.cs @@ -0,0 +1,115 @@ +using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI; +using StardewValley; +using StardewValley.Menus; +using System.Text; + +namespace stardew_access.Patches +{ + internal class DialoguePatch + { + public static void CharachterDialoguePatch(DialogueBox __instance, SpriteBatch b) + { + try + { + Dialogue dialogue = __instance.characterDialogue; + string speakerName = dialogue.speaker.Name; + List dialogues = dialogue.dialogues; + int dialogueIndex = dialogue.currentDialogueIndex; + MainClass.monitor.Log("" + dialogue.isCurrentStringContinuedOnNextScreen, LogLevel.Debug); + string toSpeak = $"{speakerName} said, {dialogues[dialogueIndex]}"; + + ScreenReader.sayWithChecker(toSpeak, false); + } + catch (Exception e) + { + MainClass.monitor.Log($"Unable to narrate dialog:\n{e.StackTrace}", LogLevel.Error); + } + + } + + + public static void HoverTextPatch(string? text, int moneyAmountToDisplayAtBottom = -1, string? boldTitleText = null, string[]? buffIconsToDisplay = null, Item? hoveredItem = null, CraftingRecipe? craftingIngredients = null) + { + try + { + StringBuilder toSpeak = new StringBuilder(); + + #region Add title if any + if (boldTitleText != null) + toSpeak.Append($"{boldTitleText}.\n"); + #endregion + + #region Add the base text + toSpeak.Append(text); + #endregion + + #region Add crafting ingredients + if (craftingIngredients != null) + { + + toSpeak.Append($"\n{craftingIngredients.description}"); + toSpeak.Append("\nIngredients\n"); + + craftingIngredients.recipeList.ToList().ForEach(recipe => + { + int count = recipe.Value; + int item = recipe.Key; + string name = craftingIngredients.getNameFromIndex(item); + + toSpeak.Append($" ,{count} {name}"); + }); + } + #endregion + + #region Add health & stamina + if (hoveredItem is StardewValley.Object && (hoveredItem as StardewValley.Object).Edibility != -300) + { + int stamina_recovery = (hoveredItem as StardewValley.Object).staminaRecoveredOnConsumption(); + toSpeak.Append($"{stamina_recovery} Energy\n"); + if (stamina_recovery >= 0) + { + int health_recovery = (hoveredItem as StardewValley.Object).healthRecoveredOnConsumption(); + toSpeak.Append($"{health_recovery} Health"); + } + } + #endregion + + #region Add buff items (effects like +1 walking speed) + if (buffIconsToDisplay != null) + { + for (int i = 0; i < buffIconsToDisplay.Length; i++) + { + string buffName = ((Convert.ToInt32(buffIconsToDisplay[i]) > 0) ? "+" : "") + buffIconsToDisplay[i] + " "; + if (i <= 11) + { + buffName = Game1.content.LoadString("Strings\\UI:ItemHover_Buff" + i, buffName); + } + try + { + int count = int.Parse(buffName.Substring(0, buffName.IndexOf(' '))); + MainClass.monitor.Log("" + count); + if (count != 0) + toSpeak.Append($"{buffName}\n"); + } + catch (Exception) { } + } + } + #endregion + + #region Add money + if (moneyAmountToDisplayAtBottom != -1) + toSpeak.Append($"\nValue: {moneyAmountToDisplayAtBottom} coins\n"); + #endregion + + #region Narrate toSpeak + ScreenReader.sayWithChecker(toSpeak.ToString(), true); + #endregion + } + catch (Exception e) + { + MainClass.monitor.Log($"Unable to narrate dialog:\n{e.StackTrace}", LogLevel.Error); + } + } + } +} diff --git a/stardew-access/Patches/MenuPatch.cs b/stardew-access/Patches/MenuPatch.cs new file mode 100644 index 0000000..26f4937 --- /dev/null +++ b/stardew-access/Patches/MenuPatch.cs @@ -0,0 +1,91 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI; +using StardewValley; +using StardewValley.Menus; + +namespace stardew_access.Patches +{ + internal class MenuPatch + { + + public static void TitleMenuPatch(TitleMenu __instance) + { + try + { + string toSpeak = ""; + + __instance.buttons.ForEach(component => + { + if (component.containsPoint(Game1.getMousePosition(true).X, Game1.getMousePosition(true).Y)) + { + string name = component.name; + string label = component.label; + toSpeak = $"{name} {label} Button"; + } + }); + + if(__instance.muteMusicButton.containsPoint(Game1.getMousePosition(true).X,Game1.getMousePosition(true).Y)) + { + toSpeak = "Mute Music Button"; + } + + if (__instance.aboutButton.containsPoint(Game1.getMousePosition(true).X, Game1.getMousePosition(true).Y)) + { + toSpeak = "About Button"; + } + + if (__instance.languageButton.containsPoint(Game1.getMousePosition(true).X, Game1.getMousePosition(true).Y)) + { + toSpeak = "Language Button"; + } + + if (__instance.windowedButton.containsPoint(Game1.getMousePosition(true).X, Game1.getMousePosition(true).Y)) + { + toSpeak = "Fullscreen toggle Button"; + } + + if (TitleMenu.subMenu != null && __instance.backButton.containsPoint(Game1.getMousePosition(true).X, Game1.getMousePosition(true).Y)) + { + string text = "Back Button"; + ScreenReader.sayWithChecker(text, true); + } + + if (TitleMenu.subMenu == null && toSpeak != "") + ScreenReader.sayWithChecker(toSpeak, true); + } + catch (Exception e) + { + MainClass.monitor.Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); + } + } + + public static void LoadGameMenuPatch(LoadGameMenu.SaveFileSlot __instance, LoadGameMenu ___menu, int i) + { + try + { + if (___menu.slotButtons[i].containsPoint(Game1.getMousePosition(true).X, Game1.getMousePosition(true).Y)) + { + if (__instance.Farmer == null) + return; + + String farmerName = __instance.Farmer.Name; + String farmName = __instance.Farmer.farmName; + String money = __instance.Farmer.Money.ToString(); + String hoursPlayed = Utility.getHoursMinutesStringFromMilliseconds(__instance.Farmer.millisecondsPlayed); + string dateStringForSaveGame = ((!__instance.Farmer.dayOfMonthForSaveGame.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)); + + string toSpeak = $"{farmName} Farm, \t\n Farmer:{farmerName}, \t\nMoney:{money}, \t\nHours Played:{hoursPlayed}, \t\nDate:{dateStringForSaveGame}"; + + ScreenReader.sayWithChecker(toSpeak, true); + } + } + catch (Exception e) + { + MainClass.monitor.Log($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}", LogLevel.Error); + } + } + } +} diff --git a/stardew-access/ScreenReader.cs b/stardew-access/ScreenReader.cs new file mode 100644 index 0000000..8e53ff4 --- /dev/null +++ b/stardew-access/ScreenReader.cs @@ -0,0 +1,77 @@ +using AccessibleOutput; +using StardewModdingAPI; + +namespace stardew_access +{ + internal class ScreenReader + { + public static IAccessibleOutput? screenReader = null; + private static string prevText = ""; + + public static void initializeScreenReader() + { + NvdaOutput? nvdaOutput = null; + JawsOutput? jawsOutput = null; + SapiOutput? sapiOutput = null; + + // Initialize NVDA + try + { + nvdaOutput = new NvdaOutput(); + } + catch (Exception ex) + { + MainClass.monitor.Log($"Error initializing NVDA:\n{ex.StackTrace}", LogLevel.Error); + } + + // Initialize JAWS + try + { + jawsOutput = new JawsOutput(); + } + catch (Exception ex) + { + MainClass.monitor.Log($"Error initializing JAWS:\n{ex.StackTrace}", LogLevel.Error); + } + + // Initialize SAPI + try + { + sapiOutput = new SapiOutput(); + } + catch (Exception ex) + { + MainClass.monitor.Log($"Error initializing SAPI:\n{ex.StackTrace}", LogLevel.Error); + } + + if (nvdaOutput != null && nvdaOutput.IsAvailable()) + screenReader = nvdaOutput; + + if (jawsOutput != null && jawsOutput.IsAvailable()) + screenReader = jawsOutput; + + if (sapiOutput != null && sapiOutput.IsAvailable()) + screenReader = sapiOutput; + } + + public static void say(string text, bool interrupt) + { + if (screenReader == null) + return; + + screenReader.Speak(text, interrupt); + } + + public static void sayWithChecker(string text, bool interrupt) + { + if (screenReader == null) + return; + + if (prevText != text) + { + prevText = text; + screenReader.Speak(text, interrupt); + } + } + } +}