Made Load Game Menu accessible, organised code

master
shoaib11120 2021-12-10 21:17:50 +05:30
parent d8c088b9ec
commit a513a14ca0
5 changed files with 326 additions and 219 deletions

View File

@ -5,7 +5,7 @@ namespace stardew_access.Game
{ {
internal class CurrentPlayer internal class CurrentPlayer
{ {
private static Farmer player = null; private static Farmer? player = null;
CurrentPlayer() CurrentPlayer()
{ {
@ -40,5 +40,23 @@ namespace stardew_access.Game
return staminaPercentage; 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;
}
} }
} }

View File

@ -1,26 +1,20 @@
using AccessibleOutput; using stardew_access.Game;
using stardew_access.Game;
using StardewModdingAPI; using StardewModdingAPI;
using StardewModdingAPI.Events; using StardewModdingAPI.Events;
using StardewValley; using StardewValley;
using HarmonyLib; using HarmonyLib;
using StardewValley.Menus; using StardewValley.Menus;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System.Text; using stardew_access.Patches;
using StardewValley.BellsAndWhistles;
using Microsoft.Xna.Framework;
namespace stardew_access namespace stardew_access
{ {
/// <summary>The mod entry point.</summary>
public class MainClass : Mod public class MainClass : Mod
{ {
public static IAccessibleOutput screenReader; private Harmony? harmony;
Harmony harmony; public static IMonitor? monitor;
public static IMonitor monitor;
private static string prevText = "";
private static DialogueBox? dialogueBox = null;
private int index = 0;
/********* /*********
** Public methods ** Public methods
*********/ *********/
@ -32,7 +26,7 @@ namespace stardew_access
monitor = Monitor; monitor = Monitor;
// Initialize the screen reader // Initialize the screen reader
initializeScreenReader(); ScreenReader.initializeScreenReader();
// Init harmony // Init harmony
harmony = new Harmony(ModManifest.UniqueID); harmony = new Harmony(ModManifest.UniqueID);
@ -40,225 +34,29 @@ namespace stardew_access
// Add patches // Add patches
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(DialogueBox), nameof(DialogueBox.draw), new Type[] {typeof(SpriteBatch)}), 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( 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 >) }), 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( harmony.Patch(
original: AccessTools.Method(typeof(TitleMenu), nameof(TitleMenu.draw) , new Type[] {typeof(SpriteBatch)}), 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.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<string> 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<Item> 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) 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 // ignore if player hasn't loaded a save yet
if (!Context.IsWorldReady) if (!Context.IsWorldReady)
return; return;
@ -266,7 +64,15 @@ namespace stardew_access
// Narrate Health And Energy // Narrate Health And Energy
if (Equals(e.Button, SButton.I)) 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);
} }
} }

View File

@ -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<string> 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);
}
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}
}
}