migrate to extension blocks

This commit is contained in:
Jesse Plamondon-Willard
2026-03-26 00:48:57 -04:00
parent 38bb6704e9
commit bdf5b0eb48
7 changed files with 333 additions and 290 deletions
+26 -23
View File
@@ -10,36 +10,39 @@ internal static class ExceptionHelper
/*********
** Public methods
*********/
/// <summary>Get a string representation of an exception suitable for writing to the error log.</summary>
/// <param name="exception">The error to summarize.</param>
public static string GetLogSummary(this Exception? exception)
extension(Exception? exception)
{
try
/// <summary>Get a string representation of an exception suitable for writing to the error log.</summary>
public string GetLogSummary()
{
string message;
switch (exception)
try
{
case TypeLoadException ex:
message = $"Failed loading type '{ex.TypeName}': {exception}";
break;
string message;
switch (exception)
{
case TypeLoadException ex:
message = $"Failed loading type '{ex.TypeName}': {exception}";
break;
case ReflectionTypeLoadException ex:
string summary = ex.ToString();
foreach (Exception? childEx in ex.LoaderExceptions)
summary += $"\n\n{childEx?.GetLogSummary()}";
message = summary;
break;
case ReflectionTypeLoadException ex:
string summary = ex.ToString();
foreach (Exception? childEx in ex.LoaderExceptions)
summary += $"\n\n{childEx?.GetLogSummary()}";
message = summary;
break;
default:
message = exception?.ToString() ?? $"<null exception>\n{Environment.StackTrace}";
break;
default:
message = exception?.ToString() ?? $"<null exception>\n{Environment.StackTrace}";
break;
}
return ExceptionHelper.SimplifyExtensionMessage(message);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed handling {exception?.GetType().FullName} (original message: {exception?.Message})", ex);
}
return ExceptionHelper.SimplifyExtensionMessage(message);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed handling {exception?.GetType().FullName} (original message: {exception?.Message})", ex);
}
}
@@ -6,15 +6,18 @@ namespace StardewModdingAPI.Toolkit.Serialization;
/// <summary>Provides extension methods for parsing JSON.</summary>
public static class JsonExtensions
{
/// <summary>Get a JSON field value from a case-insensitive field name. This will check for an exact match first, then search without case sensitivity.</summary>
/// <typeparam name="T">The value type.</typeparam>
/// <param name="obj">The JSON object to search.</param>
/// <param name="fieldName">The field name.</param>
public static T? ValueIgnoreCase<T>(this JObject obj, string fieldName)
extension(JObject obj)
{
JToken? token = obj.GetValue(fieldName, StringComparison.OrdinalIgnoreCase);
return token != null
? token.Value<T>()
: default;
/// <summary>Get a JSON field value from a case-insensitive field name. This will check for an exact match first, then search without case sensitivity.</summary>
/// <typeparam name="T">The value type.</typeparam>
/// <param name="fieldName">The field name.</param>
public T? ValueIgnoreCase<T>(string fieldName)
{
JToken? token = obj.GetValue(fieldName, StringComparison.OrdinalIgnoreCase);
return token != null
? token.Value<T>()
: default;
}
}
}
+45 -40
View File
@@ -19,54 +19,59 @@ public static class Extensions
/****
** View helpers
****/
/// <summary>Get a URL for an action method. Unlike <see cref="IUrlHelper.Action"/>, only the specified <paramref name="values"/> are added to the URL without merging values from the current HTTP request.</summary>
/// <param name="helper">The URL helper to extend.</param>
/// <param name="action">The name of the action method.</param>
/// <param name="controller">The name of the controller.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="absoluteUrl">Get an absolute URL instead of a server-relative path/</param>
/// <returns>The generated URL.</returns>
public static string? PlainAction(this IUrlHelper helper, [AspMvcAction] string action, [AspMvcController] string controller, object? values = null, bool absoluteUrl = false)
extension(IUrlHelper helper)
{
// get route values
RouteValueDictionary valuesDict = new(values);
foreach (var value in helper.ActionContext.RouteData.Values)
valuesDict.TryAdd(value.Key, null); // explicitly remove it from the URL
// get relative URL
string? url = helper.Action(action, controller, valuesDict);
if (url == null && action.EndsWith("Async"))
url = helper.Action(action[..^"Async".Length], controller, valuesDict);
// get absolute URL
if (absoluteUrl)
/// <summary>Get a URL for an action method. Unlike <see cref="IUrlHelper.Action"/>, only the specified <paramref name="values"/> are added to the URL without merging values from the current HTTP request.</summary>
/// <param name="action">The name of the action method.</param>
/// <param name="controller">The name of the controller.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="absoluteUrl">Get an absolute URL instead of a server-relative path/</param>
/// <returns>The generated URL.</returns>
public string? PlainAction([AspMvcAction] string action, [AspMvcController] string controller, object? values = null, bool absoluteUrl = false)
{
HttpRequest request = helper.ActionContext.HttpContext.Request;
Uri baseUri = new($"{request.Scheme}://{request.Host}");
url = new Uri(baseUri, url).ToString();
// get route values
RouteValueDictionary valuesDict = new(values);
foreach (var value in helper.ActionContext.RouteData.Values)
valuesDict.TryAdd(value.Key, null); // explicitly remove it from the URL
// get relative URL
string? url = helper.Action(action, controller, valuesDict);
if (url == null && action.EndsWith("Async"))
url = helper.Action(action[..^"Async".Length], controller, valuesDict);
// get absolute URL
if (absoluteUrl)
{
HttpRequest request = helper.ActionContext.HttpContext.Request;
Uri baseUri = new($"{request.Scheme}://{request.Host}");
url = new Uri(baseUri, url).ToString();
}
return url;
}
return url;
/// <summary>Convert a virtual (relative, starting with ~/) path to an application absolute path, and append a query argument to force browsers to re-download the asset if needed.</summary>
/// <param name="url">The virtual path of the content.</param>
public string ContentWithCacheBust(string url)
{
char delimiter = url.Contains('?') ? '&' : '?';
return helper.Content($"{url}{delimiter}v={Program.CacheBustValue}");
}
}
/// <summary>Convert a virtual (relative, starting with ~/) path to an application absolute path, and append a query argument to force browsers to re-download the asset if needed.</summary>
/// <param name="helper">The URL helper to extend.</param>
/// <param name="url">The virtual path of the content.</param>
public static string ContentWithCacheBust(this IUrlHelper helper, string url)
{
char delimiter = url.Contains('?') ? '&' : '?';
return helper.Content($"{url}{delimiter}v={Program.CacheBustValue}");
}
/// <summary>Get a serialized JSON representation of the value.</summary>
/// <param name="page">The page to extend.</param>
/// <param name="value">The value to serialize.</param>
/// <returns>The serialized JSON.</returns>
/// <remarks>This bypasses unnecessary validation (e.g. not allowing null values) in <see cref="IJsonHelper.Serialize"/>.</remarks>
public static IHtmlContent ForJson(this RazorPageBase page, object? value)
extension(RazorPageBase page)
{
string json = JsonConvert.SerializeObject(value);
return new HtmlString(json);
/// <summary>Get a serialized JSON representation of the value.</summary>
/// <param name="value">The value to serialize.</param>
/// <returns>The serialized JSON.</returns>
/// <remarks>This bypasses unnecessary validation (e.g. not allowing null values) in <see cref="IJsonHelper.Serialize"/>.</remarks>
public IHtmlContent ForJson(object? value)
{
string json = JsonConvert.SerializeObject(value);
return new HtmlString(json);
}
}
}
+130 -115
View File
@@ -19,178 +19,193 @@ internal static class InternalExtensions
/****
** IMonitor
****/
/// <summary>Log a message for the player or developer the first time it occurs.</summary>
/// <param name="monitor">The monitor through which to log the message.</param>
/// <param name="hash">The hash of logged messages.</param>
/// <param name="message">The message to log.</param>
/// <param name="level">The log severity level.</param>
public static void LogOnce(this IMonitor monitor, HashSet<string> hash, string message, LogLevel level = LogLevel.Trace)
/// <param name="monitor">The monitor to extend.</param>
extension(IMonitor monitor)
{
if (!hash.Contains(message))
/// <summary>Log a message for the player or developer the first time it occurs.</summary>
/// <param name="hash">The hash of logged messages.</param>
/// <param name="message">The message to log.</param>
/// <param name="level">The log severity level.</param>
public void LogOnce(HashSet<string> hash, string message, LogLevel level = LogLevel.Trace)
{
monitor.Log(message, level);
hash.Add(message);
if (!hash.Contains(message))
{
monitor.Log(message, level);
hash.Add(message);
}
}
}
/****
** IModMetadata
****/
/// <summary>Log a message using the mod's monitor.</summary>
/// <param name="metadata">The mod whose monitor to use.</param>
/// <param name="message">The message to log.</param>
/// <param name="level">The log severity level.</param>
public static void LogAsMod(this IModMetadata metadata, string message, LogLevel level = LogLevel.Trace)
/// <param name="metadata">The mod metadata to extend.</param>
extension(IModMetadata metadata)
{
if (metadata.Monitor is null)
throw new InvalidOperationException($"Can't log as mod {metadata.DisplayName}: mod is broken or a content pack. Logged message:\n[{level}] {message}");
/// <summary>Log a message using the mod's monitor.</summary>
/// <param name="message">The message to log.</param>
/// <param name="level">The log severity level.</param>
public void LogAsMod(string message, LogLevel level = LogLevel.Trace)
{
if (metadata.Monitor is null)
throw new InvalidOperationException($"Can't log as mod {metadata.DisplayName}: mod is broken or a content pack. Logged message:\n[{level}] {message}");
metadata.Monitor.Log(message, level);
}
metadata.Monitor.Log(message, level);
}
/// <summary>Log a message using the mod's monitor, but only if it hasn't already been logged since the last game launch.</summary>
/// <param name="metadata">The mod whose monitor to use.</param>
/// <param name="message">The message to log.</param>
/// <param name="level">The log severity level.</param>
public static void LogAsModOnce(this IModMetadata metadata, string message, LogLevel level = LogLevel.Trace)
{
metadata.Monitor?.LogOnce(message, level);
/// <summary>Log a message using the mod's monitor, but only if it hasn't already been logged since the last game launch.</summary>
/// <param name="message">The message to log.</param>
/// <param name="level">The log severity level.</param>
public void LogAsModOnce(string message, LogLevel level = LogLevel.Trace)
{
metadata.Monitor?.LogOnce(message, level);
}
}
/****
** ManagedEvent
****/
/// <summary>Raise the event using the default event args and notify all handlers.</summary>
/// <typeparam name="TEventArgs">The event args type to construct.</typeparam>
/// <param name="event">The event to raise.</param>
public static void RaiseEmpty<TEventArgs>(this ManagedEvent<TEventArgs> @event) where TEventArgs : new()
/// <param name="event">The event to extend.</param>
extension<TEventArgs>(ManagedEvent<TEventArgs> @event)
where TEventArgs : new()
{
if (@event.HasListeners)
@event.Raise(Singleton<TEventArgs>.Instance);
/// <summary>Raise the event using the default event args and notify all handlers.</summary>
public void RaiseEmpty()
{
if (@event.HasListeners)
@event.Raise(Singleton<TEventArgs>.Instance);
}
}
/****
** ReaderWriterLockSlim
****/
/// <summary>Run code within a read lock.</summary>
/// <param name="lock">The lock to set.</param>
/// <param name="action">The action to perform.</param>
public static void InReadLock(this ReaderWriterLockSlim @lock, Action action)
/// <param name="lock">The lock to extend.</param>
extension(ReaderWriterLockSlim @lock)
{
@lock.EnterReadLock();
try
/// <summary>Run code within a read lock.</summary>
/// <param name="action">The action to perform.</param>
public void InReadLock(Action action)
{
action();
@lock.EnterReadLock();
try
{
action();
}
finally
{
@lock.ExitReadLock();
}
}
finally
{
@lock.ExitReadLock();
}
}
/// <summary>Run code within a read lock.</summary>
/// <typeparam name="TReturn">The action's return value.</typeparam>
/// <param name="lock">The lock to set.</param>
/// <param name="action">The action to perform.</param>
public static TReturn InReadLock<TReturn>(this ReaderWriterLockSlim @lock, Func<TReturn> action)
{
@lock.EnterReadLock();
try
/// <summary>Run code within a read lock.</summary>
/// <typeparam name="TReturn">The action's return value.</typeparam>
/// <param name="action">The action to perform.</param>
public TReturn InReadLock<TReturn>(Func<TReturn> action)
{
return action();
@lock.EnterReadLock();
try
{
return action();
}
finally
{
@lock.ExitReadLock();
}
}
finally
{
@lock.ExitReadLock();
}
}
/// <summary>Run code within a write lock.</summary>
/// <param name="lock">The lock to set.</param>
/// <param name="action">The action to perform.</param>
public static void InWriteLock(this ReaderWriterLockSlim @lock, Action action)
{
@lock.EnterWriteLock();
try
/// <summary>Run code within a write lock.</summary>
/// <param name="action">The action to perform.</param>
public void InWriteLock(Action action)
{
action();
@lock.EnterWriteLock();
try
{
action();
}
finally
{
@lock.ExitWriteLock();
}
}
finally
{
@lock.ExitWriteLock();
}
}
/// <summary>Run code within a write lock.</summary>
/// <typeparam name="TReturn">The action's return value.</typeparam>
/// <param name="lock">The lock to set.</param>
/// <param name="action">The action to perform.</param>
public static TReturn InWriteLock<TReturn>(this ReaderWriterLockSlim @lock, Func<TReturn> action)
{
@lock.EnterWriteLock();
try
/// <summary>Run code within a write lock.</summary>
/// <typeparam name="TReturn">The action's return value.</typeparam>
/// <param name="action">The action to perform.</param>
public TReturn InWriteLock<TReturn>(Func<TReturn> action)
{
return action();
}
finally
{
@lock.ExitWriteLock();
@lock.EnterWriteLock();
try
{
return action();
}
finally
{
@lock.ExitWriteLock();
}
}
}
/****
** IActiveClickableMenu
****/
/// <summary>Get a string representation of the menu chain to the given menu (including the specified menu), in parent to child order.</summary>
/// <param name="menu">The menu whose chain to get.</param>
public static string GetMenuChainLabel(this IClickableMenu menu)
/// <param name="menu">The clickable menu to extend.</param>
extension(IClickableMenu menu)
{
static IEnumerable<IClickableMenu> GetAncestors(IClickableMenu menu)
/// <summary>Get a string representation of the menu chain to the given menu (including the specified menu), in parent to child order.</summary>
public string GetMenuChainLabel()
{
for (; menu != null; menu = menu.GetParentMenu())
yield return menu;
}
static IEnumerable<IClickableMenu> GetAncestors(IClickableMenu menu)
{
for (; menu != null; menu = menu.GetParentMenu())
yield return menu;
}
return string.Join(" > ", GetAncestors(menu).Reverse().Select(p => p.GetType().FullName));
return string.Join(" > ", GetAncestors(menu).Reverse().Select(p => p.GetType().FullName));
}
}
/****
** Sprite batch
****/
/// <summary>Get whether the sprite batch is between a begin and end pair.</summary>
/// <param name="spriteBatch">The sprite batch to check.</param>
/// <param name="reflection">The reflection helper with which to access private fields.</param>
public static bool IsOpen(this SpriteBatch spriteBatch, Reflector reflection)
/// <param name="spriteBatch">The sprite batch to extend.</param>
extension(SpriteBatch spriteBatch)
{
return reflection.GetField<bool>(spriteBatch, "_beginCalled").GetValue();
/// <summary>Get whether the sprite batch is between a begin and end pair.</summary>
/// <param name="reflection">The reflection helper with which to access private fields.</param>
public bool IsOpen(Reflector reflection)
{
return reflection.GetField<bool>(spriteBatch, "_beginCalled").GetValue();
}
}
/****
** Texture2D
****/
/// <summary>Set the texture name field.</summary>
/// <param name="texture">The texture whose name to set.</param>
/// <param name="assetName">The asset name to set.</param>
/// <returns>Returns the texture for chaining.</returns>
[return: NotNullIfNotNull("texture")]
public static Texture2D? SetName(this Texture2D? texture, IAssetName assetName)
/// <param name="texture">The texture to extend.</param>
extension(Texture2D? texture)
{
if (texture != null)
texture.Name = assetName.Name;
/// <summary>Set the texture name field.</summary>
/// <param name="assetName">The asset name to set.</param>
/// <returns>Returns the texture for chaining.</returns>
[return: NotNullIfNotNull(nameof(texture))]
public Texture2D? SetName(IAssetName assetName)
{
texture?.Name = assetName.Name;
return texture;
}
return texture;
}
/// <summary>Set the texture name field.</summary>
/// <param name="texture">The texture whose name to set.</param>
/// <param name="assetName">The asset name to set.</param>
/// <returns>Returns the texture for chaining.</returns>
[return: NotNullIfNotNull("texture")]
public static Texture2D? SetName(this Texture2D? texture, string assetName)
{
if (texture != null)
texture.Name = assetName;
/// <summary>Set the texture name field.</summary>
/// <param name="assetName">The asset name to set.</param>
/// <returns>Returns the texture for chaining.</returns>
[return: NotNullIfNotNull(nameof(texture))]
public Texture2D? SetName(string assetName)
{
texture?.Name = assetName;
return texture;
return texture;
}
}
}
@@ -20,7 +20,7 @@ namespace MonoMod.Utils;
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Temporary hotfix submitted by the MonoMod author.")]
[SuppressMessage("ReSharper", "PossibleNullReferenceException", Justification = "Temporary hotfix submitted by the MonoMod author.")]
static class MiniMonoModHotfix
internal static class MiniMonoModHotfix
{
// .NET Framework can break member ordering if using Module.Resolve* on certain members.
@@ -100,46 +100,52 @@ static class MiniMonoModHotfix
}
}
public static Type? GetRealDeclaringType(this MemberInfo member)
extension(MemberInfo member)
{
return member.DeclaringType ?? member.Module.GetModuleType();
public Type? GetRealDeclaringType()
{
return member.DeclaringType ?? member.Module.GetModuleType();
}
}
public static void FixReflectionCache(this Type? type)
extension(Type? type)
{
if (t_RuntimeType == null || p_RuntimeType_Cache == null || m_RuntimeTypeCache_GetFieldList == null || m_RuntimeTypeCache_GetPropertyList == null)
return;
for (; type != null; type = type.DeclaringType)
public void FixReflectionCache()
{
// All types SHOULD inherit RuntimeType, including those built at runtime.
// One might never know what awaits us in the depths of reflection hell though.
if (!t_RuntimeType.IsInstanceOfType(type))
continue;
if (t_RuntimeType == null || p_RuntimeType_Cache == null || m_RuntimeTypeCache_GetFieldList == null || m_RuntimeTypeCache_GetPropertyList == null)
return;
CacheFixEntry entry = _CacheFixed.GetValue(type, rt =>
for (; type != null; type = type.DeclaringType)
{
// All RuntimeTypes MUST have a cache, the getter is non-virtual, it creates on demand and asserts non-null.
object cache = MiniMonoModHotfix.p_RuntimeType_Cache.GetValue(rt, MiniMonoModHotfix._NoArgs)!;
Array properties = MiniMonoModHotfix._GetArray(cache, MiniMonoModHotfix.m_RuntimeTypeCache_GetPropertyList);
Array fields = MiniMonoModHotfix._GetArray(cache, MiniMonoModHotfix.m_RuntimeTypeCache_GetFieldList);
// All types SHOULD inherit RuntimeType, including those built at runtime.
// One might never know what awaits us in the depths of reflection hell though.
if (!t_RuntimeType.IsInstanceOfType(type))
continue;
_FixReflectionCacheOrder<PropertyInfo>(properties);
_FixReflectionCacheOrder<FieldInfo>(fields);
return new CacheFixEntry(cache, properties, fields, needsVerify: false);
});
if (entry.NeedsVerify && !_Verify(entry, type))
{
lock (entry)
CacheFixEntry entry = _CacheFixed.GetValue(type, rt =>
{
_FixReflectionCacheOrder<PropertyInfo>(entry.Properties);
_FixReflectionCacheOrder<FieldInfo>(entry.Fields);
}
}
// All RuntimeTypes MUST have a cache, the getter is non-virtual, it creates on demand and asserts non-null.
object cache = MiniMonoModHotfix.p_RuntimeType_Cache.GetValue(rt, MiniMonoModHotfix._NoArgs)!;
Array properties = MiniMonoModHotfix._GetArray(cache, MiniMonoModHotfix.m_RuntimeTypeCache_GetPropertyList);
Array fields = MiniMonoModHotfix._GetArray(cache, MiniMonoModHotfix.m_RuntimeTypeCache_GetFieldList);
entry.NeedsVerify = true;
_FixReflectionCacheOrder<PropertyInfo>(properties);
_FixReflectionCacheOrder<FieldInfo>(fields);
return new CacheFixEntry(cache, properties, fields, needsVerify: false);
});
if (entry.NeedsVerify && !_Verify(entry, type))
{
lock (entry)
{
_FixReflectionCacheOrder<PropertyInfo>(entry.Properties);
_FixReflectionCacheOrder<FieldInfo>(entry.Fields);
}
}
entry.NeedsVerify = true;
}
}
}
+78 -70
View File
@@ -602,100 +602,108 @@ public static class SButtonExtensions
/*********
** Public methods
*********/
/// <summary>Get the <see cref="SButton"/> equivalent for the given button.</summary>
/// <param name="key">The keyboard button to convert.</param>
public static SButton ToSButton(this Keys key)
extension(Keys key)
{
return (SButton)key;
/// <summary>Get the <see cref="SButton"/> equivalent for the given button.</summary>
public SButton ToSButton()
{
return (SButton)key;
}
}
/// <summary>Get the <see cref="SButton"/> equivalent for the given button.</summary>
/// <param name="key">The controller button to convert.</param>
public static SButton ToSButton(this Buttons key)
extension(Buttons key)
{
return (SButton)(SButtonExtensions.ControllerOffset + key);
/// <summary>Get the <see cref="SButton"/> equivalent for the given button.</summary>
public SButton ToSButton()
{
return (SButton)(SButtonExtensions.ControllerOffset + key);
}
}
/// <summary>Get the <see cref="SButton"/> equivalent for the given button.</summary>
/// <param name="input">The Stardew Valley button to convert.</param>
public static SButton ToSButton(this InputButton input)
extension(InputButton input)
{
// derived from InputButton constructors
if (input.mouseLeft)
return SButton.MouseLeft;
if (input.mouseRight)
return SButton.MouseRight;
return input.key.ToSButton();
/// <summary>Get the <see cref="SButton"/> equivalent for the given button.</summary>
public SButton ToSButton()
{
// derived from InputButton constructors
if (input.mouseLeft)
return SButton.MouseLeft;
if (input.mouseRight)
return SButton.MouseRight;
return input.key.ToSButton();
}
}
/// <summary>Get the <see cref="Keys"/> equivalent for the given button.</summary>
/// <param name="input">The button to convert.</param>
/// <param name="key">The keyboard equivalent.</param>
/// <returns>Returns whether the value was converted successfully.</returns>
public static bool TryGetKeyboard(this SButton input, out Keys key)
/// <param name="input">The button to extend.</param>
extension(SButton input)
{
if (Enum.IsDefined(typeof(Keys), (int)input))
/// <summary>Get the <see cref="Keys"/> equivalent for the given button.</summary>
/// <param name="key">The keyboard equivalent.</param>
/// <returns>Returns whether the value was converted successfully.</returns>
public bool TryGetKeyboard(out Keys key)
{
key = (Keys)input;
return true;
if (Enum.IsDefined(typeof(Keys), (int)input))
{
key = (Keys)input;
return true;
}
key = Keys.None;
return false;
}
key = Keys.None;
return false;
}
/// <summary>Get the <see cref="Buttons"/> equivalent for the given button.</summary>
/// <param name="input">The button to convert.</param>
/// <param name="button">The controller equivalent.</param>
/// <returns>Returns whether the value was converted successfully.</returns>
public static bool TryGetController(this SButton input, out Buttons button)
{
if (Enum.IsDefined(typeof(Buttons), (int)input - SButtonExtensions.ControllerOffset))
/// <summary>Get the <see cref="Buttons"/> equivalent for the given button.</summary>
/// <param name="button">The controller equivalent.</param>
/// <returns>Returns whether the value was converted successfully.</returns>
public bool TryGetController(out Buttons button)
{
button = (Buttons)(input - SButtonExtensions.ControllerOffset);
return true;
if (Enum.IsDefined(typeof(Buttons), (int)input - SButtonExtensions.ControllerOffset))
{
button = (Buttons)(input - SButtonExtensions.ControllerOffset);
return true;
}
button = 0;
return false;
}
button = 0;
return false;
}
/// <summary>Get the <see cref="InputButton"/> equivalent for the given button.</summary>
/// <param name="input">The button to convert.</param>
/// <param name="button">The Stardew Valley input button equivalent.</param>
/// <returns>Returns whether the value was converted successfully.</returns>
public static bool TryGetStardewInput(this SButton input, out InputButton button)
{
// keyboard
if (input.TryGetKeyboard(out Keys key))
/// <summary>Get the <see cref="InputButton"/> equivalent for the given button.</summary>
/// <param name="button">The Stardew Valley input button equivalent.</param>
/// <returns>Returns whether the value was converted successfully.</returns>
public bool TryGetStardewInput(out InputButton button)
{
button = new InputButton(key);
return true;
// keyboard
if (input.TryGetKeyboard(out Keys key))
{
button = new InputButton(key);
return true;
}
// mouse
if (input is SButton.MouseLeft or SButton.MouseRight)
{
button = new InputButton(mouseLeft: input == SButton.MouseLeft);
return true;
}
// not valid
button = default;
return false;
}
// mouse
if (input is SButton.MouseLeft or SButton.MouseRight)
/// <summary>Get whether the given button is equivalent to <see cref="Options.useToolButton"/>.</summary>
public bool IsUseToolButton()
{
button = new InputButton(mouseLeft: input == SButton.MouseLeft);
return true;
return input == SButton.ControllerX || Game1.options.useToolButton.Any(p => p.ToSButton() == input);
}
// not valid
button = default;
return false;
}
/// <summary>Get whether the given button is equivalent to <see cref="Options.useToolButton"/>.</summary>
/// <param name="input">The button.</param>
public static bool IsUseToolButton(this SButton input)
{
return input == SButton.ControllerX || Game1.options.useToolButton.Any(p => p.ToSButton() == input);
}
/// <summary>Get whether the given button is equivalent to <see cref="Options.actionButton"/>.</summary>
/// <param name="input">The button.</param>
public static bool IsActionButton(this SButton input)
{
return input == SButton.ControllerA || Game1.options.actionButton.Any(p => p.ToSButton() == input);
/// <summary>Get whether the given button is equivalent to <see cref="Options.actionButton"/>.</summary>
public bool IsActionButton()
{
return input == SButton.ControllerA || Game1.options.actionButton.Any(p => p.ToSButton() == input);
}
}
}
+6 -3
View File
@@ -19,10 +19,13 @@ public enum SButtonState
/// <summary>Extension methods for <see cref="SButtonState"/>.</summary>
public static class InputStatusExtensions
{
/// <summary>Whether the button was pressed or held.</summary>
/// <param name="state">The button state.</param>
public static bool IsDown(this SButtonState state)
extension(SButtonState state)
{
return state is SButtonState.Held or SButtonState.Pressed;
/// <summary>Whether the button was pressed or held.</summary>
public bool IsDown()
{
return state is SButtonState.Held or SButtonState.Pressed;
}
}
}