diff --git a/invalid-selection-license.txt b/invalid-selection-license.txt new file mode 100644 index 0000000..f1b27d8 --- /dev/null +++ b/invalid-selection-license.txt @@ -0,0 +1,32 @@ +PIXABAY LICENSE CERTIFICATE +============================================== + +This document confirms the download of an audio file pursuant to the Pixabay License as defined in the Pixabay Terms of Service available at https://https://pixabay.com/service/terms/ + +Licensor's Username: +https://pixabay.com/users/pixabay-1/ + +Licensee: +TheOneWhoKnocks + +Audio File Title: +Invalid Selection + +Audio File URL: +https://pixabay.com/sound-effects/invalid-selection-39351/ + +Audio File ID: +39351 + +Date of download: +2023-02-20 05:52:26 UTC + +Pixabay GmbH c/o Lacore Rechtsanwälte LLP +Berliner Freiheit 2, 10785 Berlin, Germany + +Pixabay is a user-contributed stock content website. The above-named Licensor is responsible for this audio file. Pixabay monitors uploaded audio files only to a reasonable extent. Pixabay cannot be held responsible for the acts or omissions of its users and does not represent or warrant that any required third-party consents or licenses have been obtained. + +For any queries related to this document please contact Pixabay via info@pixabay.com. + + +==== THIS IS NOT A TAX RECEIPT OR INVOICE ==== \ No newline at end of file diff --git a/stardew-access/CustomSoundEffects.cs b/stardew-access/CustomSoundEffects.cs index b5610e4..f4c6d43 100644 --- a/stardew-access/CustomSoundEffects.cs +++ b/stardew-access/CustomSoundEffects.cs @@ -22,6 +22,7 @@ namespace stardew_access soundEffects.Add("drop_item", TYPE.Sound); soundEffects.Add("colliding", TYPE.Sound); + soundEffects.Add("invalid-selection", TYPE.Sound); soundEffects.Add("npc_top", TYPE.Footstep); soundEffects.Add("npc_right", TYPE.Footstep); diff --git a/stardew-access/ModConfig.cs b/stardew-access/ModConfig.cs index ca29cc3..24d1241 100644 --- a/stardew-access/ModConfig.cs +++ b/stardew-access/ModConfig.cs @@ -86,9 +86,10 @@ namespace stardew_access public Boolean TTS { get; set; } = true; // Toggles the screen reader/tts. public Boolean TrackDroppedItems {get; set;} = true; // Toggles detecting the dropped items. public Single SpeechRate {get; set;} = 220; // Sets speech rate for the Mac TTS. + public Boolean DisableInventoryVerbosity {get; set;} = false; // If enabled, does not speaks 'not usable here' and 'donatable' in inventories #endregion // TODO Add the exclusion and focus list too // public String ExclusionList { get; set; } = "test"; } -} \ No newline at end of file +} diff --git a/stardew-access/Patches/DonationMenuPatches.cs b/stardew-access/Patches/DonationMenuPatches.cs index f4969ca..ffffa2f 100644 --- a/stardew-access/Patches/DonationMenuPatches.cs +++ b/stardew-access/Patches/DonationMenuPatches.cs @@ -79,7 +79,8 @@ namespace stardew_access.Patches else { // Player Inventory - int i = narrateHoveredItemInInventory(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y); + int i = InventoryUtils.narrateHoveredSlotAndReturnIndex(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y, + handleHighlightedItem: true, highlightedItemPrefix: "Donatable "); if (i != -9999) { bool isPrimaryInfoKeyPressed = MainClass.Config.PrimaryInfoKey.JustPressed(); // For donating hovered item @@ -148,74 +149,7 @@ namespace stardew_access.Patches } } - // Returns the index of the hovered item or -9999 - internal static int narrateHoveredItemInInventory(InventoryMenu inventoryMenu, List inventory, IList 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) { @@ -303,31 +237,8 @@ namespace stardew_access.Patches } 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); - } - + if (InventoryUtils.narrateHoveredSlot(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y)) return; - } for (int i = 0; i < __instance.pieceHolders.Count; i++) { @@ -353,7 +264,7 @@ namespace stardew_access.Patches else toSpeak = $"Slot {i + 1} finished: {__instance.pieceHolders[i].item.DisplayName}"; - if (__instance.heldItem != null && __instance.pieceHolders[i].item == null) + if (!MainClass.Config.DisableInventoryVerbosity && __instance.heldItem != null && __instance.pieceHolders[i].item == null) { int highlight = getPieceIndexForDonationItem(__instance.heldItem.ParentSheetIndex); if (highlight != -1 && highlight == i) @@ -414,4 +325,4 @@ namespace stardew_access.Patches #endregion } -} \ No newline at end of file +} diff --git a/stardew-access/Patches/GameMenuPatches.cs b/stardew-access/Patches/GameMenuPatches.cs index e1c2d46..f5d0ea2 100644 --- a/stardew-access/Patches/GameMenuPatches.cs +++ b/stardew-access/Patches/GameMenuPatches.cs @@ -1,5 +1,4 @@ using StardewValley; -using StardewValley.Locations; using StardewValley.Menus; using StardewValley.Objects; @@ -241,7 +240,7 @@ namespace stardew_access.Patches #endregion #region Narrate hovered item - if (narrateHoveredItemInInventory(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y, hoverPrice: __instance.hoverPrice)) + if (InventoryUtils.narrateHoveredSlot(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y, hoverPrice: __instance.hoverPrice)) { shopMenuQueryKey = ""; return; @@ -396,7 +395,7 @@ namespace stardew_access.Patches #endregion #region Narrate hovered item - if (narrateHoveredItemInInventory(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y)) + if (InventoryUtils.narrateHoveredSlot(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y)) geodeMenuQueryKey = ""; #endregion } @@ -573,14 +572,14 @@ namespace stardew_access.Patches #endregion #region Narrate hovered item - if (narrateHoveredItemInInventory(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y, true)) + if (InventoryUtils.narrateHoveredSlot(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y, true)) { gameMenuQueryKey = ""; itemGrabMenuQueryKey = ""; return; } - if (narrateHoveredItemInInventory(__instance.ItemsToGrabMenu, __instance.ItemsToGrabMenu.inventory, __instance.ItemsToGrabMenu.actualInventory, x, y, true)) + if (InventoryUtils.narrateHoveredSlot(__instance.ItemsToGrabMenu, __instance.ItemsToGrabMenu.inventory, __instance.ItemsToGrabMenu.actualInventory, x, y, true)) { gameMenuQueryKey = ""; itemGrabMenuQueryKey = ""; @@ -855,7 +854,7 @@ namespace stardew_access.Patches #endregion #region Narrate hovered item - if (narrateHoveredItemInInventory(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y)) + if (InventoryUtils.narrateHoveredSlot(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y)) { gameMenuQueryKey = ""; craftingPageQueryKey = ""; @@ -1066,7 +1065,7 @@ namespace stardew_access.Patches #endregion #region Narrate hovered item - if (narrateHoveredItemInInventory(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y, true)) + if (InventoryUtils.narrateHoveredSlot(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y, true)) { gameMenuQueryKey = ""; inventoryPageQueryKey = ""; @@ -1193,147 +1192,5 @@ namespace stardew_access.Patches MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}"); } } - - internal static bool narrateHoveredItemInInventory(InventoryMenu inventoryMenu, List inventory, IList actualInventory, int x, int y, bool giveExtraDetails = false, int hoverPrice = -1, int extraItemToShowIndex = -1, int extraItemToShowAmount = -1) - { - #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 = ""; - string healthNStamine = ""; - string buffs = ""; - string description = ""; - string price = ""; - string requirements = ""; - - #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 (giveExtraDetails) - { - description = actualInventory[i].getDescription(); - #region Add health & stamina provided by the item - if (actualInventory[i] is StardewValley.Object && ((StardewValley.Object)actualInventory[i]).Edibility != -300) - { - int stamina_recovery = ((StardewValley.Object)actualInventory[i]).staminaRecoveredOnConsumption(); - healthNStamine += $"{stamina_recovery} Energy"; - if (stamina_recovery >= 0) - { - int health_recovery = ((StardewValley.Object)actualInventory[i]).healthRecoveredOnConsumption(); - healthNStamine += $"\n\t{health_recovery} Health"; - } - } - #endregion - - #region Add buff items (effects like +1 walking speed) - // These variables are taken from the game's code itself (IClickableMenu.cs -> 1016 line) - bool edibleItem = actualInventory[i] != null && actualInventory[i] is StardewValley.Object && (int)((StardewValley.Object)actualInventory[i]).Edibility != -300; - string[]? buffIconsToDisplay = (edibleItem && Game1.objectInformation[((StardewValley.Object)actualInventory[i]).ParentSheetIndex].Split('/').Length > 7) ? actualInventory[i].ModifyItemBuffs(Game1.objectInformation[((StardewValley.Object)actualInventory[i]).ParentSheetIndex].Split('/')[7].Split(' ')) : null; - if (buffIconsToDisplay != null) - { - for (int j = 0; j < buffIconsToDisplay.Length; j++) - { - string buffName = ((Convert.ToInt32(buffIconsToDisplay[j]) > 0) ? "+" : "") + buffIconsToDisplay[j] + " "; - if (j <= 11) - { - buffName = Game1.content.LoadString("Strings\\UI:ItemHover_Buff" + j, buffName); - } - try - { - int count = int.Parse(buffName.Substring(0, buffName.IndexOf(' '))); - if (count != 0) - buffs += $"{buffName}\n"; - } - catch (Exception) { } - } - } - #endregion - } - - #region Narrate hovered required ingredients - if (extraItemToShowIndex != -1) - { - string itemName = Game1.objectInformation[extraItemToShowIndex].Split('/')[0]; - - if (extraItemToShowAmount != -1) - requirements = $"Required: {extraItemToShowAmount} {itemName}"; - else - requirements = $"Required: {itemName}"; - } - #endregion - - if (hoverPrice != -1) - { - price = $"Sell Price: {hoverPrice} g"; - } - - if (!inventoryMenu.highlightMethod(actualInventory[i])) - { - name = $"{name} not usable here"; - } - - if (giveExtraDetails) - { - if (stack > 1) - toSpeak = $"{stack} {name} {quality}, \n{requirements}, \n{price}, \n{description}, \n{healthNStamine}, \n{buffs}"; - else - toSpeak = $"{name} {quality}, \n{requirements}, \n{price}, \n{description}, \n{healthNStamine}, \n{buffs}"; - } - else - { - if (stack > 1) - toSpeak = $"{stack} {name} {quality}, \n{requirements}, \n{price}"; - else - toSpeak = $"{name} {quality}, \n{requirements}, \n{price}"; - } - } - else - { - // For empty slot - toSpeak = "Empty Slot"; - } - } - else - { - // For empty slot - toSpeak = "Empty Slot"; - } - - if (hoveredItemQueryKey != $"{toSpeak}:{i}") - { - hoveredItemQueryKey = $"{toSpeak}:{i}"; - MainClass.ScreenReader.Say(toSpeak, true); - } - return true; - } - } - #endregion - return false; - } } } diff --git a/stardew-access/Patches/InventoryUtils.cs b/stardew-access/Patches/InventoryUtils.cs new file mode 100644 index 0000000..5903f92 --- /dev/null +++ b/stardew-access/Patches/InventoryUtils.cs @@ -0,0 +1,206 @@ + +using StardewValley; +using StardewValley.Menus; + +namespace stardew_access.Patches +{ + internal class InventoryUtils + { + internal static string hoveredItemQueryKey = ""; + internal static int prevSlotIndex = -999; + + internal static bool narrateHoveredSlot(InventoryMenu inventoryMenu, List inventory, IList actualInventory, int x, int y, + bool giveExtraDetails = false, int hoverPrice = -1, int extraItemToShowIndex = -1, int extraItemToShowAmount = -1, + bool handleHighlightedItem = false, String highlightedItemPrefix = "", String highlightedItemSuffix = "") + { + if (narrateHoveredSlotAndReturnIndex(inventoryMenu, inventory, actualInventory, x, y, + giveExtraDetails = false, hoverPrice = -1, extraItemToShowIndex = -1, extraItemToShowAmount = -1, + handleHighlightedItem = false, highlightedItemPrefix = "", highlightedItemSuffix = "") == -999) + return false; + + return true; + } + + internal static int narrateHoveredSlotAndReturnIndex(InventoryMenu inventoryMenu, List inventory, IList actualInventory, int x, int y, + bool giveExtraDetails = false, int hoverPrice = -1, int extraItemToShowIndex = -1, int extraItemToShowAmount = -1, + bool handleHighlightedItem = false, String highlightedItemPrefix = "", String highlightedItemSuffix = "") + { + for (int i = 0; i < inventory.Count; i++) + { + if (!inventory[i].containsPoint(x, y)) continue; + + if ((i + 1) > actualInventory.Count || actualInventory[i] == null) + { + // For empty slot + checkAndSpeak("Empty Slot", i); + prevSlotIndex = i; + return i; + } + + string toSpeak = ""; + bool isHighlighted = inventoryMenu.highlightMethod(actualInventory[i]); + + string namePrefix = handleHighlightedItemPrefix(isHighlighted, highlightedItemPrefix); + string nameSuffix = $"{handleHighlightedItemSuffix(isHighlighted, highlightedItemSuffix)}{handleUnHighlightedItem(isHighlighted, i)}"; + string name = $"{namePrefix}{actualInventory[i].DisplayName}{nameSuffix}"; + int stack = actualInventory[i].Stack; + string quality = getQualityFromItem(actualInventory[i]); + string healthNStamine = getHealthNStaminaFromItem(actualInventory[i]); + string buffs = getBuffsFromItem(actualInventory[i]); + string description = actualInventory[i].getDescription(); + string price = getPrice(hoverPrice); + string requirements = getExtraItemInfo(extraItemToShowIndex, extraItemToShowAmount); + + if (giveExtraDetails) + { + if (stack > 1) + toSpeak = $"{stack} {name} {quality}, \n{requirements}, \n{price}, \n{description}, \n{healthNStamine}, \n{buffs}"; + else + toSpeak = $"{name} {quality}, \n{requirements}, \n{price}, \n{description}, \n{healthNStamine}, \n{buffs}"; + } + else + { + if (stack > 1) + toSpeak = $"{stack} {name} {quality}, \n{requirements}, \n{price}"; + else + toSpeak = $"{name} {quality}, \n{requirements}, \n{price}"; + } + + + checkAndSpeak(toSpeak, i); + prevSlotIndex = i; + return i; + } + + // If no slot is hovered + return -999; + } + + private static void checkAndSpeak(String toSpeak, int hoveredInventoryIndex) + { + if (hoveredItemQueryKey == $"{toSpeak}:{hoveredInventoryIndex}") return; + + hoveredItemQueryKey = $"{toSpeak}:{hoveredInventoryIndex}"; + MainClass.ScreenReader.Say(toSpeak, true); + } + + private static String getQualityFromItem(Item item) + { + if (item is not StardewValley.Object || ((StardewValley.Object)item).Quality <= 0) + return ""; + + int qualityIndex = ((StardewValley.Object)item).Quality; + if (qualityIndex == 1) + { + return "Silver quality"; + } + else if (qualityIndex == 2 || qualityIndex == 3) + { + return "Gold quality"; + } + else if (qualityIndex >= 4) + { + return "Iridium quality"; + } + + return ""; + } + + private static String getHealthNStaminaFromItem(Item item) + { + if (item is not StardewValley.Object || ((StardewValley.Object)item).Edibility == -300) + return ""; + + String toReturn = ""; + int stamina_recovery = ((StardewValley.Object)item).staminaRecoveredOnConsumption(); + toReturn += $"{stamina_recovery} Energy"; + + if (stamina_recovery < 0) return toReturn; + + int health_recovery = ((StardewValley.Object)item).healthRecoveredOnConsumption(); + toReturn += $"\n\t{health_recovery} Health"; + + return toReturn; + } + + private static String getBuffsFromItem(Item item) + { + if (item == null) return ""; + if (item is not StardewValley.Object) return ""; + if (((StardewValley.Object)item) == null) return ""; + + // These variables are taken from the game's code itself (IClickableMenu.cs -> 1016 line) + bool edibleItem = (int)((StardewValley.Object)item).Edibility != -300; + string[]? buffIconsToDisplay = (edibleItem && Game1.objectInformation[((StardewValley.Object)item).ParentSheetIndex].Split('/').Length > 7) + ? item.ModifyItemBuffs(Game1.objectInformation[((StardewValley.Object)item).ParentSheetIndex].Split('/')[7].Split(' ')) + : null; + + if (buffIconsToDisplay == null) + return ""; + + String toReturn = ""; + for (int j = 0; j < buffIconsToDisplay.Length; j++) + { + string buffName = ((Convert.ToInt32(buffIconsToDisplay[j]) > 0) ? "+" : "") + buffIconsToDisplay[j] + " "; + if (j <= 11) + { + buffName = Game1.content.LoadString("Strings\\UI:ItemHover_Buff" + j, buffName); + } + try + { + int count = int.Parse(buffName.Substring(0, buffName.IndexOf(' '))); + if (count != 0) + toReturn += $"{buffName}\n"; + } + catch (Exception) { } + } + return toReturn; + } + + private static String getExtraItemInfo(int itemIndex, int itemAmount) + { + if (itemIndex == -1) return ""; + + string itemName = Game1.objectInformation[itemIndex].Split('/')[0]; + + if (itemAmount != -1) + return $"Required: {itemAmount} {itemName}"; + else + return $"Required: {itemName}"; + } + + private static String getPrice(int price) + { + if (price == -1) return ""; + + return $"Sell Price: {price} g"; + } + + private static String handleHighlightedItemPrefix(bool isHighlighted, String prefix) + { + if (MainClass.Config.DisableInventoryVerbosity) return ""; + if (!isHighlighted) return ""; + + return prefix; + } + + private static String handleHighlightedItemSuffix(bool isHighlighted, String suffix) + { + if (MainClass.Config.DisableInventoryVerbosity) return ""; + if (!isHighlighted) return ""; + + return suffix; + } + + private static String handleUnHighlightedItem(bool isHighlighted, int hoveredInventoryIndex) + { + if (isHighlighted) return ""; + + if (prevSlotIndex != hoveredInventoryIndex) + Game1.playSound("invalid-selection"); + + if (MainClass.Config.DisableInventoryVerbosity) return ""; + return " not usable here"; + } + } +} diff --git a/stardew-access/Patches/MenuPatches.cs b/stardew-access/Patches/MenuPatches.cs index 993d580..e33a237 100644 --- a/stardew-access/Patches/MenuPatches.cs +++ b/stardew-access/Patches/MenuPatches.cs @@ -16,6 +16,7 @@ namespace stardew_access.Patches internal static string pondQueryMenuQuery = " "; internal static string forgeMenuQuery = " "; internal static string itemListMenuQuery = " "; + internal static int prevSlotIndex = -999; public static Vector2? prevTile = null; internal static void ItemListMenuPatch(ItemListMenu __instance, string ___title, int ___currentTab, int ___totalValueOfItems, List ___itemsToList) @@ -126,35 +127,9 @@ namespace stardew_access.Patches 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 (InventoryUtils.narrateHoveredSlot(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y)) + return; if (forgeMenuQuery != toSpeak) { @@ -293,34 +268,9 @@ namespace stardew_access.Patches 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 (InventoryUtils.narrateHoveredSlot(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y)) + return; if (tailoringMenuQuery != toSpeak) @@ -788,7 +738,8 @@ namespace stardew_access.Patches pondQueryMenuQuery = " "; } - GameMenuPatches.hoveredItemQueryKey = ""; + InventoryUtils.hoveredItemQueryKey = ""; + InventoryUtils.prevSlotIndex = -999; } #endregion diff --git a/stardew-access/assets/sounds/invalid-selection.wav b/stardew-access/assets/sounds/invalid-selection.wav new file mode 100644 index 0000000..83e63ba Binary files /dev/null and b/stardew-access/assets/sounds/invalid-selection.wav differ