From bf59be43dc01c0bd97d0ccc61ab8b7474cc61af4 Mon Sep 17 00:00:00 2001 From: Underscore76 <34466574+Underscore76@users.noreply.github.com> Date: Wed, 23 Dec 2020 14:54:53 -0600 Subject: [PATCH] init commit --- .gitignore | 27 +++ SpeedrunPractice.sln | 25 +++ SpeedrunPractice/Framework/Alerts.cs | 26 +++ .../Framework/AnimationCancelHelper.cs | 103 ++++++++++++ SpeedrunPractice/Framework/DrawHelper.cs | 83 +++++++++ SpeedrunPractice/Framework/PlayerInfo.cs | 53 ++++++ SpeedrunPractice/Framework/Reflector.cs | 159 ++++++++++++++++++ SpeedrunPractice/ModEntry.cs | 57 +++++++ SpeedrunPractice/Properties/AssemblyInfo.cs | 36 ++++ SpeedrunPractice/SpeedrunPractice.csproj | 72 ++++++++ SpeedrunPractice/manifest.json | 10 ++ SpeedrunPractice/packages.config | 4 + 12 files changed, 655 insertions(+) create mode 100644 .gitignore create mode 100644 SpeedrunPractice.sln create mode 100644 SpeedrunPractice/Framework/Alerts.cs create mode 100644 SpeedrunPractice/Framework/AnimationCancelHelper.cs create mode 100644 SpeedrunPractice/Framework/DrawHelper.cs create mode 100644 SpeedrunPractice/Framework/PlayerInfo.cs create mode 100644 SpeedrunPractice/Framework/Reflector.cs create mode 100644 SpeedrunPractice/ModEntry.cs create mode 100644 SpeedrunPractice/Properties/AssemblyInfo.cs create mode 100644 SpeedrunPractice/SpeedrunPractice.csproj create mode 100644 SpeedrunPractice/manifest.json create mode 100644 SpeedrunPractice/packages.config diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9bebe43 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ + +# user-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# build results +[Dd]ebug/ +[Rr]elease/ +[Bb]in/ +[Oo]bj/ +_releases/ + +# Visual Studio cache/options +.vs/ + +# ReSharper +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# NuGet packages +*.nupkg +**/packages/* +*.nuget.props +*.nuget.targets \ No newline at end of file diff --git a/SpeedrunPractice.sln b/SpeedrunPractice.sln new file mode 100644 index 0000000..04f50b5 --- /dev/null +++ b/SpeedrunPractice.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30804.86 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpeedrunPractice", "SpeedrunPractice\SpeedrunPractice.csproj", "{DB5337FB-B148-4A01-A603-C7664418B04E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DB5337FB-B148-4A01-A603-C7664418B04E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB5337FB-B148-4A01-A603-C7664418B04E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB5337FB-B148-4A01-A603-C7664418B04E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB5337FB-B148-4A01-A603-C7664418B04E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FCAAF1A5-A8BC-49C1-B6B4-0E2F195C27C5} + EndGlobalSection +EndGlobal diff --git a/SpeedrunPractice/Framework/Alerts.cs b/SpeedrunPractice/Framework/Alerts.cs new file mode 100644 index 0000000..8667c7c --- /dev/null +++ b/SpeedrunPractice/Framework/Alerts.cs @@ -0,0 +1,26 @@ +using StardewModdingAPI; +using StardewValley; + +class Alerts +{ + public static void Success(string message) + { + HUDMessage hudMessage = new HUDMessage(message, HUDMessage.achievement_type); + hudMessage.timeLeft = 1500f; + Game1.addHUDMessage(hudMessage); + } + + public static void Failure(string message) + { + HUDMessage hudMessage = new HUDMessage(message, HUDMessage.error_type); + hudMessage.timeLeft = 1500f; + Game1.addHUDMessage(hudMessage); + } + + public static void Info(string message) + { + HUDMessage hudMessage = new HUDMessage(message, HUDMessage.newQuest_type); + hudMessage.timeLeft = 1500f; + Game1.addHUDMessage(hudMessage); + } +} \ No newline at end of file diff --git a/SpeedrunPractice/Framework/AnimationCancelHelper.cs b/SpeedrunPractice/Framework/AnimationCancelHelper.cs new file mode 100644 index 0000000..eab5eb9 --- /dev/null +++ b/SpeedrunPractice/Framework/AnimationCancelHelper.cs @@ -0,0 +1,103 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using StardewModdingAPI; +using StardewValley; +using StardewValley.Tools; +//using Rectangle = xTile.Dimensions.Rectangle; + + +namespace SpeedrunPractice.Framework +{ + public class AnimationCancelHelper + { + private bool isCancellableSwing; + private const int FadeCounterMax = 60; + private int fadeCounter; + private Color TooEarlyColor = Color.Red; + private Color ValidColor = Color.Green; + private int CurrentFrame; + private List AnimationFrames; + private List AnimationColors; + private TimeSpan FrameTimeSpan = new TimeSpan(166667); + private List ValidAnimationTypes; + + public AnimationCancelHelper() + { + ValidAnimationTypes = new List() + { + FarmerSprite.toolUp, + FarmerSprite.toolRight, + FarmerSprite.toolDown, + FarmerSprite.toolLeft, + 180, 172, 164, 188 // watering can based + }; + } + + public void Update(IMonitor monitor, IModHelper helper) + { + if (PlayerInfo.UsingTool && !(PlayerInfo.CurrentTool is MeleeWeapon) && PlayerInfo.CurrentSprite != null) + { + int animationType = PlayerInfo.AnimationType; + if (!isCancellableSwing) + CurrentFrame = 1; + else + CurrentFrame++; + isCancellableSwing = ValidAnimationTypes.Contains(animationType); + fadeCounter = FadeCounterMax; + GetAnimationCancelDetails(PlayerInfo.CurrentSprite, out AnimationFrames, out AnimationColors); + + } + else + { + isCancellableSwing = false; + if (fadeCounter > 0) + { + fadeCounter--; + } + if (fadeCounter <= 0) + { + AnimationFrames = null; + AnimationColors = null; + CurrentFrame = -1; + } + } + + } + + public void Draw(SpriteBatch spriteBatch) + { + // draw centered quarter screen + // draw 1/3 down from top + // draw rectangle + Vector2 playerTile = Game1.player.getTileLocation(); + Rectangle progressRectGlobal = new Rectangle((int)(playerTile.X - 3 + 0.5) * Game1.tileSize, (int)(playerTile.Y - 2) * Game1.tileSize, Game1.tileSize * 6, Game1.tileSize / 2); + Rectangle progressRect = Game1.GlobalToLocal(Game1.viewport, progressRectGlobal); + if (fadeCounter > 0) + { + DrawHelper.DrawProgressBar(spriteBatch, progressRect, AnimationFrames, AnimationColors, CurrentFrame, Color.LightYellow); + } + } + + private void GetAnimationCancelDetails(FarmerSprite sprite, out List animationFrames, out List animationState) + { + animationFrames = new List(); + animationState = new List(); + bool canAnimCancel = false; + for(int i = 0; i < sprite.CurrentAnimation.Count; i++) + { + int frames = Math.Max(1, (int)((sprite.CurrentAnimation[i].milliseconds + FrameTimeSpan.Milliseconds - 1) / FrameTimeSpan.TotalMilliseconds)); + animationFrames.Add(frames); + if (sprite.CurrentAnimation[i].frameStartBehavior != null && sprite.CurrentAnimation[i].frameStartBehavior.Method.Name.Equals("useTool")) + canAnimCancel = true; + if (i > 0 && sprite.CurrentAnimation[i-1].frameEndBehavior != null && sprite.CurrentAnimation[i-1].frameEndBehavior.Method.Name.Equals("useTool")) + canAnimCancel = true; + animationState.Add(canAnimCancel ? ValidColor : TooEarlyColor); + } + } + } +} diff --git a/SpeedrunPractice/Framework/DrawHelper.cs b/SpeedrunPractice/Framework/DrawHelper.cs new file mode 100644 index 0000000..c8917cd --- /dev/null +++ b/SpeedrunPractice/Framework/DrawHelper.cs @@ -0,0 +1,83 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using StardewValley; + +namespace SpeedrunPractice.Framework +{ + class DrawHelper + { + private static Texture2D SolidColor; + private const int OutlineWidth = 1; + private static Color OutlineColor = Color.Black; + static DrawHelper() + { + SolidColor = new Texture2D(Game1.graphics.GraphicsDevice, 1, 1, false, SurfaceFormat.Color); + Color[] data = new Color[1] { new Color(255, 255, 255, 255) }; + SolidColor.SetData(data); + } + + + public static void DrawProgressBar(SpriteBatch spriteBatch, Rectangle region, List zoneCounts, List zoneColors) + { + // determine number of blocks + int numZones = zoneCounts.Sum(); + if (numZones == 0) + return; + // normalize for weird float issues + int zoneWidth = region.Width / numZones; + region.Width = zoneWidth * numZones; + // draw the base rectangle + DrawOutlinedRect(spriteBatch, region, OutlineWidth, Color.White, Color.Black); + + // draw the different zones + Rectangle zoneRegion = new Rectangle(region.X, region.Y, zoneWidth, region.Height); + for (int i = 0; i < zoneCounts.Count; ++i) + { + for (int j = 0; j < zoneCounts[i]; j++) + { + DrawOutlinedRect(spriteBatch, zoneRegion, OutlineWidth, zoneColors[i], Color.Black); + zoneRegion.X += zoneWidth; + } + } + } + + public static void DrawProgressBar(SpriteBatch spriteBatch, Rectangle region, List zoneCounts, List zoneColors, int highlightZone, Color highlightColor) + { + // determine number of blocks + int numZones = zoneCounts.Sum(); + if (numZones == 0) + return; + // normalize for weird float issues + int zoneWidth = region.Width / numZones; + region.Width = zoneWidth * numZones; + // draw the base rectangle + DrawOutlinedRect(spriteBatch, region, OutlineWidth, Color.White, OutlineColor); + + // draw the different zones + Rectangle zoneRegion = new Rectangle(region.X, region.Y, zoneWidth, region.Height); + for (int i = 0; i < zoneCounts.Count; ++i) + { + for (int j = 0; j < zoneCounts[i]; j++) + { + DrawOutlinedRect(spriteBatch, zoneRegion, OutlineWidth, zoneColors[i], OutlineColor); + zoneRegion.X += zoneWidth; + } + } + + // draw the highlight + zoneRegion.X = region.X + zoneWidth * highlightZone; + DrawOutlinedRect(spriteBatch, zoneRegion, OutlineWidth, highlightColor, OutlineColor); + } + + public static void DrawOutlinedRect(SpriteBatch spriteBatch, Rectangle region, int lineWidth, Color interiorColor, Color outlineColor) + { + spriteBatch.Draw(SolidColor, region, outlineColor); + spriteBatch.Draw(SolidColor, new Rectangle(region.X + lineWidth, region.Y + lineWidth, region.Width - 2 * lineWidth, region.Height - 2 * lineWidth), interiorColor); + } + } +} diff --git a/SpeedrunPractice/Framework/PlayerInfo.cs b/SpeedrunPractice/Framework/PlayerInfo.cs new file mode 100644 index 0000000..aa5fc5b --- /dev/null +++ b/SpeedrunPractice/Framework/PlayerInfo.cs @@ -0,0 +1,53 @@ +using Microsoft.Xna.Framework; +using StardewModdingAPI; +using StardewValley; +using StardewValley.Tools; +using System; + +namespace SpeedrunPractice.Framework +{ + public class PlayerInfo + { + public static bool Active { get { return Game1.player != null; } } + + public static bool CanMove { get { return Active && Game1.player.CanMove; } } + + public static bool UsingTool { get { return Active && Game1.player.UsingTool; } } + + public static Tool CurrentTool { get { return Game1.player?.CurrentTool; } } + + + public static int FacingDirection { get { return Active ? Game1.player.FacingDirection : -1; } } + + public static FarmerSprite CurrentSprite { get { return Game1.player?.FarmerSprite; } } + + public static int GetMouseFacingDirection(Vector2 position, bool worldCoordinates=true) + { + if (Active) + { + if (!worldCoordinates) + { + position.X += Game1.viewport.X; + position.Y += Game1.viewport.Y; + } + if (Utility.withinRadiusOfPlayer((int)position.X, (int)position.Y, 1, Game1.player) && + (Math.Abs(position.X - (float)Game1.player.getStandingX()) >= 32f || Math.Abs(position.Y - (float)Game1.player.getStandingY()) >= 32f)) + { + return Game1.player.getGeneralDirectionTowards(position, 0, false); + } + } + return FacingDirection; + } + + public static int AnimationType + { + get + { + if (CurrentSprite == null) + return -1; + + return (int)Reflector.GetValue(CurrentSprite, "currentSingleAnimation"); ; + } + } + } +} diff --git a/SpeedrunPractice/Framework/Reflector.cs b/SpeedrunPractice/Framework/Reflector.cs new file mode 100644 index 0000000..53b3472 --- /dev/null +++ b/SpeedrunPractice/Framework/Reflector.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace SpeedrunPractice.Framework +{ + public class Reflector + { + public static Dictionary FieldInfos = new Dictionary(); + public static Dictionary PropertyInfos = new Dictionary(); + public static Dictionary MethodInfos = new Dictionary(); + + public const BindingFlags AllFlags = (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + public const BindingFlags HiddenFlags = (BindingFlags.NonPublic | BindingFlags.Instance); + public static FieldInfo GetField(T type, string field, BindingFlags flags = HiddenFlags) + { + string key = typeof(T).Name + "__" + field; + if (FieldInfos.ContainsKey(key)) + { + return FieldInfos[key]; + } + FieldInfo info = typeof(T).GetField(field, flags); + FieldInfos.Add(key, info); + return info; + } + + public static PropertyInfo GetProperty(T type, string field, BindingFlags flags = HiddenFlags) + { + string key = typeof(T).Name + "__" + field; + if (PropertyInfos.ContainsKey(key)) + { + return PropertyInfos[key]; + } + PropertyInfo info = typeof(T).GetProperty(field, flags); + PropertyInfos.Add(key, info); + return info; + } + public static MethodInfo GetMethod(T type, string field, BindingFlags flags = HiddenFlags) + { + string key = nameof(type) + "__" + field; + if (MethodInfos.ContainsKey(key)) + { + return MethodInfos[key]; + } + MethodInfo info = typeof(T).GetMethod(field, flags); + MethodInfos.Add(key, info); + return info; + } + + + public static V GetValue(T obj, string field, BindingFlags flags = HiddenFlags) + { + FieldInfo info = GetField(obj, field, flags); + if (info != null) + { + V value = (V)info.GetValue(obj); + return value; + } + else + { + PropertyInfo pinfo = GetProperty(obj, field, flags); + if (pinfo != null) + { + V value = (V)pinfo.GetValue(obj, null); + return value; + } + } + return default(V); + } + + public static Type GetValueType(T obj, string field, BindingFlags flags = AllFlags) + { + FieldInfo info = GetField(obj, field, flags); + if (info != null) + { + return info.FieldType; + } + else + { + PropertyInfo pinfo = GetProperty(obj, field, flags); + if (pinfo != null) + { + return pinfo.PropertyType; + } + } + return null; + } + + public static object GetValue(T obj, string field, BindingFlags flags = AllFlags) + { + FieldInfo info = GetField(obj, field, flags); + if (info != null) + { + return info.GetValue(obj); + } + else + { + PropertyInfo pinfo = GetProperty(obj, field, flags); + if (pinfo != null) + { + return pinfo.GetValue(obj, null); + } + } + return null; + } + + public static void SetValue(T obj, string field, V val, BindingFlags flags = HiddenFlags) + { + FieldInfo info = GetField(obj, field, flags); + if (info != null) + { + info.SetValue(obj, val); + } + else + { + PropertyInfo pinfo = GetProperty(obj, field, flags); + if (pinfo != null) + { + pinfo.SetValue(obj, val, null); + } + } + } + + public static void InvokeMethod(T obj, string field, object[] args = null, BindingFlags flags = HiddenFlags) + { + MethodInfo info = GetMethod(obj, field, flags); + if (info != null) + { + info.Invoke(obj, args); + return; + } + + throw new MethodAccessException(string.Format("Method does not exist: {0}::{1}", typeof(T).Name, field)); + } + + public static V InvokeMethod(T obj, string field, object[] args = null, BindingFlags flags = HiddenFlags) + { + MethodInfo info = GetMethod(obj, field, flags); + if (info != null) + { + return (V)info.Invoke(obj, args); + } + + throw new MethodAccessException(string.Format("Method does not exist: {0}::{1}", typeof(T).Name, field)); + } + + public static Type[] GetTypesInNamespace(Assembly assembly, string space) + { + return assembly.GetTypes().Where(t => String.Equals(t.Namespace, space, StringComparison.Ordinal)).ToArray(); + } + + public static Type[] AllTypesInAssembly(Assembly assembly) + { + return assembly.GetTypes().ToArray(); + } + } +} diff --git a/SpeedrunPractice/ModEntry.cs b/SpeedrunPractice/ModEntry.cs new file mode 100644 index 0000000..9cd394c --- /dev/null +++ b/SpeedrunPractice/ModEntry.cs @@ -0,0 +1,57 @@ +using System; +using Microsoft.Xna.Framework; +using SpeedrunPractice.Framework; +using StardewModdingAPI; +using StardewModdingAPI.Events; +using StardewModdingAPI.Utilities; +using StardewValley; + +namespace SpeedrunPractice +{ + /// The mod entry point. + public class ModEntry : Mod + { + + private AnimationCancelHelper animationCancelHelper; + /********* + ** Public methods + *********/ + /// The mod entry point, called after the mod is first loaded. + /// Provides simplified APIs for writing mods. + public override void Entry(IModHelper helper) + { + animationCancelHelper = new AnimationCancelHelper(); + helper.Events.GameLoop.UpdateTicked += GameLoop_UpdateTicked; + helper.Events.Display.RenderedWorld += Display_RenderedWorld; + } + + + + /********* + ** Private methods + *********/ + /// Raised after the frame has been update ticked, before draw. + /// The event sender. + /// The event data. + private void GameLoop_UpdateTicked(object sender, UpdateTickedEventArgs e) + { + // ignore if player hasn't loaded a save yet + if (!Context.IsWorldReady) + return; + + this.animationCancelHelper?.Update(this.Monitor, this.Helper); + } + + /// Raised after the base world has been drawn before HUD elements. + /// The event sender. + /// The event data. + private void Display_RenderedWorld(object sender, RenderedWorldEventArgs e) + { + // ignore if player hasn't loaded a save yet + if (!Context.IsWorldReady) + return; + + animationCancelHelper?.Draw(e.SpriteBatch); + } + } +} \ No newline at end of file diff --git a/SpeedrunPractice/Properties/AssemblyInfo.cs b/SpeedrunPractice/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..439e1be --- /dev/null +++ b/SpeedrunPractice/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SpeedrunPractice")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SpeedrunPractice")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("db5337fb-b148-4a01-a603-c7664418b04e")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SpeedrunPractice/SpeedrunPractice.csproj b/SpeedrunPractice/SpeedrunPractice.csproj new file mode 100644 index 0000000..bc4d265 --- /dev/null +++ b/SpeedrunPractice/SpeedrunPractice.csproj @@ -0,0 +1,72 @@ + + + + + Debug + AnyCPU + {DB5337FB-B148-4A01-A603-C7664418B04E} + Library + Properties + SpeedrunPractice + SpeedrunPractice + v4.5.2 + 512 + true + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/SpeedrunPractice/manifest.json b/SpeedrunPractice/manifest.json new file mode 100644 index 0000000..f98b317 --- /dev/null +++ b/SpeedrunPractice/manifest.json @@ -0,0 +1,10 @@ +{ + "Name": "SpeedrunPractice", + "Author": "Underscore", + "Version": "1.0.0", + "Description": "Practice mod to help improve on speedrun tech", + "UniqueID": "underscore.speedrun_practie", + "EntryDll": "SpeedrunPractice.dll", + "MinimumApiVersion": "3.0.0", + "UpdateKeys": [] +} \ No newline at end of file diff --git a/SpeedrunPractice/packages.config b/SpeedrunPractice/packages.config new file mode 100644 index 0000000..c060e12 --- /dev/null +++ b/SpeedrunPractice/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file