stardew-access/stardew-access/ModEntry.cs

416 lines
17 KiB
C#

using stardew_access.Game;
using StardewModdingAPI;
using StardewModdingAPI.Events;
using StardewValley;
using HarmonyLib;
using StardewValley.Menus;
using Microsoft.Xna.Framework.Graphics;
using stardew_access.Patches;
using AutoHotkey.Interop;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using System.Text.RegularExpressions;
namespace stardew_access
{
public class MainClass : Mod
{
private Harmony? harmony;
private static bool readTile = true, snapMouse = true, isNarratingHudMessage = false;
public static IMonitor? monitor;
AutoHotkeyEngine ahk;
public static string hudMessageQueryKey = "";
/*********
** Public methods
*********/
/// <summary>The mod entry point, called after the mod is first loaded.</summary>
/// <param name="helper">Provides simplified APIs for writing mods.</param>
public override void Entry(IModHelper helper)
{
#region Initializations
monitor = Monitor; // Inititalize monitor
Game1.options.setGamepadMode("force_on");
// Initialize AutoHotKey
try
{
ahk = AutoHotkeyEngine.Instance;
ahk.ExecRaw("[::\nSend {LButton}");
ahk.ExecRaw("^Enter::\nSend {LButton}");
ahk.ExecRaw("]::\nSend {RButton}");
ahk.ExecRaw("+Enter::\nSend {RButton}");
}
catch (Exception e)
{
monitor.Log($"Unable to initialize AutoHotKey:\n{e.Message}\n{e.StackTrace}", LogLevel.Error);
}
ScreenReader.initializeScreenReader(); // Initialize the screen reader
this.initializeSounds();
harmony = new Harmony(ModManifest.UniqueID); // Init harmony
#endregion
#region Harmony Patches
#region Dialogue Patches
harmony.Patch(
original: AccessTools.Method(typeof(DialogueBox), nameof(DialogueBox.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(DialoguePatches), nameof(DialoguePatches.DialoguePatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(DialogueBox), nameof(DialogueBox.receiveLeftClick)),
postfix: new HarmonyMethod(typeof(DialoguePatches), nameof(DialoguePatches.ClearDialogueString))
);
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(DialoguePatches), nameof(DialoguePatches.HoverTextPatch))
);
#endregion
#region Title Menu Patches
harmony.Patch(
original: AccessTools.Method(typeof(TitleMenu), nameof(TitleMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(TitleMenuPatches), nameof(TitleMenuPatches.TitleMenuPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(LoadGameMenu.SaveFileSlot), nameof(LoadGameMenu.SaveFileSlot.Draw), new Type[] { typeof(SpriteBatch), typeof(int) }),
postfix: new HarmonyMethod(typeof(TitleMenuPatches), nameof(TitleMenuPatches.LoadGameMenuPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(CharacterCustomization), nameof(CharacterCustomization.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(TitleMenuPatches), nameof(TitleMenuPatches.NewGameMenuPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(CoopMenu), nameof(CoopMenu.update), new Type[] { typeof(GameTime) }),
postfix: new HarmonyMethod(typeof(TitleMenuPatches), nameof(TitleMenuPatches.CoopMenuPatch))
);
#endregion
#region Game Menu Patches
harmony.Patch(
original: AccessTools.Method(typeof(GameMenu), nameof(GameMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.GameMenuPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(OptionsPage), nameof(OptionsPage.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.OptionsPagePatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(ExitPage), nameof(ExitPage.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.ExitPagePatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(CraftingPage), nameof(CraftingPage.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.CraftingPagePatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(InventoryPage), nameof(InventoryPage.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.InventoryPagePatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(ItemGrabMenu), nameof(ItemGrabMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.ItemGrabMenuPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(GeodeMenu), nameof(GeodeMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.GeodeMenuPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(ShopMenu), nameof(ShopMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.ShopMenuPatch))
);
#endregion
#region Menu Patches
harmony.Patch(
original: AccessTools.Method(typeof(LetterViewerMenu), nameof(LetterViewerMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.LetterViewerMenuPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(ShippingMenu), nameof(ShippingMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.ShippingMenuPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(LevelUpMenu), nameof(LevelUpMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.LevelUpMenuPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(ConfirmationDialog), nameof(ConfirmationDialog.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.ConfirmationDialogPatch))
);
harmony.Patch(
original: AccessTools.Constructor(typeof(NamingMenu), new Type[] { typeof(NamingMenu.doneNamingBehavior), typeof(string), typeof(string) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.NamingMenuPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(MineElevatorMenu), nameof(MineElevatorMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.MineElevatorMenuPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(LanguageSelectionMenu), nameof(LanguageSelectionMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.LanguageSelectionMenuPatch))
);
#endregion
#region Quest Patches
harmony.Patch(
original: AccessTools.Method(typeof(SpecialOrdersBoard), nameof(SpecialOrdersBoard.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(QuestPatches), nameof(QuestPatches.SpecialOrdersBoardPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(QuestLog), nameof(QuestLog.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(QuestPatches), nameof(QuestPatches.QuestLogPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(Billboard), nameof(Billboard.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(QuestPatches), nameof(QuestPatches.BillboardPatch))
);
#endregion
#region Chat Menu Patches
harmony.Patch(
original: AccessTools.Method(typeof(ChatBox), nameof(ChatBox.update), new Type[] { typeof(GameTime) }),
postfix: new HarmonyMethod(typeof(ChatManuPatches), nameof(ChatManuPatches.ChatBoxPatch))
);
#endregion
#region On Menu CLose Patch
harmony.Patch(
original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.exitThisMenu)),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.IClickableMenuOnExitPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(Game1), nameof(Game1.exitActiveMenu)),
prefix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.Game1ExitActiveMenuPatch))
);
#endregion
harmony.Patch(
original: AccessTools.Method(typeof(Game1), nameof(Game1.playSound)),
prefix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.PlaySoundPatch))
);
#endregion
#region Custom Commands
helper.ConsoleCommands.Add("read_tile", "Toggle read tile feature", (string arg1, string[] arg2) =>
{
readTile = !readTile;
monitor.Log("Read Tile is " + (readTile ? "on" : "off"), LogLevel.Info);
});
helper.ConsoleCommands.Add("snap_mouse", "Toggle snap mouse feature", (string arg1, string[] arg2) =>
{
snapMouse = !snapMouse;
monitor.Log("Snap Mouse is " + (snapMouse ? "on" : "off"), LogLevel.Info);
});
helper.ConsoleCommands.Add("ref_sr", "Refresh screen reader", (string arg1, string[] arg2) =>
{
ScreenReader.initializeScreenReader();
monitor.Log("Screen Reader refreshed!", LogLevel.Info);
});
#endregion
helper.Events.Input.ButtonPressed += this.OnButtonPressed;
helper.Events.GameLoop.UpdateTicked += this.onUpdateTicked;
}
private void initializeSounds()
{
try
{
#region Drop Item Sound
CueDefinition dropItemCueDef = new CueDefinition();
dropItemCueDef.name = "sa_drop_item";
dropItemCueDef.instanceLimit = 1;
dropItemCueDef.limitBehavior = CueDefinition.LimitBehavior.ReplaceOldest;
SoundEffect dropItemAudio;
string dropItemFilePath = Path.Combine(this.Helper.DirectoryPath, "sounds/drop_item.wav");
using (FileStream stream = new(dropItemFilePath, FileMode.Open))
{
dropItemAudio = SoundEffect.FromStream(stream);
}
dropItemCueDef.SetSound(dropItemAudio, Game1.audioEngine.GetCategoryIndex("Sound"), false);
#endregion
#region Colliding sound
CueDefinition collidingCueDef = new CueDefinition();
collidingCueDef.name = "sa_colliding";
collidingCueDef.instanceLimit = 1;
collidingCueDef.limitBehavior = CueDefinition.LimitBehavior.ReplaceOldest;
SoundEffect collidingAudio;
string collidingFilePath = Path.Combine(Path.Combine(this.Helper.DirectoryPath), "sounds/NPC.wav");
using (FileStream stream = new(collidingFilePath, FileMode.Open))
{
collidingAudio = SoundEffect.FromStream(stream);
}
collidingCueDef.SetSound(collidingAudio, Game1.audioEngine.GetCategoryIndex("Sound"), false);
#endregion
Game1.soundBank.AddCue(dropItemCueDef);
Game1.soundBank.AddCue(collidingCueDef);
}
catch (Exception e)
{
monitor.Log($"Unable to initialize custom sounds:\n{e.Message}\n{e.StackTrace}", LogLevel.Error);
}
}
private void onUpdateTicked(object sender, UpdateTickedEventArgs e)
{
if (!Context.IsPlayerFree)
return;
// Reset variables
MenuPatches.resetGlobalVars();
QuestPatches.resetGlobalVars();
SlotAndLocation.narrateCurrentSlot();
SlotAndLocation.narrateCurrentLocation();
if (snapMouse)
SnapMouseToPlayer();
if(!ReadTile.isReadingTile && readTile)
ReadTile.run();
if (!isNarratingHudMessage)
{
narrateHudMessages();
}
}
private void OnButtonPressed(object sender, ButtonPressedEventArgs e)
{
if (!Context.IsPlayerFree)
return;
// Narrate health and stamina
if (Equals(e.Button, SButton.H))
{
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.getPositionY()}";
ScreenReader.say(toSpeak, true);
}
// Narrate money at hand
if (Equals(e.Button, SButton.R))
{
string toSpeak = $"You have {CurrentPlayer.getMoney()}g";
ScreenReader.say(toSpeak, true);
}
// Narrate time and season
if (Equals(e.Button, SButton.Q))
{
string toSpeak = $"Time is {CurrentPlayer.getTimeOfDay()} and it is {CurrentPlayer.getDay()} {CurrentPlayer.getDate()} of {CurrentPlayer.getSeason()}";
ScreenReader.say(toSpeak, true);
}
// Manual read tile
if(Equals(e.Button, SButton.J))
{
ReadTile.run(manuallyTriggered: true);
}
}
private void SnapMouseToPlayer()
{
int x = Game1.player.GetBoundingBox().Center.X - Game1.viewport.X;
int y = Game1.player.GetBoundingBox().Center.Y - Game1.viewport.Y;
int offset = 64;
switch (Game1.player.FacingDirection)
{
case 0:
y -= offset;
break;
case 1:
x += offset;
break;
case 2:
y += offset;
break;
case 3:
x -= offset;
break;
}
Game1.setMousePosition(x, y);
}
public static async void narrateHudMessages()
{
isNarratingHudMessage = true;
try
{
if(Game1.hudMessages.Count > 0)
{
int lastIndex = Game1.hudMessages.Count - 1;
HUDMessage lastMessage = Game1.hudMessages[lastIndex];
if (!lastMessage.noIcon)
{
string toSpeak = lastMessage.Message;
string searchQuery = toSpeak;
searchQuery = Regex.Replace(toSpeak, @"[\d+]", string.Empty);
searchQuery.Trim();
if (hudMessageQueryKey != searchQuery)
{
hudMessageQueryKey = searchQuery;
ScreenReader.say(toSpeak, true);
}
}
}
}
catch (Exception e)
{
MainClass.monitor.Log($"Unable to narrate hud messages:\n{e.Message}\n{e.StackTrace}", LogLevel.Error);
}
await Task.Delay(300);
isNarratingHudMessage = false;
}
}
}