From 8523026ed055dd6d67e925c5b0cb0a2d4598edcb Mon Sep 17 00:00:00 2001 From: Mohammad Shoaib Khan Date: Wed, 8 Mar 2023 12:06:46 +0530 Subject: [PATCH] Improved and organised code for dialogue box patch Moved letter viewer menu patch to its own class moved npc patch to its own clas moved draw hover text patch to IClickableMenuPatch.cs --- stardew-access/HarmonyPatches.cs | 8 +- stardew-access/Patches/DialogueBoxPatch.cs | 131 ++++++ stardew-access/Patches/DialoguePatches.cs | 427 ------------------ .../GameMenuPatches/CollectionsPagePatch.cs | 2 +- stardew-access/Patches/IClickableMenuPatch.cs | 194 +++++++- .../Patches/LetterViewerMenuPatch.cs | 95 ++++ stardew-access/Patches/MiniGamesPatches.cs | 4 +- stardew-access/Patches/NPCPatch.cs | 22 + 8 files changed, 446 insertions(+), 437 deletions(-) create mode 100644 stardew-access/Patches/DialogueBoxPatch.cs delete mode 100644 stardew-access/Patches/DialoguePatches.cs create mode 100644 stardew-access/Patches/LetterViewerMenuPatch.cs create mode 100644 stardew-access/Patches/NPCPatch.cs diff --git a/stardew-access/HarmonyPatches.cs b/stardew-access/HarmonyPatches.cs index c5c26cf..f2dd7ff 100644 --- a/stardew-access/HarmonyPatches.cs +++ b/stardew-access/HarmonyPatches.cs @@ -21,17 +21,17 @@ namespace stardew_access harmony.Patch( original: AccessTools.Method(typeof(DialogueBox), nameof(DialogueBox.receiveLeftClick)), - postfix: new HarmonyMethod(typeof(DialoguePatches), nameof(DialoguePatches.ClearDialogueString)) + postfix: new HarmonyMethod(typeof(DialoguePatches), nameof(DialoguePatches.RecieveLeftClickPatch)) ); 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) }), - postfix: new HarmonyMethod(typeof(DialoguePatches), nameof(DialoguePatches.HoverTextPatch)) + postfix: new HarmonyMethod(typeof(IClickableMenuPatch), nameof(IClickableMenuPatch.DrawHoverTextPatch)) ); harmony.Patch( original: AccessTools.Method(typeof(NPC), nameof(NPC.drawAboveAlwaysFrontLayer)), - postfix: new HarmonyMethod(typeof(DialoguePatches), nameof(DialoguePatches.drawAboveAlwaysFrontLayerPatch)) + postfix: new HarmonyMethod(typeof(NPCPatch), nameof(NPCPatch.DrawAboveAlwaysFrontLayerPatch)) ); #endregion @@ -129,7 +129,7 @@ namespace stardew_access #region Menu Patches harmony.Patch( original: AccessTools.Method(typeof(LetterViewerMenu), nameof(LetterViewerMenu.draw), new Type[] { typeof(SpriteBatch) }), - postfix: new HarmonyMethod(typeof(DialoguePatches), nameof(DialoguePatches.LetterViewerMenuPatch)) + postfix: new HarmonyMethod(typeof(LetterViwerMenuPatch), nameof(LetterViwerMenuPatch.DrawPatch)) ); harmony.Patch( diff --git a/stardew-access/Patches/DialogueBoxPatch.cs b/stardew-access/Patches/DialogueBoxPatch.cs new file mode 100644 index 0000000..5f036e7 --- /dev/null +++ b/stardew-access/Patches/DialogueBoxPatch.cs @@ -0,0 +1,131 @@ +using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI; +using StardewValley; +using StardewValley.Menus; + +namespace stardew_access.Patches +{ + internal class DialoguePatches + { + private static string currentDialogue = ""; + private static string previousSpeakerName = ""; + private static bool isDialogueAppearingFirstTime = true; + + internal static void DialoguePatch(DialogueBox __instance, SpriteBatch b) + { + try + { + if (__instance.transitioning) return; + + if (narrateCharacterDialogue(__instance)) return; + if (narrateQuestionDialogue(__instance)) return; + narrateBasicDialogue(__instance.getCurrentString()); + } + catch (Exception e) + { + MainClass.ErrorLog($"Unable to narrate dialog:\n{e.StackTrace}\n{e.Message}"); + } + } + + internal static void RecieveLeftClickPatch() + { + // CLears the currentDialogue string on closing dialog + Cleanup(); + } + + private static bool narrateCharacterDialogue(DialogueBox __instance) + { + if (__instance.characterDialogue == null) return false; + + // For Normal Character dialogues + Dialogue dialogue = __instance.characterDialogue; + string speakerName = dialogue.speaker.displayName; + string dialogueText = ""; + string responseText = ""; + bool hasResponses = dialogue.isCurrentDialogueAQuestion(); + + dialogueText = $"{speakerName} said {__instance.getCurrentString()}"; + + if (hasResponses) + { + responseText = getCurrentResponseText(__instance); + + CheckAndSpeak(isDialogueAppearingFirstTime ? $"{dialogueText} \n\t {responseText}" : responseText, responseText); + if (isDialogueAppearingFirstTime) isDialogueAppearingFirstTime = false; + } + else + { + CheckAndSpeak(dialogueText); + } + + return true; + } + + private static bool narrateQuestionDialogue(DialogueBox __instance) + { + if (!__instance.isQuestion) return false; + + // For Dialogues with responses/answers like the dialogue when we click on tv + string questionText = ""; + string responseText = ""; + bool hasResponses = false; + + if (__instance.responses.Count > 0) hasResponses = true; + if (!hasResponses) return false; + + questionText = __instance.getCurrentString(); + + responseText = getCurrentResponseText(__instance); + + CheckAndSpeak(isDialogueAppearingFirstTime ? $"{questionText} \n\t {responseText}" : responseText, responseText); + if (isDialogueAppearingFirstTime) isDialogueAppearingFirstTime = false; + + return true; + } + + private static void narrateBasicDialogue(string dialogue) + { + // Basic dialogues like `No mails in the mail box` + if (Game1.activeClickableMenu is not DialogueBox) return; + CheckAndSpeak(dialogue); + } + + private static string getCurrentResponseText(DialogueBox __instance) + { + List responses = __instance.responses; + if (__instance.selectedResponse >= 0 && __instance.selectedResponse < responses.Count) + { + return $"{__instance.selectedResponse + 1}: {responses[__instance.selectedResponse].responseText}"; + } + else + { + // When the dialogue is not finished writing then the selectedResponse is <0 and this results + // in the first response not being detcted, so this sets the first response option to be the default + // if the current dialogue is a question or has responses + return $"1: {responses[0].responseText}"; + } + } + + private static void CheckAndSpeak(string toSpeak) + { + if (currentDialogue == toSpeak) return; + currentDialogue = toSpeak; + + MainClass.ScreenReader.Say(toSpeak, true); + } + + private static void CheckAndSpeak(string toSpeak, string checkQuery) + { + if (currentDialogue == checkQuery) return; + currentDialogue = checkQuery; + + MainClass.ScreenReader.Say(toSpeak, true); + } + + internal static void Cleanup() + { + currentDialogue = ""; + isDialogueAppearingFirstTime = true; + } + } +} diff --git a/stardew-access/Patches/DialoguePatches.cs b/stardew-access/Patches/DialoguePatches.cs deleted file mode 100644 index e131952..0000000 --- a/stardew-access/Patches/DialoguePatches.cs +++ /dev/null @@ -1,427 +0,0 @@ -using Microsoft.Xna.Framework.Graphics; -using StardewModdingAPI; -using StardewValley; -using StardewValley.Menus; - -namespace stardew_access.Patches -{ - internal class DialoguePatches - { - internal static string currentLetterText = " "; - internal static string currentDialogue = " "; - internal static bool isDialogueAppearingFirstTime = true; - - internal static void DialoguePatch(DialogueBox __instance, SpriteBatch b) - { - try - { - if (__instance.transitioning) - return; - - if (__instance.characterDialogue != null) - { - // For Normal Character dialogues - Dialogue dialogue = __instance.characterDialogue; - string speakerName = dialogue.speaker.displayName; - List responses = __instance.responses; - string toSpeak = " "; - string dialogueText = ""; - string response = ""; - bool hasResponses = dialogue.isCurrentDialogueAQuestion(); - - dialogueText = $"{speakerName} said {__instance.getCurrentString()}"; - - if (hasResponses) - { - if (__instance.selectedResponse >= 0 && __instance.selectedResponse < responses.Count) - response = $"{__instance.selectedResponse + 1}: {responses[__instance.selectedResponse].responseText}"; - else - // When the dialogue is not finished writing then the selectedResponse is <0 and this results - // in the first response not being detcted, so this sets the first response option to be the default - // if the current dialogue is a question or has responses - response = $"1: {responses[0].responseText}"; - } - - if (hasResponses) - { - if (currentDialogue != response) - { - currentDialogue = response; - - if (isDialogueAppearingFirstTime) - { - toSpeak = $"{dialogueText} \n\t {response}"; - isDialogueAppearingFirstTime = false; - } - else - toSpeak = response; - - MainClass.ScreenReader.Say(toSpeak, true); - } - } - else - { - if (currentDialogue != dialogueText) - { - currentDialogue = dialogueText; - MainClass.ScreenReader.Say(dialogueText, true); - } - } - } - else if (__instance.isQuestion) - { - // For Dialogues with responses/answers like the dialogue when we click on tv - string toSpeak = ""; - string dialogueText = ""; - string response = ""; - bool hasResponses = false; - - if (__instance.responses.Count > 0) - hasResponses = true; - - dialogueText = __instance.getCurrentString(); - - if (hasResponses) - if (__instance.selectedResponse >= 0 && __instance.selectedResponse < __instance.responses.Count) - response = $"{__instance.selectedResponse + 1}: {__instance.responses[__instance.selectedResponse].responseText}"; - else - // When the dialogue is not finished writing then the selectedResponse is <0 and this results - // in the first response not being detcted, so this sets the first response option to be the default - // if the current dialogue is a question or has responses - response = $"1: {__instance.responses[0].responseText}"; - - - if (hasResponses) - { - if (currentDialogue != response) - { - currentDialogue = response; - - if (isDialogueAppearingFirstTime) - { - toSpeak = $"{dialogueText} \n\t {response}"; - isDialogueAppearingFirstTime = false; - } - else - toSpeak = response; - - MainClass.ScreenReader.Say(toSpeak, true); - } - } - else - { - if (currentDialogue != dialogueText) - { - currentDialogue = dialogueText; - MainClass.ScreenReader.Say(dialogueText, true); - } - } - } - else if (Game1.activeClickableMenu is DialogueBox) - { - // Basic dialogues like `No mails in the mail box` - if (currentDialogue != __instance.getCurrentString()) - { - currentDialogue = __instance.getCurrentString(); - MainClass.ScreenReader.Say(__instance.getCurrentString(), true); - } - } - } - catch (Exception e) - { - MainClass.ErrorLog($"Unable to narrate dialog:\n{e.StackTrace}\n{e.Message}"); - } - - } - - internal static void ClearDialogueString() - { - // CLears the currentDialogue string on closing dialog - currentDialogue = " "; - isDialogueAppearingFirstTime = true; - } - - internal static void HoverTextPatch(string? text, int moneyAmountToDisplayAtBottom = -1, string? boldTitleText = null, int extraItemToShowIndex = -1, int extraItemToShowAmount = -1, string[]? buffIconsToDisplay = null, Item? hoveredItem = null, CraftingRecipe? craftingIngredients = null) - { - try - { - #region Skip narrating hover text for certain menus - if (Game1.activeClickableMenu is TitleMenu && !(((TitleMenu)Game1.activeClickableMenu).GetChildMenu() is CharacterCustomization)) - return; - else if (Game1.activeClickableMenu is LetterViewerMenu || Game1.activeClickableMenu is QuestLog) - return; - else if (Game1.activeClickableMenu is Billboard) - return; - else if (Game1.activeClickableMenu is GeodeMenu) - return; - else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is InventoryPage) - return; - else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is CraftingPage) - return; - else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is OptionsPage) - return; - else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is ExitPage) - return; - else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is SocialPage) - return; - else if (Game1.activeClickableMenu is ItemGrabMenu) - return; - else if (Game1.activeClickableMenu is ShopMenu) - return; - else if (Game1.activeClickableMenu is ConfirmationDialog) - return; - else if (Game1.activeClickableMenu is JunimoNoteMenu) - return; - else if (Game1.activeClickableMenu is CarpenterMenu) - return; - else 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; - #endregion - - string toSpeak = " "; - - #region Add item count before title - if (hoveredItem != null && hoveredItem.HasBeenInInventory) - { - int count = hoveredItem.Stack; - if (count > 1) - toSpeak = $"{toSpeak} {count} "; - } - #endregion - - #region Add title if any - if (boldTitleText != null) - toSpeak = $"{toSpeak} {boldTitleText}\n"; - #endregion - - #region Add quality of item - if (hoveredItem is StardewValley.Object && ((StardewValley.Object)hoveredItem).Quality > 0) - { - int quality = ((StardewValley.Object)hoveredItem).Quality; - if (quality == 1) - { - toSpeak = $"{toSpeak} Silver quality"; - } - else if (quality == 2 || quality == 3) - { - toSpeak = $"{toSpeak} Gold quality"; - } - else if (quality >= 4) - { - toSpeak = $"{toSpeak} Iridium quality"; - } - } - #endregion - - #region Narrate hovered required ingredients - if (extraItemToShowIndex != -1) - { - string itemName = Game1.objectInformation[extraItemToShowIndex].Split('/')[0]; - - if (extraItemToShowAmount != -1) - toSpeak = $"{toSpeak} Required: {extraItemToShowAmount} {itemName}"; - else - toSpeak = $"{toSpeak} Required: {itemName}"; - } - #endregion - - #region Add money - if (moneyAmountToDisplayAtBottom != -1) - toSpeak = $"{toSpeak} \nCost: {moneyAmountToDisplayAtBottom}g\n"; - #endregion - - #region Add the base text - if (text == "???") - toSpeak = "unknown"; - else - toSpeak = $"{toSpeak} {text}"; - #endregion - - #region Add crafting ingredients - if (craftingIngredients != null) - { - toSpeak = $"{toSpeak} \n{craftingIngredients.description}"; - toSpeak = $"{toSpeak} \nIngredients\n"; - - craftingIngredients.recipeList.ToList().ForEach(recipe => - { - int count = recipe.Value; - int item = recipe.Key; - string name = craftingIngredients.getNameFromIndex(item); - - toSpeak = $"{toSpeak} ,{count} {name}"; - }); - } - #endregion - - #region Add health & stamina - if (hoveredItem is StardewValley.Object && ((StardewValley.Object)hoveredItem).Edibility != -300) - { - int stamina_recovery = ((StardewValley.Object)hoveredItem).staminaRecoveredOnConsumption(); - toSpeak = $"{toSpeak} {stamina_recovery} Energy\n"; - if (stamina_recovery >= 0) - { - int health_recovery = ((StardewValley.Object)hoveredItem).healthRecoveredOnConsumption(); - toSpeak = $"{toSpeak} {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(' '))); - if (count != 0) - toSpeak = $"{toSpeak} {buffName}\n"; - } - catch (Exception) { } - } - } - #endregion - - #region Narrate toSpeak - // To prevent it from getting conflicted by two hover texts at the same time, two seperate methods are used. - // For example, sometimes `Welcome to Pierre's` and the items in seeds shop get conflicted causing it to speak infinitely. - - if (toSpeak.ToString() != " ") - { - if (Context.IsPlayerFree) - MainClass.ScreenReader.SayWithChecker(toSpeak.ToString(), true); // Normal Checker - else - MainClass.ScreenReader.SayWithMenuChecker(toSpeak.ToString(), true); // Menu Checker - } - #endregion - } - catch (Exception e) - { - 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}"); - } - } - } -} diff --git a/stardew-access/Patches/GameMenuPatches/CollectionsPagePatch.cs b/stardew-access/Patches/GameMenuPatches/CollectionsPagePatch.cs index a708930..fdbaee4 100644 --- a/stardew-access/Patches/GameMenuPatches/CollectionsPagePatch.cs +++ b/stardew-access/Patches/GameMenuPatches/CollectionsPagePatch.cs @@ -9,7 +9,7 @@ namespace stardew_access.Patches int x = StardewValley.Game1.getMousePosition().X, y = StardewValley.Game1.getMousePosition().Y; if (__instance.letterviewerSubMenu != null) { - DialoguePatches.NarrateLetterContent(__instance.letterviewerSubMenu); + LetterViwerMenuPatch.narrateLetterContent(__instance.letterviewerSubMenu); } } catch (System.Exception e) diff --git a/stardew-access/Patches/IClickableMenuPatch.cs b/stardew-access/Patches/IClickableMenuPatch.cs index b0a6c8c..8e92817 100644 --- a/stardew-access/Patches/IClickableMenuPatch.cs +++ b/stardew-access/Patches/IClickableMenuPatch.cs @@ -1,3 +1,4 @@ +using StardewValley; using StardewValley.Menus; namespace stardew_access.Patches @@ -5,6 +6,194 @@ namespace stardew_access.Patches // These patches are global, i.e. work on every menus internal class IClickableMenuPatch { + internal static void DrawHoverTextPatch(string? text, int moneyAmountToDisplayAtBottom = -1, string? boldTitleText = null, int extraItemToShowIndex = -1, int extraItemToShowAmount = -1, string[]? buffIconsToDisplay = null, Item? hoveredItem = null, CraftingRecipe? craftingIngredients = null) + { + try + { + #region Skip narrating hover text for certain menus + if (Game1.activeClickableMenu is TitleMenu && !(((TitleMenu)Game1.activeClickableMenu).GetChildMenu() is CharacterCustomization)) + return; + else if (Game1.activeClickableMenu is LetterViewerMenu || Game1.activeClickableMenu is QuestLog) + return; + else if (Game1.activeClickableMenu is Billboard) + return; + else if (Game1.activeClickableMenu is GeodeMenu) + return; + else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is InventoryPage) + return; + else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is CraftingPage) + return; + else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is OptionsPage) + return; + else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is ExitPage) + return; + else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is SocialPage) + return; + else if (Game1.activeClickableMenu is ItemGrabMenu) + return; + else if (Game1.activeClickableMenu is ShopMenu) + return; + else if (Game1.activeClickableMenu is ConfirmationDialog) + return; + else if (Game1.activeClickableMenu is JunimoNoteMenu) + return; + else if (Game1.activeClickableMenu is CarpenterMenu) + return; + else 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; + #endregion + + string toSpeak = " "; + + #region Add item count before title + if (hoveredItem != null && hoveredItem.HasBeenInInventory) + { + int count = hoveredItem.Stack; + if (count > 1) + toSpeak = $"{toSpeak} {count} "; + } + #endregion + + #region Add title if any + if (boldTitleText != null) + toSpeak = $"{toSpeak} {boldTitleText}\n"; + #endregion + + #region Add quality of item + if (hoveredItem is StardewValley.Object && ((StardewValley.Object)hoveredItem).Quality > 0) + { + int quality = ((StardewValley.Object)hoveredItem).Quality; + if (quality == 1) + { + toSpeak = $"{toSpeak} Silver quality"; + } + else if (quality == 2 || quality == 3) + { + toSpeak = $"{toSpeak} Gold quality"; + } + else if (quality >= 4) + { + toSpeak = $"{toSpeak} Iridium quality"; + } + } + #endregion + + #region Narrate hovered required ingredients + if (extraItemToShowIndex != -1) + { + string itemName = Game1.objectInformation[extraItemToShowIndex].Split('/')[0]; + + if (extraItemToShowAmount != -1) + toSpeak = $"{toSpeak} Required: {extraItemToShowAmount} {itemName}"; + else + toSpeak = $"{toSpeak} Required: {itemName}"; + } + #endregion + + #region Add money + if (moneyAmountToDisplayAtBottom != -1) + toSpeak = $"{toSpeak} \nCost: {moneyAmountToDisplayAtBottom}g\n"; + #endregion + + #region Add the base text + if (text == "???") + toSpeak = "unknown"; + else + toSpeak = $"{toSpeak} {text}"; + #endregion + + #region Add crafting ingredients + if (craftingIngredients != null) + { + toSpeak = $"{toSpeak} \n{craftingIngredients.description}"; + toSpeak = $"{toSpeak} \nIngredients\n"; + + craftingIngredients.recipeList.ToList().ForEach(recipe => + { + int count = recipe.Value; + int item = recipe.Key; + string name = craftingIngredients.getNameFromIndex(item); + + toSpeak = $"{toSpeak} ,{count} {name}"; + }); + } + #endregion + + #region Add health & stamina + if (hoveredItem is StardewValley.Object && ((StardewValley.Object)hoveredItem).Edibility != -300) + { + int stamina_recovery = ((StardewValley.Object)hoveredItem).staminaRecoveredOnConsumption(); + toSpeak = $"{toSpeak} {stamina_recovery} Energy\n"; + if (stamina_recovery >= 0) + { + int health_recovery = ((StardewValley.Object)hoveredItem).healthRecoveredOnConsumption(); + toSpeak = $"{toSpeak} {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(' '))); + if (count != 0) + toSpeak = $"{toSpeak} {buffName}\n"; + } + catch (Exception) { } + } + } + #endregion + + #region Narrate toSpeak + // To prevent it from getting conflicted by two hover texts at the same time, two seperate methods are used. + // For example, sometimes `Welcome to Pierre's` and the items in seeds shop get conflicted causing it to speak infinitely. + + if (toSpeak.ToString() != " ") + { + if (StardewModdingAPI.Context.IsPlayerFree) + MainClass.ScreenReader.SayWithChecker(toSpeak.ToString(), true); // Normal Checker + else + MainClass.ScreenReader.SayWithMenuChecker(toSpeak.ToString(), true); // Menu Checker + } + #endregion + } + catch (Exception e) + { + MainClass.ErrorLog($"Unable to narrate dialog:\n{e.StackTrace}\n{e.Message}"); + } + } + internal static void ExitThisMenuPatch(IClickableMenu __instance) { try @@ -37,7 +226,7 @@ namespace stardew_access.Patches } else if (menu is LetterViewerMenu) { - DialoguePatches.currentLetterText = " "; + LetterViwerMenuPatch.Cleanup(); } else if (menu is LevelUpMenu) { @@ -94,8 +283,7 @@ namespace stardew_access.Patches } else if (menu is DialogueBox) { - DialoguePatches.isDialogueAppearingFirstTime = true; - DialoguePatches.currentDialogue = " "; + DialoguePatches.Cleanup(); } else if (menu is JojaCDMenu) { diff --git a/stardew-access/Patches/LetterViewerMenuPatch.cs b/stardew-access/Patches/LetterViewerMenuPatch.cs new file mode 100644 index 0000000..de7eabb --- /dev/null +++ b/stardew-access/Patches/LetterViewerMenuPatch.cs @@ -0,0 +1,95 @@ +using StardewValley; +using StardewValley.Menus; + +namespace stardew_access.Patches +{ + internal class LetterViwerMenuPatch + { + private static string currentLetterText = ""; + + internal static void DrawPatch(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 Cleanup() + { + currentLetterText = ""; + } + } +} diff --git a/stardew-access/Patches/MiniGamesPatches.cs b/stardew-access/Patches/MiniGamesPatches.cs index 8addd64..07480bf 100644 --- a/stardew-access/Patches/MiniGamesPatches.cs +++ b/stardew-access/Patches/MiniGamesPatches.cs @@ -49,7 +49,7 @@ namespace stardew_access.Patches if (___letterView != null) { - DialoguePatches.NarrateLetterContent(___letterView); + LetterViwerMenuPatch.narrateLetterContent(___letterView); } if (MainClass.ModHelper == null) @@ -117,4 +117,4 @@ namespace stardew_access.Patches 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); } } -} \ No newline at end of file +} diff --git a/stardew-access/Patches/NPCPatch.cs b/stardew-access/Patches/NPCPatch.cs new file mode 100644 index 0000000..41f9306 --- /dev/null +++ b/stardew-access/Patches/NPCPatch.cs @@ -0,0 +1,22 @@ +using StardewValley; + +namespace stardew_access.Patches +{ + internal class NPCPatch + { + 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}"); + } + } + } +}