diff --git a/TASMod/Assets/lua/core/engine.lua b/TASMod/Assets/lua/core/engine.lua index e4901dd..ae0e83b 100644 --- a/TASMod/Assets/lua/core/engine.lua +++ b/TASMod/Assets/lua/core/engine.lua @@ -11,7 +11,9 @@ function engine.halt(max_frames) local i = 0 while interface.HasStep and i < max_frames do i = i + 1 + interface:WaitPrefix() interface:StepLogic() + interface:WaitPostfix() end end diff --git a/TASMod/Automation/DialogueBox.cs b/TASMod/Automation/DialogueBox.cs index fef2b2b..bc55e6e 100644 --- a/TASMod/Automation/DialogueBox.cs +++ b/TASMod/Automation/DialogueBox.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using StardewValley; using TASMod.Helpers; using TASMod.Inputs; @@ -21,6 +22,9 @@ public override bool ActiveUpdate(out TASKeyboardState kstate, out TASMouseState if (!CurrentMenu.Active || !CurrentMenu.IsDialogue) return false; + if (Game1.currentMinigame != null) + return false; + // transitioning on/off screen if (CurrentMenu.Transitioning) return true; diff --git a/TASMod/Console/Subconsoles/LuaConsole.cs b/TASMod/Console/Subconsoles/LuaConsole.cs index 383d693..a9381de 100644 --- a/TASMod/Console/Subconsoles/LuaConsole.cs +++ b/TASMod/Console/Subconsoles/LuaConsole.cs @@ -74,13 +74,13 @@ public override void ReceiveInput(string input, bool writeAsEntry = true) Unsubscribe(); return; } - if (input == "reload") - { - Clear(); - LuaEngine.Reload(); - WriteAsEntry(input); - return; - } + //if (input == "reload") + //{ + // Clear(); + // LuaEngine.Reload(); + // WriteAsEntry(input); + // return; + //} if (input == "clr") { Clear(); diff --git a/TASMod/Controller.cs b/TASMod/Controller.cs index 77cd449..52157c1 100644 --- a/TASMod/Controller.cs +++ b/TASMod/Controller.cs @@ -39,7 +39,7 @@ public class Controller public static bool FastAdvance = false; public static bool AcceptRealInput = true; public static int FramesBetweenRender = 60; - + public static bool SkipSave = false; public static PerformanceTiming Timing; public static TASMouseState LogicMouse = null; public static TASKeyboardState LogicKeyboard = null; @@ -119,11 +119,6 @@ public static void OverrideStaticDefaults() defaults[i] = new Random(0); Game1.recentMultiplayerRandom = new Random(0); } - if (fields[i].Name == "savePathOverride") - { - defaults[i] = Constants.SavesPath; - Game1.savePathOverride = Constants.SavesPath; - } } //ModEntry.Console.Log($"number of statics: {defaults.Count}"); } diff --git a/TASMod/Extensions/GameRunner.cs b/TASMod/Extensions/GameRunner.cs index f80066c..db591cc 100644 --- a/TASMod/Extensions/GameRunner.cs +++ b/TASMod/Extensions/GameRunner.cs @@ -118,6 +118,7 @@ public static void Step(this GameRunner runner) //ModEntry.Console.Log($"\tinvoking EndDraw... {gameTime.TotalGameTime}", LogLevel.Error); runner.InvokeEndDraw(); //ModEntry.Console.Log($"finished step. {gameTime.TotalGameTime}", LogLevel.Error); + runner.EventLoop(); } public static void RunFast(this GameRunner runner) diff --git a/TASMod/Helpers/CurrentLocation.cs b/TASMod/Helpers/CurrentLocation.cs index b72beb0..039bbba 100644 --- a/TASMod/Helpers/CurrentLocation.cs +++ b/TASMod/Helpers/CurrentLocation.cs @@ -6,6 +6,13 @@ using StardewValley; using StardewValley.Monsters; using StardewValley.Locations; +using StardewValley.Objects; +using StardewValley.Projectiles; +using StardewValley.TerrainFeatures; +using System.Xml.Linq; +using xTile; +using xTile.Layers; +using xTile.ObjectModel; namespace TASMod.Helpers { diff --git a/TASMod/Helpers/CurrentMenu.cs b/TASMod/Helpers/CurrentMenu.cs index 42145a0..e95c95d 100644 --- a/TASMod/Helpers/CurrentMenu.cs +++ b/TASMod/Helpers/CurrentMenu.cs @@ -1,10 +1,11 @@ using System; +using System.Collections.Generic; using StardewValley; using StardewValley.Menus; namespace TASMod.Helpers { - public static class CurrentMenu + public class CurrentMenu { public static bool Active { get { return Game1.activeClickableMenu != null; } } @@ -59,6 +60,67 @@ public static string CurrentString return (Game1.activeClickableMenu as DialogueBox).getCurrentString(); } } + + public static Dictionary GetPageRecipes(CraftingPage page) + { + int currentCraftingPage = (int)Reflector.GetValue(page, "currentCraftingPage"); + List containerContents = Reflector.InvokeMethod>(page, "getContainerContents"); + Dictionary recipes = new Dictionary(); + foreach (ClickableTextureComponent key in page.pagesOfCraftingRecipes[currentCraftingPage].Keys) + { + string name = page.pagesOfCraftingRecipes[currentCraftingPage][key].name; + recipes.Add(name, + page.pagesOfCraftingRecipes[currentCraftingPage][key].doesFarmerHaveIngredientsInInventory(containerContents) + ); + } + return recipes; + } + public static Dictionary CraftingPageRecipes + { + get + { + if (Game1.activeClickableMenu is GameMenu menu) + { + if (menu.pages[menu.currentTab] is CraftingPage page) + { + return GetPageRecipes(page); + } + } + if (Game1.activeClickableMenu is CraftingPage page1) + { + return GetPageRecipes(page1); + } + return null; + } + } + public static int HeldItemStack + { + get + { + CraftingPage page = null; + if (Game1.activeClickableMenu is GameMenu menu) + { + if (menu.pages[menu.currentTab] is CraftingPage p) + { + page = p; + } + } + if (Game1.activeClickableMenu is CraftingPage p2) + { + page = p2; + } + if (page != null) + { + Item item = (Item)Reflector.GetValue(page, "heldItem"); + if (item == null) + { + return 0; + } + return item.Stack; + } + return 0; + } + } } } diff --git a/TASMod/Helpers/PathFinder.cs b/TASMod/Helpers/PathFinder.cs index 4619c37..90f7c98 100644 --- a/TASMod/Helpers/PathFinder.cs +++ b/TASMod/Helpers/PathFinder.cs @@ -161,7 +161,13 @@ public bool IsValid(Tile tile) } if (location.overlayObjects.ContainsKey(tile.toVector2())) { - return false; + Object obj = location.overlayObjects[tile.toVector2()]; + return obj.isPassable(); + } + if (location.Objects.ContainsKey(tile.toVector2())) + { + Object obj = location.Objects[tile.toVector2()]; + return obj.isPassable(); } // check furniture if (location.GetFurnitureAt(tile.toVector2()) != null) diff --git a/TASMod/Helpers/ToolUsage.cs b/TASMod/Helpers/ToolUsage.cs index 5b2c961..f0364e5 100644 --- a/TASMod/Helpers/ToolUsage.cs +++ b/TASMod/Helpers/ToolUsage.cs @@ -34,6 +34,9 @@ public static TASKeyboardState GetToolKey() case 11: kstate.Add("OemPlus"); break; + case 9: + kstate.Add("D0"); + break; default: kstate.Add("D" + (i + 1).ToString()); break; diff --git a/TASMod/LuaScripting/CSInterpreter.cs b/TASMod/LuaScripting/CSInterpreter.cs index 2ca3db7..49d036e 100644 --- a/TASMod/LuaScripting/CSInterpreter.cs +++ b/TASMod/LuaScripting/CSInterpreter.cs @@ -3,6 +3,8 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using StardewValley; +using StardewValley.Menus; +using TASMod.Helpers; namespace TASMod.LuaScripting { @@ -28,6 +30,8 @@ private static void Init() _interpreter.Reference(typeof(Keyboard)); _interpreter.Reference(typeof(Game1)); _interpreter.Reference(typeof(StardewValley.Object)); + _interpreter.Reference(typeof(CraftingPage)); + _interpreter.Reference(typeof(CurrentMenu)); } } } diff --git a/TASMod/LuaScripting/ScriptInterface.cs b/TASMod/LuaScripting/ScriptInterface.cs index a2d9032..bf111c3 100644 --- a/TASMod/LuaScripting/ScriptInterface.cs +++ b/TASMod/LuaScripting/ScriptInterface.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Text; using System.Linq; using NLua; @@ -8,6 +9,7 @@ using Microsoft.Xna.Framework.Input; using StardewValley; using TASMod.Inputs; +using TASMod.System; using TASMod.Recording; using TASMod.Extensions; using TASMod.Console; @@ -30,10 +32,19 @@ public class ScriptInterface public static Random LastMinesGetTreasureRoomItem; public static Random LastArtifactSpotRNG; + public bool SimulateRealAdvance = false; + public double SleepRetry = 2; + public Stopwatch _gameTimer; + public long _previousTicks = 0; + public TimeSpan _accumulatedElapsedTime; + public TimeSpan _targetElapsedTime = TimeSpan.FromTicks(166667); // 60fps + + public ScriptInterface() { _instance = this; KeyBinds = new Dictionary>(); + _gameTimer = Stopwatch.StartNew(); } public void PrintKeyBinds() @@ -113,10 +124,35 @@ public bool HasStep } } + public void WaitPrefix() + { + // uses the same logic as https://github.com/MonoGame/MonoGame/blob/develop/MonoGame.Framework/Game.cs#L58 + // idea is to force a sleep until the next tick should fire + // not super accurate but from testing reaches pretty close to 60fps + PrefixTicks: + var currentTicks = _gameTimer.Elapsed.Ticks; + _accumulatedElapsedTime += TimeSpan.FromTicks(currentTicks - _previousTicks); + _previousTicks = currentTicks; + + if (SimulateRealAdvance && _accumulatedElapsedTime < _targetElapsedTime) + { + var sleepTime = (_targetElapsedTime - _accumulatedElapsedTime).TotalMilliseconds; + if (sleepTime >= SleepRetry) + Thread.Sleep(1); + goto PrefixTicks; + } + + } + public void WaitPostfix() + { + _accumulatedElapsedTime = TimeSpan.Zero; + } + public void AdvanceFrame(LuaTable input) { + RealInputState.Update(); + WaitPostfix(); ReadInputStates(input, out TASKeyboardState kstate, out TASMouseState mstate); - Controller.State.FrameStates.Add( new FrameState( kstate.GetKeyboardState(), @@ -124,9 +160,14 @@ public void AdvanceFrame(LuaTable input) ) ); StepLogic(); + WaitPostfix(); } public void StepLogic() { + if (RealInputState.IsKeyDown(Keys.OemMinus) && RealInputState.IsKeyDown(Keys.OemPlus)) + { + throw new Exception("dropping out of step logic"); + } Controller.AcceptRealInput = false; GameRunner.instance.Step(); Controller.AcceptRealInput = true; @@ -258,6 +299,14 @@ public MineShaft SpawnMineShaft(int level) Game1.random = old_random; return mineShaft; } + + public void SetTargetFPS(int fps) + { + // 60 fps => 166667 + double rate = 60 / fps; + int ticks = (int)(166667 * rate); + _targetElapsedTime = TimeSpan.FromTicks(ticks); + } } } diff --git a/TASMod/Overlays/TextPanel.cs b/TASMod/Overlays/TextPanel.cs new file mode 100644 index 0000000..4e6d156 --- /dev/null +++ b/TASMod/Overlays/TextPanel.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.Xna.Framework.Graphics; +using StardewValley; +using StardewValley.BellsAndWhistles; +namespace TASMod.Overlays +{ + public class TextPanel : IOverlay + { + public string Text {get;set;} = ""; + //"012345678901234567890".Length; + public readonly int MaxLength = 21; + public int LeftPadding {get;set;} = 1; + public int HeightPadding {get;set;} = 34; + public override string Name => "TextPanel"; + + public override string Description => "Define some custom text to display on the screen"; + + public override void ActiveDraw(SpriteBatch spriteBatch) + { + if (Game1.currentLocation == null) return; + + Microsoft.Xna.Framework.Rectangle tsarea = Game1.game1.GraphicsDevice.Viewport.GetTitleSafeArea(); + tsarea.X += SpriteText.getWidthOfString(new string(' ', LeftPadding)) + 16; + int fontHeight = (int)(SpriteText.getHeightOfString(" ") + HeightPadding); + if (Text != "") { + SpriteText.drawString( + spriteBatch, + Text.Substring(0, Math.Min(Text.Length, MaxLength)), + tsarea.Left, tsarea.Bottom - fontHeight, + 999999, -1, 999999, 1f, 1f, false, 2, "", 4 + ); + } + } + } +} + diff --git a/TASMod/Patches/LoadGameMenu.cs b/TASMod/Patches/LoadGameMenu.cs new file mode 100644 index 0000000..787b6aa --- /dev/null +++ b/TASMod/Patches/LoadGameMenu.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using HarmonyLib; +using StardewValley; +using StardewValley.Menus; + +namespace TASMod.Patches +{ + public class LoadGameMenu_FindSaveGames : IPatch + { + public static string BaseKey = "LoadGameMenu.FindSaveGames"; + public override string Name => BaseKey; + + public override void Patch(Harmony harmony) + { + harmony.Patch( + original: AccessTools.Method(typeof(LoadGameMenu), BaseKey.Split(".")[1]), + prefix: new HarmonyMethod(this.GetType(), nameof(this.Prefix)), + postfix: new HarmonyMethod(this.GetType(), nameof(this.Postfix)) + ); + } + + public static bool Prefix() + { + return false; + } + + public static void Postfix(ref List __result) + { + __result = new List(); + string text = Constants.SavesPath; + if (Directory.Exists(text)) + { + foreach (string item in Directory.EnumerateDirectories(text).ToList()) + { + string text2 = item.Split(Path.DirectorySeparatorChar).Last(); + string text3 = Path.Combine(text, item, "SaveGameInfo"); + if (!File.Exists(Path.Combine(text, item, text2))) + { + continue; + } + + Farmer farmer = null; + try + { + using FileStream stream = File.OpenRead(text3); + farmer = (Farmer)SaveGame.farmerSerializer.Deserialize(stream); + SaveGame.loadDataToFarmer(farmer); + farmer.slotName = text2; + __result.Add(farmer); + } + catch (Exception ex) + { + Trace($"Failed to load save {text2}: {ex}"); + farmer?.unload(); + } + } + } + __result.Sort(); + } + } +} \ No newline at end of file diff --git a/TASMod/Patches/Path.cs b/TASMod/Patches/Path.cs new file mode 100644 index 0000000..2bd894b --- /dev/null +++ b/TASMod/Patches/Path.cs @@ -0,0 +1,35 @@ +// Force the game to use the new save path associated with your StardewTAS folder. +// Games will save/load into this folder instead of using your actual save files. +// The hope is that this will allow you to use your actual save files while TASing and avoid corruption. +using System; +using System.IO; +using HarmonyLib; + +namespace TASMod.Patches +{ + public class Path_Combine : IPatch + { + public static string BaseKey = "Path.Combine"; + public override string Name => BaseKey; + + public override void Patch(Harmony harmony) + { + harmony.Patch( + original: AccessTools.Method(typeof(Path), BaseKey.Split(".")[1], new Type[] { typeof(string),typeof(string) }), + prefix: new HarmonyMethod(this.GetType(), nameof(this.Prefix)) + ); + } + + public static bool Prefix(ref string path1) + { + string delim = Path.DirectorySeparatorChar.ToString(); + string origSavePath = "StardewValley" + delim + "Saves"; + if (path1.Contains(origSavePath)) + { + int index = path1.IndexOf(origSavePath); + path1 = Constants.SavesPath + delim + path1.Substring(index + origSavePath.Length); + } + return true; + } + } +} \ No newline at end of file diff --git a/TASMod/Patches/SaveGameMenu.cs b/TASMod/Patches/SaveGameMenu.cs index fc5347a..3ea6d79 100644 --- a/TASMod/Patches/SaveGameMenu.cs +++ b/TASMod/Patches/SaveGameMenu.cs @@ -24,7 +24,7 @@ public static bool Prefix(ref SaveGameMenu __instance, out bool __state) { var loader = (IEnumerator)Reflector.GetValue(__instance, "loader"); var completePause = (int)Reflector.GetValue(__instance, "completePause"); - __state = loader == null && __instance.hasDrawn && completePause == -1; + __state = loader == null && __instance.hasDrawn && completePause == -1 && Controller.SkipSave; return true; }