From 2ff937397163f0ad5940b636bc7312ac747d9c39 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 18 Oct 2017 10:59:57 -0400 Subject: fix compatibility check crashing for players with SDV 1.08 --- docs/release-notes.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 99e771ce..fc56adc8 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,4 +1,8 @@ # Release notes +## 2.1 (upcoming) +* For players: + * Fixed compatibility check crashing for players with Stardew Valley 1.08. + ## 2.0 ### Release highlights * **Mod update checks** @@ -18,7 +22,7 @@ SMAPI 2.0 adds several features to enable new kinds of mods (see [API documentation](https://stardewvalleywiki.com/Modding:SMAPI_APIs)). - The **content API** lets you edit, inject, and reload XNB data loaded by the game at any time. This let SMAPI mods do + The **content API** lets you edit, inject, and reload XNB data loaded by the game at any time. This lets SMAPI mods do anything previously only possible with XNB mods, and enables new mod scenarios not possible with XNB mods (e.g. seasonal textures, NPC clothing that depend on the weather or location, etc). -- cgit From 36b4e550f1945ef710fca2c6deab7df94e708ef7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 19 Oct 2017 21:26:00 -0400 Subject: fix e.SuppressButton() in input events not suppressing keyboard buttons --- docs/release-notes.md | 3 +++ src/SMAPI/Events/EventArgsInput.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index fc56adc8..0471874c 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -3,6 +3,9 @@ * For players: * Fixed compatibility check crashing for players with Stardew Valley 1.08. +* For modders: + * Fixed `e.SuppressButton()` in input events not correctly suppressing keyboard buttons. + ## 2.0 ### Release highlights * **Mod update checks** diff --git a/src/SMAPI/Events/EventArgsInput.cs b/src/SMAPI/Events/EventArgsInput.cs index 66cb19f2..617dac35 100644 --- a/src/SMAPI/Events/EventArgsInput.cs +++ b/src/SMAPI/Events/EventArgsInput.cs @@ -49,7 +49,7 @@ namespace StardewModdingAPI.Events { // keyboard if (this.Button.TryGetKeyboard(out Keys key)) - Game1.oldKBState = new KeyboardState(Game1.oldKBState.GetPressedKeys().Except(new[] { key }).ToArray()); + Game1.oldKBState = new KeyboardState(Game1.oldKBState.GetPressedKeys().Union(new[] { key }).ToArray()); // controller else if (this.Button.TryGetController(out Buttons controllerButton)) -- cgit From 53df85f3123f8d9cb00013bb32b61c220ccad697 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 20 Oct 2017 16:37:22 -0400 Subject: enable access to public members using reflection API --- docs/release-notes.md | 1 + src/SMAPI/Framework/Reflection/Reflector.cs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 0471874c..285d9df3 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,7 @@ * Fixed compatibility check crashing for players with Stardew Valley 1.08. * For modders: + * The reflection API now works with public code to simplify mod integrations. * Fixed `e.SuppressButton()` in input events not correctly suppressing keyboard buttons. ## 2.0 diff --git a/src/SMAPI/Framework/Reflection/Reflector.cs b/src/SMAPI/Framework/Reflection/Reflector.cs index 5c2d90fa..23a48505 100644 --- a/src/SMAPI/Framework/Reflection/Reflector.cs +++ b/src/SMAPI/Framework/Reflection/Reflector.cs @@ -38,7 +38,7 @@ namespace StardewModdingAPI.Framework.Reflection throw new ArgumentNullException(nameof(obj), "Can't get a private instance field from a null object."); // get field from hierarchy - IPrivateField field = this.GetFieldFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic); + IPrivateField field = this.GetFieldFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if (required && field == null) throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a private '{name}' instance field."); return field; @@ -52,7 +52,7 @@ namespace StardewModdingAPI.Framework.Reflection public IPrivateField GetPrivateField(Type type, string name, bool required = true) { // get field from hierarchy - IPrivateField field = this.GetFieldFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Static); + IPrivateField field = this.GetFieldFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public); if (required && field == null) throw new InvalidOperationException($"The {type.FullName} object doesn't have a private '{name}' static field."); return field; @@ -73,7 +73,7 @@ namespace StardewModdingAPI.Framework.Reflection throw new ArgumentNullException(nameof(obj), "Can't get a private instance property from a null object."); // get property from hierarchy - IPrivateProperty property = this.GetPropertyFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic); + IPrivateProperty property = this.GetPropertyFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if (required && property == null) throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a private '{name}' instance property."); return property; @@ -87,7 +87,7 @@ namespace StardewModdingAPI.Framework.Reflection public IPrivateProperty GetPrivateProperty(Type type, string name, bool required = true) { // get field from hierarchy - IPrivateProperty property = this.GetPropertyFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Static); + IPrivateProperty property = this.GetPropertyFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); if (required && property == null) throw new InvalidOperationException($"The {type.FullName} object doesn't have a private '{name}' static property."); return property; @@ -107,7 +107,7 @@ namespace StardewModdingAPI.Framework.Reflection throw new ArgumentNullException(nameof(obj), "Can't get a private instance method from a null object."); // get method from hierarchy - IPrivateMethod method = this.GetMethodFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic); + IPrivateMethod method = this.GetMethodFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if (required && method == null) throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a private '{name}' instance method."); return method; @@ -120,7 +120,7 @@ namespace StardewModdingAPI.Framework.Reflection public IPrivateMethod GetPrivateMethod(Type type, string name, bool required = true) { // get method from hierarchy - IPrivateMethod method = this.GetMethodFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Static); + IPrivateMethod method = this.GetMethodFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); if (required && method == null) throw new InvalidOperationException($"The {type.FullName} object doesn't have a private '{name}' static method."); return method; @@ -141,7 +141,7 @@ namespace StardewModdingAPI.Framework.Reflection throw new ArgumentNullException(nameof(obj), "Can't get a private instance method from a null object."); // get method from hierarchy - PrivateMethod method = this.GetMethodFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic, argumentTypes); + PrivateMethod method = this.GetMethodFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, argumentTypes); if (required && method == null) throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a private '{name}' instance method with that signature."); return method; @@ -155,7 +155,7 @@ namespace StardewModdingAPI.Framework.Reflection public IPrivateMethod GetPrivateMethod(Type type, string name, Type[] argumentTypes, bool required = true) { // get field from hierarchy - PrivateMethod method = this.GetMethodFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Static, argumentTypes); + PrivateMethod method = this.GetMethodFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, argumentTypes); if (required && method == null) throw new InvalidOperationException($"The {type.FullName} object doesn't have a private '{name}' static method with that signature."); return method; -- cgit From 85a8959e97e90b30ac8291904838e18f102e97c2 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 21 Oct 2017 21:51:48 -0400 Subject: fix mods which implement IAssetLoader being marked as conflicting with themselves --- docs/release-notes.md | 1 + src/SMAPI/Framework/SContentManager.cs | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 285d9df3..e4b2bccd 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -6,6 +6,7 @@ * For modders: * The reflection API now works with public code to simplify mod integrations. * Fixed `e.SuppressButton()` in input events not correctly suppressing keyboard buttons. + * Fixed mods which implement `IAssetLoader` directly not being allowed to load files due to incorrect conflict detection. ## 2.0 ### Release highlights diff --git a/src/SMAPI/Framework/SContentManager.cs b/src/SMAPI/Framework/SContentManager.cs index db202567..2f5d104f 100644 --- a/src/SMAPI/Framework/SContentManager.cs +++ b/src/SMAPI/Framework/SContentManager.cs @@ -510,16 +510,12 @@ namespace StardewModdingAPI.Framework { foreach (var entry in entries) { - IModMetadata metadata = entry.Key; + IModMetadata mod = entry.Key; IList interceptors = entry.Value; - // special case if mod is an interceptor - if (metadata.Mod is T modAsInterceptor) - yield return new KeyValuePair(metadata, modAsInterceptor); - // registered editors foreach (T interceptor in interceptors) - yield return new KeyValuePair(metadata, interceptor); + yield return new KeyValuePair(mod, interceptor); } } -- cgit From f74321addc79a5616cc0f43e4f5f4b8154fac827 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 22 Oct 2017 13:13:14 -0400 Subject: fix SMAPI blocking reflection access to vanilla members on overridden types (#371) --- docs/release-notes.md | 1 + src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs | 99 ++++++++++++++-------- 2 files changed, 67 insertions(+), 33 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index e4b2bccd..199e32c5 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,6 +7,7 @@ * The reflection API now works with public code to simplify mod integrations. * Fixed `e.SuppressButton()` in input events not correctly suppressing keyboard buttons. * Fixed mods which implement `IAssetLoader` directly not being allowed to load files due to incorrect conflict detection. + * Fixed SMAPI blocking reflection access to vanilla members on overridden types. ## 2.0 ### Release highlights diff --git a/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs b/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs index 8d435416..8788b142 100644 --- a/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using StardewModdingAPI.Framework.Reflection; namespace StardewModdingAPI.Framework.ModHelpers @@ -42,8 +43,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Returns the field wrapper, or null if the field doesn't exist and is false. public IPrivateField GetPrivateField(object obj, string name, bool required = true) { - this.AssertAccessAllowed(obj); - return this.Reflector.GetPrivateField(obj, name, required); + return this.AssertAccessAllowed( + this.Reflector.GetPrivateField(obj, name, required) + ); } /// Get a private static field. @@ -53,8 +55,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Whether to throw an exception if the private field is not found. public IPrivateField GetPrivateField(Type type, string name, bool required = true) { - this.AssertAccessAllowed(type); - return this.Reflector.GetPrivateField(type, name, required); + return this.AssertAccessAllowed( + this.Reflector.GetPrivateField(type, name, required) + ); } /**** @@ -67,8 +70,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Whether to throw an exception if the private property is not found. public IPrivateProperty GetPrivateProperty(object obj, string name, bool required = true) { - this.AssertAccessAllowed(obj); - return this.Reflector.GetPrivateProperty(obj, name, required); + return this.AssertAccessAllowed( + this.Reflector.GetPrivateProperty(obj, name, required) + ); } /// Get a private static property. @@ -78,8 +82,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Whether to throw an exception if the private property is not found. public IPrivateProperty GetPrivateProperty(Type type, string name, bool required = true) { - this.AssertAccessAllowed(type); - return this.Reflector.GetPrivateProperty(type, name, required); + return this.AssertAccessAllowed( + this.Reflector.GetPrivateProperty(type, name, required) + ); } /**** @@ -98,7 +103,6 @@ namespace StardewModdingAPI.Framework.ModHelpers /// public TValue GetPrivateValue(object obj, string name, bool required = true) { - this.AssertAccessAllowed(obj); IPrivateField field = this.GetPrivateField(obj, name, required); return field != null ? field.GetValue() @@ -117,7 +121,6 @@ namespace StardewModdingAPI.Framework.ModHelpers /// public TValue GetPrivateValue(Type type, string name, bool required = true) { - this.AssertAccessAllowed(type); IPrivateField field = this.GetPrivateField(type, name, required); return field != null ? field.GetValue() @@ -133,8 +136,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Whether to throw an exception if the private field is not found. public IPrivateMethod GetPrivateMethod(object obj, string name, bool required = true) { - this.AssertAccessAllowed(obj); - return this.Reflector.GetPrivateMethod(obj, name, required); + return this.AssertAccessAllowed( + this.Reflector.GetPrivateMethod(obj, name, required) + ); } /// Get a private static method. @@ -143,8 +147,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Whether to throw an exception if the private field is not found. public IPrivateMethod GetPrivateMethod(Type type, string name, bool required = true) { - this.AssertAccessAllowed(type); - return this.Reflector.GetPrivateMethod(type, name, required); + return this.AssertAccessAllowed( + this.Reflector.GetPrivateMethod(type, name, required) + ); } /**** @@ -157,8 +162,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Whether to throw an exception if the private field is not found. public IPrivateMethod GetPrivateMethod(object obj, string name, Type[] argumentTypes, bool required = true) { - this.AssertAccessAllowed(obj); - return this.Reflector.GetPrivateMethod(obj, name, argumentTypes, required); + return this.AssertAccessAllowed( + this.Reflector.GetPrivateMethod(obj, name, argumentTypes, required) + ); } /// Get a private static method. @@ -168,33 +174,60 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Whether to throw an exception if the private field is not found. public IPrivateMethod GetPrivateMethod(Type type, string name, Type[] argumentTypes, bool required = true) { - this.AssertAccessAllowed(type); - return this.Reflector.GetPrivateMethod(type, name, argumentTypes, required); + return this.AssertAccessAllowed( + this.Reflector.GetPrivateMethod(type, name, argumentTypes, required) + ); } /********* ** Private methods *********/ - /// Assert that mods can use the reflection helper to access the given type. - /// The type being accessed. - private void AssertAccessAllowed(Type type) + /// Assert that mods can use the reflection helper to access the given member. + /// The field value type. + /// The field being accessed. + /// Returns the same field instance for convenience. + private IPrivateField AssertAccessAllowed(IPrivateField field) { - // validate type namespace - if (type.Namespace != null) - { - string rootSmapiNamespace = typeof(Program).Namespace; - if (type.Namespace == rootSmapiNamespace || type.Namespace.StartsWith(rootSmapiNamespace + ".")) - throw new InvalidOperationException($"SMAPI blocked access by {this.ModName} to its internals through the reflection API. Accessing the SMAPI internals is strongly discouraged since they're subject to change, which means the mod can break without warning."); - } + this.AssertAccessAllowed(field?.FieldInfo); + return field; } - /// Assert that mods can use the reflection helper to access the given type. - /// The object being accessed. - private void AssertAccessAllowed(object obj) + /// Assert that mods can use the reflection helper to access the given member. + /// The property value type. + /// The property being accessed. + /// Returns the same property instance for convenience. + private IPrivateProperty AssertAccessAllowed(IPrivateProperty property) { - if (obj != null) - this.AssertAccessAllowed(obj.GetType()); + this.AssertAccessAllowed(property?.PropertyInfo); + return property; + } + + /// Assert that mods can use the reflection helper to access the given member. + /// The method being accessed. + /// Returns the same method instance for convenience. + private IPrivateMethod AssertAccessAllowed(IPrivateMethod method) + { + this.AssertAccessAllowed(method?.MethodInfo); + return method; + } + + /// Assert that mods can use the reflection helper to access the given member. + /// The member being accessed. + private void AssertAccessAllowed(MemberInfo member) + { + if (member == null) + return; + + // get type which defines the member + Type declaringType = member.DeclaringType; + if (declaringType == null) + throw new InvalidOperationException($"Can't validate access to {member.MemberType} {member.Name} because it has no declaring type."); // should never happen + + // validate access + string rootNamespace = typeof(Program).Namespace; + if (declaringType.Namespace == rootNamespace || declaringType.Namespace?.StartsWith(rootNamespace + ".") == true) + throw new InvalidOperationException($"SMAPI blocked access by {this.ModName} to its internals through the reflection API. Accessing the SMAPI internals is strongly discouraged since they're subject to change, which means the mod can break without warning. (Detected access to {declaringType.FullName}.{member.Name}.)"); } } } -- cgit From 99c8dd79406f5099194d72e26085a49939705259 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 22 Oct 2017 15:07:06 -0400 Subject: add InputButton.ToSButton() extension --- docs/release-notes.md | 6 ++++-- src/SMAPI/SButton.cs | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 199e32c5..65536915 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,8 +4,10 @@ * Fixed compatibility check crashing for players with Stardew Valley 1.08. * For modders: - * The reflection API now works with public code to simplify mod integrations. - * Fixed `e.SuppressButton()` in input events not correctly suppressing keyboard buttons. + * Added support for public code in reflection API, to simplify mod integrations. + * Improved input events: + * Added `ToSButton()` extension for the game's `Game1.options` button type. + * Fixed `e.SuppressButton()` not correctly suppressing keyboard buttons. * Fixed mods which implement `IAssetLoader` directly not being allowed to load files due to incorrect conflict detection. * Fixed SMAPI blocking reflection access to vanilla members on overridden types. diff --git a/src/SMAPI/SButton.cs b/src/SMAPI/SButton.cs index 0ec799db..bd6635c7 100644 --- a/src/SMAPI/SButton.cs +++ b/src/SMAPI/SButton.cs @@ -615,6 +615,18 @@ namespace StardewModdingAPI return (SButton)(SButtonExtensions.ControllerOffset + key); } + /// Get the equivalent for the given button. + /// The Stardew Valley button to convert. + internal static SButton ToSButton(this InputButton input) + { + // derived from InputButton constructors + if (input.mouseLeft) + return SButton.MouseLeft; + if (input.mouseRight) + return SButton.MouseRight; + return input.key.ToSButton(); + } + /// Get the equivalent for the given button. /// The button to convert. /// The keyboard equivalent. -- cgit From ed56cb714d7fb76f3c1b9d2f2e7b7627f8accc70 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 22 Oct 2017 15:09:36 -0400 Subject: replace input events' e.IsClick with better-designed e.IsActionButton and e.IsUseToolButton --- docs/release-notes.md | 3 +++ src/SMAPI/Events/EventArgsInput.cs | 18 +++++++++++++----- src/SMAPI/Events/InputEvents.cs | 15 ++++++++------- src/SMAPI/Framework/SGame.cs | 13 ++++++------- 4 files changed, 30 insertions(+), 19 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 65536915..452fd40a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -6,8 +6,11 @@ * For modders: * Added support for public code in reflection API, to simplify mod integrations. * Improved input events: + * Added `e.IsActionButton` and `e.IsUseToolButton`. * Added `ToSButton()` extension for the game's `Game1.options` button type. + * Deprecated `e.IsClick`, which is limited and unclear. Use `IsActionButton` or `IsUseToolButton` instead. * Fixed `e.SuppressButton()` not correctly suppressing keyboard buttons. + * Fixed `e.IsClick` (now `e.IsActionButton`) ignoring custom key bindings. * Fixed mods which implement `IAssetLoader` directly not being allowed to load files due to incorrect conflict detection. * Fixed SMAPI blocking reflection access to vanilla members on overridden types. diff --git a/src/SMAPI/Events/EventArgsInput.cs b/src/SMAPI/Events/EventArgsInput.cs index 617dac35..ff904675 100644 --- a/src/SMAPI/Events/EventArgsInput.cs +++ b/src/SMAPI/Events/EventArgsInput.cs @@ -2,7 +2,6 @@ using System; using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; -using StardewModdingAPI.Utilities; using StardewValley; namespace StardewModdingAPI.Events @@ -20,7 +19,14 @@ namespace StardewModdingAPI.Events public ICursorPosition Cursor { get; set; } /// Whether the input is considered a 'click' by the game for enabling action. - public bool IsClick { get; } + [Obsolete("Use " + nameof(EventArgsInput.IsActionButton) + " or " + nameof(EventArgsInput.IsUseToolButton) + " instead")] // deprecated in SMAPI 2.1 + public bool IsClick => this.IsActionButton; + + /// Whether the input should trigger actions on the affected tile. + public bool IsActionButton { get; } + + /// Whether the input should use tools on the affected tile. + public bool IsUseToolButton { get; } /********* @@ -29,12 +35,14 @@ namespace StardewModdingAPI.Events /// Construct an instance. /// The button on the controller, keyboard, or mouse. /// The cursor position. - /// Whether the input is considered a 'click' by the game for enabling action. - public EventArgsInput(SButton button, ICursorPosition cursor, bool isClick) + /// Whether the input should trigger actions on the affected tile. + /// Whether the input should use tools on the affected tile. + public EventArgsInput(SButton button, ICursorPosition cursor, bool isActionButton, bool isUseToolButton) { this.Button = button; this.Cursor = cursor; - this.IsClick = isClick; + this.IsActionButton = isActionButton; + this.IsUseToolButton = isUseToolButton; } /// Prevent the game from handling the vurrent button press. This doesn't prevent other mods from receiving the event. diff --git a/src/SMAPI/Events/InputEvents.cs b/src/SMAPI/Events/InputEvents.cs index c31eb698..985aed99 100644 --- a/src/SMAPI/Events/InputEvents.cs +++ b/src/SMAPI/Events/InputEvents.cs @@ -1,6 +1,5 @@ using System; using StardewModdingAPI.Framework; -using StardewModdingAPI.Utilities; namespace StardewModdingAPI.Events { @@ -24,20 +23,22 @@ namespace StardewModdingAPI.Events /// Encapsulates monitoring and logging. /// The button on the controller, keyboard, or mouse. /// The cursor position. - /// Whether the input is considered a 'click' by the game for enabling action. - internal static void InvokeButtonPressed(IMonitor monitor, SButton button, ICursorPosition cursor, bool isClick) + /// Whether the input should trigger actions on the affected tile. + /// Whether the input should use tools on the affected tile. + internal static void InvokeButtonPressed(IMonitor monitor, SButton button, ICursorPosition cursor, bool isActionButton, bool isUseToolButton) { - monitor.SafelyRaiseGenericEvent($"{nameof(InputEvents)}.{nameof(InputEvents.ButtonPressed)}", InputEvents.ButtonPressed?.GetInvocationList(), null, new EventArgsInput(button, cursor, isClick)); + monitor.SafelyRaiseGenericEvent($"{nameof(InputEvents)}.{nameof(InputEvents.ButtonPressed)}", InputEvents.ButtonPressed?.GetInvocationList(), null, new EventArgsInput(button, cursor, isActionButton, isUseToolButton)); } /// Raise a event. /// Encapsulates monitoring and logging. /// The button on the controller, keyboard, or mouse. /// The cursor position. - /// Whether the input is considered a 'click' by the game for enabling action. - internal static void InvokeButtonReleased(IMonitor monitor, SButton button, ICursorPosition cursor, bool isClick) + /// Whether the input should trigger actions on the affected tile. + /// Whether the input should use tools on the affected tile. + internal static void InvokeButtonReleased(IMonitor monitor, SButton button, ICursorPosition cursor, bool isActionButton, bool isUseToolButton) { - monitor.SafelyRaiseGenericEvent($"{nameof(InputEvents)}.{nameof(InputEvents.ButtonReleased)}", InputEvents.ButtonReleased?.GetInvocationList(), null, new EventArgsInput(button, cursor, isClick)); + monitor.SafelyRaiseGenericEvent($"{nameof(InputEvents)}.{nameof(InputEvents.ButtonReleased)}", InputEvents.ButtonReleased?.GetInvocationList(), null, new EventArgsInput(button, cursor, isActionButton, isUseToolButton)); } } } diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 6f8f7cef..ca19d726 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -12,7 +12,6 @@ using Microsoft.Xna.Framework.Input; using StardewModdingAPI.Events; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Utilities; -using StardewModdingAPI.Utilities; using StardewValley; using StardewValley.BellsAndWhistles; using StardewValley.Locations; @@ -371,7 +370,8 @@ namespace StardewModdingAPI.Framework SButton[] previousPressedKeys = this.PreviousPressedButtons; SButton[] framePressedKeys = currentlyPressedKeys.Except(previousPressedKeys).ToArray(); SButton[] frameReleasedKeys = previousPressedKeys.Except(currentlyPressedKeys).ToArray(); - bool isClick = framePressedKeys.Contains(SButton.MouseLeft) || (framePressedKeys.Contains(SButton.ControllerA) && !currentlyPressedKeys.Contains(SButton.ControllerX)); + bool isUseToolButton = Game1.options.useToolButton.Any(p => framePressedKeys.Contains(p.ToSButton())); + bool isActionButton = !isUseToolButton && Game1.options.actionButton.Any(p => framePressedKeys.Contains(p.ToSButton())); // get cursor position ICursorPosition cursor; @@ -388,7 +388,7 @@ namespace StardewModdingAPI.Framework // raise button pressed foreach (SButton button in framePressedKeys) { - InputEvents.InvokeButtonPressed(this.Monitor, button, cursor, isClick); + InputEvents.InvokeButtonPressed(this.Monitor, button, cursor, isActionButton, isUseToolButton); // legacy events if (button.TryGetKeyboard(out Keys key)) @@ -408,10 +408,9 @@ namespace StardewModdingAPI.Framework // raise button released foreach (SButton button in frameReleasedKeys) { - bool wasClick = - (button == SButton.MouseLeft && previousPressedKeys.Contains(SButton.MouseLeft)) // released left click - || (button == SButton.ControllerA && previousPressedKeys.Contains(SButton.ControllerA) && !previousPressedKeys.Contains(SButton.ControllerX)); - InputEvents.InvokeButtonReleased(this.Monitor, button, cursor, wasClick); + bool wasUseToolButton = (from opt in Game1.options.useToolButton let optButton = opt.ToSButton() where optButton == button && framePressedKeys.Contains(optButton) select optButton).Any(); + bool wasActionButton = !wasUseToolButton && (from opt in Game1.options.actionButton let optButton = opt.ToSButton() where optButton == button && framePressedKeys.Contains(optButton) select optButton).Any(); + InputEvents.InvokeButtonReleased(this.Monitor, button, cursor, wasActionButton, wasUseToolButton); // legacy events if (button.TryGetKeyboard(out Keys key)) -- cgit From 801f25a51efbed0b8b16e6b9e8f1c543fcc45c47 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 27 Oct 2017 01:01:55 -0400 Subject: update release notes (#373) --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 452fd40a..a06fc0c4 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -11,6 +11,7 @@ * Deprecated `e.IsClick`, which is limited and unclear. Use `IsActionButton` or `IsUseToolButton` instead. * Fixed `e.SuppressButton()` not correctly suppressing keyboard buttons. * Fixed `e.IsClick` (now `e.IsActionButton`) ignoring custom key bindings. + * Fixed custom map tilesheets not working unless they're explicitly loaded first. * Fixed mods which implement `IAssetLoader` directly not being allowed to load files due to incorrect conflict detection. * Fixed SMAPI blocking reflection access to vanilla members on overridden types. -- cgit From 7f16ebdb19982c182b60312883452c44fdd08fda Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 27 Oct 2017 01:42:54 -0400 Subject: hide the game's test messages from the console & log (#364) --- docs/release-notes.md | 1 + src/SMAPI/Program.cs | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index a06fc0c4..ba0815b3 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,7 @@ ## 2.1 (upcoming) * For players: * Fixed compatibility check crashing for players with Stardew Valley 1.08. + * Fixed the game's test messages being shown in the console and log. * For modders: * Added support for public code in reflection API, to simplify mod integrations. diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index fe306e24..ce547d9b 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using System.Runtime.ExceptionServices; using System.Security; +using System.Text.RegularExpressions; using System.Threading; #if SMAPI_FOR_WINDOWS using System.Management; @@ -77,6 +78,13 @@ namespace StardewModdingAPI /// Whether the program has been disposed. private bool IsDisposed; + /// Regex patterns which match console messages to suppress from the console and log. + private readonly Regex[] SuppressConsolePatterns = + { + new Regex(@"^TextBox\.Selected is now '(?:True|False)'\.$", RegexOptions.Compiled | RegexOptions.CultureInvariant), + new Regex(@"^(?:FRUIT )?TREE: IsClient:(?:True|False) randomOutput: \d+$", RegexOptions.Compiled | RegexOptions.CultureInvariant) + }; + /********* ** Public methods @@ -910,7 +918,14 @@ namespace StardewModdingAPI /// The message to log. private void HandleConsoleMessage(IMonitor monitor, string message) { - LogLevel level = message.Contains("Exception") ? LogLevel.Error : LogLevel.Trace; // intercept potential exceptions + // detect exception + LogLevel level = message.Contains("Exception") ? LogLevel.Error : LogLevel.Trace; + + // ignore suppressed message + if (level != LogLevel.Error && this.SuppressConsolePatterns.Any(p => p.IsMatch(message))) + return; + + // forward to monitor monitor.Log(message, level); } -- cgit From b945fcf5553f2df63db1fad8a73c65cd7fa7daa3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 27 Oct 2017 02:44:53 -0400 Subject: fix player_setlevel command not also changing XP (#359) --- docs/release-notes.md | 1 + .../Framework/Commands/Player/SetLevelCommand.cs | 31 +++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index ba0815b3..9366e1fc 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -3,6 +3,7 @@ * For players: * Fixed compatibility check crashing for players with Stardew Valley 1.08. * Fixed the game's test messages being shown in the console and log. + * Fixed TrainerMod's `player_setlevel` command not also setting XP. * For modders: * Added support for public code in reflection API, to simplify mod integrations. diff --git a/src/TrainerMod/Framework/Commands/Player/SetLevelCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetLevelCommand.cs index b223aa9f..54d5e47b 100644 --- a/src/TrainerMod/Framework/Commands/Player/SetLevelCommand.cs +++ b/src/TrainerMod/Framework/Commands/Player/SetLevelCommand.cs @@ -1,11 +1,34 @@ -using StardewModdingAPI; +using System.Collections.Generic; +using StardewModdingAPI; using StardewValley; +using SFarmer = StardewValley.Farmer; namespace TrainerMod.Framework.Commands.Player { /// A command which edits the player's current level for a skill. internal class SetLevelCommand : TrainerCommand { + /********* + ** Properties + *********/ + /// The experience points needed to reach each level. + /// Derived from . + private readonly IDictionary LevelExp = new Dictionary + { + [0] = 0, + [1] = 100, + [2] = 380, + [3] = 770, + [4] = 1300, + [5] = 2150, + [6] = 3300, + [7] = 4800, + [8] = 6900, + [9] = 10000, + [10] = 15000 + }; + + /********* ** Public methods *********/ @@ -30,31 +53,37 @@ namespace TrainerMod.Framework.Commands.Player { case "luck": Game1.player.LuckLevel = level; + Game1.player.experiencePoints[SFarmer.luckSkill] = this.LevelExp[level]; monitor.Log($"OK, your luck skill is now {Game1.player.LuckLevel}.", LogLevel.Info); break; case "mining": Game1.player.MiningLevel = level; + Game1.player.experiencePoints[SFarmer.miningSkill] = this.LevelExp[level]; monitor.Log($"OK, your mining skill is now {Game1.player.MiningLevel}.", LogLevel.Info); break; case "combat": Game1.player.CombatLevel = level; + Game1.player.experiencePoints[SFarmer.combatSkill] = this.LevelExp[level]; monitor.Log($"OK, your combat skill is now {Game1.player.CombatLevel}.", LogLevel.Info); break; case "farming": Game1.player.FarmingLevel = level; + Game1.player.experiencePoints[SFarmer.farmingSkill] = this.LevelExp[level]; monitor.Log($"OK, your farming skill is now {Game1.player.FarmingLevel}.", LogLevel.Info); break; case "fishing": Game1.player.FishingLevel = level; + Game1.player.experiencePoints[SFarmer.fishingSkill] = this.LevelExp[level]; monitor.Log($"OK, your fishing skill is now {Game1.player.FishingLevel}.", LogLevel.Info); break; case "foraging": Game1.player.ForagingLevel = level; + Game1.player.experiencePoints[SFarmer.foragingSkill] = this.LevelExp[level]; monitor.Log($"OK, your foraging skill is now {Game1.player.ForagingLevel}.", LogLevel.Info); break; } -- cgit From 59dd604cf2905adf5fce7e9bb7b97886891aae81 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 27 Oct 2017 03:18:48 -0400 Subject: rename TrainerMod to Console Commands to clarify purpose --- build/common.targets | 10 +- docs/release-notes.md | 1 + src/SMAPI.Installer/InteractiveInstaller.cs | 1 + .../ConsoleCommandsMod.cs | 74 +++++++++ .../Framework/Commands/ArgumentParser.cs | 158 ++++++++++++++++++ .../Framework/Commands/ITrainerCommand.cs | 32 ++++ .../Framework/Commands/Other/DebugCommand.cs | 32 ++++ .../Commands/Other/ShowDataFilesCommand.cs | 25 +++ .../Commands/Other/ShowGameFilesCommand.cs | 25 +++ .../Framework/Commands/Player/AddCommand.cs | 80 +++++++++ .../Commands/Player/ListItemTypesCommand.cs | 52 ++++++ .../Framework/Commands/Player/ListItemsCommand.cs | 75 +++++++++ .../Framework/Commands/Player/SetColorCommand.cs | 75 +++++++++ .../Framework/Commands/Player/SetHealthCommand.cs | 71 ++++++++ .../Commands/Player/SetImmunityCommand.cs | 37 +++++ .../Framework/Commands/Player/SetLevelCommand.cs | 91 +++++++++++ .../Commands/Player/SetMaxHealthCommand.cs | 37 +++++ .../Commands/Player/SetMaxStaminaCommand.cs | 37 +++++ .../Framework/Commands/Player/SetMoneyCommand.cs | 71 ++++++++ .../Framework/Commands/Player/SetNameCommand.cs | 51 ++++++ .../Framework/Commands/Player/SetSpeedCommand.cs | 30 ++++ .../Framework/Commands/Player/SetStaminaCommand.cs | 71 ++++++++ .../Framework/Commands/Player/SetStyleCommand.cs | 91 +++++++++++ .../Framework/Commands/TrainerCommand.cs | 102 ++++++++++++ .../Commands/World/DownMineLevelCommand.cs | 27 ++++ .../Framework/Commands/World/FreezeTimeCommand.cs | 66 ++++++++ .../Framework/Commands/World/SetDayCommand.cs | 38 +++++ .../Commands/World/SetMineLevelCommand.cs | 32 ++++ .../Framework/Commands/World/SetSeasonCommand.cs | 45 ++++++ .../Framework/Commands/World/SetTimeCommand.cs | 39 +++++ .../Framework/Commands/World/SetYearCommand.cs | 38 +++++ .../Framework/ItemData/ItemType.cs | 39 +++++ .../Framework/ItemData/SearchableItem.cs | 41 +++++ .../Framework/ItemRepository.cs | 179 +++++++++++++++++++++ .../Properties/AssemblyInfo.cs | 6 + .../StardewModdingAPI.Mods.ConsoleCommands.csproj | 101 ++++++++++++ src/SMAPI.Mods.ConsoleCommands/manifest.json | 13 ++ src/SMAPI.Mods.ConsoleCommands/packages.config | 4 + src/SMAPI.sln | 4 +- .../Framework/Commands/ArgumentParser.cs | 159 ------------------ .../Framework/Commands/ITrainerCommand.cs | 34 ---- .../Framework/Commands/Other/DebugCommand.cs | 33 ---- .../Commands/Other/ShowDataFilesCommand.cs | 26 --- .../Commands/Other/ShowGameFilesCommand.cs | 26 --- .../Framework/Commands/Player/AddCommand.cs | 81 ---------- .../Commands/Player/ListItemTypesCommand.cs | 53 ------ .../Framework/Commands/Player/ListItemsCommand.cs | 76 --------- .../Framework/Commands/Player/SetColorCommand.cs | 76 --------- .../Framework/Commands/Player/SetHealthCommand.cs | 72 --------- .../Commands/Player/SetImmunityCommand.cs | 38 ----- .../Framework/Commands/Player/SetLevelCommand.cs | 92 ----------- .../Commands/Player/SetMaxHealthCommand.cs | 38 ----- .../Commands/Player/SetMaxStaminaCommand.cs | 38 ----- .../Framework/Commands/Player/SetMoneyCommand.cs | 72 --------- .../Framework/Commands/Player/SetNameCommand.cs | 52 ------ .../Framework/Commands/Player/SetSpeedCommand.cs | 31 ---- .../Framework/Commands/Player/SetStaminaCommand.cs | 72 --------- .../Framework/Commands/Player/SetStyleCommand.cs | 92 ----------- .../Framework/Commands/TrainerCommand.cs | 103 ------------ .../Commands/World/DownMineLevelCommand.cs | 28 ---- .../Framework/Commands/World/FreezeTimeCommand.cs | 67 -------- .../Framework/Commands/World/SetDayCommand.cs | 39 ----- .../Commands/World/SetMineLevelCommand.cs | 33 ---- .../Framework/Commands/World/SetSeasonCommand.cs | 46 ------ .../Framework/Commands/World/SetTimeCommand.cs | 40 ----- .../Framework/Commands/World/SetYearCommand.cs | 39 ----- src/TrainerMod/Framework/ItemData/ItemType.cs | 39 ----- .../Framework/ItemData/SearchableItem.cs | 41 ----- src/TrainerMod/Framework/ItemRepository.cs | 179 --------------------- src/TrainerMod/Properties/AssemblyInfo.cs | 6 - src/TrainerMod/TrainerMod.cs | 75 --------- src/TrainerMod/TrainerMod.csproj | 101 ------------ src/TrainerMod/manifest.json | 13 -- src/TrainerMod/packages.config | 4 - 74 files changed, 1994 insertions(+), 2021 deletions(-) create mode 100644 src/SMAPI.Mods.ConsoleCommands/ConsoleCommandsMod.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ArgumentParser.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ITrainerCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/DebugCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowDataFilesCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowGameFilesCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/AddCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemTypesCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemsCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetHealthCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetImmunityCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetLevelCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxHealthCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxStaminaCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMoneyCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetNameCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetSpeedCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStaminaCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStyleCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/TrainerCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/DownMineLevelCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/FreezeTimeCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetDayCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetMineLevelCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetSeasonCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetYearCommand.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/ItemType.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/Properties/AssemblyInfo.cs create mode 100644 src/SMAPI.Mods.ConsoleCommands/StardewModdingAPI.Mods.ConsoleCommands.csproj create mode 100644 src/SMAPI.Mods.ConsoleCommands/manifest.json create mode 100644 src/SMAPI.Mods.ConsoleCommands/packages.config delete mode 100644 src/TrainerMod/Framework/Commands/ArgumentParser.cs delete mode 100644 src/TrainerMod/Framework/Commands/ITrainerCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/Other/DebugCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/Other/ShowDataFilesCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/Other/ShowGameFilesCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/Player/AddCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/Player/ListItemTypesCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/Player/ListItemsCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/Player/SetColorCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/Player/SetHealthCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/Player/SetImmunityCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/Player/SetLevelCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/Player/SetMaxHealthCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/Player/SetMaxStaminaCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/Player/SetMoneyCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/Player/SetNameCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/Player/SetSpeedCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/Player/SetStaminaCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/Player/SetStyleCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/TrainerCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/World/DownMineLevelCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/World/FreezeTimeCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/World/SetDayCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/World/SetMineLevelCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/World/SetSeasonCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/World/SetTimeCommand.cs delete mode 100644 src/TrainerMod/Framework/Commands/World/SetYearCommand.cs delete mode 100644 src/TrainerMod/Framework/ItemData/ItemType.cs delete mode 100644 src/TrainerMod/Framework/ItemData/SearchableItem.cs delete mode 100644 src/TrainerMod/Framework/ItemRepository.cs delete mode 100644 src/TrainerMod/Properties/AssemblyInfo.cs delete mode 100644 src/TrainerMod/TrainerMod.cs delete mode 100644 src/TrainerMod/TrainerMod.csproj delete mode 100644 src/TrainerMod/manifest.json delete mode 100644 src/TrainerMod/packages.config (limited to 'docs') diff --git a/build/common.targets b/build/common.targets index ee138524..aa11344e 100644 --- a/build/common.targets +++ b/build/common.targets @@ -78,7 +78,7 @@ - + @@ -89,10 +89,10 @@ - - - - + + + + diff --git a/docs/release-notes.md b/docs/release-notes.md index 9366e1fc..1202407f 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,7 @@ * Fixed compatibility check crashing for players with Stardew Valley 1.08. * Fixed the game's test messages being shown in the console and log. * Fixed TrainerMod's `player_setlevel` command not also setting XP. + * Renamed the default _TrainerMod_ mod to _Console Commands_ to clarify its purpose. * For modders: * Added support for public code in reflection API, to simplify mod integrations. diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index 1a132e54..cbc8a401 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -97,6 +97,7 @@ namespace StardewModdingApi.Installer // obsolete yield return GetInstallPath("Mods/.cache"); // 1.3-1.4 + yield return GetInstallPath("Mods/TrainerMod"); // *–2.0 (renamed to ConsoleCommands) yield return GetInstallPath("Mono.Cecil.Rocks.dll"); // 1.3–1.8 yield return GetInstallPath("StardewModdingAPI-settings.json"); // 1.0-1.4 if (modsDir.Exists) diff --git a/src/SMAPI.Mods.ConsoleCommands/ConsoleCommandsMod.cs b/src/SMAPI.Mods.ConsoleCommands/ConsoleCommandsMod.cs new file mode 100644 index 00000000..96658928 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/ConsoleCommandsMod.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using StardewModdingAPI.Events; +using StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands; + +namespace StardewModdingAPI.Mods.ConsoleCommands +{ + /// The main entry point for the mod. + public class ConsoleCommandsMod : Mod + { + /********* + ** Properties + *********/ + /// The commands to handle. + private ITrainerCommand[] Commands; + + + /********* + ** 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) + { + // register commands + this.Commands = this.ScanForCommands().ToArray(); + foreach (ITrainerCommand command in this.Commands) + helper.ConsoleCommands.Add(command.Name, command.Description, (name, args) => this.HandleCommand(command, name, args)); + + // hook events + GameEvents.UpdateTick += this.GameEvents_UpdateTick; + } + + + /********* + ** Private methods + *********/ + /// The method invoked when the game updates its state. + /// The event sender. + /// The event arguments. + private void GameEvents_UpdateTick(object sender, EventArgs e) + { + if (!Context.IsWorldReady) + return; + + foreach (ITrainerCommand command in this.Commands) + { + if (command.NeedsUpdate) + command.Update(this.Monitor); + } + } + + /// Handle a console command. + /// The command to invoke. + /// The command name specified by the user. + /// The command arguments. + private void HandleCommand(ITrainerCommand command, string commandName, string[] args) + { + ArgumentParser argParser = new ArgumentParser(commandName, args, this.Monitor); + command.Handle(this.Monitor, commandName, argParser); + } + + /// Find all commands in the assembly. + private IEnumerable ScanForCommands() + { + return ( + from type in this.GetType().Assembly.GetTypes() + where !type.IsAbstract && typeof(ITrainerCommand).IsAssignableFrom(type) + select (ITrainerCommand)Activator.CreateInstance(type) + ); + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ArgumentParser.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ArgumentParser.cs new file mode 100644 index 00000000..3ad1e168 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ArgumentParser.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands +{ + /// Provides methods for parsing command-line arguments. + internal class ArgumentParser : IReadOnlyList + { + /********* + ** Properties + *********/ + /// The command name for errors. + private readonly string CommandName; + + /// The arguments to parse. + private readonly string[] Args; + + /// Writes messages to the console and log file. + private readonly IMonitor Monitor; + + + /********* + ** Accessors + *********/ + /// Get the number of arguments. + public int Count => this.Args.Length; + + /// Get the argument at the specified index in the list. + /// The zero-based index of the element to get. + public string this[int index] => this.Args[index]; + + /// A method which parses a string argument into the given value. + /// The expected argument type. + /// The argument to parse. + /// The parsed value. + /// Returns whether the argument was successfully parsed. + public delegate bool ParseDelegate(string input, out T output); + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The command name for errors. + /// The arguments to parse. + /// Writes messages to the console and log file. + public ArgumentParser(string commandName, string[] args, IMonitor monitor) + { + this.CommandName = commandName; + this.Args = args; + this.Monitor = monitor; + } + + /// Try to read a string argument. + /// The argument index. + /// The argument name for error messages. + /// The parsed value. + /// Whether to show an error if the argument is missing. + /// Require that the argument match one of the given values (case-insensitive). + public bool TryGet(int index, string name, out string value, bool required = true, string[] oneOf = null) + { + value = null; + + // validate + if (this.Args.Length < index + 1) + { + if (required) + this.LogError($"Argument {index} ({name}) is required."); + return false; + } + if (oneOf?.Any() == true && !oneOf.Contains(this.Args[index], StringComparer.InvariantCultureIgnoreCase)) + { + this.LogError($"Argument {index} ({name}) must be one of {string.Join(", ", oneOf)}."); + return false; + } + + // get value + value = this.Args[index]; + return true; + } + + /// Try to read an integer argument. + /// The argument index. + /// The argument name for error messages. + /// The parsed value. + /// Whether to show an error if the argument is missing. + /// The minimum value allowed. + /// The maximum value allowed. + public bool TryGetInt(int index, string name, out int value, bool required = true, int? min = null, int? max = null) + { + value = 0; + + // get argument + if (!this.TryGet(index, name, out string raw, required)) + return false; + + // parse + if (!int.TryParse(raw, out value)) + { + this.LogIntFormatError(index, name, min, max); + return false; + } + + // validate + if ((min.HasValue && value < min) || (max.HasValue && value > max)) + { + this.LogIntFormatError(index, name, min, max); + return false; + } + + return true; + } + + /// Returns an enumerator that iterates through the collection. + /// An enumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return ((IEnumerable)this.Args).GetEnumerator(); + } + + /// Returns an enumerator that iterates through a collection. + /// An object that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + + /********* + ** Private methods + *********/ + /// Log a usage error. + /// The message describing the error. + private void LogError(string message) + { + this.Monitor.Log($"{message} Type 'help {this.CommandName}' for usage.", LogLevel.Error); + } + + /// Print an error for an invalid int argument. + /// The argument index. + /// The argument name for error messages. + /// The minimum value allowed. + /// The maximum value allowed. + private void LogIntFormatError(int index, string name, int? min, int? max) + { + if (min.HasValue && max.HasValue) + this.LogError($"Argument {index} ({name}) must be an integer between {min} and {max}."); + else if (min.HasValue) + this.LogError($"Argument {index} ({name}) must be an integer and at least {min}."); + else if (max.HasValue) + this.LogError($"Argument {index} ({name}) must be an integer and at most {max}."); + else + this.LogError($"Argument {index} ({name}) must be an integer."); + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ITrainerCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ITrainerCommand.cs new file mode 100644 index 00000000..a0b739f8 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ITrainerCommand.cs @@ -0,0 +1,32 @@ +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands +{ + /// A console command to register. + internal interface ITrainerCommand + { + /********* + ** Accessors + *********/ + /// The command name the user must type. + string Name { get; } + + /// The command description. + string Description { get; } + + /// Whether the command needs to perform logic when the game updates. + bool NeedsUpdate { get; } + + + /********* + ** Public methods + *********/ + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + void Handle(IMonitor monitor, string command, ArgumentParser args); + + /// Perform any logic needed on update tick. + /// Writes messages to the console and log file. + void Update(IMonitor monitor); + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/DebugCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/DebugCommand.cs new file mode 100644 index 00000000..e4010111 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/DebugCommand.cs @@ -0,0 +1,32 @@ +using StardewValley; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other +{ + /// A command which sends a debug command to the game. + internal class DebugCommand : TrainerCommand + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public DebugCommand() + : base("debug", "Run one of the game's debug commands; for example, 'debug warp FarmHouse 1 1' warps the player to the farmhouse.") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // submit command + string debugCommand = string.Join(" ", args); + string oldOutput = Game1.debugOutput; + Game1.game1.parseDebugInput(debugCommand); + + // show result + monitor.Log(Game1.debugOutput != oldOutput + ? $"> {Game1.debugOutput}" + : "Sent debug command to the game, but there was no output.", LogLevel.Info); + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowDataFilesCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowDataFilesCommand.cs new file mode 100644 index 00000000..54d27185 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowDataFilesCommand.cs @@ -0,0 +1,25 @@ +using System.Diagnostics; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other +{ + /// A command which shows the data files. + internal class ShowDataFilesCommand : TrainerCommand + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public ShowDataFilesCommand() + : base("show_data_files", "Opens the folder containing the save and log files.") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + Process.Start(Constants.DataPath); + monitor.Log($"OK, opening {Constants.DataPath}.", LogLevel.Info); + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowGameFilesCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowGameFilesCommand.cs new file mode 100644 index 00000000..0257892f --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowGameFilesCommand.cs @@ -0,0 +1,25 @@ +using System.Diagnostics; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other +{ + /// A command which shows the game files. + internal class ShowGameFilesCommand : TrainerCommand + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public ShowGameFilesCommand() + : base("show_game_files", "Opens the game folder.") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + Process.Start(Constants.ExecutionPath); + monitor.Log($"OK, opening {Constants.ExecutionPath}.", LogLevel.Info); + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/AddCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/AddCommand.cs new file mode 100644 index 00000000..81167747 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/AddCommand.cs @@ -0,0 +1,80 @@ +using System; +using System.Linq; +using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData; +using StardewValley; +using Object = StardewValley.Object; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player +{ + /// A command which adds an item to the player inventory. + internal class AddCommand : TrainerCommand + { + /********* + ** Properties + *********/ + /// Provides methods for searching and constructing items. + private readonly ItemRepository Items = new ItemRepository(); + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public AddCommand() + : base("player_add", AddCommand.GetDescription()) + { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // read arguments + if (!args.TryGet(0, "item type", out string rawType, oneOf: Enum.GetNames(typeof(ItemType)))) + return; + if (!args.TryGetInt(1, "item ID", out int id, min: 0)) + return; + if (!args.TryGetInt(2, "count", out int count, min: 1, required: false)) + count = 1; + if (!args.TryGetInt(3, "quality", out int quality, min: Object.lowQuality, max: Object.bestQuality, required: false)) + quality = Object.lowQuality; + ItemType type = (ItemType)Enum.Parse(typeof(ItemType), rawType, ignoreCase: true); + + // find matching item + SearchableItem match = this.Items.GetAll().FirstOrDefault(p => p.Type == type && p.ID == id); + if (match == null) + { + monitor.Log($"There's no {type} item with ID {id}.", LogLevel.Error); + return; + } + + // apply count & quality + match.Item.Stack = count; + if (match.Item is Object obj) + obj.quality = quality; + + // add to inventory + Game1.player.addItemByMenuIfNecessary(match.Item); + monitor.Log($"OK, added {match.Name} ({match.Type} #{match.ID}) to your inventory.", LogLevel.Info); + } + + /********* + ** Private methods + *********/ + private static string GetDescription() + { + string[] typeValues = Enum.GetNames(typeof(ItemType)); + return "Gives the player an item.\n" + + "\n" + + "Usage: player_add [count] [quality]\n" + + $"- type: the item type (one of {string.Join(", ", typeValues)}).\n" + + "- item: the item ID (use the 'list_items' command to see a list).\n" + + "- count (optional): how many of the item to give.\n" + + $"- quality (optional): one of {Object.lowQuality} (normal), {Object.medQuality} (silver), {Object.highQuality} (gold), or {Object.bestQuality} (iridium).\n" + + "\n" + + "This example adds the galaxy sword to your inventory:\n" + + " player_add weapon 4"; + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemTypesCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemTypesCommand.cs new file mode 100644 index 00000000..34f1760c --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemTypesCommand.cs @@ -0,0 +1,52 @@ +using System.Linq; +using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player +{ + /// A command which list item types. + internal class ListItemTypesCommand : TrainerCommand + { + /********* + ** Properties + *********/ + /// Provides methods for searching and constructing items. + private readonly ItemRepository Items = new ItemRepository(); + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public ListItemTypesCommand() + : base("list_item_types", "Lists item types you can filter in other commands.\n\nUsage: list_item_types") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // validate + if (!Context.IsWorldReady) + { + monitor.Log("You need to load a save to use this command.", LogLevel.Error); + return; + } + + // handle + ItemType[] matches = + ( + from item in this.Items.GetAll() + orderby item.Type.ToString() + select item.Type + ) + .Distinct() + .ToArray(); + string summary = "Searching...\n"; + if (matches.Any()) + monitor.Log(summary + this.GetTableString(matches, new[] { "type" }, val => new[] { val.ToString() }), LogLevel.Info); + else + monitor.Log(summary + "No item types found.", LogLevel.Info); + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemsCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemsCommand.cs new file mode 100644 index 00000000..942a50b8 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemsCommand.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player +{ + /// A command which list items available to spawn. + internal class ListItemsCommand : TrainerCommand + { + /********* + ** Properties + *********/ + /// Provides methods for searching and constructing items. + private readonly ItemRepository Items = new ItemRepository(); + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public ListItemsCommand() + : base("list_items", "Lists and searches items in the game data.\n\nUsage: list_items [search]\n- search (optional): an arbitrary search string to filter by.") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // validate + if (!Context.IsWorldReady) + { + monitor.Log("You need to load a save to use this command.", LogLevel.Error); + return; + } + + // handle + SearchableItem[] matches = + ( + from item in this.GetItems(args.ToArray()) + orderby item.Type.ToString(), item.Name + select item + ) + .ToArray(); + string summary = "Searching...\n"; + if (matches.Any()) + monitor.Log(summary + this.GetTableString(matches, new[] { "type", "name", "id" }, val => new[] { val.Type.ToString(), val.Name, val.ID.ToString() }), LogLevel.Info); + else + monitor.Log(summary + "No items found", LogLevel.Info); + } + + + /********* + ** Private methods + *********/ + /// Get all items which can be searched and added to the player's inventory through the console. + /// The search string to find. + private IEnumerable GetItems(string[] searchWords) + { + // normalise search term + searchWords = searchWords?.Where(word => !string.IsNullOrWhiteSpace(word)).ToArray(); + if (searchWords?.Any() == false) + searchWords = null; + + // find matches + return ( + from item in this.Items.GetAll() + let term = $"{item.ID}|{item.Type}|{item.Name}|{item.DisplayName}" + where searchWords == null || searchWords.All(word => term.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) != -1) + select item + ); + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs new file mode 100644 index 00000000..5d098593 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs @@ -0,0 +1,75 @@ +using Microsoft.Xna.Framework; +using StardewValley; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player +{ + /// A command which edits the color of a player feature. + internal class SetColorCommand : TrainerCommand + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public SetColorCommand() + : base("player_changecolor", "Sets the color of a player feature.\n\nUsage: player_changecolor \n- target: what to change (one of 'hair', 'eyes', or 'pants').\n- color: a color value in RGB format, like (255,255,255).") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // parse arguments + if (!args.TryGet(0, "target", out string target, oneOf: new[] { "hair", "eyes", "pants" })) + return; + if (!args.TryGet(1, "color", out string rawColor)) + return; + + // parse color + if (!this.TryParseColor(rawColor, out Color color)) + { + this.LogUsageError(monitor, "Argument 1 (color) must be an RBG value like '255,150,0'."); + return; + } + + // handle + switch (target) + { + case "hair": + Game1.player.hairstyleColor = color; + monitor.Log("OK, your hair color is updated.", LogLevel.Info); + break; + + case "eyes": + Game1.player.changeEyeColor(color); + monitor.Log("OK, your eye color is updated.", LogLevel.Info); + break; + + case "pants": + Game1.player.pantsColor = color; + monitor.Log("OK, your pants color is updated.", LogLevel.Info); + break; + } + } + + + /********* + ** Private methods + *********/ + /// Try to parse a color from a string. + /// The input string. + /// The color to set. + private bool TryParseColor(string input, out Color color) + { + string[] colorHexes = input.Split(new[] { ',' }, 3); + if (int.TryParse(colorHexes[0], out int r) && int.TryParse(colorHexes[1], out int g) && int.TryParse(colorHexes[2], out int b)) + { + color = new Color(r, g, b); + return true; + } + + color = Color.Transparent; + return false; + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetHealthCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetHealthCommand.cs new file mode 100644 index 00000000..2e8f6630 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetHealthCommand.cs @@ -0,0 +1,71 @@ +using System.Linq; +using StardewValley; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player +{ + /// A command which edits the player's current health. + internal class SetHealthCommand : TrainerCommand + { + /********* + ** Properties + *********/ + /// Whether to keep the player's health at its maximum. + private bool InfiniteHealth; + + + /********* + ** Accessors + *********/ + /// Whether the command needs to perform logic when the game updates. + public override bool NeedsUpdate => this.InfiniteHealth; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public SetHealthCommand() + : base("player_sethealth", "Sets the player's health.\n\nUsage: player_sethealth [value]\n- value: an integer amount, or 'inf' for infinite health.") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // no-argument mode + if (!args.Any()) + { + monitor.Log($"You currently have {(this.InfiniteHealth ? "infinite" : Game1.player.health.ToString())} health. Specify a value to change it.", LogLevel.Info); + return; + } + + // handle + string amountStr = args[0]; + if (amountStr == "inf") + { + this.InfiniteHealth = true; + monitor.Log("OK, you now have infinite health.", LogLevel.Info); + } + else + { + this.InfiniteHealth = false; + if (int.TryParse(amountStr, out int amount)) + { + Game1.player.health = amount; + monitor.Log($"OK, you now have {Game1.player.health} health.", LogLevel.Info); + } + else + this.LogArgumentNotInt(monitor); + } + } + + /// Perform any logic needed on update tick. + /// Writes messages to the console and log file. + public override void Update(IMonitor monitor) + { + if (this.InfiniteHealth) + Game1.player.health = Game1.player.maxHealth; + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetImmunityCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetImmunityCommand.cs new file mode 100644 index 00000000..9c66c4fe --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetImmunityCommand.cs @@ -0,0 +1,37 @@ +using System.Linq; +using StardewValley; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player +{ + /// A command which edits the player's current immunity. + internal class SetImmunityCommand : TrainerCommand + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public SetImmunityCommand() + : base("player_setimmunity", "Sets the player's immunity.\n\nUsage: player_setimmunity [value]\n- value: an integer amount.") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // validate + if (!args.Any()) + { + monitor.Log($"You currently have {Game1.player.immunity} immunity. Specify a value to change it.", LogLevel.Info); + return; + } + + // handle + if (args.TryGetInt(0, "amount", out int amount, min: 0)) + { + Game1.player.immunity = amount; + monitor.Log($"OK, you now have {Game1.player.immunity} immunity.", LogLevel.Info); + } + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetLevelCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetLevelCommand.cs new file mode 100644 index 00000000..68891267 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetLevelCommand.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using StardewValley; +using SFarmer = StardewValley.Farmer; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player +{ + /// A command which edits the player's current level for a skill. + internal class SetLevelCommand : TrainerCommand + { + /********* + ** Properties + *********/ + /// The experience points needed to reach each level. + /// Derived from . + private readonly IDictionary LevelExp = new Dictionary + { + [0] = 0, + [1] = 100, + [2] = 380, + [3] = 770, + [4] = 1300, + [5] = 2150, + [6] = 3300, + [7] = 4800, + [8] = 6900, + [9] = 10000, + [10] = 15000 + }; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public SetLevelCommand() + : base("player_setlevel", "Sets the player's specified skill to the specified value.\n\nUsage: player_setlevel \n- skill: the skill to set (one of 'luck', 'mining', 'combat', 'farming', 'fishing', or 'foraging').\n- value: the target level (a number from 1 to 10).") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // validate + if (!args.TryGet(0, "skill", out string skill, oneOf: new[] { "luck", "mining", "combat", "farming", "fishing", "foraging" })) + return; + if (!args.TryGetInt(1, "level", out int level, min: 0, max: 10)) + return; + + // handle + switch (skill) + { + case "luck": + Game1.player.LuckLevel = level; + Game1.player.experiencePoints[SFarmer.luckSkill] = this.LevelExp[level]; + monitor.Log($"OK, your luck skill is now {Game1.player.LuckLevel}.", LogLevel.Info); + break; + + case "mining": + Game1.player.MiningLevel = level; + Game1.player.experiencePoints[SFarmer.miningSkill] = this.LevelExp[level]; + monitor.Log($"OK, your mining skill is now {Game1.player.MiningLevel}.", LogLevel.Info); + break; + + case "combat": + Game1.player.CombatLevel = level; + Game1.player.experiencePoints[SFarmer.combatSkill] = this.LevelExp[level]; + monitor.Log($"OK, your combat skill is now {Game1.player.CombatLevel}.", LogLevel.Info); + break; + + case "farming": + Game1.player.FarmingLevel = level; + Game1.player.experiencePoints[SFarmer.farmingSkill] = this.LevelExp[level]; + monitor.Log($"OK, your farming skill is now {Game1.player.FarmingLevel}.", LogLevel.Info); + break; + + case "fishing": + Game1.player.FishingLevel = level; + Game1.player.experiencePoints[SFarmer.fishingSkill] = this.LevelExp[level]; + monitor.Log($"OK, your fishing skill is now {Game1.player.FishingLevel}.", LogLevel.Info); + break; + + case "foraging": + Game1.player.ForagingLevel = level; + Game1.player.experiencePoints[SFarmer.foragingSkill] = this.LevelExp[level]; + monitor.Log($"OK, your foraging skill is now {Game1.player.ForagingLevel}.", LogLevel.Info); + break; + } + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxHealthCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxHealthCommand.cs new file mode 100644 index 00000000..f4ae0694 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxHealthCommand.cs @@ -0,0 +1,37 @@ +using System.Linq; +using StardewValley; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player +{ + /// A command which edits the player's maximum health. + internal class SetMaxHealthCommand : TrainerCommand + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public SetMaxHealthCommand() + : base("player_setmaxhealth", "Sets the player's max health.\n\nUsage: player_setmaxhealth [value]\n- value: an integer amount.") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // validate + if (!args.Any()) + { + monitor.Log($"You currently have {Game1.player.maxHealth} max health. Specify a value to change it.", LogLevel.Info); + return; + } + + // handle + if (args.TryGetInt(0, "amount", out int amount, min: 1)) + { + Game1.player.maxHealth = amount; + monitor.Log($"OK, you now have {Game1.player.maxHealth} max health.", LogLevel.Info); + } + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxStaminaCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxStaminaCommand.cs new file mode 100644 index 00000000..5bce5ea3 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxStaminaCommand.cs @@ -0,0 +1,37 @@ +using System.Linq; +using StardewValley; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player +{ + /// A command which edits the player's maximum stamina. + internal class SetMaxStaminaCommand : TrainerCommand + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public SetMaxStaminaCommand() + : base("player_setmaxstamina", "Sets the player's max stamina.\n\nUsage: player_setmaxstamina [value]\n- value: an integer amount.") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // validate + if (!args.Any()) + { + monitor.Log($"You currently have {Game1.player.MaxStamina} max stamina. Specify a value to change it.", LogLevel.Info); + return; + } + + // handle + if (args.TryGetInt(0, "amount", out int amount, min: 1)) + { + Game1.player.MaxStamina = amount; + monitor.Log($"OK, you now have {Game1.player.MaxStamina} max stamina.", LogLevel.Info); + } + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMoneyCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMoneyCommand.cs new file mode 100644 index 00000000..3fc504b1 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMoneyCommand.cs @@ -0,0 +1,71 @@ +using System.Linq; +using StardewValley; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player +{ + /// A command which edits the player's current money. + internal class SetMoneyCommand : TrainerCommand + { + /********* + ** Properties + *********/ + /// Whether to keep the player's money at a set value. + private bool InfiniteMoney; + + + /********* + ** Accessors + *********/ + /// Whether the command needs to perform logic when the game updates. + public override bool NeedsUpdate => this.InfiniteMoney; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public SetMoneyCommand() + : base("player_setmoney", "Sets the player's money.\n\nUsage: player_setmoney \n- value: an integer amount, or 'inf' for infinite money.") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // validate + if (!args.Any()) + { + monitor.Log($"You currently have {(this.InfiniteMoney ? "infinite" : Game1.player.Money.ToString())} gold. Specify a value to change it.", LogLevel.Info); + return; + } + + // handle + string amountStr = args[0]; + if (amountStr == "inf") + { + this.InfiniteMoney = true; + monitor.Log("OK, you now have infinite money.", LogLevel.Info); + } + else + { + this.InfiniteMoney = false; + if (int.TryParse(amountStr, out int amount)) + { + Game1.player.Money = amount; + monitor.Log($"OK, you now have {Game1.player.Money} gold.", LogLevel.Info); + } + else + this.LogArgumentNotInt(monitor); + } + } + + /// Perform any logic needed on update tick. + /// Writes messages to the console and log file. + public override void Update(IMonitor monitor) + { + if (this.InfiniteMoney) + Game1.player.money = 999999; + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetNameCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetNameCommand.cs new file mode 100644 index 00000000..5b1225e8 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetNameCommand.cs @@ -0,0 +1,51 @@ +using StardewValley; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player +{ + /// A command which edits the player's name. + internal class SetNameCommand : TrainerCommand + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public SetNameCommand() + : base("player_setname", "Sets the player's name.\n\nUsage: player_setname \n- target: what to rename (one of 'player' or 'farm').\n- name: the new name to set.") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // parse arguments + if (!args.TryGet(0, "target", out string target, oneOf: new[] { "player", "farm" })) + return; + args.TryGet(1, "name", out string name, required: false); + + // handle + switch (target) + { + case "player": + if (!string.IsNullOrWhiteSpace(name)) + { + Game1.player.Name = args[1]; + monitor.Log($"OK, your name is now {Game1.player.Name}.", LogLevel.Info); + } + else + monitor.Log($"Your name is currently '{Game1.player.Name}'. Type 'help player_setname' for usage.", LogLevel.Info); + break; + + case "farm": + if (!string.IsNullOrWhiteSpace(name)) + { + Game1.player.farmName = args[1]; + monitor.Log($"OK, your farm's name is now {Game1.player.farmName}.", LogLevel.Info); + } + else + monitor.Log($"Your farm's name is currently '{Game1.player.farmName}'. Type 'help player_setname' for usage.", LogLevel.Info); + break; + } + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetSpeedCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetSpeedCommand.cs new file mode 100644 index 00000000..e9693540 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetSpeedCommand.cs @@ -0,0 +1,30 @@ +using StardewValley; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player +{ + /// A command which edits the player's current added speed. + internal class SetSpeedCommand : TrainerCommand + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public SetSpeedCommand() + : base("player_setspeed", "Sets the player's added speed to the specified value.\n\nUsage: player_setspeed \n- value: an integer amount (0 is normal).") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // parse arguments + if (!args.TryGetInt(0, "added speed", out int amount, min: 0)) + return; + + // handle + Game1.player.addedSpeed = amount; + monitor.Log($"OK, your added speed is now {Game1.player.addedSpeed}.", LogLevel.Info); + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStaminaCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStaminaCommand.cs new file mode 100644 index 00000000..866c3d22 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStaminaCommand.cs @@ -0,0 +1,71 @@ +using System.Linq; +using StardewValley; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player +{ + /// A command which edits the player's current stamina. + internal class SetStaminaCommand : TrainerCommand + { + /********* + ** Properties + *********/ + /// Whether to keep the player's stamina at its maximum. + private bool InfiniteStamina; + + + /********* + ** Accessors + *********/ + /// Whether the command needs to perform logic when the game updates. + public override bool NeedsUpdate => this.InfiniteStamina; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public SetStaminaCommand() + : base("player_setstamina", "Sets the player's stamina.\n\nUsage: player_setstamina [value]\n- value: an integer amount, or 'inf' for infinite stamina.") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // validate + if (!args.Any()) + { + monitor.Log($"You currently have {(this.InfiniteStamina ? "infinite" : Game1.player.Stamina.ToString())} stamina. Specify a value to change it.", LogLevel.Info); + return; + } + + // handle + string amountStr = args[0]; + if (amountStr == "inf") + { + this.InfiniteStamina = true; + monitor.Log("OK, you now have infinite stamina.", LogLevel.Info); + } + else + { + this.InfiniteStamina = false; + if (int.TryParse(amountStr, out int amount)) + { + Game1.player.Stamina = amount; + monitor.Log($"OK, you now have {Game1.player.Stamina} stamina.", LogLevel.Info); + } + else + this.LogArgumentNotInt(monitor); + } + } + + /// Perform any logic needed on update tick. + /// Writes messages to the console and log file. + public override void Update(IMonitor monitor) + { + if (this.InfiniteStamina) + Game1.player.stamina = Game1.player.MaxStamina; + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStyleCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStyleCommand.cs new file mode 100644 index 00000000..b59be2e5 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStyleCommand.cs @@ -0,0 +1,91 @@ +using StardewValley; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player +{ + /// A command which edits a player style. + internal class SetStyleCommand : TrainerCommand + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public SetStyleCommand() + : base("player_changestyle", "Sets the style of a player feature.\n\nUsage: player_changecolor .\n- target: what to change (one of 'hair', 'shirt', 'skin', 'acc', 'shoe', 'swim', or 'gender').\n- value: the integer style ID.") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // parse arguments + if (!args.TryGet(0, "target", out string target, oneOf: new[] { "hair", "shirt", "acc", "skin", "shoe", "swim", "gender" })) + return; + if (!args.TryGetInt(1, "style ID", out int styleID)) + return; + + // handle + switch (target) + { + case "hair": + Game1.player.changeHairStyle(styleID); + monitor.Log("OK, your hair style is updated.", LogLevel.Info); + break; + + case "shirt": + Game1.player.changeShirt(styleID); + monitor.Log("OK, your shirt style is updated.", LogLevel.Info); + break; + + case "acc": + Game1.player.changeAccessory(styleID); + monitor.Log("OK, your accessory style is updated.", LogLevel.Info); + break; + + case "skin": + Game1.player.changeSkinColor(styleID); + monitor.Log("OK, your skin color is updated.", LogLevel.Info); + break; + + case "shoe": + Game1.player.changeShoeColor(styleID); + monitor.Log("OK, your shoe style is updated.", LogLevel.Info); + break; + + case "swim": + switch (styleID) + { + case 0: + Game1.player.changeOutOfSwimSuit(); + monitor.Log("OK, you're no longer in your swimming suit.", LogLevel.Info); + break; + case 1: + Game1.player.changeIntoSwimsuit(); + monitor.Log("OK, you're now in your swimming suit.", LogLevel.Info); + break; + default: + this.LogUsageError(monitor, "The swim value should be 0 (no swimming suit) or 1 (swimming suit)."); + break; + } + break; + + case "gender": + switch (styleID) + { + case 0: + Game1.player.changeGender(true); + monitor.Log("OK, you're now male.", LogLevel.Info); + break; + case 1: + Game1.player.changeGender(false); + monitor.Log("OK, you're now female.", LogLevel.Info); + break; + default: + this.LogUsageError(monitor, "The gender value should be 0 (male) or 1 (female)."); + break; + } + break; + } + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/TrainerCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/TrainerCommand.cs new file mode 100644 index 00000000..466b8f6e --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/TrainerCommand.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands +{ + /// The base implementation for a trainer command. + internal abstract class TrainerCommand : ITrainerCommand + { + /********* + ** Accessors + *********/ + /// The command name the user must type. + public string Name { get; } + + /// The command description. + public string Description { get; } + + /// Whether the command needs to perform logic when the game updates. + public virtual bool NeedsUpdate { get; } = false; + + + /********* + ** Public methods + *********/ + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public abstract void Handle(IMonitor monitor, string command, ArgumentParser args); + + /// Perform any logic needed on update tick. + /// Writes messages to the console and log file. + public virtual void Update(IMonitor monitor) { } + + + /********* + ** Protected methods + *********/ + /// Construct an instance. + /// The command name the user must type. + /// The command description. + protected TrainerCommand(string name, string description) + { + this.Name = name; + this.Description = description; + } + + /// Log an error indicating incorrect usage. + /// Writes messages to the console and log file. + /// A sentence explaining the problem. + protected void LogUsageError(IMonitor monitor, string error) + { + monitor.Log($"{error} Type 'help {this.Name}' for usage.", LogLevel.Error); + } + + /// Log an error indicating a value must be an integer. + /// Writes messages to the console and log file. + protected void LogArgumentNotInt(IMonitor monitor) + { + this.LogUsageError(monitor, "The value must be a whole number."); + } + + /// Get an ASCII table to show tabular data in the console. + /// The data type. + /// The data to display. + /// The table header. + /// Returns a set of fields for a data value. + protected string GetTableString(IEnumerable data, string[] header, Func getRow) + { + // get table data + int[] widths = header.Select(p => p.Length).ToArray(); + string[][] rows = data + .Select(item => + { + string[] fields = getRow(item); + if (fields.Length != widths.Length) + throw new InvalidOperationException($"Expected {widths.Length} columns, but found {fields.Length}: {string.Join(", ", fields)}"); + + for (int i = 0; i < fields.Length; i++) + widths[i] = Math.Max(widths[i], fields[i].Length); + + return fields; + }) + .ToArray(); + + // render fields + List lines = new List(rows.Length + 2) + { + header, + header.Select((value, i) => "".PadRight(widths[i], '-')).ToArray() + }; + lines.AddRange(rows); + + return string.Join( + Environment.NewLine, + lines.Select(line => string.Join(" | ", line.Select((field, i) => field.PadRight(widths[i], ' ')).ToArray()) + ) + ); + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/DownMineLevelCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/DownMineLevelCommand.cs new file mode 100644 index 00000000..da117006 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/DownMineLevelCommand.cs @@ -0,0 +1,27 @@ +using StardewValley; +using StardewValley.Locations; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World +{ + /// A command which moves the player to the next mine level. + internal class DownMineLevelCommand : TrainerCommand + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public DownMineLevelCommand() + : base("world_downminelevel", "Goes down one mine level.") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + int level = (Game1.currentLocation as MineShaft)?.mineLevel ?? 0; + monitor.Log($"OK, warping you to mine level {level + 1}.", LogLevel.Info); + Game1.enterMine(false, level + 1, ""); + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/FreezeTimeCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/FreezeTimeCommand.cs new file mode 100644 index 00000000..2627b714 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/FreezeTimeCommand.cs @@ -0,0 +1,66 @@ +using System.Linq; +using StardewValley; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World +{ + /// A command which freezes the current time. + internal class FreezeTimeCommand : TrainerCommand + { + /********* + ** Properties + *********/ + /// The time of day at which to freeze time. + internal static int FrozenTime; + + /// Whether to freeze time. + private bool FreezeTime; + + + /********* + ** Accessors + *********/ + /// Whether the command needs to perform logic when the game updates. + public override bool NeedsUpdate => this.FreezeTime; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public FreezeTimeCommand() + : base("world_freezetime", "Freezes or resumes time.\n\nUsage: world_freezetime [value]\n- value: one of 0 (resume), 1 (freeze), or blank (toggle).") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + if (args.Any()) + { + // parse arguments + if (!args.TryGetInt(0, "value", out int value, min: 0, max: 1)) + return; + + // handle + this.FreezeTime = value == 1; + FreezeTimeCommand.FrozenTime = Game1.timeOfDay; + monitor.Log($"OK, time is now {(this.FreezeTime ? "frozen" : "resumed")}.", LogLevel.Info); + } + else + { + this.FreezeTime = !this.FreezeTime; + FreezeTimeCommand.FrozenTime = Game1.timeOfDay; + monitor.Log($"OK, time is now {(this.FreezeTime ? "frozen" : "resumed")}.", LogLevel.Info); + } + } + + /// Perform any logic needed on update tick. + /// Writes messages to the console and log file. + public override void Update(IMonitor monitor) + { + if (this.FreezeTime) + Game1.timeOfDay = FreezeTimeCommand.FrozenTime; + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetDayCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetDayCommand.cs new file mode 100644 index 00000000..8d6bd759 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetDayCommand.cs @@ -0,0 +1,38 @@ +using System.Linq; +using StardewValley; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World +{ + /// A command which sets the current day. + internal class SetDayCommand : TrainerCommand + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public SetDayCommand() + : base("world_setday", "Sets the day to the specified value.\n\nUsage: world_setday .\n- value: the target day (a number from 1 to 28).") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // no-argument mode + if (!args.Any()) + { + monitor.Log($"The current date is {Game1.currentSeason} {Game1.dayOfMonth}. Specify a value to change the day.", LogLevel.Info); + return; + } + + // parse arguments + if (!args.TryGetInt(0, "day", out int day, min: 1, max: 28)) + return; + + // handle + Game1.dayOfMonth = day; + monitor.Log($"OK, the date is now {Game1.currentSeason} {Game1.dayOfMonth}.", LogLevel.Info); + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetMineLevelCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetMineLevelCommand.cs new file mode 100644 index 00000000..1024b7b6 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetMineLevelCommand.cs @@ -0,0 +1,32 @@ +using System; +using StardewValley; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World +{ + /// A command which moves the player to the given mine level. + internal class SetMineLevelCommand : TrainerCommand + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public SetMineLevelCommand() + : base("world_setminelevel", "Sets the mine level?\n\nUsage: world_setminelevel \n- value: The target level (a number starting at 1).") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // parse arguments + if (!args.TryGetInt(0, "mine level", out int level, min: 1)) + return; + + // handle + level = Math.Max(1, level); + monitor.Log($"OK, warping you to mine level {level}.", LogLevel.Info); + Game1.enterMine(true, level, ""); + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetSeasonCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetSeasonCommand.cs new file mode 100644 index 00000000..897d052f --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetSeasonCommand.cs @@ -0,0 +1,45 @@ +using System.Linq; +using StardewValley; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World +{ + /// A command which sets the current season. + internal class SetSeasonCommand : TrainerCommand + { + /********* + ** Properties + *********/ + /// The valid season names. + private readonly string[] ValidSeasons = { "winter", "spring", "summer", "fall" }; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public SetSeasonCommand() + : base("world_setseason", "Sets the season to the specified value.\n\nUsage: world_setseason \n- season: the target season (one of 'spring', 'summer', 'fall', 'winter').") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // no-argument mode + if (!args.Any()) + { + monitor.Log($"The current season is {Game1.currentSeason}. Specify a value to change it.", LogLevel.Info); + return; + } + + // parse arguments + if (!args.TryGet(0, "season", out string season, oneOf: this.ValidSeasons)) + return; + + // handle + Game1.currentSeason = season; + monitor.Log($"OK, the date is now {Game1.currentSeason} {Game1.dayOfMonth}.", LogLevel.Info); + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs new file mode 100644 index 00000000..d6c71387 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs @@ -0,0 +1,39 @@ +using System.Linq; +using StardewValley; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World +{ + /// A command which sets the current time. + internal class SetTimeCommand : TrainerCommand + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public SetTimeCommand() + : base("world_settime", "Sets the time to the specified value.\n\nUsage: world_settime \n- value: the target time in military time (like 0600 for 6am and 1800 for 6pm).") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // no-argument mode + if (!args.Any()) + { + monitor.Log($"The current time is {Game1.timeOfDay}. Specify a value to change it.", LogLevel.Info); + return; + } + + // parse arguments + if (!args.TryGetInt(0, "time", out int time, min: 600, max: 2600)) + return; + + // handle + Game1.timeOfDay = time; + FreezeTimeCommand.FrozenTime = Game1.timeOfDay; + monitor.Log($"OK, the time is now {Game1.timeOfDay.ToString().PadLeft(4, '0')}.", LogLevel.Info); + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetYearCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetYearCommand.cs new file mode 100644 index 00000000..66abd6dc --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetYearCommand.cs @@ -0,0 +1,38 @@ +using System.Linq; +using StardewValley; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World +{ + /// A command which sets the current year. + internal class SetYearCommand : TrainerCommand + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public SetYearCommand() + : base("world_setyear", "Sets the year to the specified value.\n\nUsage: world_setyear \n- year: the target year (a number starting from 1).") { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // no-argument mode + if (!args.Any()) + { + monitor.Log($"The current year is {Game1.year}. Specify a value to change the year.", LogLevel.Info); + return; + } + + // parse arguments + if (!args.TryGetInt(0, "year", out int year, min: 1)) + return; + + // handle + Game1.year = year; + monitor.Log($"OK, the year is now {Game1.year}.", LogLevel.Info); + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/ItemType.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/ItemType.cs new file mode 100644 index 00000000..797d4650 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/ItemType.cs @@ -0,0 +1,39 @@ +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData +{ + /// An item type that can be searched and added to the player through the console. + internal enum ItemType + { + /// A big craftable object in + BigCraftable, + + /// A item. + Boots, + + /// A fish item. + Fish, + + /// A flooring item. + Flooring, + + /// A item. + Furniture, + + /// A item. + Hat, + + /// Any object in (except rings). + Object, + + /// A item. + Ring, + + /// A tool. + Tool, + + /// A wall item. + Wallpaper, + + /// A or item. + Weapon + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs new file mode 100644 index 00000000..3eede413 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs @@ -0,0 +1,41 @@ +using StardewValley; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData +{ + /// A game item with metadata. + internal class SearchableItem + { + /********* + ** Accessors + *********/ + /// The item type. + public ItemType Type { get; } + + /// The item instance. + public Item Item { get; } + + /// The item's unique ID for its type. + public int ID { get; } + + /// The item's default name. + public string Name => this.Item.Name; + + /// The item's display name for the current language. + public string DisplayName => this.Item.DisplayName; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The item type. + /// The unique ID (if different from the item's parent sheet index). + /// The item instance. + public SearchableItem(ItemType type, int id, Item item) + { + this.Type = type; + this.ID = id; + this.Item = item; + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs new file mode 100644 index 00000000..b5fe9f2f --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs @@ -0,0 +1,179 @@ +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData; +using StardewValley; +using StardewValley.Objects; +using StardewValley.Tools; +using SObject = StardewValley.Object; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework +{ + /// Provides methods for searching and constructing items. + internal class ItemRepository + { + /********* + ** Properties + *********/ + /// The custom ID offset for items don't have a unique ID in the game. + private readonly int CustomIDOffset = 1000; + + + /********* + ** Public methods + *********/ + /// Get all spawnable items. + public IEnumerable GetAll() + { + // get tools + for (int quality = Tool.stone; quality <= Tool.iridium; quality++) + { + yield return new SearchableItem(ItemType.Tool, ToolFactory.axe, ToolFactory.getToolFromDescription(ToolFactory.axe, quality)); + yield return new SearchableItem(ItemType.Tool, ToolFactory.hoe, ToolFactory.getToolFromDescription(ToolFactory.hoe, quality)); + yield return new SearchableItem(ItemType.Tool, ToolFactory.pickAxe, ToolFactory.getToolFromDescription(ToolFactory.pickAxe, quality)); + yield return new SearchableItem(ItemType.Tool, ToolFactory.wateringCan, ToolFactory.getToolFromDescription(ToolFactory.wateringCan, quality)); + if (quality != Tool.iridium) + yield return new SearchableItem(ItemType.Tool, ToolFactory.fishingRod, ToolFactory.getToolFromDescription(ToolFactory.fishingRod, quality)); + } + yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset, new MilkPail()); // these don't have any sort of ID, so we'll just assign some arbitrary ones + yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset + 1, new Shears()); + yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset + 2, new Pan()); + + // wallpapers + for (int id = 0; id < 112; id++) + yield return new SearchableItem(ItemType.Wallpaper, id, new Wallpaper(id)); + + // flooring + for (int id = 0; id < 40; id++) + yield return new SearchableItem(ItemType.Flooring, id, new Wallpaper(id, isFloor: true)); + + // equipment + foreach (int id in Game1.content.Load>("Data\\Boots").Keys) + yield return new SearchableItem(ItemType.Boots, id, new Boots(id)); + foreach (int id in Game1.content.Load>("Data\\hats").Keys) + yield return new SearchableItem(ItemType.Hat, id, new Hat(id)); + foreach (int id in Game1.objectInformation.Keys) + { + if (id >= Ring.ringLowerIndexRange && id <= Ring.ringUpperIndexRange) + yield return new SearchableItem(ItemType.Ring, id, new Ring(id)); + } + + // weapons + foreach (int id in Game1.content.Load>("Data\\weapons").Keys) + { + Item weapon = (id >= 32 && id <= 34) + ? (Item)new Slingshot(id) + : new MeleeWeapon(id); + yield return new SearchableItem(ItemType.Weapon, id, weapon); + } + + // furniture + foreach (int id in Game1.content.Load>("Data\\Furniture").Keys) + { + if (id == 1466 || id == 1468) + yield return new SearchableItem(ItemType.Furniture, id, new TV(id, Vector2.Zero)); + else + yield return new SearchableItem(ItemType.Furniture, id, new Furniture(id, Vector2.Zero)); + } + + // fish + foreach (int id in Game1.content.Load>("Data\\Fish").Keys) + yield return new SearchableItem(ItemType.Fish, id, new SObject(id, 999)); + + // craftables + foreach (int id in Game1.bigCraftablesInformation.Keys) + yield return new SearchableItem(ItemType.BigCraftable, id, new SObject(Vector2.Zero, id)); + + // objects + foreach (int id in Game1.objectInformation.Keys) + { + if (id >= Ring.ringLowerIndexRange && id <= Ring.ringUpperIndexRange) + continue; // handled separated + + SObject item = new SObject(id, 1); + yield return new SearchableItem(ItemType.Object, id, item); + + // fruit products + if (item.category == SObject.FruitsCategory) + { + yield return new SearchableItem(ItemType.Object, this.CustomIDOffset + id, new SObject(348, 1) + { + name = $"{item.Name} Wine", + price = item.price * 3, + preserve = SObject.PreserveType.Wine, + preservedParentSheetIndex = item.parentSheetIndex + }); + yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 2 + id, new SObject(344, 1) + { + name = $"{item.Name} Jelly", + price = 50 + item.Price * 2, + preserve = SObject.PreserveType.Jelly, + preservedParentSheetIndex = item.parentSheetIndex + }); + } + + // vegetable products + else if (item.category == SObject.VegetableCategory) + { + yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 3 + id, new SObject(350, 1) + { + name = $"{item.Name} Juice", + price = (int)(item.price * 2.25d), + preserve = SObject.PreserveType.Juice, + preservedParentSheetIndex = item.parentSheetIndex + }); + yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 4 + id, new SObject(342, 1) + { + name = $"Pickled {item.Name}", + price = 50 + item.Price * 2, + preserve = SObject.PreserveType.Pickle, + preservedParentSheetIndex = item.parentSheetIndex + }); + } + + // flower honey + else if (item.category == SObject.flowersCategory) + { + // get honey type + SObject.HoneyType? type = null; + switch (item.parentSheetIndex) + { + case 376: + type = SObject.HoneyType.Poppy; + break; + case 591: + type = SObject.HoneyType.Tulip; + break; + case 593: + type = SObject.HoneyType.SummerSpangle; + break; + case 595: + type = SObject.HoneyType.FairyRose; + break; + case 597: + type = SObject.HoneyType.BlueJazz; + break; + case 421: // sunflower standing in for all other flowers + type = SObject.HoneyType.Wild; + break; + } + + // yield honey + if (type != null) + { + SObject honey = new SObject(Vector2.Zero, 340, item.Name + " Honey", false, true, false, false) + { + name = "Wild Honey", + honeyType = type + }; + if (type != SObject.HoneyType.Wild) + { + honey.name = $"{item.Name} Honey"; + honey.price += item.price * 2; + } + yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 5 + id, honey); + } + } + } + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/Properties/AssemblyInfo.cs b/src/SMAPI.Mods.ConsoleCommands/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..ac15ec72 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("SMAPI.Mods.ConsoleCommands")] +[assembly: AssemblyDescription("")] +[assembly: Guid("76791e28-b1b5-407c-82d6-50c3e5b7e037")] diff --git a/src/SMAPI.Mods.ConsoleCommands/StardewModdingAPI.Mods.ConsoleCommands.csproj b/src/SMAPI.Mods.ConsoleCommands/StardewModdingAPI.Mods.ConsoleCommands.csproj new file mode 100644 index 00000000..437d0986 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/StardewModdingAPI.Mods.ConsoleCommands.csproj @@ -0,0 +1,101 @@ + + + + + Debug + x86 + {28480467-1A48-46A7-99F8-236D95225359} + Library + Properties + StardewModdingAPI.Mods.ConsoleCommands + ConsoleCommands + v4.5 + 512 + + + true + full + true + $(SolutionDir)\..\bin\Debug\Mods\ConsoleCommands\ + DEBUG;TRACE + prompt + 4 + x86 + false + true + + + pdbonly + true + $(SolutionDir)\..\bin\Release\Mods\ConsoleCommands\ + TRACE + prompt + 4 + false + true + x86 + + + + ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + + + + + + + + + + + Properties\GlobalAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {f1a573b0-f436-472c-ae29-0b91ea6b9f8f} + StardewModdingAPI + False + + + + + PreserveNewest + + + + + + \ No newline at end of file diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json new file mode 100644 index 00000000..664dfabf --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -0,0 +1,13 @@ +{ + "Name": "Console Commands", + "Author": "SMAPI", + "Version": { + "MajorVersion": 2, + "MinorVersion": 0, + "PatchVersion": 0, + "Build": null + }, + "Description": "Adds SMAPI console commands that let you manipulate the game.", + "UniqueID": "SMAPI.ConsoleCommands", + "EntryDll": "ConsoleCommands.dll" +} diff --git a/src/SMAPI.Mods.ConsoleCommands/packages.config b/src/SMAPI.Mods.ConsoleCommands/packages.config new file mode 100644 index 00000000..ee51c237 --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/SMAPI.sln b/src/SMAPI.sln index 89a8d45c..8d730f37 100644 --- a/src/SMAPI.sln +++ b/src/SMAPI.sln @@ -1,9 +1,9 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26730.16 +VisualStudioVersion = 15.0.27004.2002 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrainerMod", "TrainerMod\TrainerMod.csproj", "{28480467-1A48-46A7-99F8-236D95225359}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleCommands", "SMAPI.Mods.ConsoleCommands\StardewModdingAPI.Mods.ConsoleCommands.csproj", "{28480467-1A48-46A7-99F8-236D95225359}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StardewModdingAPI", "SMAPI\StardewModdingAPI.csproj", "{F1A573B0-F436-472C-AE29-0B91EA6B9F8F}" EndProject diff --git a/src/TrainerMod/Framework/Commands/ArgumentParser.cs b/src/TrainerMod/Framework/Commands/ArgumentParser.cs deleted file mode 100644 index 6bcd3ff8..00000000 --- a/src/TrainerMod/Framework/Commands/ArgumentParser.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using StardewModdingAPI; - -namespace TrainerMod.Framework.Commands -{ - /// Provides methods for parsing command-line arguments. - internal class ArgumentParser : IReadOnlyList - { - /********* - ** Properties - *********/ - /// The command name for errors. - private readonly string CommandName; - - /// The arguments to parse. - private readonly string[] Args; - - /// Writes messages to the console and log file. - private readonly IMonitor Monitor; - - - /********* - ** Accessors - *********/ - /// Get the number of arguments. - public int Count => this.Args.Length; - - /// Get the argument at the specified index in the list. - /// The zero-based index of the element to get. - public string this[int index] => this.Args[index]; - - /// A method which parses a string argument into the given value. - /// The expected argument type. - /// The argument to parse. - /// The parsed value. - /// Returns whether the argument was successfully parsed. - public delegate bool ParseDelegate(string input, out T output); - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The command name for errors. - /// The arguments to parse. - /// Writes messages to the console and log file. - public ArgumentParser(string commandName, string[] args, IMonitor monitor) - { - this.CommandName = commandName; - this.Args = args; - this.Monitor = monitor; - } - - /// Try to read a string argument. - /// The argument index. - /// The argument name for error messages. - /// The parsed value. - /// Whether to show an error if the argument is missing. - /// Require that the argument match one of the given values (case-insensitive). - public bool TryGet(int index, string name, out string value, bool required = true, string[] oneOf = null) - { - value = null; - - // validate - if (this.Args.Length < index + 1) - { - if (required) - this.LogError($"Argument {index} ({name}) is required."); - return false; - } - if (oneOf?.Any() == true && !oneOf.Contains(this.Args[index], StringComparer.InvariantCultureIgnoreCase)) - { - this.LogError($"Argument {index} ({name}) must be one of {string.Join(", ", oneOf)}."); - return false; - } - - // get value - value = this.Args[index]; - return true; - } - - /// Try to read an integer argument. - /// The argument index. - /// The argument name for error messages. - /// The parsed value. - /// Whether to show an error if the argument is missing. - /// The minimum value allowed. - /// The maximum value allowed. - public bool TryGetInt(int index, string name, out int value, bool required = true, int? min = null, int? max = null) - { - value = 0; - - // get argument - if (!this.TryGet(index, name, out string raw, required)) - return false; - - // parse - if (!int.TryParse(raw, out value)) - { - this.LogIntFormatError(index, name, min, max); - return false; - } - - // validate - if ((min.HasValue && value < min) || (max.HasValue && value > max)) - { - this.LogIntFormatError(index, name, min, max); - return false; - } - - return true; - } - - /// Returns an enumerator that iterates through the collection. - /// An enumerator that can be used to iterate through the collection. - public IEnumerator GetEnumerator() - { - return ((IEnumerable)this.Args).GetEnumerator(); - } - - /// Returns an enumerator that iterates through a collection. - /// An object that can be used to iterate through the collection. - IEnumerator IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } - - - /********* - ** Private methods - *********/ - /// Log a usage error. - /// The message describing the error. - private void LogError(string message) - { - this.Monitor.Log($"{message} Type 'help {this.CommandName}' for usage.", LogLevel.Error); - } - - /// Print an error for an invalid int argument. - /// The argument index. - /// The argument name for error messages. - /// The minimum value allowed. - /// The maximum value allowed. - private void LogIntFormatError(int index, string name, int? min, int? max) - { - if (min.HasValue && max.HasValue) - this.LogError($"Argument {index} ({name}) must be an integer between {min} and {max}."); - else if (min.HasValue) - this.LogError($"Argument {index} ({name}) must be an integer and at least {min}."); - else if (max.HasValue) - this.LogError($"Argument {index} ({name}) must be an integer and at most {max}."); - else - this.LogError($"Argument {index} ({name}) must be an integer."); - } - } -} diff --git a/src/TrainerMod/Framework/Commands/ITrainerCommand.cs b/src/TrainerMod/Framework/Commands/ITrainerCommand.cs deleted file mode 100644 index 3d97e799..00000000 --- a/src/TrainerMod/Framework/Commands/ITrainerCommand.cs +++ /dev/null @@ -1,34 +0,0 @@ -using StardewModdingAPI; - -namespace TrainerMod.Framework.Commands -{ - /// A TrainerMod command to register. - internal interface ITrainerCommand - { - /********* - ** Accessors - *********/ - /// The command name the user must type. - string Name { get; } - - /// The command description. - string Description { get; } - - /// Whether the command needs to perform logic when the game updates. - bool NeedsUpdate { get; } - - - /********* - ** Public methods - *********/ - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - void Handle(IMonitor monitor, string command, ArgumentParser args); - - /// Perform any logic needed on update tick. - /// Writes messages to the console and log file. - void Update(IMonitor monitor); - } -} diff --git a/src/TrainerMod/Framework/Commands/Other/DebugCommand.cs b/src/TrainerMod/Framework/Commands/Other/DebugCommand.cs deleted file mode 100644 index 8c6e9f3b..00000000 --- a/src/TrainerMod/Framework/Commands/Other/DebugCommand.cs +++ /dev/null @@ -1,33 +0,0 @@ -using StardewModdingAPI; -using StardewValley; - -namespace TrainerMod.Framework.Commands.Other -{ - /// A command which sends a debug command to the game. - internal class DebugCommand : TrainerCommand - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public DebugCommand() - : base("debug", "Run one of the game's debug commands; for example, 'debug warp FarmHouse 1 1' warps the player to the farmhouse.") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // submit command - string debugCommand = string.Join(" ", args); - string oldOutput = Game1.debugOutput; - Game1.game1.parseDebugInput(debugCommand); - - // show result - monitor.Log(Game1.debugOutput != oldOutput - ? $"> {Game1.debugOutput}" - : "Sent debug command to the game, but there was no output.", LogLevel.Info); - } - } -} diff --git a/src/TrainerMod/Framework/Commands/Other/ShowDataFilesCommand.cs b/src/TrainerMod/Framework/Commands/Other/ShowDataFilesCommand.cs deleted file mode 100644 index 367a70c6..00000000 --- a/src/TrainerMod/Framework/Commands/Other/ShowDataFilesCommand.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Diagnostics; -using StardewModdingAPI; - -namespace TrainerMod.Framework.Commands.Other -{ - /// A command which shows the data files. - internal class ShowDataFilesCommand : TrainerCommand - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public ShowDataFilesCommand() - : base("show_data_files", "Opens the folder containing the save and log files.") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - Process.Start(Constants.DataPath); - monitor.Log($"OK, opening {Constants.DataPath}.", LogLevel.Info); - } - } -} diff --git a/src/TrainerMod/Framework/Commands/Other/ShowGameFilesCommand.cs b/src/TrainerMod/Framework/Commands/Other/ShowGameFilesCommand.cs deleted file mode 100644 index 67fa83a3..00000000 --- a/src/TrainerMod/Framework/Commands/Other/ShowGameFilesCommand.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Diagnostics; -using StardewModdingAPI; - -namespace TrainerMod.Framework.Commands.Other -{ - /// A command which shows the game files. - internal class ShowGameFilesCommand : TrainerCommand - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public ShowGameFilesCommand() - : base("show_game_files", "Opens the game folder.") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - Process.Start(Constants.ExecutionPath); - monitor.Log($"OK, opening {Constants.ExecutionPath}.", LogLevel.Info); - } - } -} diff --git a/src/TrainerMod/Framework/Commands/Player/AddCommand.cs b/src/TrainerMod/Framework/Commands/Player/AddCommand.cs deleted file mode 100644 index 47840202..00000000 --- a/src/TrainerMod/Framework/Commands/Player/AddCommand.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Linq; -using StardewModdingAPI; -using StardewValley; -using TrainerMod.Framework.ItemData; -using Object = StardewValley.Object; - -namespace TrainerMod.Framework.Commands.Player -{ - /// A command which adds an item to the player inventory. - internal class AddCommand : TrainerCommand - { - /********* - ** Properties - *********/ - /// Provides methods for searching and constructing items. - private readonly ItemRepository Items = new ItemRepository(); - - - /********* - ** Public methods - *********/ - /// Construct an instance. - public AddCommand() - : base("player_add", AddCommand.GetDescription()) - { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // read arguments - if (!args.TryGet(0, "item type", out string rawType, oneOf: Enum.GetNames(typeof(ItemType)))) - return; - if (!args.TryGetInt(1, "item ID", out int id, min: 0)) - return; - if (!args.TryGetInt(2, "count", out int count, min: 1, required: false)) - count = 1; - if (!args.TryGetInt(3, "quality", out int quality, min: Object.lowQuality, max: Object.bestQuality, required: false)) - quality = Object.lowQuality; - ItemType type = (ItemType)Enum.Parse(typeof(ItemType), rawType, ignoreCase: true); - - // find matching item - SearchableItem match = this.Items.GetAll().FirstOrDefault(p => p.Type == type && p.ID == id); - if (match == null) - { - monitor.Log($"There's no {type} item with ID {id}.", LogLevel.Error); - return; - } - - // apply count & quality - match.Item.Stack = count; - if (match.Item is Object obj) - obj.quality = quality; - - // add to inventory - Game1.player.addItemByMenuIfNecessary(match.Item); - monitor.Log($"OK, added {match.Name} ({match.Type} #{match.ID}) to your inventory.", LogLevel.Info); - } - - /********* - ** Private methods - *********/ - private static string GetDescription() - { - string[] typeValues = Enum.GetNames(typeof(ItemType)); - return "Gives the player an item.\n" - + "\n" - + "Usage: player_add [count] [quality]\n" - + $"- type: the item type (one of {string.Join(", ", typeValues)}).\n" - + "- item: the item ID (use the 'list_items' command to see a list).\n" - + "- count (optional): how many of the item to give.\n" - + $"- quality (optional): one of {Object.lowQuality} (normal), {Object.medQuality} (silver), {Object.highQuality} (gold), or {Object.bestQuality} (iridium).\n" - + "\n" - + "This example adds the galaxy sword to your inventory:\n" - + " player_add weapon 4"; - } - } -} diff --git a/src/TrainerMod/Framework/Commands/Player/ListItemTypesCommand.cs b/src/TrainerMod/Framework/Commands/Player/ListItemTypesCommand.cs deleted file mode 100644 index 5f14edbb..00000000 --- a/src/TrainerMod/Framework/Commands/Player/ListItemTypesCommand.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Linq; -using StardewModdingAPI; -using TrainerMod.Framework.ItemData; - -namespace TrainerMod.Framework.Commands.Player -{ - /// A command which list item types. - internal class ListItemTypesCommand : TrainerCommand - { - /********* - ** Properties - *********/ - /// Provides methods for searching and constructing items. - private readonly ItemRepository Items = new ItemRepository(); - - - /********* - ** Public methods - *********/ - /// Construct an instance. - public ListItemTypesCommand() - : base("list_item_types", "Lists item types you can filter in other commands.\n\nUsage: list_item_types") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // validate - if (!Context.IsWorldReady) - { - monitor.Log("You need to load a save to use this command.", LogLevel.Error); - return; - } - - // handle - ItemType[] matches = - ( - from item in this.Items.GetAll() - orderby item.Type.ToString() - select item.Type - ) - .Distinct() - .ToArray(); - string summary = "Searching...\n"; - if (matches.Any()) - monitor.Log(summary + this.GetTableString(matches, new[] { "type" }, val => new[] { val.ToString() }), LogLevel.Info); - else - monitor.Log(summary + "No item types found.", LogLevel.Info); - } - } -} diff --git a/src/TrainerMod/Framework/Commands/Player/ListItemsCommand.cs b/src/TrainerMod/Framework/Commands/Player/ListItemsCommand.cs deleted file mode 100644 index 7f4f454c..00000000 --- a/src/TrainerMod/Framework/Commands/Player/ListItemsCommand.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using StardewModdingAPI; -using TrainerMod.Framework.ItemData; - -namespace TrainerMod.Framework.Commands.Player -{ - /// A command which list items available to spawn. - internal class ListItemsCommand : TrainerCommand - { - /********* - ** Properties - *********/ - /// Provides methods for searching and constructing items. - private readonly ItemRepository Items = new ItemRepository(); - - - /********* - ** Public methods - *********/ - /// Construct an instance. - public ListItemsCommand() - : base("list_items", "Lists and searches items in the game data.\n\nUsage: list_items [search]\n- search (optional): an arbitrary search string to filter by.") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // validate - if (!Context.IsWorldReady) - { - monitor.Log("You need to load a save to use this command.", LogLevel.Error); - return; - } - - // handle - SearchableItem[] matches = - ( - from item in this.GetItems(args.ToArray()) - orderby item.Type.ToString(), item.Name - select item - ) - .ToArray(); - string summary = "Searching...\n"; - if (matches.Any()) - monitor.Log(summary + this.GetTableString(matches, new[] { "type", "name", "id" }, val => new[] { val.Type.ToString(), val.Name, val.ID.ToString() }), LogLevel.Info); - else - monitor.Log(summary + "No items found", LogLevel.Info); - } - - - /********* - ** Private methods - *********/ - /// Get all items which can be searched and added to the player's inventory through the console. - /// The search string to find. - private IEnumerable GetItems(string[] searchWords) - { - // normalise search term - searchWords = searchWords?.Where(word => !string.IsNullOrWhiteSpace(word)).ToArray(); - if (searchWords?.Any() == false) - searchWords = null; - - // find matches - return ( - from item in this.Items.GetAll() - let term = $"{item.ID}|{item.Type}|{item.Name}|{item.DisplayName}" - where searchWords == null || searchWords.All(word => term.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) != -1) - select item - ); - } - } -} diff --git a/src/TrainerMod/Framework/Commands/Player/SetColorCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetColorCommand.cs deleted file mode 100644 index 28ace0df..00000000 --- a/src/TrainerMod/Framework/Commands/Player/SetColorCommand.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Microsoft.Xna.Framework; -using StardewModdingAPI; -using StardewValley; - -namespace TrainerMod.Framework.Commands.Player -{ - /// A command which edits the color of a player feature. - internal class SetColorCommand : TrainerCommand - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public SetColorCommand() - : base("player_changecolor", "Sets the color of a player feature.\n\nUsage: player_changecolor \n- target: what to change (one of 'hair', 'eyes', or 'pants').\n- color: a color value in RGB format, like (255,255,255).") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // parse arguments - if (!args.TryGet(0, "target", out string target, oneOf: new[] { "hair", "eyes", "pants" })) - return; - if (!args.TryGet(1, "color", out string rawColor)) - return; - - // parse color - if (!this.TryParseColor(rawColor, out Color color)) - { - this.LogUsageError(monitor, "Argument 1 (color) must be an RBG value like '255,150,0'."); - return; - } - - // handle - switch (target) - { - case "hair": - Game1.player.hairstyleColor = color; - monitor.Log("OK, your hair color is updated.", LogLevel.Info); - break; - - case "eyes": - Game1.player.changeEyeColor(color); - monitor.Log("OK, your eye color is updated.", LogLevel.Info); - break; - - case "pants": - Game1.player.pantsColor = color; - monitor.Log("OK, your pants color is updated.", LogLevel.Info); - break; - } - } - - - /********* - ** Private methods - *********/ - /// Try to parse a color from a string. - /// The input string. - /// The color to set. - private bool TryParseColor(string input, out Color color) - { - string[] colorHexes = input.Split(new[] { ',' }, 3); - if (int.TryParse(colorHexes[0], out int r) && int.TryParse(colorHexes[1], out int g) && int.TryParse(colorHexes[2], out int b)) - { - color = new Color(r, g, b); - return true; - } - - color = Color.Transparent; - return false; - } - } -} diff --git a/src/TrainerMod/Framework/Commands/Player/SetHealthCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetHealthCommand.cs deleted file mode 100644 index f64e9035..00000000 --- a/src/TrainerMod/Framework/Commands/Player/SetHealthCommand.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Linq; -using StardewModdingAPI; -using StardewValley; - -namespace TrainerMod.Framework.Commands.Player -{ - /// A command which edits the player's current health. - internal class SetHealthCommand : TrainerCommand - { - /********* - ** Properties - *********/ - /// Whether to keep the player's health at its maximum. - private bool InfiniteHealth; - - - /********* - ** Accessors - *********/ - /// Whether the command needs to perform logic when the game updates. - public override bool NeedsUpdate => this.InfiniteHealth; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - public SetHealthCommand() - : base("player_sethealth", "Sets the player's health.\n\nUsage: player_sethealth [value]\n- value: an integer amount, or 'inf' for infinite health.") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // no-argument mode - if (!args.Any()) - { - monitor.Log($"You currently have {(this.InfiniteHealth ? "infinite" : Game1.player.health.ToString())} health. Specify a value to change it.", LogLevel.Info); - return; - } - - // handle - string amountStr = args[0]; - if (amountStr == "inf") - { - this.InfiniteHealth = true; - monitor.Log("OK, you now have infinite health.", LogLevel.Info); - } - else - { - this.InfiniteHealth = false; - if (int.TryParse(amountStr, out int amount)) - { - Game1.player.health = amount; - monitor.Log($"OK, you now have {Game1.player.health} health.", LogLevel.Info); - } - else - this.LogArgumentNotInt(monitor); - } - } - - /// Perform any logic needed on update tick. - /// Writes messages to the console and log file. - public override void Update(IMonitor monitor) - { - if (this.InfiniteHealth) - Game1.player.health = Game1.player.maxHealth; - } - } -} diff --git a/src/TrainerMod/Framework/Commands/Player/SetImmunityCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetImmunityCommand.cs deleted file mode 100644 index 59b28a3c..00000000 --- a/src/TrainerMod/Framework/Commands/Player/SetImmunityCommand.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Linq; -using StardewModdingAPI; -using StardewValley; - -namespace TrainerMod.Framework.Commands.Player -{ - /// A command which edits the player's current immunity. - internal class SetImmunityCommand : TrainerCommand - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public SetImmunityCommand() - : base("player_setimmunity", "Sets the player's immunity.\n\nUsage: player_setimmunity [value]\n- value: an integer amount.") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // validate - if (!args.Any()) - { - monitor.Log($"You currently have {Game1.player.immunity} immunity. Specify a value to change it.", LogLevel.Info); - return; - } - - // handle - if (args.TryGetInt(0, "amount", out int amount, min: 0)) - { - Game1.player.immunity = amount; - monitor.Log($"OK, you now have {Game1.player.immunity} immunity.", LogLevel.Info); - } - } - } -} diff --git a/src/TrainerMod/Framework/Commands/Player/SetLevelCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetLevelCommand.cs deleted file mode 100644 index 54d5e47b..00000000 --- a/src/TrainerMod/Framework/Commands/Player/SetLevelCommand.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System.Collections.Generic; -using StardewModdingAPI; -using StardewValley; -using SFarmer = StardewValley.Farmer; - -namespace TrainerMod.Framework.Commands.Player -{ - /// A command which edits the player's current level for a skill. - internal class SetLevelCommand : TrainerCommand - { - /********* - ** Properties - *********/ - /// The experience points needed to reach each level. - /// Derived from . - private readonly IDictionary LevelExp = new Dictionary - { - [0] = 0, - [1] = 100, - [2] = 380, - [3] = 770, - [4] = 1300, - [5] = 2150, - [6] = 3300, - [7] = 4800, - [8] = 6900, - [9] = 10000, - [10] = 15000 - }; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - public SetLevelCommand() - : base("player_setlevel", "Sets the player's specified skill to the specified value.\n\nUsage: player_setlevel \n- skill: the skill to set (one of 'luck', 'mining', 'combat', 'farming', 'fishing', or 'foraging').\n- value: the target level (a number from 1 to 10).") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // validate - if (!args.TryGet(0, "skill", out string skill, oneOf: new[] { "luck", "mining", "combat", "farming", "fishing", "foraging" })) - return; - if (!args.TryGetInt(1, "level", out int level, min: 0, max: 10)) - return; - - // handle - switch (skill) - { - case "luck": - Game1.player.LuckLevel = level; - Game1.player.experiencePoints[SFarmer.luckSkill] = this.LevelExp[level]; - monitor.Log($"OK, your luck skill is now {Game1.player.LuckLevel}.", LogLevel.Info); - break; - - case "mining": - Game1.player.MiningLevel = level; - Game1.player.experiencePoints[SFarmer.miningSkill] = this.LevelExp[level]; - monitor.Log($"OK, your mining skill is now {Game1.player.MiningLevel}.", LogLevel.Info); - break; - - case "combat": - Game1.player.CombatLevel = level; - Game1.player.experiencePoints[SFarmer.combatSkill] = this.LevelExp[level]; - monitor.Log($"OK, your combat skill is now {Game1.player.CombatLevel}.", LogLevel.Info); - break; - - case "farming": - Game1.player.FarmingLevel = level; - Game1.player.experiencePoints[SFarmer.farmingSkill] = this.LevelExp[level]; - monitor.Log($"OK, your farming skill is now {Game1.player.FarmingLevel}.", LogLevel.Info); - break; - - case "fishing": - Game1.player.FishingLevel = level; - Game1.player.experiencePoints[SFarmer.fishingSkill] = this.LevelExp[level]; - monitor.Log($"OK, your fishing skill is now {Game1.player.FishingLevel}.", LogLevel.Info); - break; - - case "foraging": - Game1.player.ForagingLevel = level; - Game1.player.experiencePoints[SFarmer.foragingSkill] = this.LevelExp[level]; - monitor.Log($"OK, your foraging skill is now {Game1.player.ForagingLevel}.", LogLevel.Info); - break; - } - } - } -} diff --git a/src/TrainerMod/Framework/Commands/Player/SetMaxHealthCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetMaxHealthCommand.cs deleted file mode 100644 index 4b9d87dc..00000000 --- a/src/TrainerMod/Framework/Commands/Player/SetMaxHealthCommand.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Linq; -using StardewModdingAPI; -using StardewValley; - -namespace TrainerMod.Framework.Commands.Player -{ - /// A command which edits the player's maximum health. - internal class SetMaxHealthCommand : TrainerCommand - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public SetMaxHealthCommand() - : base("player_setmaxhealth", "Sets the player's max health.\n\nUsage: player_setmaxhealth [value]\n- value: an integer amount.") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // validate - if (!args.Any()) - { - monitor.Log($"You currently have {Game1.player.maxHealth} max health. Specify a value to change it.", LogLevel.Info); - return; - } - - // handle - if (args.TryGetInt(0, "amount", out int amount, min: 1)) - { - Game1.player.maxHealth = amount; - monitor.Log($"OK, you now have {Game1.player.maxHealth} max health.", LogLevel.Info); - } - } - } -} diff --git a/src/TrainerMod/Framework/Commands/Player/SetMaxStaminaCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetMaxStaminaCommand.cs deleted file mode 100644 index 3997bb1b..00000000 --- a/src/TrainerMod/Framework/Commands/Player/SetMaxStaminaCommand.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Linq; -using StardewModdingAPI; -using StardewValley; - -namespace TrainerMod.Framework.Commands.Player -{ - /// A command which edits the player's maximum stamina. - internal class SetMaxStaminaCommand : TrainerCommand - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public SetMaxStaminaCommand() - : base("player_setmaxstamina", "Sets the player's max stamina.\n\nUsage: player_setmaxstamina [value]\n- value: an integer amount.") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // validate - if (!args.Any()) - { - monitor.Log($"You currently have {Game1.player.MaxStamina} max stamina. Specify a value to change it.", LogLevel.Info); - return; - } - - // handle - if (args.TryGetInt(0, "amount", out int amount, min: 1)) - { - Game1.player.MaxStamina = amount; - monitor.Log($"OK, you now have {Game1.player.MaxStamina} max stamina.", LogLevel.Info); - } - } - } -} diff --git a/src/TrainerMod/Framework/Commands/Player/SetMoneyCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetMoneyCommand.cs deleted file mode 100644 index 55e069a4..00000000 --- a/src/TrainerMod/Framework/Commands/Player/SetMoneyCommand.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Linq; -using StardewModdingAPI; -using StardewValley; - -namespace TrainerMod.Framework.Commands.Player -{ - /// A command which edits the player's current money. - internal class SetMoneyCommand : TrainerCommand - { - /********* - ** Properties - *********/ - /// Whether to keep the player's money at a set value. - private bool InfiniteMoney; - - - /********* - ** Accessors - *********/ - /// Whether the command needs to perform logic when the game updates. - public override bool NeedsUpdate => this.InfiniteMoney; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - public SetMoneyCommand() - : base("player_setmoney", "Sets the player's money.\n\nUsage: player_setmoney \n- value: an integer amount, or 'inf' for infinite money.") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // validate - if (!args.Any()) - { - monitor.Log($"You currently have {(this.InfiniteMoney ? "infinite" : Game1.player.Money.ToString())} gold. Specify a value to change it.", LogLevel.Info); - return; - } - - // handle - string amountStr = args[0]; - if (amountStr == "inf") - { - this.InfiniteMoney = true; - monitor.Log("OK, you now have infinite money.", LogLevel.Info); - } - else - { - this.InfiniteMoney = false; - if (int.TryParse(amountStr, out int amount)) - { - Game1.player.Money = amount; - monitor.Log($"OK, you now have {Game1.player.Money} gold.", LogLevel.Info); - } - else - this.LogArgumentNotInt(monitor); - } - } - - /// Perform any logic needed on update tick. - /// Writes messages to the console and log file. - public override void Update(IMonitor monitor) - { - if (this.InfiniteMoney) - Game1.player.money = 999999; - } - } -} diff --git a/src/TrainerMod/Framework/Commands/Player/SetNameCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetNameCommand.cs deleted file mode 100644 index 3fd4475c..00000000 --- a/src/TrainerMod/Framework/Commands/Player/SetNameCommand.cs +++ /dev/null @@ -1,52 +0,0 @@ -using StardewModdingAPI; -using StardewValley; - -namespace TrainerMod.Framework.Commands.Player -{ - /// A command which edits the player's name. - internal class SetNameCommand : TrainerCommand - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public SetNameCommand() - : base("player_setname", "Sets the player's name.\n\nUsage: player_setname \n- target: what to rename (one of 'player' or 'farm').\n- name: the new name to set.") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // parse arguments - if (!args.TryGet(0, "target", out string target, oneOf: new[] { "player", "farm" })) - return; - args.TryGet(1, "name", out string name, required: false); - - // handle - switch (target) - { - case "player": - if (!string.IsNullOrWhiteSpace(name)) - { - Game1.player.Name = args[1]; - monitor.Log($"OK, your name is now {Game1.player.Name}.", LogLevel.Info); - } - else - monitor.Log($"Your name is currently '{Game1.player.Name}'. Type 'help player_setname' for usage.", LogLevel.Info); - break; - - case "farm": - if (!string.IsNullOrWhiteSpace(name)) - { - Game1.player.farmName = args[1]; - monitor.Log($"OK, your farm's name is now {Game1.player.farmName}.", LogLevel.Info); - } - else - monitor.Log($"Your farm's name is currently '{Game1.player.farmName}'. Type 'help player_setname' for usage.", LogLevel.Info); - break; - } - } - } -} diff --git a/src/TrainerMod/Framework/Commands/Player/SetSpeedCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetSpeedCommand.cs deleted file mode 100644 index 40b87b62..00000000 --- a/src/TrainerMod/Framework/Commands/Player/SetSpeedCommand.cs +++ /dev/null @@ -1,31 +0,0 @@ -using StardewModdingAPI; -using StardewValley; - -namespace TrainerMod.Framework.Commands.Player -{ - /// A command which edits the player's current added speed. - internal class SetSpeedCommand : TrainerCommand - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public SetSpeedCommand() - : base("player_setspeed", "Sets the player's added speed to the specified value.\n\nUsage: player_setspeed \n- value: an integer amount (0 is normal).") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // parse arguments - if (!args.TryGetInt(0, "added speed", out int amount, min: 0)) - return; - - // handle - Game1.player.addedSpeed = amount; - monitor.Log($"OK, your added speed is now {Game1.player.addedSpeed}.", LogLevel.Info); - } - } -} diff --git a/src/TrainerMod/Framework/Commands/Player/SetStaminaCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetStaminaCommand.cs deleted file mode 100644 index d44d1370..00000000 --- a/src/TrainerMod/Framework/Commands/Player/SetStaminaCommand.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Linq; -using StardewModdingAPI; -using StardewValley; - -namespace TrainerMod.Framework.Commands.Player -{ - /// A command which edits the player's current stamina. - internal class SetStaminaCommand : TrainerCommand - { - /********* - ** Properties - *********/ - /// Whether to keep the player's stamina at its maximum. - private bool InfiniteStamina; - - - /********* - ** Accessors - *********/ - /// Whether the command needs to perform logic when the game updates. - public override bool NeedsUpdate => this.InfiniteStamina; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - public SetStaminaCommand() - : base("player_setstamina", "Sets the player's stamina.\n\nUsage: player_setstamina [value]\n- value: an integer amount, or 'inf' for infinite stamina.") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // validate - if (!args.Any()) - { - monitor.Log($"You currently have {(this.InfiniteStamina ? "infinite" : Game1.player.Stamina.ToString())} stamina. Specify a value to change it.", LogLevel.Info); - return; - } - - // handle - string amountStr = args[0]; - if (amountStr == "inf") - { - this.InfiniteStamina = true; - monitor.Log("OK, you now have infinite stamina.", LogLevel.Info); - } - else - { - this.InfiniteStamina = false; - if (int.TryParse(amountStr, out int amount)) - { - Game1.player.Stamina = amount; - monitor.Log($"OK, you now have {Game1.player.Stamina} stamina.", LogLevel.Info); - } - else - this.LogArgumentNotInt(monitor); - } - } - - /// Perform any logic needed on update tick. - /// Writes messages to the console and log file. - public override void Update(IMonitor monitor) - { - if (this.InfiniteStamina) - Game1.player.stamina = Game1.player.MaxStamina; - } - } -} diff --git a/src/TrainerMod/Framework/Commands/Player/SetStyleCommand.cs b/src/TrainerMod/Framework/Commands/Player/SetStyleCommand.cs deleted file mode 100644 index 96e34af2..00000000 --- a/src/TrainerMod/Framework/Commands/Player/SetStyleCommand.cs +++ /dev/null @@ -1,92 +0,0 @@ -using StardewModdingAPI; -using StardewValley; - -namespace TrainerMod.Framework.Commands.Player -{ - /// A command which edits a player style. - internal class SetStyleCommand : TrainerCommand - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public SetStyleCommand() - : base("player_changestyle", "Sets the style of a player feature.\n\nUsage: player_changecolor .\n- target: what to change (one of 'hair', 'shirt', 'skin', 'acc', 'shoe', 'swim', or 'gender').\n- value: the integer style ID.") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // parse arguments - if (!args.TryGet(0, "target", out string target, oneOf: new[] { "hair", "shirt", "acc", "skin", "shoe", "swim", "gender" })) - return; - if (!args.TryGetInt(1, "style ID", out int styleID)) - return; - - // handle - switch (target) - { - case "hair": - Game1.player.changeHairStyle(styleID); - monitor.Log("OK, your hair style is updated.", LogLevel.Info); - break; - - case "shirt": - Game1.player.changeShirt(styleID); - monitor.Log("OK, your shirt style is updated.", LogLevel.Info); - break; - - case "acc": - Game1.player.changeAccessory(styleID); - monitor.Log("OK, your accessory style is updated.", LogLevel.Info); - break; - - case "skin": - Game1.player.changeSkinColor(styleID); - monitor.Log("OK, your skin color is updated.", LogLevel.Info); - break; - - case "shoe": - Game1.player.changeShoeColor(styleID); - monitor.Log("OK, your shoe style is updated.", LogLevel.Info); - break; - - case "swim": - switch (styleID) - { - case 0: - Game1.player.changeOutOfSwimSuit(); - monitor.Log("OK, you're no longer in your swimming suit.", LogLevel.Info); - break; - case 1: - Game1.player.changeIntoSwimsuit(); - monitor.Log("OK, you're now in your swimming suit.", LogLevel.Info); - break; - default: - this.LogUsageError(monitor, "The swim value should be 0 (no swimming suit) or 1 (swimming suit)."); - break; - } - break; - - case "gender": - switch (styleID) - { - case 0: - Game1.player.changeGender(true); - monitor.Log("OK, you're now male.", LogLevel.Info); - break; - case 1: - Game1.player.changeGender(false); - monitor.Log("OK, you're now female.", LogLevel.Info); - break; - default: - this.LogUsageError(monitor, "The gender value should be 0 (male) or 1 (female)."); - break; - } - break; - } - } - } -} diff --git a/src/TrainerMod/Framework/Commands/TrainerCommand.cs b/src/TrainerMod/Framework/Commands/TrainerCommand.cs deleted file mode 100644 index abe9ee41..00000000 --- a/src/TrainerMod/Framework/Commands/TrainerCommand.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using StardewModdingAPI; - -namespace TrainerMod.Framework.Commands -{ - /// The base implementation for a trainer command. - internal abstract class TrainerCommand : ITrainerCommand - { - /********* - ** Accessors - *********/ - /// The command name the user must type. - public string Name { get; } - - /// The command description. - public string Description { get; } - - /// Whether the command needs to perform logic when the game updates. - public virtual bool NeedsUpdate { get; } = false; - - - /********* - ** Public methods - *********/ - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public abstract void Handle(IMonitor monitor, string command, ArgumentParser args); - - /// Perform any logic needed on update tick. - /// Writes messages to the console and log file. - public virtual void Update(IMonitor monitor) { } - - - /********* - ** Protected methods - *********/ - /// Construct an instance. - /// The command name the user must type. - /// The command description. - protected TrainerCommand(string name, string description) - { - this.Name = name; - this.Description = description; - } - - /// Log an error indicating incorrect usage. - /// Writes messages to the console and log file. - /// A sentence explaining the problem. - protected void LogUsageError(IMonitor monitor, string error) - { - monitor.Log($"{error} Type 'help {this.Name}' for usage.", LogLevel.Error); - } - - /// Log an error indicating a value must be an integer. - /// Writes messages to the console and log file. - protected void LogArgumentNotInt(IMonitor monitor) - { - this.LogUsageError(monitor, "The value must be a whole number."); - } - - /// Get an ASCII table to show tabular data in the console. - /// The data type. - /// The data to display. - /// The table header. - /// Returns a set of fields for a data value. - protected string GetTableString(IEnumerable data, string[] header, Func getRow) - { - // get table data - int[] widths = header.Select(p => p.Length).ToArray(); - string[][] rows = data - .Select(item => - { - string[] fields = getRow(item); - if (fields.Length != widths.Length) - throw new InvalidOperationException($"Expected {widths.Length} columns, but found {fields.Length}: {string.Join(", ", fields)}"); - - for (int i = 0; i < fields.Length; i++) - widths[i] = Math.Max(widths[i], fields[i].Length); - - return fields; - }) - .ToArray(); - - // render fields - List lines = new List(rows.Length + 2) - { - header, - header.Select((value, i) => "".PadRight(widths[i], '-')).ToArray() - }; - lines.AddRange(rows); - - return string.Join( - Environment.NewLine, - lines.Select(line => string.Join(" | ", line.Select((field, i) => field.PadRight(widths[i], ' ')).ToArray()) - ) - ); - } - } -} diff --git a/src/TrainerMod/Framework/Commands/World/DownMineLevelCommand.cs b/src/TrainerMod/Framework/Commands/World/DownMineLevelCommand.cs deleted file mode 100644 index 4e62cf77..00000000 --- a/src/TrainerMod/Framework/Commands/World/DownMineLevelCommand.cs +++ /dev/null @@ -1,28 +0,0 @@ -using StardewModdingAPI; -using StardewValley; -using StardewValley.Locations; - -namespace TrainerMod.Framework.Commands.World -{ - /// A command which moves the player to the next mine level. - internal class DownMineLevelCommand : TrainerCommand - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public DownMineLevelCommand() - : base("world_downminelevel", "Goes down one mine level.") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - int level = (Game1.currentLocation as MineShaft)?.mineLevel ?? 0; - monitor.Log($"OK, warping you to mine level {level + 1}.", LogLevel.Info); - Game1.enterMine(false, level + 1, ""); - } - } -} diff --git a/src/TrainerMod/Framework/Commands/World/FreezeTimeCommand.cs b/src/TrainerMod/Framework/Commands/World/FreezeTimeCommand.cs deleted file mode 100644 index 13d08398..00000000 --- a/src/TrainerMod/Framework/Commands/World/FreezeTimeCommand.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Linq; -using StardewModdingAPI; -using StardewValley; - -namespace TrainerMod.Framework.Commands.World -{ - /// A command which freezes the current time. - internal class FreezeTimeCommand : TrainerCommand - { - /********* - ** Properties - *********/ - /// The time of day at which to freeze time. - internal static int FrozenTime; - - /// Whether to freeze time. - private bool FreezeTime; - - - /********* - ** Accessors - *********/ - /// Whether the command needs to perform logic when the game updates. - public override bool NeedsUpdate => this.FreezeTime; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - public FreezeTimeCommand() - : base("world_freezetime", "Freezes or resumes time.\n\nUsage: world_freezetime [value]\n- value: one of 0 (resume), 1 (freeze), or blank (toggle).") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - if (args.Any()) - { - // parse arguments - if (!args.TryGetInt(0, "value", out int value, min: 0, max: 1)) - return; - - // handle - this.FreezeTime = value == 1; - FreezeTimeCommand.FrozenTime = Game1.timeOfDay; - monitor.Log($"OK, time is now {(this.FreezeTime ? "frozen" : "resumed")}.", LogLevel.Info); - } - else - { - this.FreezeTime = !this.FreezeTime; - FreezeTimeCommand.FrozenTime = Game1.timeOfDay; - monitor.Log($"OK, time is now {(this.FreezeTime ? "frozen" : "resumed")}.", LogLevel.Info); - } - } - - /// Perform any logic needed on update tick. - /// Writes messages to the console and log file. - public override void Update(IMonitor monitor) - { - if (this.FreezeTime) - Game1.timeOfDay = FreezeTimeCommand.FrozenTime; - } - } -} diff --git a/src/TrainerMod/Framework/Commands/World/SetDayCommand.cs b/src/TrainerMod/Framework/Commands/World/SetDayCommand.cs deleted file mode 100644 index 54267384..00000000 --- a/src/TrainerMod/Framework/Commands/World/SetDayCommand.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Linq; -using StardewModdingAPI; -using StardewValley; - -namespace TrainerMod.Framework.Commands.World -{ - /// A command which sets the current day. - internal class SetDayCommand : TrainerCommand - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public SetDayCommand() - : base("world_setday", "Sets the day to the specified value.\n\nUsage: world_setday .\n- value: the target day (a number from 1 to 28).") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // no-argument mode - if (!args.Any()) - { - monitor.Log($"The current date is {Game1.currentSeason} {Game1.dayOfMonth}. Specify a value to change the day.", LogLevel.Info); - return; - } - - // parse arguments - if (!args.TryGetInt(0, "day", out int day, min: 1, max: 28)) - return; - - // handle - Game1.dayOfMonth = day; - monitor.Log($"OK, the date is now {Game1.currentSeason} {Game1.dayOfMonth}.", LogLevel.Info); - } - } -} diff --git a/src/TrainerMod/Framework/Commands/World/SetMineLevelCommand.cs b/src/TrainerMod/Framework/Commands/World/SetMineLevelCommand.cs deleted file mode 100644 index 225ec091..00000000 --- a/src/TrainerMod/Framework/Commands/World/SetMineLevelCommand.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using StardewModdingAPI; -using StardewValley; - -namespace TrainerMod.Framework.Commands.World -{ - /// A command which moves the player to the given mine level. - internal class SetMineLevelCommand : TrainerCommand - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public SetMineLevelCommand() - : base("world_setminelevel", "Sets the mine level?\n\nUsage: world_setminelevel \n- value: The target level (a number starting at 1).") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // parse arguments - if (!args.TryGetInt(0, "mine level", out int level, min: 1)) - return; - - // handle - level = Math.Max(1, level); - monitor.Log($"OK, warping you to mine level {level}.", LogLevel.Info); - Game1.enterMine(true, level, ""); - } - } -} diff --git a/src/TrainerMod/Framework/Commands/World/SetSeasonCommand.cs b/src/TrainerMod/Framework/Commands/World/SetSeasonCommand.cs deleted file mode 100644 index 96c3d920..00000000 --- a/src/TrainerMod/Framework/Commands/World/SetSeasonCommand.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Linq; -using StardewModdingAPI; -using StardewValley; - -namespace TrainerMod.Framework.Commands.World -{ - /// A command which sets the current season. - internal class SetSeasonCommand : TrainerCommand - { - /********* - ** Properties - *********/ - /// The valid season names. - private readonly string[] ValidSeasons = { "winter", "spring", "summer", "fall" }; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - public SetSeasonCommand() - : base("world_setseason", "Sets the season to the specified value.\n\nUsage: world_setseason \n- season: the target season (one of 'spring', 'summer', 'fall', 'winter').") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // no-argument mode - if (!args.Any()) - { - monitor.Log($"The current season is {Game1.currentSeason}. Specify a value to change it.", LogLevel.Info); - return; - } - - // parse arguments - if (!args.TryGet(0, "season", out string season, oneOf: this.ValidSeasons)) - return; - - // handle - Game1.currentSeason = season; - monitor.Log($"OK, the date is now {Game1.currentSeason} {Game1.dayOfMonth}.", LogLevel.Info); - } - } -} diff --git a/src/TrainerMod/Framework/Commands/World/SetTimeCommand.cs b/src/TrainerMod/Framework/Commands/World/SetTimeCommand.cs deleted file mode 100644 index c827ea5e..00000000 --- a/src/TrainerMod/Framework/Commands/World/SetTimeCommand.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Linq; -using StardewModdingAPI; -using StardewValley; - -namespace TrainerMod.Framework.Commands.World -{ - /// A command which sets the current time. - internal class SetTimeCommand : TrainerCommand - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public SetTimeCommand() - : base("world_settime", "Sets the time to the specified value.\n\nUsage: world_settime \n- value: the target time in military time (like 0600 for 6am and 1800 for 6pm).") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // no-argument mode - if (!args.Any()) - { - monitor.Log($"The current time is {Game1.timeOfDay}. Specify a value to change it.", LogLevel.Info); - return; - } - - // parse arguments - if (!args.TryGetInt(0, "time", out int time, min: 600, max: 2600)) - return; - - // handle - Game1.timeOfDay = time; - FreezeTimeCommand.FrozenTime = Game1.timeOfDay; - monitor.Log($"OK, the time is now {Game1.timeOfDay.ToString().PadLeft(4, '0')}.", LogLevel.Info); - } - } -} diff --git a/src/TrainerMod/Framework/Commands/World/SetYearCommand.cs b/src/TrainerMod/Framework/Commands/World/SetYearCommand.cs deleted file mode 100644 index 760fc170..00000000 --- a/src/TrainerMod/Framework/Commands/World/SetYearCommand.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Linq; -using StardewModdingAPI; -using StardewValley; - -namespace TrainerMod.Framework.Commands.World -{ - /// A command which sets the current year. - internal class SetYearCommand : TrainerCommand - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public SetYearCommand() - : base("world_setyear", "Sets the year to the specified value.\n\nUsage: world_setyear \n- year: the target year (a number starting from 1).") { } - - /// Handle the command. - /// Writes messages to the console and log file. - /// The command name. - /// The command arguments. - public override void Handle(IMonitor monitor, string command, ArgumentParser args) - { - // no-argument mode - if (!args.Any()) - { - monitor.Log($"The current year is {Game1.year}. Specify a value to change the year.", LogLevel.Info); - return; - } - - // parse arguments - if (!args.TryGetInt(0, "year", out int year, min: 1)) - return; - - // handle - Game1.year = year; - monitor.Log($"OK, the year is now {Game1.year}.", LogLevel.Info); - } - } -} diff --git a/src/TrainerMod/Framework/ItemData/ItemType.cs b/src/TrainerMod/Framework/ItemData/ItemType.cs deleted file mode 100644 index 423455e9..00000000 --- a/src/TrainerMod/Framework/ItemData/ItemType.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace TrainerMod.Framework.ItemData -{ - /// An item type that can be searched and added to the player through the console. - internal enum ItemType - { - /// A big craftable object in - BigCraftable, - - /// A item. - Boots, - - /// A fish item. - Fish, - - /// A flooring item. - Flooring, - - /// A item. - Furniture, - - /// A item. - Hat, - - /// Any object in (except rings). - Object, - - /// A item. - Ring, - - /// A tool. - Tool, - - /// A wall item. - Wallpaper, - - /// A or item. - Weapon - } -} diff --git a/src/TrainerMod/Framework/ItemData/SearchableItem.cs b/src/TrainerMod/Framework/ItemData/SearchableItem.cs deleted file mode 100644 index 146da1a8..00000000 --- a/src/TrainerMod/Framework/ItemData/SearchableItem.cs +++ /dev/null @@ -1,41 +0,0 @@ -using StardewValley; - -namespace TrainerMod.Framework.ItemData -{ - /// A game item with metadata. - internal class SearchableItem - { - /********* - ** Accessors - *********/ - /// The item type. - public ItemType Type { get; } - - /// The item instance. - public Item Item { get; } - - /// The item's unique ID for its type. - public int ID { get; } - - /// The item's default name. - public string Name => this.Item.Name; - - /// The item's display name for the current language. - public string DisplayName => this.Item.DisplayName; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The item type. - /// The unique ID (if different from the item's parent sheet index). - /// The item instance. - public SearchableItem(ItemType type, int id, Item item) - { - this.Type = type; - this.ID = id; - this.Item = item; - } - } -} diff --git a/src/TrainerMod/Framework/ItemRepository.cs b/src/TrainerMod/Framework/ItemRepository.cs deleted file mode 100644 index 96d3159e..00000000 --- a/src/TrainerMod/Framework/ItemRepository.cs +++ /dev/null @@ -1,179 +0,0 @@ -using System.Collections.Generic; -using Microsoft.Xna.Framework; -using StardewValley; -using StardewValley.Objects; -using StardewValley.Tools; -using TrainerMod.Framework.ItemData; -using SObject = StardewValley.Object; - -namespace TrainerMod.Framework -{ - /// Provides methods for searching and constructing items. - internal class ItemRepository - { - /********* - ** Properties - *********/ - /// The custom ID offset for items don't have a unique ID in the game. - private readonly int CustomIDOffset = 1000; - - - /********* - ** Public methods - *********/ - /// Get all spawnable items. - public IEnumerable GetAll() - { - // get tools - for (int quality = Tool.stone; quality <= Tool.iridium; quality++) - { - yield return new SearchableItem(ItemType.Tool, ToolFactory.axe, ToolFactory.getToolFromDescription(ToolFactory.axe, quality)); - yield return new SearchableItem(ItemType.Tool, ToolFactory.hoe, ToolFactory.getToolFromDescription(ToolFactory.hoe, quality)); - yield return new SearchableItem(ItemType.Tool, ToolFactory.pickAxe, ToolFactory.getToolFromDescription(ToolFactory.pickAxe, quality)); - yield return new SearchableItem(ItemType.Tool, ToolFactory.wateringCan, ToolFactory.getToolFromDescription(ToolFactory.wateringCan, quality)); - if (quality != Tool.iridium) - yield return new SearchableItem(ItemType.Tool, ToolFactory.fishingRod, ToolFactory.getToolFromDescription(ToolFactory.fishingRod, quality)); - } - yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset, new MilkPail()); // these don't have any sort of ID, so we'll just assign some arbitrary ones - yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset + 1, new Shears()); - yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset + 2, new Pan()); - - // wallpapers - for (int id = 0; id < 112; id++) - yield return new SearchableItem(ItemType.Wallpaper, id, new Wallpaper(id)); - - // flooring - for (int id = 0; id < 40; id++) - yield return new SearchableItem(ItemType.Flooring, id, new Wallpaper(id, isFloor: true)); - - // equipment - foreach (int id in Game1.content.Load>("Data\\Boots").Keys) - yield return new SearchableItem(ItemType.Boots, id, new Boots(id)); - foreach (int id in Game1.content.Load>("Data\\hats").Keys) - yield return new SearchableItem(ItemType.Hat, id, new Hat(id)); - foreach (int id in Game1.objectInformation.Keys) - { - if (id >= Ring.ringLowerIndexRange && id <= Ring.ringUpperIndexRange) - yield return new SearchableItem(ItemType.Ring, id, new Ring(id)); - } - - // weapons - foreach (int id in Game1.content.Load>("Data\\weapons").Keys) - { - Item weapon = (id >= 32 && id <= 34) - ? (Item)new Slingshot(id) - : new MeleeWeapon(id); - yield return new SearchableItem(ItemType.Weapon, id, weapon); - } - - // furniture - foreach (int id in Game1.content.Load>("Data\\Furniture").Keys) - { - if (id == 1466 || id == 1468) - yield return new SearchableItem(ItemType.Furniture, id, new TV(id, Vector2.Zero)); - else - yield return new SearchableItem(ItemType.Furniture, id, new Furniture(id, Vector2.Zero)); - } - - // fish - foreach (int id in Game1.content.Load>("Data\\Fish").Keys) - yield return new SearchableItem(ItemType.Fish, id, new SObject(id, 999)); - - // craftables - foreach (int id in Game1.bigCraftablesInformation.Keys) - yield return new SearchableItem(ItemType.BigCraftable, id, new SObject(Vector2.Zero, id)); - - // objects - foreach (int id in Game1.objectInformation.Keys) - { - if (id >= Ring.ringLowerIndexRange && id <= Ring.ringUpperIndexRange) - continue; // handled separated - - SObject item = new SObject(id, 1); - yield return new SearchableItem(ItemType.Object, id, item); - - // fruit products - if (item.category == SObject.FruitsCategory) - { - yield return new SearchableItem(ItemType.Object, this.CustomIDOffset + id, new SObject(348, 1) - { - name = $"{item.Name} Wine", - price = item.price * 3, - preserve = SObject.PreserveType.Wine, - preservedParentSheetIndex = item.parentSheetIndex - }); - yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 2 + id, new SObject(344, 1) - { - name = $"{item.Name} Jelly", - price = 50 + item.Price * 2, - preserve = SObject.PreserveType.Jelly, - preservedParentSheetIndex = item.parentSheetIndex - }); - } - - // vegetable products - else if (item.category == SObject.VegetableCategory) - { - yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 3 + id, new SObject(350, 1) - { - name = $"{item.Name} Juice", - price = (int)(item.price * 2.25d), - preserve = SObject.PreserveType.Juice, - preservedParentSheetIndex = item.parentSheetIndex - }); - yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 4 + id, new SObject(342, 1) - { - name = $"Pickled {item.Name}", - price = 50 + item.Price * 2, - preserve = SObject.PreserveType.Pickle, - preservedParentSheetIndex = item.parentSheetIndex - }); - } - - // flower honey - else if (item.category == SObject.flowersCategory) - { - // get honey type - SObject.HoneyType? type = null; - switch (item.parentSheetIndex) - { - case 376: - type = SObject.HoneyType.Poppy; - break; - case 591: - type = SObject.HoneyType.Tulip; - break; - case 593: - type = SObject.HoneyType.SummerSpangle; - break; - case 595: - type = SObject.HoneyType.FairyRose; - break; - case 597: - type = SObject.HoneyType.BlueJazz; - break; - case 421: // sunflower standing in for all other flowers - type = SObject.HoneyType.Wild; - break; - } - - // yield honey - if (type != null) - { - SObject honey = new SObject(Vector2.Zero, 340, item.Name + " Honey", false, true, false, false) - { - name = "Wild Honey", - honeyType = type - }; - if (type != SObject.HoneyType.Wild) - { - honey.name = $"{item.Name} Honey"; - honey.price += item.price * 2; - } - yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 5 + id, honey); - } - } - } - } - } -} diff --git a/src/TrainerMod/Properties/AssemblyInfo.cs b/src/TrainerMod/Properties/AssemblyInfo.cs deleted file mode 100644 index 0b19e78a..00000000 --- a/src/TrainerMod/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("TrainerMod")] -[assembly: AssemblyDescription("")] -[assembly: Guid("76791e28-b1b5-407c-82d6-50c3e5b7e037")] \ No newline at end of file diff --git a/src/TrainerMod/TrainerMod.cs b/src/TrainerMod/TrainerMod.cs deleted file mode 100644 index 5db02cd6..00000000 --- a/src/TrainerMod/TrainerMod.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using StardewModdingAPI; -using StardewModdingAPI.Events; -using TrainerMod.Framework.Commands; - -namespace TrainerMod -{ - /// The main entry point for the mod. - public class TrainerMod : Mod - { - /********* - ** Properties - *********/ - /// The commands to handle. - private ITrainerCommand[] Commands; - - - /********* - ** 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) - { - // register commands - this.Commands = this.ScanForCommands().ToArray(); - foreach (ITrainerCommand command in this.Commands) - helper.ConsoleCommands.Add(command.Name, command.Description, (name, args) => this.HandleCommand(command, name, args)); - - // hook events - GameEvents.UpdateTick += this.GameEvents_UpdateTick; - } - - - /********* - ** Private methods - *********/ - /// The method invoked when the game updates its state. - /// The event sender. - /// The event arguments. - private void GameEvents_UpdateTick(object sender, EventArgs e) - { - if (!Context.IsWorldReady) - return; - - foreach (ITrainerCommand command in this.Commands) - { - if (command.NeedsUpdate) - command.Update(this.Monitor); - } - } - - /// Handle a TrainerMod command. - /// The command to invoke. - /// The command name specified by the user. - /// The command arguments. - private void HandleCommand(ITrainerCommand command, string commandName, string[] args) - { - ArgumentParser argParser = new ArgumentParser(commandName, args, this.Monitor); - command.Handle(this.Monitor, commandName, argParser); - } - - /// Find all commands in the assembly. - private IEnumerable ScanForCommands() - { - return ( - from type in this.GetType().Assembly.GetTypes() - where !type.IsAbstract && typeof(ITrainerCommand).IsAssignableFrom(type) - select (ITrainerCommand)Activator.CreateInstance(type) - ); - } - } -} diff --git a/src/TrainerMod/TrainerMod.csproj b/src/TrainerMod/TrainerMod.csproj deleted file mode 100644 index cb5ec47e..00000000 --- a/src/TrainerMod/TrainerMod.csproj +++ /dev/null @@ -1,101 +0,0 @@ - - - - - Debug - x86 - {28480467-1A48-46A7-99F8-236D95225359} - Library - Properties - TrainerMod - TrainerMod - v4.5 - 512 - - - true - full - true - $(SolutionDir)\..\bin\Debug\Mods\TrainerMod\ - DEBUG;TRACE - prompt - 4 - x86 - false - true - - - pdbonly - true - $(SolutionDir)\..\bin\Release\Mods\TrainerMod\ - TRACE - prompt - 4 - false - true - x86 - - - - ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll - - - - - - - - - - - Properties\GlobalAssemblyInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {f1a573b0-f436-472c-ae29-0b91ea6b9f8f} - StardewModdingAPI - False - - - - - PreserveNewest - - - - - - \ No newline at end of file diff --git a/src/TrainerMod/manifest.json b/src/TrainerMod/manifest.json deleted file mode 100644 index 22e35bce..00000000 --- a/src/TrainerMod/manifest.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Name": "Trainer Mod", - "Author": "SMAPI", - "Version": { - "MajorVersion": 2, - "MinorVersion": 0, - "PatchVersion": 0, - "Build": null - }, - "Description": "Adds SMAPI console commands that let you manipulate the game.", - "UniqueID": "SMAPI.TrainerMod", - "EntryDll": "TrainerMod.dll" -} diff --git a/src/TrainerMod/packages.config b/src/TrainerMod/packages.config deleted file mode 100644 index ee51c237..00000000 --- a/src/TrainerMod/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file -- cgit From 78958dfe9f78a581225a3f3e037abf001dde2b5b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 29 Oct 2017 14:14:03 -0400 Subject: document SMAPI web services in technical doc (#358) --- docs/technical-docs.md | 107 ++++++++++++++++++++++++++++++++++++++++++++----- src/SMAPI.sln | 3 +- 2 files changed, 99 insertions(+), 11 deletions(-) (limited to 'docs') diff --git a/docs/technical-docs.md b/docs/technical-docs.md index d37d327d..37ec7f69 100644 --- a/docs/technical-docs.md +++ b/docs/technical-docs.md @@ -3,16 +3,25 @@ This file provides more technical documentation about SMAPI. If you only want to use or create mods, this section isn't relevant to you; see the main README to use or create mods. -## Contents -* [Development](#development) - * [Compiling from source](#compiling-from-source) - * [Debugging a local build](#debugging-a-local-build) - * [Preparing a release](#preparing-a-release) -* [Customisation](#customisation) - * [Configuration file](#configuration-file) - * [Command-line arguments](#command-line-arguments) - * [Compile flags](#compile-flags) - +# Contents +* [SMAPI](#smapi) + * [Development](#development) + * [Compiling from source](#compiling-from-source) + * [Debugging a local build](#debugging-a-local-build) + * [Preparing a release](#preparing-a-release) + * [Customisation](#customisation) + * [Configuration file](#configuration-file) + * [Command-line arguments](#command-line-arguments) + * [Compile flags](#compile-flags) +* [SMAPI web services](#smapi-web-services) + * [Overview](#overview) + * [Log parser](#log-parser) + * [Mods API](#mods-api) + * [Development](#development-2) + * [Local development](#local-development) + * [Deploying to Amazon Beanstalk](#deploying-to-amazon-beanstalk) + +# SMAPI ## Development ### Compiling from source Using an official SMAPI release is recommended for most users. @@ -135,3 +144,81 @@ SMAPI uses a small number of conditional compilation constants, which you can se flag | purpose ---- | ------- `SMAPI_FOR_WINDOWS` | Indicates that SMAPI is being compiled on Windows for players on Windows. Set automatically in `crossplatform.targets`. + + +# SMAPI web services +## Overview +The `StardewModdingAPI.Web` project provides an API and web UI hosted at `*.smapi.io`. + +### Log parser +The log parser provides a web UI for uploading, parsing, and sharing SMAPI logs. The logs are +persisted in a compressed form to Pastebin. + +The log parser lives at https://log.smapi.io. + +### Mods API +The mods API provides version info for mods hosted by Chucklefish, GitHub, or Nexus Mods. It's used +by SMAPI to perform update checks. The `{version}` URL token is the version of SMAPI making the +request; it doesn't do anything currently, but lets us version breaking changes if needed. + +Each mod is identified by a repository key and unique identifier (like `nexus:541`). The following +repositories are supported: + +key | repository +------------- | ---------- +`chucklefish` | A mod page on the [Chucklefish mod site](https://community.playstarbound.com/resources/categories/22), identified by the mod ID in the page URL. +`github` | A repository on [GitHub](https://github.com), identified by its owner and repository name (like `Zoryn4163/SMAPI-Mods`). This checks the version of the latest repository release. +`nexus` | A mod page on [Nexus Mods](https://www.nexusmods.com/stardewvalley), identified by the mod ID in the page URL. + + +The API accepts either `GET` or `POST` for convenience: +> ``` +>GET https://api.smapi.io/v2.0/mods?modKeys=nexus:541,chucklefish:4228 +>``` + +>``` +>POST https://api.smapi.io/v2.0/mods +>{ +> "ModKeys": [ "nexus:541", "chucklefish:4228" ] +>} +>``` + +It returns a response like this: +>``` +>{ +> "chucklefish:4228": { +> "name": "Entoarox Framework", +> "version": "1.8.0", +> "url": "https://community.playstarbound.com/resources/4228" +> }, +> "nexus:541": { +> "name": "Lookup Anything", +> "version": "1.16", +> "url": "http://www.nexusmods.com/stardewvalley/mods/541" +> } +>} +>``` + +## Development +### Local development +`StardewModdingAPI.Web` is a regular ASP.NET MVC Core app, so you can just launch it from within +Visual Studio to run a local version. + +There are two differences when it's run locally: all endpoints use HTTP instead of HTTPS, and the +subdomain portion becomes a route (e.g. `log.smapi.io` → `localhost:59482/log`). + +Before running it locally, you need to enter your credentials in the `appsettings.Development.json` +file. See the next section for a description of each setting. This file is listed in `.gitignore` +to prevent accidentally committing credentials. + +### Deploying to Amazon Beanstalk +The app can be deployed to a standard Amazon Beanstalk IIS environment. When creating the +environment, make sure to specify the following environment properties: + +property name | description +------------------------------- | ----------------- +`LogParser:PastebinDevKey` | The [Pastebin developer key](https://pastebin.com/api#1) used to authenticate with the Pastebin API. +`LogParser:PastebinUserKey` | The [Pastebin user key](https://pastebin.com/api#8) used to authenticate with the Pastebin API. Can be left blank to post anonymously. +`LogParser:SectionUrl` | The root URL of the log page, like `https://log.smapi.io/`. +`ModUpdateCheck:GitHubPassword` | The password with which to authenticate to GitHub when fetching release info. +`ModUpdateCheck:GitHubUsername` | The username with which to authenticate to GitHub when fetching release info. diff --git a/src/SMAPI.sln b/src/SMAPI.sln index 8d730f37..1cb858bc 100644 --- a/src/SMAPI.sln +++ b/src/SMAPI.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27004.2002 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleCommands", "SMAPI.Mods.ConsoleCommands\StardewModdingAPI.Mods.ConsoleCommands.csproj", "{28480467-1A48-46A7-99F8-236D95225359}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StardewModdingAPI.Mods.ConsoleCommands", "SMAPI.Mods.ConsoleCommands\StardewModdingAPI.Mods.ConsoleCommands.csproj", "{28480467-1A48-46A7-99F8-236D95225359}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StardewModdingAPI", "SMAPI\StardewModdingAPI.csproj", "{F1A573B0-F436-472C-AE29-0B91EA6B9F8F}" EndProject @@ -37,6 +37,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{EB35A917-6 ..\docs\mod-build-config.md = ..\docs\mod-build-config.md ..\docs\README.md = ..\docs\README.md ..\docs\release-notes.md = ..\docs\release-notes.md + ..\docs\technical-docs.md = ..\docs\technical-docs.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{09CF91E5-5BAB-4650-A200-E5EA9A633046}" -- cgit From c3cd9a3120cd74cb1dfc4455d3ecca203b163013 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 29 Oct 2017 22:00:22 -0400 Subject: + missing release note (#358) --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 1202407f..c810334b 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -5,6 +5,7 @@ * Fixed the game's test messages being shown in the console and log. * Fixed TrainerMod's `player_setlevel` command not also setting XP. * Renamed the default _TrainerMod_ mod to _Console Commands_ to clarify its purpose. + * Added a log parser service at [log.smapi.io](https://log.smapi.io). * For modders: * Added support for public code in reflection API, to simplify mod integrations. -- cgit From 1bea3a9e3273b8222cc2cc5c153bfb70fdba521a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 29 Oct 2017 23:15:18 -0400 Subject: let SemanticVersion be constructed from a System.Version (#375) --- docs/release-notes.md | 1 + src/SMAPI.Common/SemanticVersionImpl.cs | 13 +++++++++++++ src/SMAPI.Tests/Utilities/SemanticVersionTests.cs | 20 ++++++++++++++++++-- src/SMAPI/SemanticVersion.cs | 6 ++++++ 4 files changed, 38 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index c810334b..dc88bfa4 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -15,6 +15,7 @@ * Deprecated `e.IsClick`, which is limited and unclear. Use `IsActionButton` or `IsUseToolButton` instead. * Fixed `e.SuppressButton()` not correctly suppressing keyboard buttons. * Fixed `e.IsClick` (now `e.IsActionButton`) ignoring custom key bindings. + * `SemanticVersion` can now be constructed from a `System.Version`. * Fixed custom map tilesheets not working unless they're explicitly loaded first. * Fixed mods which implement `IAssetLoader` directly not being allowed to load files due to incorrect conflict detection. * Fixed SMAPI blocking reflection access to vanilla members on overridden types. diff --git a/src/SMAPI.Common/SemanticVersionImpl.cs b/src/SMAPI.Common/SemanticVersionImpl.cs index 193d23f9..53cf5a21 100644 --- a/src/SMAPI.Common/SemanticVersionImpl.cs +++ b/src/SMAPI.Common/SemanticVersionImpl.cs @@ -48,6 +48,19 @@ namespace StardewModdingAPI.Common this.Tag = this.GetNormalisedTag(tag); } + /// Construct an instance. + /// The assembly version. + /// The is null. + public SemanticVersionImpl(Version version) + { + if (version == null) + throw new ArgumentNullException(nameof(version), "The input version can't be null."); + + this.Major = version.Major; + this.Minor = version.Minor; + this.Patch = version.Build; + } + /// Construct an instance. /// The semantic version string. /// The is null. diff --git a/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs b/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs index 73ecd56e..d3e0988e 100644 --- a/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs +++ b/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs @@ -16,7 +16,7 @@ namespace StardewModdingAPI.Tests.Utilities /**** ** Constructor ****/ - [Test(Description = "Assert that the constructor sets the expected values for all valid versions.")] + [Test(Description = "Assert that the constructor sets the expected values for all valid versions when constructed from a string.")] [TestCase("1.0", ExpectedResult = "1.0")] [TestCase("1.0.0", ExpectedResult = "1.0")] [TestCase("3000.4000.5000", ExpectedResult = "3000.4000.5000")] @@ -28,7 +28,7 @@ namespace StardewModdingAPI.Tests.Utilities return new SemanticVersion(input).ToString(); } - [Test(Description = "Assert that the constructor sets the expected values for all valid versions.")] + [Test(Description = "Assert that the constructor sets the expected values for all valid versions when constructed from the individual numbers.")] [TestCase(1, 0, 0, null, ExpectedResult = "1.0")] [TestCase(3000, 4000, 5000, null, ExpectedResult = "3000.4000.5000")] [TestCase(1, 2, 3, "", ExpectedResult = "1.2.3")] @@ -48,6 +48,22 @@ namespace StardewModdingAPI.Tests.Utilities return version.ToString(); } + [Test(Description = "Assert that the constructor sets the expected values for all valid versions when constructed from an assembly version.")] + [TestCase(1, 0, 0, ExpectedResult = "1.0")] + [TestCase(1, 2, 3, ExpectedResult = "1.2.3")] + [TestCase(3000, 4000, 5000, ExpectedResult = "3000.4000.5000")] + public string Constructor_FromAssemblyVersion(int major, int minor, int patch) + { + // act + ISemanticVersion version = new SemanticVersion(new Version(major, minor, patch)); + + // assert + Assert.AreEqual(major, version.MajorVersion, "The major version doesn't match the given value."); + Assert.AreEqual(minor, version.MinorVersion, "The minor version doesn't match the given value."); + Assert.AreEqual(patch, version.PatchVersion, "The patch version doesn't match the given value."); + return version.ToString(); + } + [Test(Description = "Assert that the constructor throws the expected exception for invalid versions.")] [TestCase(null)] [TestCase("")] diff --git a/src/SMAPI/SemanticVersion.cs b/src/SMAPI/SemanticVersion.cs index ce86dceb..4826c947 100644 --- a/src/SMAPI/SemanticVersion.cs +++ b/src/SMAPI/SemanticVersion.cs @@ -49,6 +49,12 @@ namespace StardewModdingAPI public SemanticVersion(string version) : this(new SemanticVersionImpl(version)) { } + /// Construct an instance. + /// The assembly version. + /// The is null. + public SemanticVersion(Version version) + : this(new SemanticVersionImpl(version)) { } + /// Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version. /// The version to compare with this instance. /// The value is null. -- cgit From e606b074d7e292dc3957586ea138a64447a9bd8a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 29 Oct 2017 23:17:21 -0400 Subject: add installer version & platform to window title, simplify output (#375) --- docs/release-notes.md | 3 +++ src/SMAPI.Installer/InteractiveInstaller.cs | 16 +++++++++++----- src/SMAPI.Installer/StardewModdingAPI.Installer.csproj | 3 ++- src/SMAPI.sln | 1 + 4 files changed, 17 insertions(+), 6 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index dc88bfa4..18946a4f 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -20,6 +20,9 @@ * Fixed mods which implement `IAssetLoader` directly not being allowed to load files due to incorrect conflict detection. * Fixed SMAPI blocking reflection access to vanilla members on overridden types. +* For SMAPI developers: + * Added the SMAPI installer version and platform to the window title to simplify troubleshooting. + ## 2.0 ### Release highlights * **Mod update checks** diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index cbc8a401..2ac53229 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Threading; using Microsoft.Win32; using StardewModdingApi.Installer.Enums; +using StardewModdingAPI.Common; namespace StardewModdingApi.Installer { @@ -136,6 +137,13 @@ namespace StardewModdingApi.Installer /// public void Run(string[] args) { + /**** + ** Get platform & set window title + ****/ + Platform platform = this.DetectPlatform(); + Console.Title = $"SMAPI {new SemanticVersionImpl(this.GetType().Assembly.GetName().Version)} installer on {platform}"; + Console.WriteLine(); + /**** ** read command-line arguments ****/ @@ -160,10 +168,6 @@ namespace StardewModdingApi.Installer /**** ** collect details ****/ - // get platform - Platform platform = this.DetectPlatform(); - this.PrintDebug($"Platform: {(platform == Platform.Windows ? "Windows" : "Linux or Mac")}."); - // get game path DirectoryInfo installDir = this.InteractivelyGetInstallPath(platform, gamePathArg); if (installDir == null) @@ -183,7 +187,9 @@ namespace StardewModdingApi.Installer unixLauncher = Path.Combine(installDir.FullName, "StardewValley"), unixLauncherBackup = Path.Combine(installDir.FullName, "StardewValley-original") }; - this.PrintDebug($"Install path: {installDir}."); + + // show output + Console.WriteLine($"Your game folder: {installDir}."); /**** ** validate assumptions diff --git a/src/SMAPI.Installer/StardewModdingAPI.Installer.csproj b/src/SMAPI.Installer/StardewModdingAPI.Installer.csproj index f8e368a4..d3a6aa0b 100644 --- a/src/SMAPI.Installer/StardewModdingAPI.Installer.csproj +++ b/src/SMAPI.Installer/StardewModdingAPI.Installer.csproj @@ -1,4 +1,4 @@ - + @@ -50,6 +50,7 @@ Always + diff --git a/src/SMAPI.sln b/src/SMAPI.sln index 1cb858bc..b42e39ce 100644 --- a/src/SMAPI.sln +++ b/src/SMAPI.sln @@ -52,6 +52,7 @@ EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution SMAPI.Common\StardewModdingAPI.Common.projitems*{2aa02fb6-ff03-41cf-a215-2ee60ab4f5dc}*SharedItemsImports = 13 + SMAPI.Common\StardewModdingAPI.Common.projitems*{443ddf81-6aaf-420a-a610-3459f37e5575}*SharedItemsImports = 4 SMAPI.Common\StardewModdingAPI.Common.projitems*{ea4f1e80-743f-4a1d-9757-ae66904a196a}*SharedItemsImports = 4 SMAPI.Common\StardewModdingAPI.Common.projitems*{f1a573b0-f436-472c-ae29-0b91ea6b9f8f}*SharedItemsImports = 4 EndGlobalSection -- cgit From 6b5c03da4dd29592c41f06d2d3172ba832be9123 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 29 Oct 2017 23:21:14 -0400 Subject: expand post-install instructions, show Steam launch options value (#375) --- docs/release-notes.md | 1 + src/SMAPI.Installer/InteractiveInstaller.cs | 22 +++++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 18946a4f..99b9b965 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -6,6 +6,7 @@ * Fixed TrainerMod's `player_setlevel` command not also setting XP. * Renamed the default _TrainerMod_ mod to _Console Commands_ to clarify its purpose. * Added a log parser service at [log.smapi.io](https://log.smapi.io). + * Added better Steam instructions to the SMAPI installer. * For modders: * Added support for public code in reflection API, to simplify mod integrations. diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index 2ac53229..b5c2735b 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -347,22 +347,26 @@ namespace StardewModdingApi.Installer this.InteractivelyRemoveAppDataMods(platform, modsDir, packagedModsDir); } Console.WriteLine(); + Console.WriteLine(); /**** - ** exit + ** final instructions ****/ - this.PrintColor("Done!", ConsoleColor.DarkGreen); if (platform == Platform.Windows) { - this.PrintColor( - action == ScriptAction.Install - ? "Don't forget to launch StardewModdingAPI.exe instead of the normal game executable. See the readme.txt for details." - : "If you manually changed shortcuts or Steam to launch SMAPI, don't forget to change those back.", - ConsoleColor.DarkGreen - ); + if (action == ScriptAction.Install) + { + this.PrintColor("SMAPI is installed! If you use Steam, set your launch options to enable achievements (see smapi.io/install):", ConsoleColor.DarkGreen); + this.PrintColor($" \"{Path.Combine(installDir.FullName, "StardewModdingAPI.exe")}\" %command%", ConsoleColor.DarkGreen); + Console.WriteLine(); + this.PrintColor("If you don't use Steam, launch StardewModdingAPI.exe in your game folder to play with mods.", ConsoleColor.DarkGreen); + } + else + this.PrintColor("SMAPI is removed! If you configured Steam to launch SMAPI, don't forget to clear your launch options.", ConsoleColor.DarkGreen); } else if (action == ScriptAction.Install) - this.PrintColor("You can launch the game the same way as before to play with mods.", ConsoleColor.DarkGreen); + this.PrintColor("SMAPI is installed! Launch the game the same way as before to play with mods.", ConsoleColor.DarkGreen); + Console.ReadKey(); } -- cgit From 08c30eeffd8cc62d00db33d91e3a9a6ab1d376a3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 30 Oct 2017 00:02:20 -0400 Subject: let mods invalidate assets matching a predicate (#363) --- docs/release-notes.md | 11 ++++++----- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 13 +++++++++++-- src/SMAPI/Framework/SContentManager.cs | 26 ++++++++++++++++++------- src/SMAPI/IContentHelper.cs | 5 +++++ 4 files changed, 41 insertions(+), 14 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 99b9b965..c192d4ee 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,15 +1,16 @@ # Release notes ## 2.1 (upcoming) * For players: - * Fixed compatibility check crashing for players with Stardew Valley 1.08. - * Fixed the game's test messages being shown in the console and log. - * Fixed TrainerMod's `player_setlevel` command not also setting XP. - * Renamed the default _TrainerMod_ mod to _Console Commands_ to clarify its purpose. * Added a log parser service at [log.smapi.io](https://log.smapi.io). * Added better Steam instructions to the SMAPI installer. + * Renamed the default _TrainerMod_ mod to _Console Commands_ to clarify its purpose. + * Hid the game's test messages from the console log. + * Fixed compatibility check crashing for players with Stardew Valley 1.08. + * Fixed TrainerMod's `player_setlevel` command not also setting XP. * For modders: - * Added support for public code in reflection API, to simplify mod integrations. + * The reflection API now works with public code to simplify mod integrations. + * The content API now lets you invalidated multiple assets at once. * Improved input events: * Added `e.IsActionButton` and `e.IsUseToolButton`. * Added `ToSButton()` extension for the game's `Game1.options` button type. diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index ae812e71..711897eb 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -163,9 +163,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Returns whether the given asset key was cached. public bool InvalidateCache(string key) { - this.Monitor.Log($"Requested cache invalidation for '{key}'.", LogLevel.Trace); string actualKey = this.GetActualAssetKey(key, ContentSource.GameContent); - return this.ContentManager.InvalidateCache((otherKey, type) => otherKey.Equals(actualKey, StringComparison.InvariantCultureIgnoreCase)); + this.Monitor.Log($"Requested cache invalidation for '{actualKey}'.", LogLevel.Trace); + return this.ContentManager.InvalidateCache(asset => asset.AssetNameEquals(actualKey)); } /// Remove all assets of the given type from the cache so they're reloaded on the next request. This can be a very expensive operation and should only be used in very specific cases. This will reload core game assets if needed, but references to the former assets will still show the previous content. @@ -177,6 +177,15 @@ namespace StardewModdingAPI.Framework.ModHelpers return this.ContentManager.InvalidateCache((key, type) => typeof(T).IsAssignableFrom(type)); } + /// Remove matching assets from the content cache so they're reloaded on the next request. This will reload core game assets if needed, but references to the former asset will still show the previous content. + /// A predicate matching the assets to invalidate. + /// Returns whether any cache entries were invalidated. + public bool InvalidateCache(Func predicate) + { + this.Monitor.Log("Requested cache invalidation for all assets matching a predicate.", LogLevel.Trace); + return this.ContentManager.InvalidateCache(predicate); + } + /********* ** Private methods *********/ diff --git a/src/SMAPI/Framework/SContentManager.cs b/src/SMAPI/Framework/SContentManager.cs index 54ebba83..a755a6df 100644 --- a/src/SMAPI/Framework/SContentManager.cs +++ b/src/SMAPI/Framework/SContentManager.cs @@ -287,18 +287,30 @@ namespace StardewModdingAPI.Framework throw new InvalidOperationException("SMAPI could not access the interceptor methods."); // should never happen // invalidate matching keys - return this.InvalidateCache((assetName, assetType) => + return this.InvalidateCache(asset => { - IAssetInfo info = new AssetInfo(this.GetLocale(), assetName, assetType, this.NormaliseAssetName); - // check loaders - MethodInfo canLoadGeneric = canLoad.MakeGenericMethod(assetType); - if (loaders.Any(loader => (bool)canLoadGeneric.Invoke(loader, new object[] { info }))) + MethodInfo canLoadGeneric = canLoad.MakeGenericMethod(asset.DataType); + if (loaders.Any(loader => (bool)canLoadGeneric.Invoke(loader, new object[] { asset }))) return true; // check editors - MethodInfo canEditGeneric = canEdit.MakeGenericMethod(assetType); - return editors.Any(editor => (bool)canEditGeneric.Invoke(editor, new object[] { info })); + MethodInfo canEditGeneric = canEdit.MakeGenericMethod(asset.DataType); + return editors.Any(editor => (bool)canEditGeneric.Invoke(editor, new object[] { asset })); + }); + } + + /// Purge matched assets from the cache. + /// Matches the asset keys to invalidate. + /// Whether to dispose invalidated assets. This should only be true when they're being invalidated as part of a dispose, to avoid crashing the game. + /// Returns whether any cache entries were invalidated. + public bool InvalidateCache(Func predicate, bool dispose = false) + { + string locale = this.GetLocale(); + return this.InvalidateCache((assetName, type) => + { + IAssetInfo info = new AssetInfo(locale, assetName, type, this.NormaliseAssetName); + return predicate(info); }); } diff --git a/src/SMAPI/IContentHelper.cs b/src/SMAPI/IContentHelper.cs index 7900809f..e3362502 100644 --- a/src/SMAPI/IContentHelper.cs +++ b/src/SMAPI/IContentHelper.cs @@ -53,5 +53,10 @@ namespace StardewModdingAPI /// The asset type to remove from the cache. /// Returns whether any assets were invalidated. bool InvalidateCache(); + + /// Remove matching assets from the content cache so they're reloaded on the next request. This will reload core game assets if needed, but references to the former asset will still show the previous content. + /// A predicate matching the assets to invalidate. + /// Returns whether any cache entries were invalidated. + bool InvalidateCache(Func predicate); } } -- cgit From cb74ce5a1716093818f7b4711af752d73e2187d9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 30 Oct 2017 18:15:26 -0400 Subject: improve update-check errors when connection is offline (#380) --- docs/release-notes.md | 1 + src/SMAPI/Program.cs | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index c192d4ee..6a827b0f 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -5,6 +5,7 @@ * Added better Steam instructions to the SMAPI installer. * Renamed the default _TrainerMod_ mod to _Console Commands_ to clarify its purpose. * Hid the game's test messages from the console log. + * Improved update-check errors when connection is offline. * Fixed compatibility check crashing for players with Stardew Valley 1.08. * Fixed TrainerMod's `player_setlevel` command not also setting XP. diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index ce547d9b..b742467b 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Net; using System.Reflection; using System.Runtime.ExceptionServices; using System.Security; @@ -518,8 +519,11 @@ namespace StardewModdingAPI } catch (Exception ex) { - this.Monitor.Log("Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.", LogLevel.Warn); - this.Monitor.Log($"Error: {ex.GetLogSummary()}"); + this.Monitor.Log("Couldn't check for a new version of SMAPI. This won't affect your game, but you won't be notified of new versions if this keeps happening.", LogLevel.Warn); + this.Monitor.Log(ex is WebException && ex.InnerException == null + ? $"Error: {ex.Message}" + : $"Error: {ex.GetLogSummary()}" + ); } // check mod versions @@ -606,7 +610,11 @@ namespace StardewModdingAPI } catch (Exception ex) { - this.Monitor.Log($"Couldn't check for new mod versions:\n{ex.GetLogSummary()}", LogLevel.Trace); + this.Monitor.Log("Couldn't check for new mod versions. This won't affect your game, but you won't be notified of mod updates if this keeps happening.", LogLevel.Warn); + this.Monitor.Log(ex is WebException && ex.InnerException == null + ? ex.Message + : ex.ToString() + ); } }).Start(); } -- cgit From a0a72e310d29bb9148e4c33a058823cd33bbb98d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 30 Oct 2017 19:26:45 -0400 Subject: explicitly disallow absolute paths as asset keys in content API (#381) --- docs/release-notes.md | 7 ++++--- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 14 +++++++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 6a827b0f..2a50c045 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -19,9 +19,10 @@ * Fixed `e.SuppressButton()` not correctly suppressing keyboard buttons. * Fixed `e.IsClick` (now `e.IsActionButton`) ignoring custom key bindings. * `SemanticVersion` can now be constructed from a `System.Version`. - * Fixed custom map tilesheets not working unless they're explicitly loaded first. - * Fixed mods which implement `IAssetLoader` directly not being allowed to load files due to incorrect conflict detection. - * Fixed SMAPI blocking reflection access to vanilla members on overridden types. + * Fixed reflection API blocking access to vanilla members on overridden types. + * Fixed content API allowing absolute paths as asset keys. + * Fixed content API failing to load custom map tilesheets that aren't preloaded. + * Fixed content API incorrectly detecting duplicate loaders when a mod implements `IAssetLoader` directly. * For SMAPI developers: * Added the SMAPI installer version and platform to the window title to simplify troubleshooting. diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 711897eb..be9594ee 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using Microsoft.Xna.Framework.Content; @@ -88,7 +89,7 @@ namespace StardewModdingAPI.Framework.ModHelpers try { - this.ContentManager.AssertValidAssetKeyFormat(key); + this.AssertValidAssetKeyFormat(key); switch (source) { case ContentSource.GameContent: @@ -189,6 +190,17 @@ namespace StardewModdingAPI.Framework.ModHelpers /********* ** Private methods *********/ + /// Assert that the given key has a valid format. + /// The asset key to check. + /// The asset key is empty or contains invalid characters. + [SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Local", Justification = "Parameter is only used for assertion checks by design.")] + private void AssertValidAssetKeyFormat(string key) + { + this.ContentManager.AssertValidAssetKeyFormat(key); + if (Path.IsPathRooted(key)) + throw new ArgumentException("The asset key must not be an absolute path."); + } + /// Fix custom map tilesheet paths so they can be found by the content manager. /// The map whose tilesheets to fix. /// The map asset key within the mod folder. -- cgit From 089e6de749ae7cb109af00164d2597c6644c255e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 1 Nov 2017 17:41:29 -0400 Subject: update for 2.1 release --- build/GlobalAssemblyInfo.cs | 4 ++-- docs/release-notes.md | 20 ++++++++++---------- src/SMAPI/Constants.cs | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) (limited to 'docs') diff --git a/build/GlobalAssemblyInfo.cs b/build/GlobalAssemblyInfo.cs index 196d67c5..9af704e0 100644 --- a/build/GlobalAssemblyInfo.cs +++ b/build/GlobalAssemblyInfo.cs @@ -2,5 +2,5 @@ using System.Reflection; using System.Runtime.InteropServices; [assembly: ComVisible(false)] -[assembly: AssemblyVersion("2.0.0.0")] -[assembly: AssemblyFileVersion("2.0.0.0")] +[assembly: AssemblyVersion("2.1.0.0")] +[assembly: AssemblyFileVersion("2.1.0.0")] diff --git a/docs/release-notes.md b/docs/release-notes.md index 2a50c045..1a9e4681 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,31 +1,31 @@ # Release notes -## 2.1 (upcoming) +## 2.1 * For players: - * Added a log parser service at [log.smapi.io](https://log.smapi.io). + * Added a log parser at [log.smapi.io](https://log.smapi.io). * Added better Steam instructions to the SMAPI installer. - * Renamed the default _TrainerMod_ mod to _Console Commands_ to clarify its purpose. - * Hid the game's test messages from the console log. - * Improved update-check errors when connection is offline. - * Fixed compatibility check crashing for players with Stardew Valley 1.08. - * Fixed TrainerMod's `player_setlevel` command not also setting XP. + * Renamed the bundled _TrainerMod_ to _ConsoleCommands_ to make its purpose clearer. + * Removed the game's test messages from the console log. + * Improved update-check errors when playing offline. + * Fixed compatibility check for players with Stardew Valley 1.08. + * Fixed `player_setlevel` command not setting XP too. * For modders: * The reflection API now works with public code to simplify mod integrations. * The content API now lets you invalidated multiple assets at once. - * Improved input events: + * The `InputEvents` have been improved: * Added `e.IsActionButton` and `e.IsUseToolButton`. * Added `ToSButton()` extension for the game's `Game1.options` button type. * Deprecated `e.IsClick`, which is limited and unclear. Use `IsActionButton` or `IsUseToolButton` instead. * Fixed `e.SuppressButton()` not correctly suppressing keyboard buttons. * Fixed `e.IsClick` (now `e.IsActionButton`) ignoring custom key bindings. * `SemanticVersion` can now be constructed from a `System.Version`. - * Fixed reflection API blocking access to vanilla members on overridden types. + * Fixed reflection API blocking access to some non-SMAPI members. * Fixed content API allowing absolute paths as asset keys. * Fixed content API failing to load custom map tilesheets that aren't preloaded. * Fixed content API incorrectly detecting duplicate loaders when a mod implements `IAssetLoader` directly. * For SMAPI developers: - * Added the SMAPI installer version and platform to the window title to simplify troubleshooting. + * Added the installer version and platform to the installer window title to simplify troubleshooting. ## 2.0 ### Release highlights diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 7721fd5e..a2dbdd98 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -29,7 +29,7 @@ namespace StardewModdingAPI ** Public ****/ /// SMAPI's current semantic version. - public static ISemanticVersion ApiVersion { get; } = new SemanticVersion(2, 0, 0); + public static ISemanticVersion ApiVersion { get; } = new SemanticVersion(2, 1, 0); /// The minimum supported version of Stardew Valley. public static ISemanticVersion MinimumGameVersion { get; } = new SemanticVersion("1.2.30"); -- cgit