using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI; using StardewValley; using StardewValley.Menus; using System.Text; namespace stardew_access.Patches { internal class DialoguePatches { private static string currentDialogue = " "; 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; string toSpeak = " "; bool hasResponses = false; if (__instance.responses.Count > 0) hasResponses = true; if (currentDialogue != __instance.getCurrentString()) { toSpeak = __instance.getCurrentString(); currentDialogue = toSpeak; toSpeak = $"{speakerName} said {toSpeak}"; } if (__instance.responses.Count > 0) { for (int i = 0; i < __instance.responses.Count; i++) { if (i == __instance.selectedResponse) { toSpeak += $" \t\n Selected response: {__instance.responses[i].responseText}"; } } } if (toSpeak != " ") { if (hasResponses) MainClass.screenReader.SayWithChecker(toSpeak, false); else MainClass.screenReader.SayWithChecker(toSpeak, true); } } else if (__instance.isQuestion) { // For Dialogues with responses/answers like the dialogue when we click on tv string toSpeak = " "; bool hasResponses = false; if (__instance.responses.Count > 0) hasResponses = true; if (currentDialogue != __instance.getCurrentString()) { toSpeak = __instance.getCurrentString(); currentDialogue = toSpeak; } for (int i = 0; i < __instance.responses.Count; i++) { if (i == __instance.selectedResponse) { toSpeak += $" \t\n Selected response: {__instance.responses[i].responseText}"; } } if (toSpeak != " ") { if (hasResponses) MainClass.screenReader.SayWithChecker(toSpeak, false); else MainClass.screenReader.SayWithChecker(toSpeak, 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.monitor.Log($"Unable to narrate dialog:\n{e.StackTrace}\n{e.Message}", LogLevel.Error); } } internal static void ClearDialogueString() { // CLears the currentDialogue string on closing dialog currentDialogue = " "; } 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 && !((Game1.activeClickableMenu as TitleMenu).GetChildMenu() is CharacterCustomization)) return; if (Game1.activeClickableMenu is LetterViewerMenu || Game1.activeClickableMenu is QuestLog) return; if (Game1.activeClickableMenu is Billboard) return; if (Game1.activeClickableMenu is GeodeMenu) return; if (Game1.activeClickableMenu is GameMenu && (Game1.activeClickableMenu as GameMenu).GetCurrentPage() is InventoryPage) return; if (Game1.activeClickableMenu is GameMenu && (Game1.activeClickableMenu as GameMenu).GetCurrentPage() is CraftingPage) return; if (Game1.activeClickableMenu is GameMenu && (Game1.activeClickableMenu as GameMenu).GetCurrentPage() is OptionsPage) return; if (Game1.activeClickableMenu is GameMenu && (Game1.activeClickableMenu as GameMenu).GetCurrentPage() is ExitPage) return; if (Game1.activeClickableMenu is GameMenu && (Game1.activeClickableMenu as GameMenu).GetCurrentPage() is SocialPage) return; if (Game1.activeClickableMenu is ItemGrabMenu) return; if (Game1.activeClickableMenu is ShopMenu) return; if (Game1.activeClickableMenu is ConfirmationDialog) return; if (Game1.activeClickableMenu is JunimoNoteMenu) return; #endregion StringBuilder toSpeak = new StringBuilder(" "); #region Add item count before title if (hoveredItem != null && hoveredItem.HasBeenInInventory) { int count = hoveredItem.Stack; if (count > 1) toSpeak.Append($"{count} "); } #endregion #region Add title if any if (boldTitleText != null) toSpeak.Append($"{boldTitleText}\n"); #endregion #region Add quality of item if (hoveredItem is StardewValley.Object && (hoveredItem as StardewValley.Object).quality > 0) { int quality = (hoveredItem as StardewValley.Object).quality; if (quality == 1) { toSpeak.Append("Silver quality"); } else if (quality == 2 || quality == 3) { toSpeak.Append("Gold quality"); } else if (quality >= 4) { toSpeak.Append("Iridium quality"); } } #endregion #region Narrate hovered required ingredients if (extraItemToShowIndex != -1) { string itemName = Game1.objectInformation[extraItemToShowIndex].Split('/')[0]; if (extraItemToShowAmount != -1) toSpeak.Append($"Required: {extraItemToShowAmount} {itemName}"); else toSpeak.Append($"Required: {itemName}"); } #endregion #region Add money if (moneyAmountToDisplayAtBottom != -1) toSpeak.Append($"\nCost: {moneyAmountToDisplayAtBottom}g\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(' '))); if (count != 0) toSpeak.Append($"{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.monitor.Log($"Unable to narrate dialog:\n{e.StackTrace}\n{e.Message}", LogLevel.Error); } } } }