mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-27 23:44:35 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
344aa046ac | ||
|
|
51397d78a7 | ||
|
|
28770e4cd8 | ||
|
|
e8ea720ca0 | ||
|
|
7945173061 | ||
|
|
98a37721aa | ||
|
|
08fd56754e | ||
|
|
accfd04209 | ||
|
|
936da28f6a | ||
|
|
6f94e4ed21 | ||
|
|
181013efec | ||
|
|
a8dfbb616c | ||
|
|
b79c14ce50 | ||
|
|
17dafadec8 | ||
|
|
f9274eda09 | ||
|
|
10daf01a55 | ||
|
|
1daffd7560 | ||
|
|
26541dc07d | ||
|
|
2a7570f022 | ||
|
|
71138c7bbe | ||
|
|
2993ee2bbc | ||
|
|
79bbebd12a |
No files matched your search
@@ -104,3 +104,6 @@ dotnet_diagnostic.S1244.severity = none
|
||||
|
||||
# S2325: Methods and properties that don't access instance data should be static
|
||||
dotnet_diagnostic.S2325.severity = none
|
||||
|
||||
# IDE0005: Using directive is unnecessary.
|
||||
dotnet_diagnostic.IDE0005.severity = warning
|
||||
@@ -5,8 +5,6 @@ on: ["workflow_dispatch", "push", "pull_request"]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -40,21 +38,14 @@ jobs:
|
||||
with:
|
||||
name: AllOfUs.MiraAPI.nupkg
|
||||
path: MiraAPI/bin/Release/AllOfUs.MiraAPI.*.nupkg
|
||||
|
||||
|
||||
- uses: softprops/action-gh-release@v2
|
||||
if: github.ref_type == 'tag'
|
||||
with:
|
||||
draft: true
|
||||
files: MiraAPI/bin/Release/net6.0/MiraAPI.dll
|
||||
|
||||
- name: NuGet login
|
||||
if: github.ref_type == 'tag'
|
||||
uses: NuGet/login@v1
|
||||
id: login
|
||||
with:
|
||||
user: xtracube
|
||||
|
||||
|
||||
- name: Push NuGet package
|
||||
if: github.ref_type == 'tag'
|
||||
run: |
|
||||
dotnet nuget push MiraAPI/bin/Release/AllOfUs.MiraAPI.*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ steps.login.outputs.NUGET_API_KEY }}
|
||||
dotnet nuget push MiraAPI/bin/Release/AllOfUs.MiraAPI.*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
|
||||
<VersionPrefix>0.5.0</VersionPrefix>
|
||||
<VersionPrefix>0.6.0</VersionPrefix>
|
||||
<VersionSuffix>dev</VersionSuffix>
|
||||
|
||||
<Authors>All Of Us, XtraCube, Angxl</Authors>
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using System.Reflection;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
|
||||
namespace MiraAPI.Example;
|
||||
|
||||
public static class ExampleAssets
|
||||
{
|
||||
public static LoadableResourceAsset ExampleButton { get; } = new("MiraAPI.Example.Resources.ExampleButton.png");
|
||||
public static LoadableResourceAsset CallMeetingButton { get; } = new("MiraAPI.Example.Resources.CallMeeting.png");
|
||||
private static readonly Assembly _assembly = typeof(ExampleAssets).Assembly;
|
||||
|
||||
public static LoadableResourceAsset ExampleButton { get; } = new("MiraAPI.Example.Resources.ExampleButton.png", _assembly);
|
||||
public static LoadableResourceAsset CallMeetingButton { get; } = new("MiraAPI.Example.Resources.CallMeeting.png", _assembly);
|
||||
|
||||
// Credit to EpicHorrors for the teleport button asset.
|
||||
public static LoadableResourceAsset TeleportButton { get; } = new("MiraAPI.Example.Resources.TeleportButton.png");
|
||||
public static LoadableResourceAsset SharpshootButton { get; } = new("MiraAPI.Example.Resources.SharpshootButton.png");
|
||||
public static LoadableResourceAsset Banner { get; } = new("MiraAPI.Example.Resources.FortniteBanner.jpeg");
|
||||
public static LoadableResourceAsset TeleportButton { get; } = new("MiraAPI.Example.Resources.TeleportButton.png", _assembly);
|
||||
public static LoadableResourceAsset SharpshootButton { get; } = new("MiraAPI.Example.Resources.SharpshootButton.png", _assembly);
|
||||
public static LoadableResourceAsset Banner { get; } = new("MiraAPI.Example.Resources.FortniteBanner.jpeg", _assembly);
|
||||
}
|
||||
@@ -31,7 +31,7 @@ public static class ExampleEventHandlers
|
||||
|
||||
MiraEventManager.RegisterEventHandler<CompleteTaskEvent>(@event =>
|
||||
{
|
||||
Logger<ExamplePlugin>.Info($"{@event.Player.Data.PlayerName} completed {@event.Task.TaskType.ToString()}");
|
||||
Logger<ExamplePlugin>.Info($"{@event.Player.Data.PlayerName} completed {@event.Task.TaskType}");
|
||||
});
|
||||
|
||||
// You can also unregister event handlers using the handle returned from RegisterEventHandler.
|
||||
@@ -89,7 +89,7 @@ public static class ExampleEventHandlers
|
||||
[RegisterEvent]
|
||||
public static void UpdateSystemEventHandler(UpdateSystemEvent @event)
|
||||
{
|
||||
Logger<ExamplePlugin>.Error(@event.SystemType.ToString());
|
||||
Logger<ExamplePlugin>.Error(@event.SystemType);
|
||||
}
|
||||
|
||||
// Example event handler
|
||||
|
||||
@@ -19,7 +19,7 @@ public partial class ExamplePlugin : BasePlugin, IMiraPlugin
|
||||
{
|
||||
public ExamplePlugin()
|
||||
{
|
||||
MiraLocaleManager.Register("mira.example");
|
||||
MiraLocaleManager.Register("mira.example", typeof(ExamplePlugin).Assembly);
|
||||
}
|
||||
|
||||
public Harmony Harmony { get; } = new(Id);
|
||||
|
||||
@@ -30,16 +30,12 @@ public class ExampleOptions : AbstractOptionGroup
|
||||
[ModdedNumberOption("options.exampleOptions1.numberOpt", min: 0, max: 10, suffixType: MiraNumberSuffixes.Distance)]
|
||||
public int NumberOpt2 { get; set; } = 4; // Can also be int!
|
||||
|
||||
[ModdedEnumOption("options.exampleOptions1.bestApi", typeof(Api), ["Mira API", "Mitochondria", "Reactor"])]
|
||||
public Api BestApi { get; set; } = Api.MiraAPI;
|
||||
|
||||
[ModdedEnumOption<Api>("options.exampleOptions1.worstApi", ["Mira API", "Mitochondria", "Reactor"])]
|
||||
|
||||
// These three can target both instance and static methods
|
||||
[ModdedOptionGetter(nameof(GetApi))] // Can also automatically resolve to {Option Name}Getter
|
||||
[ModdedOptionSetter(nameof(SetApi))] // Can also automatically resolve to {Option Name}Setter
|
||||
[ModdedOptionChanged(nameof(ApiChanged))] // Can also automatically resolve to {Option Name}Changed
|
||||
public Api WorstApi { get; set; } = Api.MiraAPI;
|
||||
[ModdedEnumOption<Api>("options.exampleOptions1.bestApi", ["Mira API", "Mitochondria", "Reactor"])]
|
||||
public Api BestApi { get; set; } = Api.MiraAPI;
|
||||
|
||||
[ModdedEnumOptionList<ValueState>("Value State {0}", ["None", "Primary", "Secondary"])]
|
||||
public ModdedArray<ValueState> ValueStates { get; set; } = [ValueState.Primary, ValueState.Secondary, ValueState.None, ValueState.None, ValueState.None];
|
||||
|
||||
@@ -47,7 +47,6 @@ Eliminate every other faction.</string>
|
||||
<string name="options.exampleOptions1.toggleOpt2">Toggle Opt 2</string>
|
||||
<string name="options.exampleOptions1.numberOpt">Number Opt</string>
|
||||
<string name="options.exampleOptions1.bestApi">Best API</string>
|
||||
<string name="options.exampleOptions1.worstApi">Worst API</string>
|
||||
<string name="options.exampleOptions1.players">Choose A Player</string>
|
||||
<string name="options.exampleOptions2">Example Options 2</string>
|
||||
<string name="options.exampleOptions2.youMustChoseAPlayer">YOU MUST CHOSE A PLAYER</string>
|
||||
|
||||
@@ -58,13 +58,13 @@ public class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColo
|
||||
|
||||
/// <inheritdoc cref="CustomColor"/>
|
||||
public CustomColor(StringNames name, Color32 mainColor)
|
||||
: this(name, mainColor, mainColor.GetShadowColor(60))
|
||||
: this(name, mainColor, mainColor.DarkenAbsolute(60))
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CustomColor"/>
|
||||
public CustomColor(string name, Color32 mainColor)
|
||||
: this(name, mainColor, mainColor.GetShadowColor(60))
|
||||
: this(name, mainColor, mainColor.DarkenAbsolute(60))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace MiraAPI.Colors;
|
||||
/// <param name="shadowColor">The static shadow color used when dynamic colors are disabled.</param>
|
||||
/// <param name="defaultTime">The default time that is used to automatically fill in the static colors when the latter is not provided.</param>
|
||||
public sealed class VariableColor(StringNames name, Func<float, Color> mainEvaluator, Func<float, Color>? shadowEvaluator, Color32? mainColor = null, Color32? shadowColor = null, float defaultTime = 0f)
|
||||
: CustomColor(name, mainColor ?? mainEvaluator(defaultTime), shadowColor ?? shadowEvaluator?.Invoke(defaultTime) ?? (mainColor ?? mainEvaluator(defaultTime)).GetShadowColor(60))
|
||||
: CustomColor(name, mainColor ?? mainEvaluator(defaultTime), shadowColor ?? shadowEvaluator?.Invoke(defaultTime) ?? (mainColor ?? mainEvaluator(defaultTime)).DarkenAbsolute(60))
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a delegate that takes in a time value and returns the dynamic main color.
|
||||
@@ -26,7 +26,7 @@ public sealed class VariableColor(StringNames name, Func<float, Color> mainEvalu
|
||||
/// <summary>
|
||||
/// Gets or sets a delegate that takes in a time value and returns the dynamic shadow color.
|
||||
/// </summary>
|
||||
public Func<float, Color> ShadowEvaluator { get; set; } = shadowEvaluator ?? (t => mainEvaluator(t).GetShadowColor(0.24f));
|
||||
public Func<float, Color> ShadowEvaluator { get; set; } = shadowEvaluator ?? (t => mainEvaluator(t).DarkenAbsolute(0.24f));
|
||||
|
||||
/// <inheritdoc cref="VariableColor"/>
|
||||
public VariableColor(StringNames name, Func<float, Color> mainEvaluator, Color32? mainColor = null, float defaultTime = 0f)
|
||||
|
||||
@@ -35,7 +35,7 @@ public static class MiraEventManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Error($"Error invoking event handler for {typeof(T).Name}: {ex.ToString()}");
|
||||
Error($"Error invoking event handler for {typeof(T).Name}: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public static class MiraEventManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Error($"Error invoking event handler for {type.Name}: {ex.ToString()}");
|
||||
Error($"Error invoking event handler for {type.Name}: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public abstract class AbstractOptionGroup
|
||||
/// Gets a value indicating whether the group should be shown in the modifiers menu.
|
||||
/// </summary>
|
||||
// Completed: make this not a boolean
|
||||
[Obsolete("Use ParentMenu instead.", true)]
|
||||
[Obsolete("Use ParentMenu instead. This will be removed in a future version.", true)]
|
||||
[SuppressMessage("Info Code Smell", "S1133:Deprecated code should be removed", Justification = "Retained.")]
|
||||
public virtual bool ShowInModifiersMenu => false;
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace MiraAPI.GameOptions.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Provides unboxed access to option values to prevent memory allocations.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value that the option carries.</typeparam>
|
||||
public interface ITypedOption<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the unboxed value of the option.
|
||||
/// </summary>
|
||||
/// <returns>The unboxed option value.</returns>
|
||||
T GetValueUnboxed();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the unboxed value of the option.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value to set.</param>
|
||||
void SetValueUnboxed(T value);
|
||||
}
|
||||
@@ -11,6 +11,7 @@ namespace MiraAPI.GameOptions.Attributes;
|
||||
/// <param name="enumType">The <see cref="Enum"/> type.</param>
|
||||
/// <param name="values">An option list of string values to use in place of the <see langword="enum"/> name.</param>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
[Obsolete("Use ModdedEnumOptionAttribute<T> instead. This will be removed in a future version.")]
|
||||
public class ModdedEnumOptionAttribute(string title, Type enumType, string[]? values = null)
|
||||
: ModdedOptionAttribute(title)
|
||||
{
|
||||
@@ -32,7 +33,7 @@ public class ModdedEnumOptionAttribute(string title, Type enumType, string[]? va
|
||||
{
|
||||
return HolderOption is ModdedEnumOption opt
|
||||
? Enum.ToObject(enumType, opt.Value)
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{Title}\" with EnumType {enumType.FullName} is not a ModdedEnumOption");
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{Title}\" is not a ModdedEnumOption with enum type {enumType.FullName}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +45,7 @@ public class ModdedEnumOptionAttribute(string title, Type enumType, string[]? va
|
||||
/// <param name="values">An option list of string values to use in place of the <see langword="enum"/> name.</param>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ModdedEnumOptionAttribute<T>(string title, string[]? values = null)
|
||||
: ModdedOptionAttribute(title)
|
||||
: ModdedOptionAttribute(title), ITypedOption<T>
|
||||
where T : struct, Enum
|
||||
{
|
||||
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
|
||||
@@ -56,15 +57,26 @@ public class ModdedEnumOptionAttribute<T>(string title, string[]? values = null)
|
||||
/// <inheritdoc />
|
||||
public override void SetValue(object value)
|
||||
{
|
||||
var opt = HolderOption as ModdedEnumOption<T>;
|
||||
opt?.SetValue((T)value);
|
||||
SetValueUnboxed((T)value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValueUnboxed(T value)
|
||||
{
|
||||
(HolderOption as ModdedEnumOption<T>)?.SetValue(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue()
|
||||
{
|
||||
return GetValueUnboxed();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValueUnboxed()
|
||||
{
|
||||
return HolderOption is ModdedEnumOption<T> opt
|
||||
? opt.Value
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{Title}\" with EnumType {typeof(T).FullName} is not a ModdedEnumOption<{typeof(T).Name}>");
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{Title}\" is not a ModdedEnumOption<{typeof(T).Name}>");
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ public class ModdedNumberOptionAttribute(
|
||||
MiraNumberSuffixes suffixType = MiraNumberSuffixes.None,
|
||||
string? formatString = null,
|
||||
bool halfIncrements = false)
|
||||
: ModdedOptionAttribute(title)
|
||||
: ModdedOptionAttribute(title), ITypedOption<int>, ITypedOption<float>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedNumberOptionAttribute"/> class.
|
||||
@@ -88,6 +88,18 @@ public class ModdedNumberOptionAttribute(
|
||||
opt.SetValue(final);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValueUnboxed(float value)
|
||||
{
|
||||
(HolderOption as ModdedNumberOption)?.SetValue(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void ITypedOption<int>.SetValueUnboxed(int value)
|
||||
{
|
||||
(HolderOption as ModdedNumberOption)?.SetValue(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue()
|
||||
{
|
||||
@@ -97,8 +109,22 @@ public class ModdedNumberOptionAttribute(
|
||||
var value = opt.Value;
|
||||
|
||||
if (ValueType == typeof(int))
|
||||
return (int)Math.Round(value);
|
||||
return (int)MathF.Round(value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float GetValueUnboxed()
|
||||
{
|
||||
return HolderOption is ModdedNumberOption opt
|
||||
? opt.Value
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{Title}\" is not a ModdedNumberOption");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
int ITypedOption<int>.GetValueUnboxed()
|
||||
{
|
||||
return (int)Math.Round(GetValueUnboxed());
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace MiraAPI.GameOptions.Attributes;
|
||||
public class ModdedPlayerOptionAttribute(
|
||||
string title,
|
||||
bool allowNone = true)
|
||||
: ModdedOptionAttribute(title)
|
||||
: ModdedOptionAttribute(title), ITypedOption<int>, ITypedOption<NetworkedPlayerInfo?>
|
||||
{
|
||||
internal override IModdedOption? CreateOption(object? value, PropertyInfo property)
|
||||
{
|
||||
@@ -33,6 +33,22 @@ public class ModdedPlayerOptionAttribute(
|
||||
return opt.SelectedPlayer!;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkedPlayerInfo? GetValueUnboxed()
|
||||
{
|
||||
return HolderOption is ModdedPlayerOption opt
|
||||
? opt.SelectedPlayer
|
||||
: throw new InvalidOperationException($"Holder option for {Title} is not a ModdedPlayerOption.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
int ITypedOption<int>.GetValueUnboxed()
|
||||
{
|
||||
return HolderOption is ModdedPlayerOption opt
|
||||
? opt.Value
|
||||
: throw new InvalidOperationException($"Holder option for {Title} is not a ModdedPlayerOption.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetValue(object value)
|
||||
{
|
||||
@@ -41,9 +57,32 @@ public class ModdedPlayerOptionAttribute(
|
||||
|
||||
var final = value switch
|
||||
{
|
||||
NetworkedPlayerInfo info => info ? opt.Values.IndexOf(info) : (allowNone ? -1 : 0),
|
||||
NetworkedPlayerInfo info => info ? opt.Values.IndexOf(info) : -1,
|
||||
_ => (int)value,
|
||||
};
|
||||
|
||||
if (final == -1 && !allowNone)
|
||||
throw new ArgumentException("Cannot assign -1 or null to a ModdedPlayerOption that does not allow none as an option.");
|
||||
|
||||
opt?.SetValue(final);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValueUnboxed(NetworkedPlayerInfo? value)
|
||||
{
|
||||
if (!value && !allowNone)
|
||||
throw new ArgumentException("Cannot assign null to a ModdedPlayerOption that does not allow none as an option.");
|
||||
|
||||
if (HolderOption is ModdedPlayerOption opt)
|
||||
opt.SetValue(value ? opt.Values.IndexOf(value!) : -1);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void ITypedOption<int>.SetValueUnboxed(int value)
|
||||
{
|
||||
if (value == -1 && !allowNone)
|
||||
throw new ArgumentException("Cannot assign -1 to a ModdedPlayerOption that does not allow none as an option.");
|
||||
|
||||
(HolderOption as ModdedPlayerOption)?.SetValue(value);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace MiraAPI.GameOptions.Attributes;
|
||||
public class ModdedStringOptionAttribute(
|
||||
string title,
|
||||
string[] values)
|
||||
: ModdedOptionAttribute(title)
|
||||
: ModdedOptionAttribute(title), ITypedOption<string>
|
||||
{
|
||||
internal override IModdedOption? CreateOption(object? value, PropertyInfo property)
|
||||
{
|
||||
@@ -23,6 +23,12 @@ public class ModdedStringOptionAttribute(
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue()
|
||||
{
|
||||
return GetValueUnboxed();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetValueUnboxed()
|
||||
{
|
||||
return HolderOption is ModdedStringOption opt
|
||||
? opt.Value
|
||||
@@ -32,7 +38,12 @@ public class ModdedStringOptionAttribute(
|
||||
/// <inheritdoc />
|
||||
public override void SetValue(object value)
|
||||
{
|
||||
var opt = HolderOption as ModdedStringOption;
|
||||
opt?.SetValue((string)value);
|
||||
SetValueUnboxed((string)value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValueUnboxed(string value)
|
||||
{
|
||||
(HolderOption as ModdedStringOption)?.SetValue(value);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ public class ModdedToggleOptionAttribute(
|
||||
string title,
|
||||
string on,
|
||||
string off)
|
||||
: ModdedOptionAttribute(title)
|
||||
: ModdedOptionAttribute(title), ITypedOption<bool>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedToggleOptionAttribute"/> class.
|
||||
@@ -35,12 +35,23 @@ public class ModdedToggleOptionAttribute(
|
||||
/// <inheritdoc />
|
||||
public override void SetValue(object value)
|
||||
{
|
||||
var opt = HolderOption as ModdedToggleOption;
|
||||
opt?.SetValue((bool)value);
|
||||
SetValueUnboxed((bool)value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValueUnboxed(bool value)
|
||||
{
|
||||
(HolderOption as ModdedToggleOption)?.SetValue(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue()
|
||||
{
|
||||
return GetValueUnboxed();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool GetValueUnboxed()
|
||||
{
|
||||
return HolderOption is ModdedToggleOption opt
|
||||
? opt.Value
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace MiraAPI.GameOptions.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Provides unboxed access to option list values to prevent memory allocations.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value that the option list carries.</typeparam>
|
||||
public interface ITypedOptionList<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the unboxed value of the option at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="idx">The index of the option.</param>
|
||||
/// <returns>The option at the provided index.</returns>
|
||||
T GetValueUnboxed(int idx);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the unboxed value of the option at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="idx">The index of the option.</param>
|
||||
/// <param name="value">The new value of the option to set.</param>
|
||||
void SetValueUnboxed(int idx, T value);
|
||||
}
|
||||
@@ -12,6 +12,7 @@ namespace MiraAPI.GameOptions.Attributes;
|
||||
/// <param name="enumType">The <see cref="Enum"/> type.</param>
|
||||
/// <param name="values">An option list of string values to use in place of the <see langword="enum"/> name.</param>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
[Obsolete("Use ModdedEnumOptionListAttribute<T> instead. This will be removed in a future version.")]
|
||||
public class ModdedEnumOptionListAttribute(string title, Type enumType, string[]? values = null)
|
||||
: ModdedOptionListAttribute(title)
|
||||
{
|
||||
@@ -34,7 +35,7 @@ public class ModdedEnumOptionListAttribute(string title, Type enumType, string[]
|
||||
{
|
||||
return HolderOptionList?[idx] is ModdedEnumOption opt
|
||||
? Enum.ToObject(enumType, opt.Value)
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" with EnumType {enumType.FullName} is not a ModdedEnumOption");
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" is not a ModdedEnumOption with enum type {enumType.FullName}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +47,7 @@ public class ModdedEnumOptionListAttribute(string title, Type enumType, string[]
|
||||
/// <param name="values">The option's possible values.</param>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ModdedEnumOptionListAttribute<T>(string title, string[]? values = null)
|
||||
: ModdedOptionListAttribute(title)
|
||||
: ModdedOptionListAttribute(title), ITypedOptionList<T>
|
||||
where T : struct, Enum
|
||||
{
|
||||
internal override IModdedOptionList CreateOptionList(IList value, PropertyInfo property)
|
||||
@@ -59,15 +60,26 @@ public class ModdedEnumOptionListAttribute<T>(string title, string[]? values = n
|
||||
/// <inheritdoc />
|
||||
public override void SetValue(int idx, object value)
|
||||
{
|
||||
var opt = HolderOptionList?[idx] as ModdedEnumOption<T>;
|
||||
opt?.SetValue((T)value);
|
||||
SetValueUnboxed(idx, (T)value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValueUnboxed(int idx, T value)
|
||||
{
|
||||
(HolderOptionList?[idx] as ModdedEnumOption<T>)?.SetValue(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue(int idx)
|
||||
{
|
||||
return GetValueUnboxed(idx);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValueUnboxed(int idx)
|
||||
{
|
||||
return HolderOptionList?[idx] is ModdedEnumOption<T> opt
|
||||
? opt.Value
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" with EnumType {typeof(T).FullName} is not a ModdedEnumOption<{typeof(T).FullName}>");
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" is not a ModdedEnumOption<{typeof(T).FullName}>");
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ public class ModdedNumberOptionListAttribute(
|
||||
MiraNumberSuffixes suffixType = MiraNumberSuffixes.None,
|
||||
string? formatString = null,
|
||||
bool halfIncrements = false)
|
||||
: ModdedOptionListAttribute(title)
|
||||
: ModdedOptionListAttribute(title), ITypedOptionList<int>, ITypedOptionList<float>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedNumberOptionListAttribute"/> class.
|
||||
@@ -92,6 +92,18 @@ public class ModdedNumberOptionListAttribute(
|
||||
opt.SetValue(final);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValueUnboxed(int idx, float value)
|
||||
{
|
||||
(HolderOptionList?[idx] as ModdedNumberOption)?.SetValue(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void ITypedOptionList<int>.SetValueUnboxed(int idx, int value)
|
||||
{
|
||||
(HolderOptionList?[idx] as ModdedNumberOption)?.SetValue(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue(int idx)
|
||||
{
|
||||
@@ -101,8 +113,22 @@ public class ModdedNumberOptionListAttribute(
|
||||
var value = opt.Value;
|
||||
|
||||
if (ValueType == typeof(int))
|
||||
return (int)Math.Round(value);
|
||||
return (int)MathF.Round(value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float GetValueUnboxed(int idx)
|
||||
{
|
||||
return HolderOptionList?[idx] is ModdedNumberOption opt
|
||||
? opt.Value
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" is not a ModdedNumberOption");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
int ITypedOptionList<int>.GetValueUnboxed(int idx)
|
||||
{
|
||||
return (int)Math.Round(GetValueUnboxed(idx));
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using MiraAPI.Translation;
|
||||
|
||||
namespace MiraAPI.GameOptions.Attributes;
|
||||
|
||||
@@ -75,10 +74,9 @@ public abstract class ModdedOptionListAttribute(string title) : PropertyOptionAt
|
||||
/// </summary>
|
||||
/// <param name="index">The option's index.</param>
|
||||
/// <returns>The formatted option's title.</returns>
|
||||
[SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "I don't even know if this is worth it.")]
|
||||
protected string GetFormattedTitle(int index)
|
||||
{
|
||||
var match = Regex.Match(Title.Translate(), @"\{(\d+)(?::[^}]+)?\}", default, Regex.InfiniteMatchTimeout);
|
||||
var match = Regex.Match(Title, @"\{(\d+)(?::[^}]+)?\}", default, Regex.InfiniteMatchTimeout);
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
@@ -87,7 +85,7 @@ public abstract class ModdedOptionListAttribute(string title) : PropertyOptionAt
|
||||
|
||||
if (match.Value == "{0}" && !match.NextMatch().Success)
|
||||
{
|
||||
return string.Format(Title, index);
|
||||
return string.Format(CultureInfo.InvariantCulture, Title, index);
|
||||
}
|
||||
|
||||
Error($"ModdedOptionList Title of {Title} cannot contain more than the first parameter for formatting.");
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace MiraAPI.GameOptions.Attributes;
|
||||
public class ModdedPlayerOptionListAttribute(
|
||||
string title,
|
||||
bool allowNone = true)
|
||||
: ModdedOptionListAttribute(title)
|
||||
: ModdedOptionListAttribute(title), ITypedOptionList<int>, ITypedOptionList<NetworkedPlayerInfo?>
|
||||
{
|
||||
internal override IModdedOptionList CreateOptionList(IList value, PropertyInfo property)
|
||||
{
|
||||
@@ -32,12 +32,35 @@ public class ModdedPlayerOptionListAttribute(
|
||||
|
||||
var final = value switch
|
||||
{
|
||||
NetworkedPlayerInfo info => info ? opt.Values.IndexOf(info) : (allowNone ? -1 : 0),
|
||||
NetworkedPlayerInfo info => info ? opt.Values.IndexOf(info) : -1,
|
||||
_ => (int)value,
|
||||
};
|
||||
|
||||
if (final == -1 && !allowNone)
|
||||
throw new ArgumentException("Cannot assign -1 or null to a ModdedPlayerOption that does not allow none as an option.");
|
||||
|
||||
opt?.SetValue(final);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValueUnboxed(int idx, NetworkedPlayerInfo? value)
|
||||
{
|
||||
if (!value && !allowNone)
|
||||
throw new ArgumentException("Cannot assign null to a ModdedPlayerOption that does not allow none as an option.");
|
||||
|
||||
if (HolderOptionList?[idx] is ModdedPlayerOption opt)
|
||||
opt.SetValue(value ? opt.Values.IndexOf(value!) : -1);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValueUnboxed(int idx, int value)
|
||||
{
|
||||
if (value == -1 && !allowNone)
|
||||
throw new ArgumentException("Cannot assign -1 to a ModdedPlayerOption that does not allow none as an option.");
|
||||
|
||||
(HolderOptionList?[idx] as ModdedPlayerOption)?.SetValue(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue(int idx)
|
||||
{
|
||||
@@ -49,4 +72,20 @@ public class ModdedPlayerOptionListAttribute(
|
||||
|
||||
return opt.SelectedPlayer!;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkedPlayerInfo? GetValueUnboxed(int idx)
|
||||
{
|
||||
return HolderOptionList?[idx] is ModdedPlayerOption opt
|
||||
? opt.SelectedPlayer
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" is not a ModdedPlayerOption");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
int ITypedOptionList<int>.GetValueUnboxed(int idx)
|
||||
{
|
||||
return HolderOptionList?[idx] is ModdedPlayerOption opt
|
||||
? opt.Value
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" is not a ModdedPlayerOption");
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace MiraAPI.GameOptions.Attributes;
|
||||
public class ModdedStringOptionListAttribute(
|
||||
string title,
|
||||
string[] values)
|
||||
: ModdedOptionListAttribute(title)
|
||||
: ModdedOptionListAttribute(title), ITypedOptionList<string>
|
||||
{
|
||||
internal override IModdedOptionList CreateOptionList(IList value, PropertyInfo property)
|
||||
{
|
||||
@@ -28,12 +28,23 @@ public class ModdedStringOptionListAttribute(
|
||||
/// <inheritdoc />
|
||||
public override void SetValue(int idx, object value)
|
||||
{
|
||||
var opt = HolderOptionList?[idx] as ModdedStringOption;
|
||||
opt?.SetValue((string)value);
|
||||
SetValueUnboxed(idx, (string)value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValueUnboxed(int idx, string value)
|
||||
{
|
||||
(HolderOptionList?[idx] as ModdedStringOption)?.SetValue(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue(int idx)
|
||||
{
|
||||
return GetValueUnboxed(idx);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetValueUnboxed(int idx)
|
||||
{
|
||||
return HolderOptionList?[idx] is ModdedStringOption opt
|
||||
? opt.Value
|
||||
|
||||
@@ -16,7 +16,7 @@ public class ModdedToggleOptionListAttribute(
|
||||
string title,
|
||||
string on,
|
||||
string off)
|
||||
: ModdedOptionListAttribute(title)
|
||||
: ModdedOptionListAttribute(title), ITypedOptionList<bool>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedToggleOptionListAttribute"/> class.
|
||||
@@ -37,12 +37,23 @@ public class ModdedToggleOptionListAttribute(
|
||||
/// <inheritdoc />
|
||||
public override void SetValue(int idx, object value)
|
||||
{
|
||||
var opt = HolderOptionList?[idx] as ModdedToggleOption;
|
||||
opt?.SetValue((bool)value);
|
||||
SetValueUnboxed(idx, (bool)value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValueUnboxed(int idx, bool value)
|
||||
{
|
||||
(HolderOptionList?[idx] as ModdedToggleOption)?.SetValue(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue(int idx)
|
||||
{
|
||||
return GetValueUnboxed(idx);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool GetValueUnboxed(int idx)
|
||||
{
|
||||
return HolderOptionList?[idx] is ModdedToggleOption opt
|
||||
? opt.Value
|
||||
|
||||
@@ -77,6 +77,11 @@ public interface IModdedOption
|
||||
/// </summary>
|
||||
bool IncludeInPreset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the option should show a notification, toggled on by an overall option synchronization.
|
||||
/// </summary>
|
||||
public bool ShowNotification { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="BepInEx.Configuration.ConfigDefinition"/> for the option, used for BepInEx configuration.
|
||||
/// </summary>
|
||||
@@ -93,6 +98,12 @@ public interface IModdedOption
|
||||
/// <returns>The created <see cref="global::OptionBehaviour"/> object.</returns>
|
||||
OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the translated display value of the toggle option.
|
||||
/// </summary>
|
||||
/// <returns>The display value.</returns>
|
||||
string GetDisplayValue();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value as a <see langword="float"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using MiraAPI.GameOptions.Attributes;
|
||||
|
||||
namespace MiraAPI.GameOptions;
|
||||
|
||||
@@ -72,15 +73,34 @@ public sealed class ModdedArray<T> : IList<T>, IReadOnlyList<T>, IList
|
||||
/// <param name="index">The index of the value.</param>
|
||||
public T this[int index]
|
||||
{
|
||||
get => ModdedOptionsManager.ListInstanceCache.TryGetValue(this, out var attribute)
|
||||
? (T)attribute.GetValue(index)
|
||||
: items[index];
|
||||
get
|
||||
{
|
||||
if (ModdedOptionsManager.ListInstanceCache.TryGetValue(this, out var attribute))
|
||||
{
|
||||
items[index] = attribute is ITypedOptionList<T> typedAttr
|
||||
? typedAttr.GetValueUnboxed(index)
|
||||
: (T)attribute.GetValue(index);
|
||||
}
|
||||
|
||||
return items[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (ModdedOptionsManager.ListInstanceCache.TryGetValue(this, out var attribute))
|
||||
attribute.SetValue(index, value!);
|
||||
{
|
||||
if (attribute is ITypedOptionList<T> typedAttr)
|
||||
typedAttr.SetValueUnboxed(index, value);
|
||||
else
|
||||
attribute.SetValue(index, value!);
|
||||
|
||||
items[index] = attribute is ITypedOptionList<T> typedAttr2
|
||||
? typedAttr2.GetValueUnboxed(index)
|
||||
: (T)attribute.GetValue(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
version++;
|
||||
}
|
||||
@@ -178,31 +198,31 @@ public sealed class ModdedArray<T> : IList<T>, IReadOnlyList<T>, IList
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Insert(int index, T item)
|
||||
void IList<T>.Insert(int index, T item)
|
||||
{
|
||||
throw new NotSupportedException("ModdedArray is of a fixed size.");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveAt(int index)
|
||||
void IList<T>.RemoveAt(int index)
|
||||
{
|
||||
throw new NotSupportedException("ModdedArray is of a fixed size.");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Add(T item)
|
||||
void ICollection<T>.Add(T item)
|
||||
{
|
||||
throw new NotSupportedException("ModdedArray is of a fixed size.");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Clear()
|
||||
void ICollection<T>.Clear()
|
||||
{
|
||||
throw new NotSupportedException("ModdedArray is of a fixed size.");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Remove(T item)
|
||||
bool ICollection<T>.Remove(T item)
|
||||
{
|
||||
throw new NotSupportedException("ModdedArray is of a fixed size.");
|
||||
}
|
||||
|
||||
@@ -30,8 +30,14 @@ public static class ModdedOptionsManager
|
||||
private static readonly Dictionary<Type, AbstractOptionGroup> TypeToGroup = [];
|
||||
private static readonly HashSet<PropertyInfo> OptionAttributes = [];
|
||||
|
||||
private static readonly HarmonyMethod CachedGetterPatch = new(typeof(ModdedOptionsManager), nameof(PropertyGetterPatch));
|
||||
private static readonly HarmonyMethod CachedSetterPatch = new(typeof(ModdedOptionsManager), nameof(PropertySetterPatch));
|
||||
private static readonly MethodInfo OpenGetterPatch = typeof(ModdedOptionsManager)
|
||||
.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||
.First(m => m.Name == nameof(PropertyGetterPatch) && m.IsGenericMethodDefinition);
|
||||
private static readonly MethodInfo OpenSetterPatch = typeof(ModdedOptionsManager)
|
||||
.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||
.First(m => m.Name == nameof(PropertySetterPatch) && m.IsGenericMethodDefinition);
|
||||
private static readonly Dictionary<Type, HarmonyMethod> ConcretisedGetters = [];
|
||||
private static readonly Dictionary<Type, HarmonyMethod> ConcretisedSetters = [];
|
||||
|
||||
internal static readonly ConditionalWeakTable<object, ModdedOptionListAttribute> ListInstanceCache = [];
|
||||
internal static readonly Dictionary<OptionBehaviour, ModdedPlayerOption> CreatedPlayerOptions = [];
|
||||
@@ -170,20 +176,34 @@ public static class ModdedOptionsManager
|
||||
return;
|
||||
}
|
||||
|
||||
var propType = property.PropertyType;
|
||||
|
||||
if (!ConcretisedGetters.TryGetValue(propType, out var concreteGetter))
|
||||
{
|
||||
concreteGetter = new(OpenGetterPatch.MakeGenericMethod(propType));
|
||||
ConcretisedGetters[propType] = concreteGetter;
|
||||
}
|
||||
|
||||
var getterOriginal = property.GetGetMethod(true)!;
|
||||
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(getterOriginal, prefix: CachedGetterPatch);
|
||||
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(getterOriginal, prefix: concreteGetter);
|
||||
GetterLookups[getterOriginal] = attribute;
|
||||
|
||||
var setterOriginal = property.GetSetMethod(true);
|
||||
|
||||
if (setterOriginal != null)
|
||||
{
|
||||
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(setterOriginal, postfix: CachedSetterPatch);
|
||||
if (!ConcretisedSetters.TryGetValue(propType, out var concreteSetter))
|
||||
{
|
||||
concreteSetter = new(OpenSetterPatch.MakeGenericMethod(propType));
|
||||
ConcretisedSetters[propType] = concreteSetter;
|
||||
}
|
||||
|
||||
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(setterOriginal, postfix: concreteSetter);
|
||||
SetterLookups[setterOriginal] = attribute;
|
||||
}
|
||||
|
||||
attribute.HolderOption = option;
|
||||
attribute.ValueType = property.PropertyType;
|
||||
attribute.ValueType = propType;
|
||||
|
||||
var customAttributes = property.GetCustomAttributes(false);
|
||||
|
||||
@@ -281,15 +301,27 @@ public static class ModdedOptionsManager
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ConcretisedGetters.TryGetValue(listType, out var concreteGetter))
|
||||
{
|
||||
concreteGetter = new(OpenGetterPatch.MakeGenericMethod(listType));
|
||||
ConcretisedGetters[listType] = concreteGetter;
|
||||
}
|
||||
|
||||
var getterOriginal = property.GetGetMethod(true)!;
|
||||
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(getterOriginal, prefix: CachedGetterPatch);
|
||||
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(getterOriginal, prefix: concreteGetter);
|
||||
GetterLookups[getterOriginal] = attribute;
|
||||
|
||||
var setterOriginal = property.GetSetMethod(true);
|
||||
|
||||
if (setterOriginal != null)
|
||||
{
|
||||
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(setterOriginal, postfix: CachedSetterPatch);
|
||||
if (!ConcretisedSetters.TryGetValue(listType, out var concreteSetter))
|
||||
{
|
||||
concreteSetter = new(OpenSetterPatch.MakeGenericMethod(listType));
|
||||
ConcretisedSetters[listType] = concreteSetter;
|
||||
}
|
||||
|
||||
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(setterOriginal, postfix: concreteSetter);
|
||||
SetterLookups[setterOriginal] = attribute;
|
||||
}
|
||||
|
||||
@@ -327,7 +359,7 @@ public static class ModdedOptionsManager
|
||||
var option = optionList[i];
|
||||
|
||||
if (visibilityFactory != null)
|
||||
option.Visible = (Func<bool>)(visibilityFactory(i) ?? DelegateUtilities.AlwaysTrue);
|
||||
option.Visible = visibilityFactory(i) as Func<bool> ?? DelegateUtilities.AlwaysTrue;
|
||||
|
||||
if (getterFactory != null)
|
||||
option.Getter = getterFactory(i);
|
||||
@@ -365,11 +397,11 @@ public static class ModdedOptionsManager
|
||||
|
||||
while (chunks.Count > 0)
|
||||
{
|
||||
Rpc<SyncOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, chunks.Dequeue());
|
||||
Rpc<SyncOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, new MiraOptionNetData(false, chunks.Dequeue()));
|
||||
}
|
||||
}
|
||||
|
||||
internal static void HandleSyncOptions(NetData[] data)
|
||||
internal static void HandleSyncOptions(bool showNotifs, NetData[] data)
|
||||
{
|
||||
// necessary to disable then re-enable this setting
|
||||
// we don't know how other plugins handle their configs
|
||||
@@ -388,6 +420,7 @@ public static class ModdedOptionsManager
|
||||
continue;
|
||||
}
|
||||
|
||||
option.ShowNotification = showNotifs;
|
||||
option.HandleNetData(netData.Data);
|
||||
}
|
||||
|
||||
@@ -397,7 +430,7 @@ public static class ModdedOptionsManager
|
||||
plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin];
|
||||
}
|
||||
|
||||
if (LobbyInfoPane.Instance)
|
||||
if (LobbyInfoPane.InstanceExists)
|
||||
{
|
||||
LobbyInfoPane.Instance.RefreshPane();
|
||||
}
|
||||
@@ -410,6 +443,7 @@ public static class ModdedOptionsManager
|
||||
/// <param name="value">The new object value.</param>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony naming convention.")]
|
||||
[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Read above.")]
|
||||
[Obsolete("Use PropertySetterPatch<T> instead. This will be removed in a future version.")]
|
||||
public static void PropertySetterPatch(MethodBase __originalMethod, object value)
|
||||
{
|
||||
if (!SetterLookups.TryGetValue(__originalMethod, out var attribute))
|
||||
@@ -421,6 +455,28 @@ public static class ModdedOptionsManager
|
||||
ListInstanceCache.AddOrUpdate(value, listAttribute);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patches the setter of a property to update the value of the option.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the option value.</typeparam>
|
||||
/// <param name="__originalMethod">The original setter method.</param>
|
||||
/// <param name="value">The new object value.</param>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony naming convention.")]
|
||||
[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Read above.")]
|
||||
public static void PropertySetterPatch<T>(MethodBase __originalMethod, T value)
|
||||
{
|
||||
if (!SetterLookups.TryGetValue(__originalMethod, out var attribute))
|
||||
return;
|
||||
|
||||
if (attribute is ITypedOption<T> typedAttribute)
|
||||
typedAttribute.SetValueUnboxed(value);
|
||||
else
|
||||
attribute.SetValue(value!);
|
||||
|
||||
if (attribute is ModdedOptionListAttribute listAttribute && value is not null)
|
||||
ListInstanceCache.AddOrUpdate(value, listAttribute);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patches the getter of a property to return the value of the option.
|
||||
/// </summary>
|
||||
@@ -429,6 +485,7 @@ public static class ModdedOptionsManager
|
||||
/// <returns><see langword="false"/> so the original getter gets skipped.</returns>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony naming convention")]
|
||||
[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Read above.")]
|
||||
[Obsolete("Use PropertyGetterPatch<T> instead. This will be removed in a future version.")]
|
||||
public static bool PropertyGetterPatch(MethodBase __originalMethod, ref object __result)
|
||||
{
|
||||
if (!GetterLookups.TryGetValue(__originalMethod, out var attribute))
|
||||
@@ -437,4 +494,26 @@ public static class ModdedOptionsManager
|
||||
__result = attribute.GetValue();
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patches the getter of a property to return the value of the option.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the option value.</typeparam>
|
||||
/// <param name="__originalMethod">The original getter method.</param>
|
||||
/// <param name="__result">The result of the property getter.</param>
|
||||
/// <returns><see langword="false"/> so the original getter gets skipped.</returns>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony naming convention")]
|
||||
[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Read above.")]
|
||||
public static bool PropertyGetterPatch<T>(MethodBase __originalMethod, ref T __result)
|
||||
{
|
||||
if (!GetterLookups.TryGetValue(__originalMethod, out var attribute))
|
||||
return true;
|
||||
|
||||
if (attribute is ITypedOption<T> typedAttribute)
|
||||
__result = typedAttribute.GetValueUnboxed();
|
||||
else
|
||||
__result = (T)attribute.GetValue();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ namespace MiraAPI.GameOptions.OptionTypes;
|
||||
/// <summary>
|
||||
/// An option for selecting an <see langword="enum"/> value.
|
||||
/// </summary>
|
||||
[Obsolete("Use ModdedEnumOption<T> instead. This will be removed in a future version.")]
|
||||
public class ModdedEnumOption : ModdedOption<int>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -93,19 +94,7 @@ public class ModdedEnumOption : ModdedOption<int>
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged(int newValue)
|
||||
{
|
||||
ModdedOptionsManager.AddSettingsChangeMessage(
|
||||
HudManager.Instance.Notifier,
|
||||
StringName,
|
||||
Data.GetValueString(newValue),
|
||||
Configuration.PopUpTextColor,
|
||||
Configuration.PopUpIconTmp,
|
||||
false);
|
||||
if (!OptionBehaviour)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (OptionBehaviour is StringOption opt)
|
||||
if (OptionBehaviour && OptionBehaviour is StringOption opt)
|
||||
{
|
||||
opt.Value = newValue;
|
||||
}
|
||||
@@ -196,19 +185,7 @@ public class ModdedEnumOption<T> : ModdedOption<T>
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged(T newValue)
|
||||
{
|
||||
ModdedOptionsManager.AddSettingsChangeMessage(
|
||||
HudManager.Instance.Notifier,
|
||||
StringName,
|
||||
Data.GetValueString(Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo)),
|
||||
Configuration.PopUpTextColor,
|
||||
Configuration.PopUpIconTmp,
|
||||
false);
|
||||
if (!OptionBehaviour)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (OptionBehaviour is StringOption opt)
|
||||
if (OptionBehaviour && OptionBehaviour is StringOption opt)
|
||||
{
|
||||
opt.Value = Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using AmongUs.GameOptions;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.Utilities;
|
||||
@@ -52,6 +53,8 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
/// </summary>
|
||||
public bool ShiftIncrement { get; }
|
||||
|
||||
private static readonly Regex TrailingZeroRegex = new(@"\.0+(?!\d)", RegexOptions.Compiled, TimeSpan.FromMilliseconds(100));
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedNumberOption"/> class.
|
||||
/// </summary>
|
||||
@@ -155,7 +158,7 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
optionComponent.ZeroValue = ZeroWordValue;
|
||||
|
||||
numberOption.SetUpFromData(Data, 20);
|
||||
numberOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
||||
numberOption.OnValueChanged = (Action<OptionBehaviour>)ValueChanged;
|
||||
|
||||
numberOption.Title = StringName;
|
||||
numberOption.Value = Value;
|
||||
@@ -171,6 +174,22 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
return numberOption;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetDisplayValue()
|
||||
{
|
||||
if (this is { NegativeWordValue: not "#", Value: -1 })
|
||||
{
|
||||
return NegativeWordValue;
|
||||
}
|
||||
|
||||
if (this is { ZeroWordValue: not "#", Value: 0 })
|
||||
{
|
||||
return ZeroWordValue;
|
||||
}
|
||||
|
||||
return TrailingZeroRegex.Replace(Data.GetValueString(Value), string.Empty);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float GetFloatData()
|
||||
{
|
||||
@@ -198,21 +217,18 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged(float newValue)
|
||||
{
|
||||
Value = Mathf.Clamp(newValue, Min, Max);
|
||||
ModdedOptionsManager.AddSettingsChangeMessage(
|
||||
HudManager.Instance.Notifier,
|
||||
StringName,
|
||||
Data.GetValueString(newValue),
|
||||
Configuration.PopUpTextColor,
|
||||
Configuration.PopUpIconTmp,
|
||||
false);
|
||||
|
||||
if (OptionBehaviour is NumberOption opt)
|
||||
if (OptionBehaviour && OptionBehaviour is NumberOption opt)
|
||||
{
|
||||
opt.Value = Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override float ModifyValue(float newValue, float oldValue)
|
||||
{
|
||||
return Math.Clamp(newValue, Min, Max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts the option to an <see langword="int"/>.
|
||||
/// </summary>
|
||||
@@ -220,6 +236,6 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
/// <returns>Integer value.</returns>
|
||||
public static implicit operator int(ModdedNumberOption option)
|
||||
{
|
||||
return (int)option.Value;
|
||||
return (int)Math.Round(option.Value);
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,8 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
get => Getter != null ? Getter(_value) : _value;
|
||||
protected set
|
||||
{
|
||||
value = ModifyValue(value, _value);
|
||||
|
||||
if (Setter != null)
|
||||
value = Setter(value, _value);
|
||||
|
||||
@@ -79,6 +81,9 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
/// <inheritdoc />
|
||||
public bool IncludeInPreset { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ShowNotification { get; set; } = true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public OptionBehaviour? OptionBehaviour { get; protected set; }
|
||||
|
||||
@@ -156,10 +161,22 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
entry.Value = _value;
|
||||
}
|
||||
|
||||
Rpc<SyncOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [GetNetData()], true);
|
||||
Rpc<SyncOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, new MiraOptionNetData(true, [GetNetData()]), true);
|
||||
}
|
||||
|
||||
if (ShowNotification)
|
||||
{
|
||||
ModdedOptionsManager.AddSettingsChangeMessage(
|
||||
HudManager.Instance.Notifier,
|
||||
StringName,
|
||||
GetDisplayValue(),
|
||||
Configuration.PopUpTextColor,
|
||||
Configuration.PopUpIconTmp,
|
||||
false);
|
||||
}
|
||||
|
||||
OnValueChanged(_value);
|
||||
ShowNotification = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -209,12 +226,29 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
/// <inheritdoc />
|
||||
public abstract void HandleNetData(byte[] data);
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string GetDisplayValue()
|
||||
{
|
||||
return Data.GetValueString(GetFloatData());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the value changed event.
|
||||
/// </summary>
|
||||
/// <param name="newValue">The new value.</param>
|
||||
protected abstract void OnValueChanged(T newValue);
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the incoming value with respect to the previous value. This is invoked before the <see cref="ModdedOption{T}.Setter"/> (if it's set).
|
||||
/// </summary>
|
||||
/// <param name="newValue">The new value.</param>
|
||||
/// <param name="oldValue">The old value.</param>
|
||||
/// <returns>The modified value.</returns>
|
||||
protected virtual T ModifyValue(T newValue, T oldValue)
|
||||
{
|
||||
return newValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value from the option behaviour.
|
||||
/// </summary>
|
||||
|
||||
@@ -138,19 +138,7 @@ public class ModdedPlayerOption : ModdedOption<int>
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged(int newValue)
|
||||
{
|
||||
ModdedOptionsManager.AddSettingsChangeMessage(
|
||||
HudManager.Instance.Notifier,
|
||||
StringName,
|
||||
Data.GetValueString(newValue),
|
||||
Configuration.PopUpTextColor,
|
||||
Configuration.PopUpIconTmp,
|
||||
false);
|
||||
if (!OptionBehaviour)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (OptionBehaviour is PlayerOption opt)
|
||||
if (OptionBehaviour && OptionBehaviour is PlayerOption opt)
|
||||
{
|
||||
opt.Value = newValue;
|
||||
}
|
||||
|
||||
@@ -168,13 +168,7 @@ public class ModdedStringOption : ModdedOption<string>
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged(string newValue)
|
||||
{
|
||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(GetIndex(newValue)), false);
|
||||
if (!OptionBehaviour)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (OptionBehaviour is StringOption opt)
|
||||
if (OptionBehaviour && OptionBehaviour is StringOption opt)
|
||||
{
|
||||
opt.Value = GetIndex(newValue);
|
||||
}
|
||||
|
||||
@@ -72,6 +72,18 @@ public class ModdedToggleOption : ModdedOption<bool>
|
||||
return toggleOption;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetDisplayValue()
|
||||
{
|
||||
return Value
|
||||
? (OnValue == "On"
|
||||
? TranslationController.Instance.GetString(StringNames.SettingsOn)
|
||||
: OnValue.Translate())
|
||||
: (OffValue == "Off"
|
||||
? TranslationController.Instance.GetString(StringNames.SettingsOff)
|
||||
: OffValue.Translate());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float GetFloatData()
|
||||
{
|
||||
@@ -99,19 +111,7 @@ public class ModdedToggleOption : ModdedOption<bool>
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged(bool newValue)
|
||||
{
|
||||
ModdedOptionsManager.AddSettingsChangeMessage(
|
||||
HudManager.Instance.Notifier,
|
||||
StringName,
|
||||
(newValue ? OnValue : OffValue).Translate(),
|
||||
Configuration.PopUpTextColor,
|
||||
Configuration.PopUpIconTmp,
|
||||
false);
|
||||
if (!OptionBehaviour)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (OptionBehaviour is ToggleOption toggleOpt)
|
||||
if (OptionBehaviour && OptionBehaviour is ToggleOption toggleOpt)
|
||||
{
|
||||
toggleOpt.CheckMark.enabled = newValue;
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public partial class MiraApiPlugin : BasePlugin, IMiraPlugin
|
||||
PluginManager = new MiraPluginManager();
|
||||
PluginManager.Initialize(this, this);
|
||||
|
||||
MiraLocaleManager.Register("mira.api");
|
||||
MiraLocaleManager.Register("mira.api", typeof(MiraApiPlugin).Assembly);
|
||||
LocalizationManager.Register(new MiraLocalizationProvider());
|
||||
|
||||
IL2CPPChainloader.Instance.Finished +=
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.PluginLoading;
|
||||
@@ -96,6 +97,48 @@ public abstract class BaseModifier : IOptionable
|
||||
/// </summary>
|
||||
public virtual string ModifierCategoryTitle => MiraLocaleManager.Get("Modifier");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether to exclude the "amount" from the displayed amount text within the role guide.
|
||||
/// </summary>
|
||||
public virtual bool ShowModifierAsUnique => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the displayed amount and chance of the modifier. Used in the role guide.
|
||||
/// By default, 0 amount or chance results in a complete 0 amount and chance, so override if necessary.
|
||||
/// </summary>
|
||||
/// <param name="amount">The amount of the modifier.</param>
|
||||
/// <param name="chance">The chance of the modifier.</param>
|
||||
/// <param name="source">The mod source of the modifier.</param>
|
||||
/// <returns>The <see cref="string"/> result.</returns>
|
||||
public virtual string GetAmountText(int amount, int chance, string source)
|
||||
{
|
||||
if (amount == 0)
|
||||
{
|
||||
chance = 0;
|
||||
}
|
||||
|
||||
if (ShowModifierAsUnique)
|
||||
{
|
||||
return string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}% ({1})",
|
||||
chance.ToString(CultureInfo.InvariantCulture),
|
||||
source);
|
||||
}
|
||||
|
||||
if (chance == 0)
|
||||
{
|
||||
amount = 0;
|
||||
}
|
||||
|
||||
return string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} at {1}% ({2})",
|
||||
amount.ToString(CultureInfo.InvariantCulture),
|
||||
chance.ToString(CultureInfo.InvariantCulture),
|
||||
source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the modifier is forcibly shown or disabled in the wiki screen.
|
||||
/// </summary>
|
||||
|
||||
@@ -77,7 +77,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Error($"Error while deactivating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
Error($"Error while deactivating modifier {modifier.ModifierName}: {e}");
|
||||
}
|
||||
|
||||
Modifiers.Remove(modifier);
|
||||
@@ -95,7 +95,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Error($"Error while activating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
Error($"Error while activating modifier {modifier.ModifierName}: {e}");
|
||||
}
|
||||
|
||||
if (modifier is TimedModifier { AutoStart: true } timer)
|
||||
@@ -122,7 +122,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Error($"Error while (fixed) updating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
Error($"Error while (fixed) updating modifier {modifier.ModifierName}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Error($"Error while updating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
Error($"Error while updating modifier {modifier.ModifierName}: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ public abstract class TimedModifier : BaseModifier
|
||||
/// <returns>A string with the time remaining.</returns>
|
||||
public override string GetDescription()
|
||||
{
|
||||
return $"Remaining Time: {Math.Round(Duration - TimeRemaining, 0)}s/{Duration}s";
|
||||
return $"Remaining Time: {MathF.Round(Duration - TimeRemaining, 0)}s/{Duration}s";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -571,7 +571,7 @@ public static class CustomMurderRpc
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Error($"Error with kill animation: {e.ToString()}");
|
||||
Error($"Error with kill animation: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -705,7 +705,7 @@ public static class CustomMurderRpc
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Error($"Error with kill animation: {e.ToString()}");
|
||||
Error($"Error with kill animation: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,29 +9,32 @@ namespace MiraAPI.Networking;
|
||||
[RegisterCustomRpc((uint)MiraRpc.SyncGameOptions)]
|
||||
#pragma warning disable SA1515 // Single-line comment should be preceded by blank line (Justification: ReSharper suppression.)
|
||||
// ReSharper disable once ClassNeverInstantiated.Global (Justification: Instantiated by Reactor.)
|
||||
internal sealed class SyncOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, NetData[]>(plugin, id)
|
||||
internal sealed class SyncOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, MiraOptionNetData>(plugin, id)
|
||||
#pragma warning restore SA1515 // Single-line comment should be preceded by blank line
|
||||
{
|
||||
public override RpcLocalHandling LocalHandling => RpcLocalHandling.None;
|
||||
|
||||
public override void Write(MessageWriter writer, NetData[]? data)
|
||||
public override void Write(MessageWriter writer, MiraOptionNetData data)
|
||||
{
|
||||
if (data == null)
|
||||
/*if (data == null)
|
||||
{
|
||||
writer.WritePacked(0U);
|
||||
return;
|
||||
}
|
||||
}*/
|
||||
|
||||
writer.WritePacked((uint)data.Length);
|
||||
foreach (var netData in data)
|
||||
var info = data.Data;
|
||||
writer.Write(data.ShouldShowNotifications);
|
||||
writer.WritePacked((uint)info.Length);
|
||||
foreach (var netData in info)
|
||||
{
|
||||
writer.WritePacked(netData.Id);
|
||||
writer.WriteBytesAndSize(netData.Data);
|
||||
}
|
||||
}
|
||||
|
||||
public override NetData[] Read(MessageReader reader)
|
||||
public override MiraOptionNetData Read(MessageReader reader)
|
||||
{
|
||||
var shouldShowNotif = reader.ReadBoolean();
|
||||
var length = reader.ReadPackedUInt32();
|
||||
var data = new NetData[length];
|
||||
for (var i = 0; i < length; i++)
|
||||
@@ -41,16 +44,16 @@ internal sealed class SyncOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCust
|
||||
data[i] = new NetData(dataId, bytes);
|
||||
}
|
||||
|
||||
return data;
|
||||
return new MiraOptionNetData(shouldShowNotif, data);
|
||||
}
|
||||
|
||||
public override void Handle(PlayerControl playerControl, NetData[]? data)
|
||||
public override void Handle(PlayerControl playerControl, MiraOptionNetData data)
|
||||
{
|
||||
if (AmongUsClient.Instance.HostId != playerControl.OwnerId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ModdedOptionsManager.HandleSyncOptions(data ?? []);
|
||||
ModdedOptionsManager.HandleSyncOptions(data.ShouldShowNotifications, data.Data ?? []);
|
||||
}
|
||||
}
|
||||
@@ -5,32 +5,41 @@ using Reactor.Networking.Rpc;
|
||||
|
||||
namespace MiraAPI.Networking;
|
||||
|
||||
internal readonly struct MiraOptionNetData(bool showNotif, NetData[] data)
|
||||
{
|
||||
public bool ShouldShowNotifications { get; } = showNotif;
|
||||
public NetData[] Data { get; } = data;
|
||||
}
|
||||
|
||||
[RegisterCustomRpc((uint)MiraRpc.SyncRoleOptions)]
|
||||
#pragma warning disable SA1515 // Single-line comment should be preceded by blank line (Justification: ReSharper suppression.)
|
||||
// ReSharper disable once ClassNeverInstantiated.Global (Justification: Instantiated by Reactor.)
|
||||
internal sealed class SyncRoleOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, NetData[]>(plugin, id)
|
||||
internal sealed class SyncRoleOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, MiraOptionNetData>(plugin, id)
|
||||
#pragma warning restore SA1515 // Single-line comment should be preceded by blank line
|
||||
{
|
||||
public override RpcLocalHandling LocalHandling => RpcLocalHandling.None;
|
||||
|
||||
public override void Write(MessageWriter writer, NetData[]? data)
|
||||
public override void Write(MessageWriter writer, MiraOptionNetData data)
|
||||
{
|
||||
if (data == null)
|
||||
/*if (data == null)
|
||||
{
|
||||
writer.WritePacked(0U);
|
||||
return;
|
||||
}
|
||||
}*/
|
||||
|
||||
writer.WritePacked((uint)data.Length);
|
||||
foreach (var netData in data)
|
||||
var info = data.Data;
|
||||
writer.Write(data.ShouldShowNotifications);
|
||||
writer.WritePacked((uint)info.Length);
|
||||
foreach (var netData in info)
|
||||
{
|
||||
writer.WritePacked(netData.Id);
|
||||
writer.WriteBytesAndSize(netData.Data);
|
||||
}
|
||||
}
|
||||
|
||||
public override NetData[] Read(MessageReader reader)
|
||||
public override MiraOptionNetData Read(MessageReader reader)
|
||||
{
|
||||
var shouldShowNotif = reader.ReadBoolean();
|
||||
var length = reader.ReadPackedUInt32();
|
||||
var data = new NetData[length];
|
||||
for (var i = 0; i < length; i++)
|
||||
@@ -40,16 +49,16 @@ internal sealed class SyncRoleOptionsRpc(MiraApiPlugin plugin, uint id) : Player
|
||||
data[i] = new NetData(dataId, bytes);
|
||||
}
|
||||
|
||||
return data;
|
||||
return new MiraOptionNetData(shouldShowNotif, data);
|
||||
}
|
||||
|
||||
public override void Handle(PlayerControl playerControl, NetData[]? data)
|
||||
public override void Handle(PlayerControl playerControl, MiraOptionNetData data)
|
||||
{
|
||||
if (AmongUsClient.Instance.HostId != playerControl.OwnerId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CustomRoleManager.HandleSyncRoleOptions(data ?? []);
|
||||
CustomRoleManager.HandleSyncRoleOptions(data.ShouldShowNotifications, data.Data ?? []);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.RegularExpressions;
|
||||
using AmongUs.Data;
|
||||
using HarmonyLib;
|
||||
@@ -42,7 +41,7 @@ internal static class PlayerTabPatch
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(ChatNotification), nameof(ChatNotification.Update))]
|
||||
internal static class ChatNotifRainbowPatch
|
||||
internal static class ChatNotifVariablePatch
|
||||
{
|
||||
private static readonly Regex RichTags = new(@"<[^>]*>", default, Regex.InfiniteMatchTimeout);
|
||||
|
||||
@@ -60,14 +59,13 @@ internal static class ChatNotifRainbowPatch
|
||||
var str = color.ToHtmlStringRGBA();
|
||||
var nameText = __instance.playerNameText;
|
||||
nameText.text = "<color=#" + str + ">" + RichTags.Replace(nameText.text, string.Empty);
|
||||
nameText.outlineColor = color.GetShadowColor(0.5f);
|
||||
nameText.outlineColor = color.DarkenAbsolute(0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(HostInfoPanel), nameof(HostInfoPanel.Update))]
|
||||
internal static class RainbowLobbyInfoPanePatch
|
||||
internal static class VariableLobbyInfoPanePatch
|
||||
{
|
||||
[SuppressMessage("Style", "IDE0045:Convert to conditional expression", Justification = "Operator becomes too large.")]
|
||||
public static void Postfix(HostInfoPanel __instance)
|
||||
{
|
||||
if (!__instance.gameObject.activeInHierarchy)
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Usables;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
@@ -27,7 +28,7 @@ public static class CanUsePatches
|
||||
|
||||
[HarmonyPriority(Priority.Last)]
|
||||
[HarmonyPrefix]
|
||||
public static bool CanUsePatch(Il2CppSystem.Object __instance, [HarmonyArgument(1)] out bool canUse, [HarmonyArgument(2)] out bool couldUse)
|
||||
public static bool CanUsePatch(MonoBehaviour __instance, [HarmonyArgument(1)] out bool canUse, [HarmonyArgument(2)] out bool couldUse)
|
||||
{
|
||||
canUse = couldUse = false;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Usables;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
@@ -25,7 +26,7 @@ public static class UsePatches
|
||||
|
||||
[HarmonyPriority(Priority.Last)]
|
||||
[HarmonyPrefix]
|
||||
public static bool UsePatch(Il2CppSystem.Object __instance)
|
||||
public static bool UsePatch(MonoBehaviour __instance)
|
||||
{
|
||||
var @event = new PlayerUseEvent(__instance.Cast<IUsable>());
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
|
||||
@@ -186,9 +186,11 @@ public static class TaskAdderPatches
|
||||
systemTypes = SystemTypes.UpperEngine;
|
||||
}
|
||||
|
||||
if (!folders.TryGetValue(systemTypes.ToString(), out var taskFolder3))
|
||||
var systemType = systemTypes.ToString();
|
||||
|
||||
if (!folders.TryGetValue(systemType, out var taskFolder3))
|
||||
{
|
||||
taskFolder3 = folders[systemTypes.ToString()] = Object.Instantiate(__instance.RootFolderPrefab, __instance.transform);
|
||||
taskFolder3 = folders[systemType] = Object.Instantiate(__instance.RootFolderPrefab, __instance.transform);
|
||||
taskFolder3.SetFolderColor(TaskFolder.FolderColor.Tan);
|
||||
taskFolder3.gameObject.SetActive(false);
|
||||
taskFolder3.FolderName = systemTypes == SystemTypes.UpperEngine
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.GameModes;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Roles;
|
||||
|
||||
namespace MiraAPI.Patches.GameModes;
|
||||
|
||||
@@ -55,4 +57,17 @@ internal static class GameStartManagerPatches
|
||||
__instance.MinPlayers = 1;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix, HarmonyPatch(nameof(GameStartManager.BeginGame))]
|
||||
public static void Prefix(GameStartManager __instance)
|
||||
{
|
||||
if (!AmongUsClient.Instance.AmHost)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ModdedOptionsManager.SyncAllOptions();
|
||||
CustomRoleManager.SyncAllRoleSettings();
|
||||
GameModeOption.RpcSyncGamemode(PlayerControl.LocalPlayer, GameModeOption.Value);
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ public static class HudManagerPatches
|
||||
|
||||
public static IEnumerator CoResizeUI()
|
||||
{
|
||||
while (!HudManager.Instance) yield return null;
|
||||
while (!HudManager.InstanceExists) yield return null;
|
||||
|
||||
yield return new WaitForSeconds(0.01f);
|
||||
ResizeUI(LocalSettingsTabSingleton<MiraApiSettings>.Instance.ButtonUIFactorSlider.Value);
|
||||
|
||||
@@ -464,7 +464,7 @@ public static class LobbyViewPanePatches
|
||||
|
||||
if (name is not (StringNames.CrewmateRolesHeader or StringNames.ImpostorRolesHeader))
|
||||
{
|
||||
var veryDarkColor = group.Color.DarkenColor(.35f);
|
||||
var veryDarkColor = group.Color.DarkenRelative(.35f);
|
||||
categoryHeaderRoleVariant.Title.color = veryDarkColor;
|
||||
categoryHeaderRoleVariant.Background.color = group.Color;
|
||||
}
|
||||
|
||||
@@ -495,7 +495,7 @@ public static class RoleSettingMenuPatches
|
||||
|
||||
if (AmongUsClient.Instance.AmHost)
|
||||
{
|
||||
Rpc<SyncRoleOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [role.GetNetData()], true);
|
||||
Rpc<SyncRoleOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, new MiraOptionNetData(true, [role.GetNetData()]), true);
|
||||
}
|
||||
|
||||
GameOptionsManager.Instance.GameHostOptions = GameOptionsManager.Instance.CurrentGameOptions;
|
||||
|
||||
@@ -16,6 +16,7 @@ using MiraAPI.Roles;
|
||||
using MiraAPI.Translation;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using MiraAPI.Utilities.Components;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using TMPro;
|
||||
@@ -754,7 +755,8 @@ public static class RoleGuidePatches
|
||||
modifier.ModifierCategoryTitle,
|
||||
modifier.ParentMod.MiraPlugin.GetAbbreviatedModName(),
|
||||
null,
|
||||
modifier));
|
||||
modifier,
|
||||
modifier.TypeId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -790,7 +792,8 @@ public static class RoleGuidePatches
|
||||
roleBehaviour.GetCategoryTitle(),
|
||||
modId,
|
||||
roleBehaviour,
|
||||
null));
|
||||
null,
|
||||
(uint)roleBehaviour.Role));
|
||||
}
|
||||
|
||||
_roleEntriesMode = activeModeType;
|
||||
@@ -928,7 +931,7 @@ public static class RoleGuidePatches
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DetailedPanel(string title, string category, string modId, RoleBehaviour? role, GameModifier? modifier)
|
||||
private sealed class DetailedPanel(string title, string category, string modId, RoleBehaviour? role, GameModifier? modifier, uint typeId)
|
||||
{
|
||||
public MatchInfoRolePanel? Panel { get; set; }
|
||||
public string Title { get; } = title;
|
||||
@@ -936,6 +939,7 @@ public static class RoleGuidePatches
|
||||
public string ModId { get; } = modId;
|
||||
public RoleBehaviour? Role { get; } = role;
|
||||
public GameModifier? Modifier { get; } = modifier;
|
||||
public uint TypeId { get; } = typeId;
|
||||
public int Amount { get; set; }
|
||||
public int Chance { get; set; }
|
||||
public float Likelihood { get; set; }
|
||||
@@ -1157,7 +1161,9 @@ public static class RoleGuidePatches
|
||||
entry.Visible = entry.CanShow && MatchesSearch(entry, searchText);
|
||||
if (entry.Panel)
|
||||
{
|
||||
SetPanelCount(entry.Panel!, entry.Amount, entry.Chance, entry.ModId);
|
||||
dynamic entryType = entry.Modifier != null ? entry.Modifier : entry.Role!;
|
||||
var text = GetPanelCountText(entryType, entry.Amount, entry.Chance, entry.ModId);
|
||||
SetPanelCount(entry.Panel!, text);
|
||||
if (!entry.Visible && entry.Panel!.gameObject.activeSelf)
|
||||
{
|
||||
entry.Panel.gameObject.SetActive(false);
|
||||
@@ -1182,16 +1188,58 @@ public static class RoleGuidePatches
|
||||
return shown;
|
||||
}
|
||||
|
||||
public static IEnumerable<GameModifier> GetActiveModifiers()
|
||||
{
|
||||
return PlayerControl.LocalPlayer.GetModifiers<GameModifier>();
|
||||
}
|
||||
|
||||
public static IEnumerable<RoleBehaviour> GetActiveRoles()
|
||||
{
|
||||
IEnumerable<RoleBehaviour> comparisons = [];
|
||||
|
||||
var curRole = PlayerControl.LocalPlayer.Data.Role;
|
||||
|
||||
comparisons = comparisons.AddItem(curRole);
|
||||
|
||||
if (PlayerControl.LocalPlayer && PlayerControl.LocalPlayer.Data && PlayerControl.LocalPlayer.Data.IsDead &&
|
||||
!comparisons.Any(x => x.Role == PlayerControl.LocalPlayer.Data.RoleWhenAlive.Value))
|
||||
{
|
||||
comparisons = comparisons.AddItem(RoleManager.Instance.GetRole(PlayerControl.LocalPlayer.Data.RoleWhenAlive.Value));
|
||||
}
|
||||
|
||||
return comparisons;
|
||||
}
|
||||
|
||||
public static List<uint> GetActiveIds()
|
||||
{
|
||||
var activeModifiers = GetActiveModifiers();
|
||||
List<uint> comparisons = activeModifiers.Select(x => x.TypeId).ToList();
|
||||
comparisons.AddRange(GetActiveRoles().Select(x => (uint)x.Role));
|
||||
|
||||
return comparisons;
|
||||
}
|
||||
|
||||
private static List<DetailedPanel> SortEntries(List<DetailedPanel> entries, string searchText)
|
||||
{
|
||||
var activeModifiers = PlayerControl.LocalPlayer.GetModifiers<GameModifier>().Select(x => x.TypeId);
|
||||
var roles = GetActiveRoles().Select(x => (uint)x.Role).ToList();
|
||||
List<uint> comparisons = activeModifiers.ToList();
|
||||
comparisons.AddRange(roles);
|
||||
|
||||
var comparer = new BasicGuideComparer(comparisons);
|
||||
|
||||
var sorted = entries.Where(p => p.Visible)
|
||||
.OrderByDescending(p => p.Title.Equals(searchText, StringComparison.OrdinalIgnoreCase))
|
||||
.ThenByDescending(p =>
|
||||
p.Title.Contains(searchText, StringComparison.OrdinalIgnoreCase) ||
|
||||
p.Category.Contains(searchText, StringComparison.OrdinalIgnoreCase));
|
||||
return [.. _sortMethod is SortingMethod.Alphabetical
|
||||
p.Category.Contains(searchText, StringComparison.OrdinalIgnoreCase))
|
||||
.ThenBy(x => x.Modifier != null ? x.Modifier.TypeId : (uint)x.Role!.Role, comparer);
|
||||
return
|
||||
[
|
||||
.. _sortMethod is SortingMethod.Alphabetical
|
||||
? sorted.ThenBy(p => p.GetSortKey(_sortGrouping))
|
||||
: sorted.ThenByDescending(p => p.GetSortKey(_sortGrouping))];
|
||||
: sorted.ThenByDescending(p => p.GetSortKey(_sortGrouping))
|
||||
];
|
||||
}
|
||||
|
||||
private static bool MatchesSearch(DetailedPanel entry, string searchText)
|
||||
@@ -1651,10 +1699,15 @@ public static class RoleGuidePatches
|
||||
|
||||
public static Sprite GetRandomRoleIcon()
|
||||
{
|
||||
var aliveRole = PlayerControl.LocalPlayer.Data?.Role;
|
||||
if (aliveRole != null && aliveRole.RoleIconSolid != null)
|
||||
var roles = GetActiveRoles().Shuffle().ToList();
|
||||
foreach (var role in roles)
|
||||
{
|
||||
return aliveRole.RoleIconSolid;
|
||||
if (role.RoleIconSolid == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return role.RoleIconSolid;
|
||||
}
|
||||
|
||||
return GetRandomFactionIcon();
|
||||
@@ -1793,21 +1846,22 @@ public static class RoleGuidePatches
|
||||
return false;
|
||||
}
|
||||
|
||||
__instance.roleCount.text = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} at {1}%",
|
||||
numPerGame.ToString(CultureInfo.InvariantCulture),
|
||||
chancePerGame);
|
||||
if (role is ICustomRole customRole)
|
||||
{
|
||||
__instance.roleName.text = customRole.RoleName;
|
||||
__instance.roleDescription.text =
|
||||
$"<size=60%>{role.GetCategoryTitle()}</size>\n" + customRole.RoleMedDescription;
|
||||
__instance.roleIcon.sprite = customRole.Configuration.Icon?.LoadAsset();
|
||||
__instance.roleCount.text += $" ({customRole.ParentMod.MiraPlugin.GetAbbreviatedModName()})";
|
||||
var text = GetCustomRolePanelCountText(customRole, numPerGame, chancePerGame, customRole.ParentMod.MiraPlugin.GetAbbreviatedModName());
|
||||
SetPanelCount(__instance, text);
|
||||
}
|
||||
else
|
||||
{
|
||||
__instance.roleCount.text = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} at {1}%",
|
||||
numPerGame.ToString(CultureInfo.InvariantCulture),
|
||||
chancePerGame);
|
||||
__instance.roleName.text = role.NiceName;
|
||||
__instance.roleDescription.text = $"<size=60%>{role.GetCategoryTitle()}</size>\n" + role.BlurbMed;
|
||||
__instance.roleIcon.sprite = role.RoleIconColor;
|
||||
@@ -1823,9 +1877,20 @@ public static class RoleGuidePatches
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void SetPanelCount(MatchInfoRolePanel panel, int amount, int chance, string modId)
|
||||
// NOTE: This is specifically meant to be a different method name as to not have the game check this method and the GetPanelCountText method made for roles overall.
|
||||
private static string GetCustomRolePanelCountText(ICustomRole custom, int amount, int chance, string modId)
|
||||
{
|
||||
panel.roleCount.text = string.Format(
|
||||
return custom.GetAmountText(amount, chance, modId);
|
||||
}
|
||||
|
||||
private static string GetPanelCountText(RoleBehaviour role, int amount, int chance, string modId)
|
||||
{
|
||||
if (role is ICustomRole custom)
|
||||
{
|
||||
return custom.GetAmountText(amount, chance, modId);
|
||||
}
|
||||
|
||||
return string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} at {1}% ({2})",
|
||||
amount.ToString(CultureInfo.InvariantCulture),
|
||||
@@ -1833,6 +1898,18 @@ public static class RoleGuidePatches
|
||||
modId);
|
||||
}
|
||||
|
||||
private static string GetPanelCountText(BaseModifier modifier, int amount, int chance, string modId)
|
||||
{
|
||||
return modifier.GetAmountText(amount, chance, modId);
|
||||
}
|
||||
|
||||
private static void SetPanelCount(MatchInfoRolePanel panel, string amountChanceText)
|
||||
{
|
||||
panel.roleCount.text = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
amountChanceText);
|
||||
}
|
||||
|
||||
public static void SetRolePanel(
|
||||
this MatchInfoRolePanel instance,
|
||||
RoleBehaviour role,
|
||||
@@ -1845,14 +1922,16 @@ public static class RoleGuidePatches
|
||||
instance.roleDescription.text =
|
||||
$"<size=60%>{role.GetCategoryTitle()}</size>\n" + customRole.RoleMedDescription;
|
||||
instance.roleIcon.sprite = customRole.Configuration.Icon?.LoadAsset();
|
||||
SetPanelCount(instance, numPerGame, chancePerGame, customRole.ParentMod.MiraPlugin.GetAbbreviatedModName());
|
||||
var text = GetCustomRolePanelCountText(customRole, numPerGame, chancePerGame, customRole.ParentMod.MiraPlugin.GetAbbreviatedModName());
|
||||
SetPanelCount(instance, text);
|
||||
}
|
||||
else
|
||||
{
|
||||
instance.roleName.text = role.NiceName;
|
||||
instance.roleDescription.text = $"<size=60%>{role.GetCategoryTitle()}</size>\n" + role.BlurbMed;
|
||||
instance.roleIcon.sprite = role.RoleIconColor;
|
||||
SetPanelCount(instance, numPerGame, chancePerGame, "AU");
|
||||
var text = GetPanelCountText(role, numPerGame, chancePerGame, "AU");
|
||||
SetPanelCount(instance, text);
|
||||
}
|
||||
|
||||
instance.roleIcon.SetSizeLimit(0.13f);
|
||||
@@ -1873,7 +1952,8 @@ public static class RoleGuidePatches
|
||||
instance.roleDescription.text =
|
||||
$"<size=60%>{modifier.ModifierCategoryTitle}</size>\n" + modifier.ModifierMedDescription;
|
||||
instance.roleIcon.sprite = modifier.ModifierIcon?.LoadAsset();
|
||||
SetPanelCount(instance, numPerGame, chancePerGame, modifier.ParentMod.MiraPlugin.GetAbbreviatedModName());
|
||||
var text = GetPanelCountText(modifier, numPerGame, chancePerGame, modifier.ParentMod.MiraPlugin.GetAbbreviatedModName());
|
||||
SetPanelCount(instance, text);
|
||||
|
||||
instance.roleIcon.SetSizeLimit(0.13f);
|
||||
instance.roleIcon.material.SetInt(PlayerMaterial.MaskLayer, 50);
|
||||
@@ -2006,7 +2086,7 @@ public static class RoleGuidePatches
|
||||
vanillaEntries = BuildVanillaSettingsEntries(guide);
|
||||
}
|
||||
|
||||
_settingsPages = GuideOptionPages.BuildPages(guide.GetBoolString);
|
||||
_settingsPages = GuideOptionPages.BuildPages();
|
||||
if (_settingsPage > _settingsPages.Count)
|
||||
{
|
||||
_settingsPage = 0;
|
||||
|
||||
@@ -25,6 +25,26 @@ public class MiraPluginInfo
|
||||
PluginId = info.Metadata.GUID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin's ID, as defined in the plugin's BepInEx metadata.
|
||||
/// </summary>
|
||||
public string PluginId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin's instance as an <see cref="IMiraPlugin"/>.
|
||||
/// </summary>
|
||||
public IMiraPlugin MiraPlugin { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin's BepInEx metadata.
|
||||
/// </summary>
|
||||
public PluginInfo PluginInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin's <see cref="ConfigFile"/>.
|
||||
/// </summary>
|
||||
public ConfigFile PluginConfig { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IReadOnlyCollection{T}"/> of this <see cref="IMiraPlugin"/>'s <see cref="BaseModifier"/>s. This is not safe for <see cref="BaseModifier"/>s with constructors.
|
||||
/// </summary>
|
||||
@@ -45,15 +65,6 @@ public class MiraPluginInfo
|
||||
/// </summary>
|
||||
public ReadOnlyDictionary<ushort, RoleBehaviour> Roles { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read only dictionary of Game Mode IDs and the AbstractGameMode object they are associated with.
|
||||
/// </summary>
|
||||
/// <returns>Read only dictionary of IDs and Game Modes.</returns>
|
||||
public ReadOnlyDictionary<uint, AbstractGameMode> GetModes()
|
||||
{
|
||||
return new ReadOnlyDictionary<uint, AbstractGameMode>(GameModes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IReadOnlyCollection{T}"/> of this <see cref="IMiraPlugin"/>'s <see cref="CustomActionButton"/>.
|
||||
/// </summary>
|
||||
@@ -74,6 +85,15 @@ public class MiraPluginInfo
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<OptionPreset> Presets { get; internal set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read only dictionary of Game Mode IDs and the AbstractGameMode object they are associated with.
|
||||
/// </summary>
|
||||
/// <returns>Read only dictionary of IDs and Game Modes.</returns>
|
||||
public ReadOnlyDictionary<uint, AbstractGameMode> GetModes()
|
||||
{
|
||||
return new ReadOnlyDictionary<uint, AbstractGameMode>(GameModes);
|
||||
}
|
||||
|
||||
internal void SavePublicCollections()
|
||||
{
|
||||
Presets = [.. InternalPresets];
|
||||
@@ -87,39 +107,13 @@ public class MiraPluginInfo
|
||||
}
|
||||
|
||||
internal List<OptionPreset> InternalPresets { get; } = [];
|
||||
|
||||
internal List<AbstractOptionGroup> InternalOptionGroups { get; } = [];
|
||||
|
||||
internal List<IModdedOption> InternalOptions { get; } = [];
|
||||
|
||||
internal List<BaseModifier> InternalModifiers { get; } = [];
|
||||
internal List<CustomActionButton> InternalButtons { get; } = [];
|
||||
internal List<AbstractOptionGroup> InternalOptionGroups { get; } = [];
|
||||
internal List<MeetingActionButton> InternalMeetingButtons { get; } = [];
|
||||
internal List<TargetedMeetingButton> InternalTargetedMeetingButtons { get; } = [];
|
||||
|
||||
internal Dictionary<uint, AbstractGameMode> GameModes { get; } = [];
|
||||
internal Dictionary<ushort, RoleBehaviour> InternalRoles { get; } = [];
|
||||
|
||||
internal List<CustomActionButton> InternalButtons { get; } = [];
|
||||
|
||||
internal List<TargetedMeetingButton> InternalTargetedMeetingButtons { get; } = [];
|
||||
|
||||
internal List<MeetingActionButton> InternalMeetingButtons { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin's ID, as defined in the plugin's BepInEx metadata.
|
||||
/// </summary>
|
||||
public string PluginId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin's instance as an <see cref="IMiraPlugin"/>.
|
||||
/// </summary>
|
||||
public IMiraPlugin MiraPlugin { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin's BepInEx metadata.
|
||||
/// </summary>
|
||||
public PluginInfo PluginInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin's <see cref="ConfigFile"/>.
|
||||
/// </summary>
|
||||
public ConfigFile PluginConfig { get; }
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using BepInEx;
|
||||
using BepInEx.Configuration;
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
using HarmonyLib;
|
||||
@@ -24,7 +25,6 @@ using MiraAPI.Roles;
|
||||
using MiraAPI.Translation;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Networking;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.PluginLoading;
|
||||
|
||||
@@ -49,100 +49,14 @@ public sealed class MiraPluginManager
|
||||
Instance = this;
|
||||
CustomGameModeManager.RegisterDefaultMode();
|
||||
CustomGameModeManager.GetAndSetGameMode();
|
||||
|
||||
if (IL2CPPChainloader.Instance.Plugins.TryGetValue(MiraApiPlugin.Id, out var newPlugin))
|
||||
{
|
||||
var assembly = Assembly.GetCallingAssembly();
|
||||
var info = new MiraPluginInfo(apiPlugin, newPlugin);
|
||||
var roles = new List<Type>();
|
||||
var info = RegisterPlugin(typeof(MiraPluginManager).Assembly, apiPlugin, newPlugin, miraApiPlugin, true);
|
||||
|
||||
var oldConfigSetting = info.PluginConfig.SaveOnConfigSet;
|
||||
info.PluginConfig.SaveOnConfigSet = false;
|
||||
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
if (type.IsAbstract || type.GetCustomAttribute<MiraIgnoreAttribute>() != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var method in AccessTools.GetDeclaredMethods(type))
|
||||
{
|
||||
var eventAttribute = method.GetCustomAttribute<RegisterEventAttribute>();
|
||||
if (eventAttribute == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!method.IsStatic)
|
||||
{
|
||||
Error($"Event method {method.Name} in {type.Name} must be static.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var parameters = method.GetParameters();
|
||||
if (parameters.Length != 1 || !parameters[0].ParameterType.IsSubclassOf(typeof(MiraEvent)))
|
||||
{
|
||||
Error($"Invalid event registration method {method.Name} in {type.Name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var paramType = parameters[0].ParameterType;
|
||||
MiraEventManager.RegisterEventHandler(paramType, method, eventAttribute.Priority);
|
||||
}
|
||||
|
||||
if (RegisterModifier(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterOptions(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterLocalTabs(type, miraApiPlugin))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterRole(type, info, out var role))
|
||||
{
|
||||
roles.Add(role);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterButton(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterGameOver(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterGameModeAttribute(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RegisterColorClasses(type);
|
||||
RegisterKeybinds(type, miraApiPlugin);
|
||||
}
|
||||
|
||||
info.PluginConfig.Save();
|
||||
info.PluginConfig.SaveOnConfigSet = oldConfigSetting;
|
||||
MiraApiConfig = info.PluginConfig;
|
||||
|
||||
info.InternalOptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority));
|
||||
QueuedRoleRegistrations.Add(info, roles);
|
||||
|
||||
_registeredPlugins.Add(assembly, info);
|
||||
MiraApiPluginInfo = info;
|
||||
|
||||
info.SavePublicCollections();
|
||||
PresetManager.CreateDefaultPreset(info);
|
||||
PresetManager.LoadPresets(info);
|
||||
Info($"Registering Mira API as mod.");
|
||||
}
|
||||
|
||||
@@ -153,110 +67,14 @@ public sealed class MiraPluginManager
|
||||
return;
|
||||
}
|
||||
|
||||
var info = new MiraPluginInfo(miraPlugin, pluginInfo);
|
||||
var roles = new List<Type>();
|
||||
|
||||
var oldConfigSetting = info.PluginConfig.SaveOnConfigSet;
|
||||
info.PluginConfig.SaveOnConfigSet = false;
|
||||
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
if (type.GetCustomAttribute<MiraIgnoreAttribute>() != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var method in AccessTools.GetDeclaredMethods(type))
|
||||
{
|
||||
var eventAttribute = method.GetCustomAttribute<RegisterEventAttribute>();
|
||||
if (eventAttribute == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!method.IsStatic)
|
||||
{
|
||||
Error($"Event method {method.Name} in {type.Name} must be static.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var parameters = method.GetParameters();
|
||||
if (parameters.Length != 1 || !parameters[0].ParameterType.IsSubclassOf(typeof(MiraEvent)))
|
||||
{
|
||||
Error($"Invalid event registration method {method.Name} in {type.Name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var paramType = parameters[0].ParameterType;
|
||||
MiraEventManager.RegisterEventHandler(paramType, method, eventAttribute.Priority);
|
||||
}
|
||||
|
||||
if (RegisterModifier(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterOptions(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterLocalTabs(type, plugin))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterRole(type, info, out var role))
|
||||
{
|
||||
roles.Add(role);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterButton(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterGameOver(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterMeetingAbility(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterTargetedMeetingAbility(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterGameModeAttribute(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RegisterColorClasses(type);
|
||||
RegisterKeybinds(type, plugin);
|
||||
}
|
||||
|
||||
info.PluginConfig.Save();
|
||||
info.PluginConfig.SaveOnConfigSet = oldConfigSetting;
|
||||
|
||||
info.InternalOptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority));
|
||||
QueuedRoleRegistrations.Add(info, roles);
|
||||
|
||||
_registeredPlugins.Add(assembly, info);
|
||||
|
||||
info.SavePublicCollections();
|
||||
PresetManager.CreateDefaultPreset(info);
|
||||
PresetManager.LoadPresets(info);
|
||||
RegisterPlugin(assembly, miraPlugin, pluginInfo, plugin, false);
|
||||
Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API.");
|
||||
};
|
||||
IL2CPPChainloader.Instance.Finished += PaletteManager.RegisterAllColors;
|
||||
|
||||
IL2CPPChainloader.Instance.Finished += () =>
|
||||
{
|
||||
PaletteManager.RegisterAllColors();
|
||||
|
||||
// Save all buttons into a read-only collection for easy access
|
||||
CustomButtonManager.Buttons = new ReadOnlyCollection<CustomActionButton>(CustomButtonManager.CustomButtons);
|
||||
CustomButtonManager.EventTypes = new ReadOnlyDictionary<Type, Type>(CustomButtonManager.ButtonEventTypes);
|
||||
@@ -269,9 +87,101 @@ public sealed class MiraPluginManager
|
||||
|
||||
ModifierManager.Modifiers = new ReadOnlyCollection<BaseModifier>(ModifierManager.InternalModifiers);
|
||||
};
|
||||
}
|
||||
|
||||
RegisterKeybinds(typeof(MiraGlobalKeybinds), PluginSingleton<MiraApiPlugin>.Instance);
|
||||
RegisterLocalTabs(typeof(MiraApiSettings), PluginSingleton<MiraApiPlugin>.Instance);
|
||||
private MiraPluginInfo RegisterPlugin(Assembly assembly, IMiraPlugin plugin, PluginInfo pluginInfo, BasePlugin basePlugin, bool isMira)
|
||||
{
|
||||
var info = new MiraPluginInfo(plugin, pluginInfo);
|
||||
var roles = new List<Type>();
|
||||
|
||||
var oldConfigSetting = info.PluginConfig.SaveOnConfigSet;
|
||||
info.PluginConfig.SaveOnConfigSet = false;
|
||||
|
||||
ProcessAssemblyTypes(assembly, info, basePlugin, roles, isMira);
|
||||
|
||||
info.PluginConfig.Save();
|
||||
info.PluginConfig.SaveOnConfigSet = oldConfigSetting;
|
||||
|
||||
info.InternalOptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority));
|
||||
QueuedRoleRegistrations.Add(info, roles);
|
||||
_registeredPlugins.Add(assembly, info);
|
||||
|
||||
info.SavePublicCollections();
|
||||
PresetManager.CreateDefaultPreset(info);
|
||||
PresetManager.LoadPresets(info);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private static void ProcessAssemblyTypes(Assembly assembly, MiraPluginInfo info, BasePlugin basePlugin, List<Type> roles, bool isMira)
|
||||
{
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
if ((isMira && type.IsAbstract) || type.GetCustomAttribute<MiraIgnoreAttribute>() != null)
|
||||
continue;
|
||||
|
||||
RegisterEvents(type);
|
||||
|
||||
if (RegisterModifier(type, info))
|
||||
continue;
|
||||
|
||||
if (RegisterOptions(type, info))
|
||||
continue;
|
||||
|
||||
if (RegisterLocalTabs(type, basePlugin))
|
||||
continue;
|
||||
|
||||
if (RegisterRole(type, info, out var role))
|
||||
{
|
||||
roles.Add(role);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterButton(type, info))
|
||||
continue;
|
||||
|
||||
if (RegisterGameOver(type))
|
||||
continue;
|
||||
|
||||
if (RegisterMeetingAbility(type, info))
|
||||
continue;
|
||||
|
||||
if (RegisterTargetedMeetingAbility(type, info))
|
||||
continue;
|
||||
|
||||
if (RegisterGameModeAttribute(type, info))
|
||||
continue;
|
||||
|
||||
RegisterColorClasses(type);
|
||||
RegisterKeybinds(type, basePlugin);
|
||||
}
|
||||
}
|
||||
|
||||
private static void RegisterEvents(Type type)
|
||||
{
|
||||
foreach (var method in AccessTools.GetDeclaredMethods(type))
|
||||
{
|
||||
var eventAttribute = method.GetCustomAttribute<RegisterEventAttribute>();
|
||||
|
||||
if (eventAttribute == null)
|
||||
continue;
|
||||
|
||||
if (!method.IsStatic)
|
||||
{
|
||||
Error($"Event method {method.Name} in {type.Name} must be static.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var parameters = method.GetParameters();
|
||||
if (parameters.Length != 1 || !parameters[0].ParameterType.IsSubclassOf(typeof(MiraEvent)))
|
||||
{
|
||||
Error($"Invalid event registration method {method.Name} in {type.Name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var paramType = parameters[0].ParameterType;
|
||||
MiraEventManager.RegisterEventHandler(paramType, method, eventAttribute.Priority);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -358,7 +268,7 @@ public sealed class MiraPluginManager
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Error($"Failed to register options for {type.Name}: {e.ToString()}");
|
||||
Error($"Failed to register options for {type.Name}: {e}");
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -543,7 +453,7 @@ public sealed class MiraPluginManager
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Error($"Failed to register options for {type.Name}: {e.ToString()}");
|
||||
Error($"Failed to register options for {type.Name}: {e}");
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -71,8 +71,6 @@ Outlive the timer or eliminate the crew.</string>
|
||||
<string name="MiraApi.Wiki.PreviousPage">Previous</string>
|
||||
<string name="MiraApi.Wiki.NextPage">Next</string>
|
||||
<string name="MiraApi.Wiki.NoResultsFound">No Results Found</string>
|
||||
<string name="On">On</string>
|
||||
<string name="Off">Off</string>
|
||||
<string name="Enabled">Enabled</string>
|
||||
<string name="Disabled">Disabled</string>
|
||||
<string name="Active">Active</string>
|
||||
|
||||
@@ -280,11 +280,11 @@ public static class CustomRoleManager
|
||||
|
||||
while (data.Count > 0)
|
||||
{
|
||||
Rpc<SyncRoleOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.Dequeue());
|
||||
Rpc<SyncRoleOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, new MiraOptionNetData(false, data.Dequeue()));
|
||||
}
|
||||
}
|
||||
|
||||
internal static void HandleSyncRoleOptions(NetData[] data)
|
||||
internal static void HandleSyncRoleOptions(bool showNotifs, NetData[] data)
|
||||
{
|
||||
// necessary to disable then re-enable this setting
|
||||
// we don't know how other plugins handle their configs
|
||||
@@ -312,7 +312,10 @@ public static class CustomRoleManager
|
||||
var num = BitConverter.ToInt32(netData.Data, 0);
|
||||
var chance = BitConverter.ToInt32(netData.Data, 4);
|
||||
|
||||
HudManager.Instance.Notifier.AddRoleSettingsChangeMessage(role.StringName, num, chance, role.TeamType, false);
|
||||
if (showNotifs)
|
||||
{
|
||||
HudManager.Instance.Notifier.AddRoleSettingsChangeMessage(role.StringName, num, chance, role.TeamType, false);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using BepInEx.Configuration;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
@@ -92,6 +93,48 @@ public interface ICustomRole : IOptionable
|
||||
_ => MiraLocaleManager.Get("MiraApi.RoleTeam.Other"),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether to exclude the "amount" from the displayed amount text within the role guide.
|
||||
/// </summary>
|
||||
public virtual bool ShowRoleAsUnique => Configuration.MaxRoleCount == 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the displayed amount and chance of the role. Used in the role guide.
|
||||
/// By default, 0 amount or chance results in a complete 0 amount and chance, so override if necessary.
|
||||
/// </summary>
|
||||
/// <param name="amount">The amount of the role.</param>
|
||||
/// <param name="chance">The chance of the role.</param>
|
||||
/// <param name="source">The mod source of the role.</param>
|
||||
/// <returns>The <see cref="string"/> result.</returns>
|
||||
public virtual string GetAmountText(int amount, int chance, string source)
|
||||
{
|
||||
if (amount == 0)
|
||||
{
|
||||
chance = 0;
|
||||
}
|
||||
|
||||
if (ShowRoleAsUnique)
|
||||
{
|
||||
return string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}% ({1})",
|
||||
chance.ToString(CultureInfo.InvariantCulture),
|
||||
source);
|
||||
}
|
||||
|
||||
if (chance == 0)
|
||||
{
|
||||
amount = 0;
|
||||
}
|
||||
|
||||
return string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} at {1}% ({2})",
|
||||
amount.ToString(CultureInfo.InvariantCulture),
|
||||
chance.ToString(CultureInfo.InvariantCulture),
|
||||
source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Color"/> of the role.
|
||||
/// </summary>
|
||||
|
||||
@@ -72,6 +72,7 @@ public static class MiraLocaleManager
|
||||
/// </summary>
|
||||
public static Dictionary<MiraLanguage, string> LangCultureList { get; } = new()
|
||||
{
|
||||
// Vanilla
|
||||
{ MiraLanguage.English, "en-US" },
|
||||
{ MiraLanguage.Latam, "es-419" },
|
||||
{ MiraLanguage.Brazilian, "pt-BR" },
|
||||
@@ -88,15 +89,17 @@ public static class MiraLocaleManager
|
||||
{ MiraLanguage.SChinese, "zh-CN" },
|
||||
{ MiraLanguage.TChinese, "zh-TW" },
|
||||
{ MiraLanguage.Irish, "ga-IE" },
|
||||
{ MiraLanguage.Polish, "pl-PL" }, // Custom
|
||||
{ MiraLanguage.Turkish, "tr-TR" }, // Custom
|
||||
{ MiraLanguage.Swedish, "sv-SE" }, // Custom
|
||||
{ MiraLanguage.Lithuanian, "lt-LT" }, // Custom
|
||||
{ MiraLanguage.Czech, "cs-CZ" }, // Custom
|
||||
{ MiraLanguage.LChinese, "lzh-CN" }, // Custom
|
||||
{ MiraLanguage.Greek, "el-GR" }, // Custom
|
||||
{ MiraLanguage.Arabic, "arb" }, // Custom
|
||||
{ MiraLanguage.Hebrew, "heb" }, // Custom
|
||||
|
||||
// Custom
|
||||
{ MiraLanguage.Polish, "pl-PL" },
|
||||
{ MiraLanguage.Turkish, "tr-TR" },
|
||||
{ MiraLanguage.Swedish, "sv-SE" },
|
||||
{ MiraLanguage.Lithuanian, "lt-LT" },
|
||||
{ MiraLanguage.Czech, "cs-CZ" },
|
||||
{ MiraLanguage.LChinese, "lzh-CN" },
|
||||
{ MiraLanguage.Greek, "el-GR" },
|
||||
{ MiraLanguage.Arabic, "arb" },
|
||||
{ MiraLanguage.Hebrew, "heb" },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -123,26 +126,49 @@ public static class MiraLocaleManager
|
||||
/// Call once per mod during plugin Load().
|
||||
/// </summary>
|
||||
/// <param name="modGuid">The mod GUID (e.g., "mira.example").</param>
|
||||
[Obsolete("Pass typeof(YourPlugin).Assembly directly to Register for better performance. This will be removed in a future version.")]
|
||||
public static void Register(string modGuid)
|
||||
{
|
||||
var callingAssembly = Assembly.GetCallingAssembly();
|
||||
|
||||
LoadInternalStrings(callingAssembly, callingAssembly.GetName().Name!, modGuid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers translations for a mod.
|
||||
/// Call once per mod during plugin Load().
|
||||
/// </summary>
|
||||
/// <param name="modGuid">The mod GUID (e.g., "mira.example").</param>
|
||||
/// <param name="assembly">The <see cref="Assembly"/> to load the strings from.</param>
|
||||
public static void Register(string modGuid, Assembly assembly)
|
||||
{
|
||||
LoadInternalStrings(assembly, assembly.GetName().Name!, modGuid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers translations for a mod.
|
||||
/// Call once per mod during plugin Load().
|
||||
/// </summary>
|
||||
/// <param name="modGuid">The mod GUID (e.g., "mira.example").</param>
|
||||
/// <param name="internalName">The mod's root namespace (e.g., "mira.api").</param>
|
||||
[Obsolete("Pass typeof(YourPlugin).Assembly directly to Register for better performance. This will be removed in a future version.")]
|
||||
public static void Register(string modGuid, string internalName)
|
||||
{
|
||||
var callingAssembly = Assembly.GetCallingAssembly();
|
||||
|
||||
LoadInternalStrings(callingAssembly, internalName, modGuid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers translations for a mod.
|
||||
/// Call once per mod during plugin Load().
|
||||
/// </summary>
|
||||
/// <param name="modGuid">The mod GUID (e.g., "mira.example").</param>
|
||||
/// <param name="internalName">The mod's root namespace (e.g., "mira.api").</param>
|
||||
/// <param name="assembly">The <see cref="Assembly"/> to load the strings from.</param>
|
||||
public static void Register(string modGuid, string internalName, Assembly assembly)
|
||||
{
|
||||
LoadInternalStrings(assembly, internalName, modGuid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translates a key into the current language.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -6,17 +7,39 @@ namespace MiraAPI.Utilities.Assets;
|
||||
/// <summary>
|
||||
/// A utility class for loading .WAV audio assets from the DLL's embedded resources.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the wave file.</param>
|
||||
public class LoadableAudioResourceAsset(string path) : LoadableAsset<AudioClip>
|
||||
public class LoadableAudioResourceAsset : LoadableAsset<AudioClip>
|
||||
{
|
||||
private readonly Assembly _assembly = Assembly.GetCallingAssembly();
|
||||
private readonly string _path;
|
||||
private readonly Assembly _assembly;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoadableAudioResourceAsset"/> class with the provided resource path and the assembly containing the asset.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the wave file.</param>
|
||||
/// <param name="assembly">The <see cref="Assembly"/> containing the asset.</param>
|
||||
public LoadableAudioResourceAsset(string path, Assembly assembly)
|
||||
{
|
||||
_path = path;
|
||||
_assembly = assembly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoadableAudioResourceAsset"/> class with the provided resource path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the wave file.</param>
|
||||
[Obsolete("Pass typeof(YourPlugin).Assembly directly to the constructor for better performance. This will be removed in a future version.")]
|
||||
public LoadableAudioResourceAsset(string path)
|
||||
{
|
||||
_path = path;
|
||||
_assembly = Assembly.GetCallingAssembly();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override AudioClip LoadAsset()
|
||||
{
|
||||
if (!LoadedAsset)
|
||||
{
|
||||
LoadedAsset = AudioTools.LoadAudioFromResources(path, _assembly);
|
||||
LoadedAsset = AudioTools.LoadAudioFromResources(_path, _assembly);
|
||||
}
|
||||
|
||||
return LoadedAsset!;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Reflection;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Utilities.Assets;
|
||||
@@ -6,18 +7,44 @@ namespace MiraAPI.Utilities.Assets;
|
||||
/// <summary>
|
||||
/// A utility class for loading assets from embedded resources.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the embedded resource.</param>
|
||||
/// <param name="pixelsPerUnit">The pixels per unit for the loaded sprite.</param>
|
||||
public class LoadableResourceAsset(string path, float pixelsPerUnit = 100f) : LoadableAsset<Sprite>
|
||||
public class LoadableResourceAsset : LoadableAsset<Sprite>
|
||||
{
|
||||
private readonly Assembly _assembly = Assembly.GetCallingAssembly();
|
||||
private readonly string _path;
|
||||
private readonly float _pixelsPerUnit;
|
||||
private readonly Assembly _assembly;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoadableResourceAsset"/> class with the provided resource path and the assembly containing the asset.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the embedded resource.</param>
|
||||
/// <param name="assembly">The <see cref="Assembly"/> containing the asset.</param>
|
||||
/// <param name="pixelsPerUnit">The pixels per unit for the loaded sprite.</param>
|
||||
public LoadableResourceAsset(string path, Assembly assembly, float pixelsPerUnit = 100f)
|
||||
{
|
||||
_path = path;
|
||||
_assembly = assembly;
|
||||
_pixelsPerUnit = pixelsPerUnit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoadableResourceAsset"/> class with the provided resource path and the assembly containing the asset.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the embedded resource.</param>
|
||||
/// <param name="pixelsPerUnit">The pixels per unit for the loaded sprite.</param>
|
||||
[Obsolete("Pass typeof(YourPlugin).Assembly directly to the constructor for better performance. This will be removed in a future version.")]
|
||||
public LoadableResourceAsset(string path, float pixelsPerUnit = 100f)
|
||||
{
|
||||
_path = path;
|
||||
_pixelsPerUnit = pixelsPerUnit;
|
||||
_assembly = Assembly.GetCallingAssembly();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Sprite LoadAsset()
|
||||
{
|
||||
if (!LoadedAsset)
|
||||
{
|
||||
LoadedAsset = SpriteTools.LoadSpriteFromPath(path, _assembly, pixelsPerUnit);
|
||||
LoadedAsset = SpriteTools.LoadSpriteFromPath(_path, _assembly, _pixelsPerUnit);
|
||||
}
|
||||
|
||||
return LoadedAsset!;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Reactor.Utilities;
|
||||
using System.Reflection;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Utilities.Assets;
|
||||
@@ -8,11 +9,13 @@ namespace MiraAPI.Utilities.Assets;
|
||||
/// </summary>
|
||||
public static class MiraAssets
|
||||
{
|
||||
private static readonly Assembly _assembly = typeof(MiraAssets).Assembly;
|
||||
|
||||
static MiraAssets()
|
||||
{
|
||||
var boxTex = SpriteTools.LoadTextureFromResourcePath(
|
||||
"MiraAPI.Resources.RoundedBox.png",
|
||||
System.Reflection.Assembly.GetExecutingAssembly());
|
||||
_assembly);
|
||||
var boxSprite = Sprite.Create(
|
||||
boxTex,
|
||||
new Rect(0, 0, boxTex.width, boxTex.height),
|
||||
@@ -58,7 +61,7 @@ public static class MiraAssets
|
||||
/// <summary>
|
||||
/// Gets the empty <see cref="Sprite"/> asset.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset Empty { get; } = new("MiraAPI.Resources.Empty.png");
|
||||
public static LoadableResourceAsset Empty { get; } = new("MiraAPI.Resources.Empty.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RoundedBox <see cref="Sprite"/>, which is a rounded rectangle used for UI elements.
|
||||
@@ -68,312 +71,312 @@ public static class MiraAssets
|
||||
/// <summary>
|
||||
/// Gets the Next Button sprite.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset NextButton { get; } = new("MiraAPI.Resources.NextButton.png");
|
||||
public static LoadableResourceAsset NextButton { get; } = new("MiraAPI.Resources.NextButton.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Mira settings icon.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset SettingsIcon { get; } = new("MiraAPI.Resources.Settings.png");
|
||||
public static LoadableResourceAsset SettingsIcon { get; } = new("MiraAPI.Resources.Settings.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the highlighted Next Button sprite.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset NextButtonActive { get; } = new("MiraAPI.Resources.NextButtonActive.png");
|
||||
public static LoadableResourceAsset NextButtonActive { get; } = new("MiraAPI.Resources.NextButtonActive.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Cog icon used in Role Settings Menu.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset Cog { get; } = new("MiraAPI.Resources.Cog.png");
|
||||
public static LoadableResourceAsset Cog { get; } = new("MiraAPI.Resources.Cog.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Checkmark Box used in the Settings Menu.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset CheckmarkBox { get; } = new("MiraAPI.Resources.CheckMarkBox.png");
|
||||
public static LoadableResourceAsset CheckmarkBox { get; } = new("MiraAPI.Resources.CheckMarkBox.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Reset Box used in the Settings Menu.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset ResetButton { get; } = new("MiraAPI.Resources.ResetButton.png");
|
||||
public static LoadableResourceAsset ResetButton { get; } = new("MiraAPI.Resources.ResetButton.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Checkmark used in the Settings Menu.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset Checkmark { get; } = new("MiraAPI.Resources.Checkmark.png");
|
||||
public static LoadableResourceAsset Checkmark { get; } = new("MiraAPI.Resources.Checkmark.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the white CategoryHeader used in the Settings Menu.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset CategoryHeader { get; } = new("MiraAPI.Resources.CategoryHeader.png");
|
||||
public static LoadableResourceAsset CategoryHeader { get; } = new("MiraAPI.Resources.CategoryHeader.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the Keybind icons.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset KeybindButton { get; } = new("MiraAPI.Resources.KeybindButton.png");
|
||||
public static LoadableResourceAsset KeybindButton { get; } = new("MiraAPI.Resources.KeybindButton.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for crewmate role files in TaskAdderGame.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset CrewmateFile { get; } = new("MiraAPI.Resources.CrewmateFile.png");
|
||||
public static LoadableResourceAsset CrewmateFile { get; } = new("MiraAPI.Resources.CrewmateFile.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for impostor role files in TaskAdderGame.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset ImpostorFile { get; } = new("MiraAPI.Resources.ImpostorFile.png");
|
||||
public static LoadableResourceAsset ImpostorFile { get; } = new("MiraAPI.Resources.ImpostorFile.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for custom team role files in TaskAdderGame.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset CustomTeamFile { get; } = new("MiraAPI.Resources.CustomFile.png");
|
||||
public static LoadableResourceAsset CustomTeamFile { get; } = new("MiraAPI.Resources.CustomFile.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for modifier file in TaskAdderGame.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset ModifierFile { get; } = new("MiraAPI.Resources.ModifierFile.png");
|
||||
public static LoadableResourceAsset ModifierFile { get; } = new("MiraAPI.Resources.ModifierFile.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the Classic gamemode.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset ClassicGamemodeIcon { get; } = new("MiraAPI.Resources.ClassicGamemodeIcon.png");
|
||||
public static LoadableResourceAsset ClassicGamemodeIcon { get; } = new("MiraAPI.Resources.ClassicGamemodeIcon.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the Hide n Seek gamemode.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset HnSGamemodeIcon { get; } = new("MiraAPI.Resources.HnSGamemodeIcon.png");
|
||||
public static LoadableResourceAsset HnSGamemodeIcon { get; } = new("MiraAPI.Resources.HnSGamemodeIcon.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for timed modifier file in TaskAdderGame.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset TimedModifierFile { get; } = new("MiraAPI.Resources.TimedModifierFile.png");
|
||||
public static LoadableResourceAsset TimedModifierFile { get; } = new("MiraAPI.Resources.TimedModifierFile.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the new chat button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset ChatHoverSprite { get; } = new("MiraAPI.Resources.NormalChatHover.png");
|
||||
public static LoadableResourceAsset ChatHoverSprite { get; } = new("MiraAPI.Resources.NormalChatHover.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the new chat button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset ChatIdleSprite { get; } = new("MiraAPI.Resources.NormalChatIdle.png");
|
||||
public static LoadableResourceAsset ChatIdleSprite { get; } = new("MiraAPI.Resources.NormalChatIdle.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the new chat button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset ChatOpenSprite { get; } = new("MiraAPI.Resources.NormalChatOpen.png");
|
||||
public static LoadableResourceAsset ChatOpenSprite { get; } = new("MiraAPI.Resources.NormalChatOpen.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for players button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset WikiPlayersButtonHoverSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarPlayersButtonHover.png");
|
||||
public static LoadableResourceAsset WikiPlayersButtonHoverSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarPlayersButtonHover.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the players button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset WikiPlayersButtonIdleSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarPlayersButtonIdle.png");
|
||||
public static LoadableResourceAsset WikiPlayersButtonIdleSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarPlayersButtonIdle.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the players button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset WikiPlayersButtonOpenSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarPlayersButtonOpen.png");
|
||||
public static LoadableResourceAsset WikiPlayersButtonOpenSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarPlayersButtonOpen.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the settings button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset WikiSettingsButtonHoverSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarSettingsButtonHover.png");
|
||||
public static LoadableResourceAsset WikiSettingsButtonHoverSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarSettingsButtonHover.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the settings button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset WikiSettingsButtonIdleSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarSettingsButtonIdle.png");
|
||||
public static LoadableResourceAsset WikiSettingsButtonIdleSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarSettingsButtonIdle.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the settings button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset WikiSettingsButtonOpenSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarSettingsButtonOpen.png");
|
||||
public static LoadableResourceAsset WikiSettingsButtonOpenSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarSettingsButtonOpen.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset WikiRolesButtonHoverSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarRolesButtonHover.png");
|
||||
public static LoadableResourceAsset WikiRolesButtonHoverSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarRolesButtonHover.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset WikiRolesButtonIdleSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarRolesButtonIdle.png");
|
||||
public static LoadableResourceAsset WikiRolesButtonIdleSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarRolesButtonIdle.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset WikiRolesButtonOpenSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarRolesButtonOpen.png");
|
||||
public static LoadableResourceAsset WikiRolesButtonOpenSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarRolesButtonOpen.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the modifiers button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset WikiModifiersButtonHoverSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarModifiersButtonHover.png");
|
||||
public static LoadableResourceAsset WikiModifiersButtonHoverSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarModifiersButtonHover.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the modifiers button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset WikiModifiersButtonIdleSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarModifiersButtonIdle.png");
|
||||
public static LoadableResourceAsset WikiModifiersButtonIdleSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarModifiersButtonIdle.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the modifiers button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset WikiModifiersButtonOpenSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarModifiersButtonOpen.png");
|
||||
public static LoadableResourceAsset WikiModifiersButtonOpenSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarModifiersButtonOpen.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset GroupCountHoverSprite { get; } = new("MiraAPI.Resources.Wiki.GroupCountHover.png");
|
||||
public static LoadableResourceAsset GroupCountHoverSprite { get; } = new("MiraAPI.Resources.Wiki.GroupCountHover.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset GroupCountIdleSprite { get; } = new("MiraAPI.Resources.Wiki.GroupCountIdle.png");
|
||||
public static LoadableResourceAsset GroupCountIdleSprite { get; } = new("MiraAPI.Resources.Wiki.GroupCountIdle.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset GroupFactionHoverSprite { get; } = new("MiraAPI.Resources.Wiki.GroupFactionHover.png");
|
||||
public static LoadableResourceAsset GroupFactionHoverSprite { get; } = new("MiraAPI.Resources.Wiki.GroupFactionHover.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset GroupFactionIdleSprite { get; } = new("MiraAPI.Resources.Wiki.GroupFactionIdle.png");
|
||||
public static LoadableResourceAsset GroupFactionIdleSprite { get; } = new("MiraAPI.Resources.Wiki.GroupFactionIdle.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset GroupNoneHoverSprite { get; } = new("MiraAPI.Resources.Wiki.GroupNoneHover.png");
|
||||
public static LoadableResourceAsset GroupNoneHoverSprite { get; } = new("MiraAPI.Resources.Wiki.GroupNoneHover.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset GroupNoneIdleSprite { get; } = new("MiraAPI.Resources.Wiki.GroupNoneIdle.png");
|
||||
public static LoadableResourceAsset GroupNoneIdleSprite { get; } = new("MiraAPI.Resources.Wiki.GroupNoneIdle.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset SearchClearHoverSprite { get; } = new("MiraAPI.Resources.Wiki.SearchClearHover.png");
|
||||
public static LoadableResourceAsset SearchClearHoverSprite { get; } = new("MiraAPI.Resources.Wiki.SearchClearHover.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset SearchClearIdleSprite { get; } = new("MiraAPI.Resources.Wiki.SearchClearIdle.png");
|
||||
public static LoadableResourceAsset SearchClearIdleSprite { get; } = new("MiraAPI.Resources.Wiki.SearchClearIdle.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset SearchIconHoverSprite { get; } = new("MiraAPI.Resources.Wiki.SearchIconHover.png");
|
||||
public static LoadableResourceAsset SearchIconHoverSprite { get; } = new("MiraAPI.Resources.Wiki.SearchIconHover.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset SearchIconIdleSprite { get; } = new("MiraAPI.Resources.Wiki.SearchIconIdle.png");
|
||||
public static LoadableResourceAsset SearchIconIdleSprite { get; } = new("MiraAPI.Resources.Wiki.SearchIconIdle.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset SortingAzHoverSprite { get; } = new("MiraAPI.Resources.Wiki.SortingAzHover.png");
|
||||
public static LoadableResourceAsset SortingAzHoverSprite { get; } = new("MiraAPI.Resources.Wiki.SortingAzHover.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset SortingAzIdleSprite { get; } = new("MiraAPI.Resources.Wiki.SortingAzIdle.png");
|
||||
public static LoadableResourceAsset SortingAzIdleSprite { get; } = new("MiraAPI.Resources.Wiki.SortingAzIdle.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset SortingZaHoverSprite { get; } = new("MiraAPI.Resources.Wiki.SortingZaHover.png");
|
||||
public static LoadableResourceAsset SortingZaHoverSprite { get; } = new("MiraAPI.Resources.Wiki.SortingZaHover.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset SortingZaIdleSprite { get; } = new("MiraAPI.Resources.Wiki.SortingZaIdle.png");
|
||||
public static LoadableResourceAsset SortingZaIdleSprite { get; } = new("MiraAPI.Resources.Wiki.SortingZaIdle.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset VisibleAllHoverSprite { get; } = new("MiraAPI.Resources.Wiki.VisibleAllHover.png");
|
||||
public static LoadableResourceAsset VisibleAllHoverSprite { get; } = new("MiraAPI.Resources.Wiki.VisibleAllHover.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset VisibleAllIdleSprite { get; } = new("MiraAPI.Resources.Wiki.VisibleAllIdle.png");
|
||||
public static LoadableResourceAsset VisibleAllIdleSprite { get; } = new("MiraAPI.Resources.Wiki.VisibleAllIdle.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset VisibleOffHoverSprite { get; } = new("MiraAPI.Resources.Wiki.VisibleOffHover.png");
|
||||
public static LoadableResourceAsset VisibleOffHoverSprite { get; } = new("MiraAPI.Resources.Wiki.VisibleOffHover.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset VisibleOffIdleSprite { get; } = new("MiraAPI.Resources.Wiki.VisibleOffIdle.png");
|
||||
public static LoadableResourceAsset VisibleOffIdleSprite { get; } = new("MiraAPI.Resources.Wiki.VisibleOffIdle.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset VisibleOnHoverSprite { get; } = new("MiraAPI.Resources.Wiki.VisibleOnHover.png");
|
||||
public static LoadableResourceAsset VisibleOnHoverSprite { get; } = new("MiraAPI.Resources.Wiki.VisibleOnHover.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset VisibleOnIdleSprite { get; } = new("MiraAPI.Resources.Wiki.VisibleOnIdle.png");
|
||||
public static LoadableResourceAsset VisibleOnIdleSprite { get; } = new("MiraAPI.Resources.Wiki.VisibleOnIdle.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for crewmates.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset IconCrewmate { get; } = new("MiraAPI.Resources.Wiki.IconCrewmate.png");
|
||||
public static LoadableResourceAsset IconCrewmate { get; } = new("MiraAPI.Resources.Wiki.IconCrewmate.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for impostors.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset IconImpostor { get; } = new("MiraAPI.Resources.Wiki.IconImpostor.png");
|
||||
public static LoadableResourceAsset IconImpostor { get; } = new("MiraAPI.Resources.Wiki.IconImpostor.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for neutrals.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset IconNeutral { get; } = new("MiraAPI.Resources.Wiki.IconNeutral.png");
|
||||
public static LoadableResourceAsset IconNeutral { get; } = new("MiraAPI.Resources.Wiki.IconNeutral.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the player list.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset IconPlayerList { get; } = new("MiraAPI.Resources.Wiki.IconPlayerList.png");
|
||||
public static LoadableResourceAsset IconPlayerList { get; } = new("MiraAPI.Resources.Wiki.IconPlayerList.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for settings.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset IconSettings { get; } = new("MiraAPI.Resources.Wiki.IconSettings.png");
|
||||
public static LoadableResourceAsset IconSettings { get; } = new("MiraAPI.Resources.Wiki.IconSettings.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for terminology.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset IconTerminology { get; } = new("MiraAPI.Resources.Wiki.IconTerminology.png");
|
||||
public static LoadableResourceAsset IconTerminology { get; } = new("MiraAPI.Resources.Wiki.IconTerminology.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Page switching sprite.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset PageLeftHover { get; } = new("MiraAPI.Resources.Wiki.PageLeftHover.png");
|
||||
public static LoadableResourceAsset PageLeftHover { get; } = new("MiraAPI.Resources.Wiki.PageLeftHover.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Page switching sprite.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset PageLeftIdle { get; } = new("MiraAPI.Resources.Wiki.PageLeftIdle.png");
|
||||
public static LoadableResourceAsset PageLeftIdle { get; } = new("MiraAPI.Resources.Wiki.PageLeftIdle.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Page switching sprite.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset PageRightHover { get; } = new("MiraAPI.Resources.Wiki.PageRightHover.png");
|
||||
public static LoadableResourceAsset PageRightHover { get; } = new("MiraAPI.Resources.Wiki.PageRightHover.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Page switching sprite.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset PageRightIdle { get; } = new("MiraAPI.Resources.Wiki.PageRightIdle.png");
|
||||
public static LoadableResourceAsset PageRightIdle { get; } = new("MiraAPI.Resources.Wiki.PageRightIdle.png", _assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used below the tab icons in the wiki.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset TabButtonContainer { get; } = new("MiraAPI.Resources.Wiki.TabButtonContainer.png");
|
||||
public static LoadableResourceAsset TabButtonContainer { get; } = new("MiraAPI.Resources.Wiki.TabButtonContainer.png", _assembly);
|
||||
|
||||
internal static LoadableResourceAsset BlankSprite { get; } = new("MiraAPI.Resources.BlankSprite.png");
|
||||
internal static LoadableResourceAsset BlankSprite { get; } = new("MiraAPI.Resources.BlankSprite.png", _assembly);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// A comparer used to sort role/modifier IDs based on which ones are currently active.
|
||||
/// </summary>
|
||||
/// <param name="activeIds">The collection of currently active IDs.</param>
|
||||
public class BasicGuideComparer(IEnumerable<uint> activeIds) : IComparer<uint>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public int Compare(uint x, uint y)
|
||||
{
|
||||
var xActive = activeIds.Contains(x);
|
||||
var yActive = activeIds.Contains(y);
|
||||
|
||||
if (xActive && !yActive)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!xActive && yActive)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,26 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for the <see cref="Color"/> struct.
|
||||
/// Extension methods for the <see cref="Color"/> and <see cref="Color32"/> structs.
|
||||
/// </summary>
|
||||
public static class ColorExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static float ToFloat(this byte @byte)
|
||||
{
|
||||
return @byte / 255f;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static byte ToByte(this float @float)
|
||||
{
|
||||
return (byte)MathF.Round(@float * 255f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the relative luminance of a <see cref="Color"/> (WCAG 2.1 specification). 0 is black, 1 is white.
|
||||
/// </summary>
|
||||
@@ -19,6 +33,18 @@ public static class ColorExtensions
|
||||
(0.0722f * Linearize(color.b));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the relative luminance of a <see cref="Color32"/> (WCAG 2.1 specification). 0 is black, 1 is white.
|
||||
/// </summary>
|
||||
/// <param name="color"><see cref="Color32"/> to calculate luminance for.</param>
|
||||
/// <returns>Luminance value between 0 and 1.</returns>
|
||||
public static float GetRelativeLuminance(this Color32 color)
|
||||
{
|
||||
return (0.2126f * Linearize(color.r.ToFloat())) +
|
||||
(0.7152f * Linearize(color.g.ToFloat())) +
|
||||
(0.0722f * Linearize(color.b.ToFloat()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linearizes an sRGB component into linear space.
|
||||
/// </summary>
|
||||
@@ -26,7 +52,7 @@ public static class ColorExtensions
|
||||
/// <returns>The linearized result.</returns>
|
||||
public static float Linearize(float c)
|
||||
{
|
||||
return c <= 0.03928f ? c / 12.92f : Mathf.Pow((c + 0.055f) / 1.055f, 2.4f);
|
||||
return c <= 0.03928f ? c / 12.92f : MathF.Pow((c + 0.055f) / 1.055f, 2.4f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,11 +65,27 @@ public static class ColorExtensions
|
||||
{
|
||||
var luminanceA = colorA.GetRelativeLuminance();
|
||||
var luminanceB = colorB.GetRelativeLuminance();
|
||||
return (Mathf.Max(luminanceA, luminanceB) + 0.05f) / (Mathf.Min(luminanceA, luminanceB) + 0.05f);
|
||||
|
||||
return (Math.Max(luminanceA, luminanceB) + 0.05f) / (Math.Min(luminanceA, luminanceB) + 0.05f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates an accessible alternate <see cref="Color"/> with sufficient contrast.
|
||||
/// Calculates the contrast ratio between two <see cref="Color32"/>s.
|
||||
/// </summary>
|
||||
/// <param name="colorA">First <see cref="Color32"/>.</param>
|
||||
/// <param name="colorB">Second <see cref="Color32"/>.</param>
|
||||
/// <returns>Contrast ratio between the two <see cref="Color32"/>s.</returns>
|
||||
public static float GetContrastRatio(this Color32 colorA, Color32 colorB)
|
||||
{
|
||||
var luminanceA = colorA.GetRelativeLuminance();
|
||||
var luminanceB = colorB.GetRelativeLuminance();
|
||||
|
||||
return (Math.Max(luminanceA, luminanceB) + 0.05f) / (Math.Min(luminanceA, luminanceB) + 0.05f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates an accessible alternate <see cref="Color"/> by shifting towards black or white
|
||||
/// based on which extreme offers the highest potential contrast from the original color.
|
||||
/// </summary>
|
||||
/// <param name="color">Base <see cref="Color"/>.</param>
|
||||
/// <param name="desiredRatio">Target contrast ratio (default 4.5f for AA).</param>
|
||||
@@ -57,10 +99,120 @@ public static class ColorExtensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the <see cref="Color"/> with the minimum contrast ratio to the target <see cref="Color"/>.
|
||||
/// Generates an accessible alternate <see cref="Color32"/> with sufficient contrast.
|
||||
/// </summary>
|
||||
/// <param name="color">Base <see cref="Color32"/>.</param>
|
||||
/// <param name="desiredRatio">Target contrast ratio (default 4.5f for AA).</param>
|
||||
/// <returns>Alternate <see cref="Color32"/> with sufficient contrast.</returns>
|
||||
public static Color32 FindAlternateColor(this Color32 color, float desiredRatio = 4.5f)
|
||||
{
|
||||
var colorF = new Color(color.r.ToFloat(), color.g.ToFloat(), color.b.ToFloat());
|
||||
var result = colorF.FindAlternateColor(desiredRatio);
|
||||
return new Color32(result.r.ToByte(), result.g.ToByte(), result.b.ToByte(), color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Darkens a <see cref="Color"/> towards black until it reaches the target WCAG contrast ratio against a background.
|
||||
/// </summary>
|
||||
/// <param name="color">The foreground <see cref="Color"/> to modify.</param>
|
||||
/// <param name="background">The background <see cref="Color"/> it sits on.</param>
|
||||
/// <param name="targetRatio">Target contrast ratio (default 4.5f for AA).</param>
|
||||
/// <returns>A darkened <see cref="Color"/> meeting the contrast ratio, or black if impossible.</returns>
|
||||
public static Color DarkenToWCAG(this Color color, Color background, float targetRatio = 4.5f)
|
||||
{
|
||||
return color.GetContrastRatio(background) >= targetRatio
|
||||
? color
|
||||
: FindMinimumContrastColor(color, Color.black, targetRatio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Darkens a <see cref="Color32"/> towards black until it reaches the target WCAG contrast ratio against a background.
|
||||
/// </summary>
|
||||
/// <param name="color">The foreground <see cref="Color32"/> to modify.</param>
|
||||
/// <param name="background">The background <see cref="Color32"/> it sits on.</param>
|
||||
/// <param name="targetRatio">Target contrast ratio (default 4.5f for AA).</param>
|
||||
/// <returns>A darkened <see cref="Color32"/> meeting the contrast ratio, or black if impossible.</returns>
|
||||
public static Color32 DarkenToWCAG(this Color32 color, Color32 background, float targetRatio = 4.5f)
|
||||
{
|
||||
var colorF = new Color(color.r.ToFloat(), color.g.ToFloat(), color.b.ToFloat(), color.a.ToFloat());
|
||||
var bgF = new Color(background.r.ToFloat(), background.g.ToFloat(), background.b.ToFloat(), background.a.ToFloat());
|
||||
|
||||
var result = colorF.DarkenToWCAG(bgF, targetRatio);
|
||||
return new Color32(result.r.ToByte(), result.g.ToByte(), result.b.ToByte(), color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightens a <see cref="Color"/> towards white until it reaches the target WCAG contrast ratio against a background.
|
||||
/// </summary>
|
||||
/// <param name="color">The foreground <see cref="Color"/> to modify.</param>
|
||||
/// <param name="background">The background <see cref="Color"/> it sits on.</param>
|
||||
/// <param name="targetRatio">Target contrast ratio (default 4.5f for AA).</param>
|
||||
/// <returns>A lightened <see cref="Color"/> meeting the contrast ratio, or white if impossible.</returns>
|
||||
public static Color LightenToWCAG(this Color color, Color background, float targetRatio = 4.5f)
|
||||
{
|
||||
return color.GetContrastRatio(background) >= targetRatio
|
||||
? color
|
||||
: FindMinimumContrastColor(color, Color.white, targetRatio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightens a <see cref="Color32"/> towards white until it reaches the target WCAG contrast ratio against a background.
|
||||
/// </summary>
|
||||
/// <param name="color">The foreground <see cref="Color32"/> to modify.</param>
|
||||
/// <param name="background">The background <see cref="Color32"/> it sits on.</param>
|
||||
/// <param name="targetRatio">Target contrast ratio (default 4.5f for AA).</param>
|
||||
/// <returns>A lightened <see cref="Color32"/> meeting the contrast ratio, or white if impossible.</returns>
|
||||
public static Color32 LightenToWCAG(this Color32 color, Color32 background, float targetRatio = 4.5f)
|
||||
{
|
||||
var colorF = new Color(color.r.ToFloat(), color.g.ToFloat(), color.b.ToFloat(), color.a.ToFloat());
|
||||
var bgF = new Color(background.r.ToFloat(), background.g.ToFloat(), background.b.ToFloat(), background.a.ToFloat());
|
||||
|
||||
var result = colorF.LightenToWCAG(bgF, targetRatio);
|
||||
return new Color32(result.r.ToByte(), result.g.ToByte(), result.b.ToByte(), color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically decides whether to lighten or darken the <see cref="Color"/> to achieve WCAG compliance
|
||||
/// based on which extreme offers the highest potential contrast against the provided background.
|
||||
/// </summary>
|
||||
/// <param name="color">The foreground <see cref="Color"/> to modify.</param>
|
||||
/// <param name="background">The background <see cref="Color"/> it sits on.</param>
|
||||
/// <param name="targetRatio">Target contrast ratio (default 4.5f for AA).</param>
|
||||
/// <returns>A modified <see cref="Color"/> meeting the WCAG contrast requirement.</returns>
|
||||
public static Color EnsureWCAGContrast(this Color color, Color background, float targetRatio = 4.5f)
|
||||
{
|
||||
if (color.GetContrastRatio(background) >= targetRatio)
|
||||
return color;
|
||||
|
||||
var blackContrast = Color.black.GetContrastRatio(background);
|
||||
var whiteContrast = Color.white.GetContrastRatio(background);
|
||||
|
||||
return blackContrast > whiteContrast
|
||||
? color.DarkenToWCAG(background, targetRatio)
|
||||
: color.LightenToWCAG(background, targetRatio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically decides whether to lighten or darken the <see cref="Color32"/> to achieve WCAG compliance.
|
||||
/// </summary>
|
||||
/// <param name="color">The foreground <see cref="Color32"/> to modify.</param>
|
||||
/// <param name="background">The background <see cref="Color32"/> it sits on.</param>
|
||||
/// <param name="targetRatio">Target contrast ratio (default 4.5f for AA).</param>
|
||||
/// <returns>A modified <see cref="Color32"/> meeting the WCAG contrast requirement.</returns>
|
||||
public static Color32 EnsureWCAGContrast(this Color32 color, Color32 background, float targetRatio = 4.5f)
|
||||
{
|
||||
var colorF = new Color(color.r.ToFloat(), color.g.ToFloat(), color.b.ToFloat(), color.a.ToFloat());
|
||||
var bgF = new Color(background.r.ToFloat(), background.g.ToFloat(), background.b.ToFloat(), background.a.ToFloat());
|
||||
|
||||
var result = colorF.EnsureWCAGContrast(bgF, targetRatio);
|
||||
return new Color32(result.r.ToByte(), result.g.ToByte(), result.b.ToByte(), color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the <see cref="Color"/> with the minimum contrast ratio to the target <see cref="Color"/> using binary search.
|
||||
/// </summary>
|
||||
/// <param name="baseColor">The base <see cref="Color"/> to start from.</param>
|
||||
/// <param name="targetColor">The target <see cref="Color"/> to compare against.</param>
|
||||
/// <param name="targetColor">The target <see cref="Color"/> to approach (usually white or black).</param>
|
||||
/// <param name="desiredRatio">The desired contrast ratio.</param>
|
||||
/// <returns>The <see cref="Color"/> with the minimum contrast ratio to the target <see cref="Color"/>.</returns>
|
||||
public static Color FindMinimumContrastColor(Color baseColor, Color targetColor, float desiredRatio)
|
||||
@@ -72,18 +224,28 @@ public static class ColorExtensions
|
||||
? desiredRatio * (baseLuminance + 0.05f) - 0.05f
|
||||
: (baseLuminance + 0.05f) / desiredRatio - 0.05f;
|
||||
|
||||
if (isLightening && targetLuminance > 1f) return Color.white;
|
||||
if (!isLightening && targetLuminance < 0f) return Color.black;
|
||||
if (isLightening && targetLuminance > 1f)
|
||||
return Color.white;
|
||||
|
||||
if (!isLightening && targetLuminance < 0f)
|
||||
return Color.black;
|
||||
|
||||
float low = 0f, high = 1f;
|
||||
var bestColor = targetColor;
|
||||
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
var t = (low + high) / 2f;
|
||||
var testColor = Color.Lerp(baseColor, targetColor, t);
|
||||
|
||||
var testColor = new Color(
|
||||
baseColor.r + (targetColor.r - baseColor.r) * t,
|
||||
baseColor.g + (targetColor.g - baseColor.g) * t,
|
||||
baseColor.b + (targetColor.b - baseColor.b) * t,
|
||||
baseColor.a);
|
||||
|
||||
var testLuminance = testColor.GetRelativeLuminance();
|
||||
|
||||
if ((isLightening && testLuminance >= targetLuminance) || (!isLightening && testLuminance <= targetLuminance))
|
||||
if (isLightening ? testLuminance >= targetLuminance : testLuminance <= targetLuminance)
|
||||
{
|
||||
high = t;
|
||||
bestColor = testColor;
|
||||
@@ -96,4 +258,128 @@ public static class ColorExtensions
|
||||
|
||||
return bestColor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts a flat amount from the <see cref="Color"/>'s RGB channels.
|
||||
/// </summary>
|
||||
/// <param name="color">The original color.</param>
|
||||
/// <param name="amount">The flat amount to subtract (typically 0.0 to 1.0).</param>
|
||||
/// <returns>A uniformly darkened <see cref="Color"/>.</returns>
|
||||
public static Color DarkenAbsolute(this Color color, float amount)
|
||||
{
|
||||
return new Color(
|
||||
Math.Max(0f, color.r - amount),
|
||||
Math.Max(0f, color.g - amount),
|
||||
Math.Max(0f, color.b - amount),
|
||||
color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts a flat byte amount from the <see cref="Color32"/>'s RGB channels.
|
||||
/// </summary>
|
||||
/// <param name="color">The original <see cref="Color32"/>.</param>
|
||||
/// <param name="amount">The flat amount to subtract (0 to 255).</param>
|
||||
/// <returns>A uniformly darkened <see cref="Color32"/>.</returns>
|
||||
public static Color32 DarkenAbsolute(this Color32 color, byte amount)
|
||||
{
|
||||
return new Color32(
|
||||
(byte)Math.Max(0, color.r - amount),
|
||||
(byte)Math.Max(0, color.g - amount),
|
||||
(byte)Math.Max(0, color.b - amount),
|
||||
color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a flat amount to the <see cref="Color"/>'s RGB channels.
|
||||
/// </summary>
|
||||
/// <param name="color">The original color.</param>
|
||||
/// <param name="amount">The flat amount to add (typically 0.0 to 1.0).</param>
|
||||
/// <returns>A uniformly lightened <see cref="Color"/>.</returns>
|
||||
public static Color LightenAbsolute(this Color color, float amount)
|
||||
{
|
||||
return new Color(
|
||||
Math.Min(1f, color.r + amount),
|
||||
Math.Min(1f, color.g + amount),
|
||||
Math.Min(1f, color.b + amount),
|
||||
color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a flat byte amount to the <see cref="Color32"/>'s RGB channels.
|
||||
/// </summary>
|
||||
/// <param name="color">The original <see cref="Color32"/>.</param>
|
||||
/// <param name="amount">The flat amount to add (0 to 255).</param>
|
||||
/// <returns>A uniformly lightened <see cref="Color32"/>.</returns>
|
||||
public static Color32 LightenAbsolute(this Color32 color, byte amount)
|
||||
{
|
||||
return new Color32(
|
||||
(byte)Math.Min(255, color.r + amount),
|
||||
(byte)Math.Min(255, color.g + amount),
|
||||
(byte)Math.Min(255, color.b + amount),
|
||||
color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Darkens the <see cref="Color"/> multiplicatively, preserving the ratio between RGB channels to maintain hue.
|
||||
/// </summary>
|
||||
/// <param name="color">The original color.</param>
|
||||
/// <param name="percentage">The percentage to darken by (e.g., 0.2 for 20% darker).</param>
|
||||
/// <returns>A ratio-preserved darkened <see cref="Color"/>.</returns>
|
||||
public static Color DarkenRelative(this Color color, float percentage)
|
||||
{
|
||||
var multiplier = Math.Max(0f, Math.Min(1f, 1f - percentage));
|
||||
return new Color(
|
||||
color.r * multiplier,
|
||||
color.g * multiplier,
|
||||
color.b * multiplier,
|
||||
color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Darkens the <see cref="Color32"/> multiplicatively, preserving the ratio between RGB channels.
|
||||
/// </summary>
|
||||
/// <param name="color">The original <see cref="Color32"/>.</param>
|
||||
/// <param name="percentage">The percentage to darken by (e.g., 0.2 for 20% darker).</param>
|
||||
/// <returns>A ratio-preserved darkened <see cref="Color32"/>.</returns>
|
||||
public static Color32 DarkenRelative(this Color32 color, float percentage)
|
||||
{
|
||||
var multiplier = Math.Max(0f, Math.Min(1f, 1f - percentage));
|
||||
return new Color32(
|
||||
(color.r * multiplier).ToByte(),
|
||||
(color.g * multiplier).ToByte(),
|
||||
(color.b * multiplier).ToByte(),
|
||||
color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightens the <see cref="Color"/> multiplicatively, preserving the ratio between RGB channels.
|
||||
/// </summary>
|
||||
/// <param name="color">The original color.</param>
|
||||
/// <param name="percentage">The percentage to lighten by (e.g., 0.2 for 20% lighter).</param>
|
||||
/// <returns>A ratio-preserved lightened <see cref="Color"/>.</returns>
|
||||
public static Color LightenRelative(this Color color, float percentage)
|
||||
{
|
||||
var multiplier = 1f + percentage;
|
||||
return new Color(
|
||||
Math.Min(1f, color.r * multiplier),
|
||||
Math.Min(1f, color.g * multiplier),
|
||||
Math.Min(1f, color.b * multiplier),
|
||||
color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightens the <see cref="Color32"/> multiplicatively, preserving the ratio between RGB channels.
|
||||
/// </summary>
|
||||
/// <param name="color">The original <see cref="Color32"/>.</param>
|
||||
/// <param name="percentage">The percentage to lighten by (e.g., 0.2 for 20% lighter).</param>
|
||||
/// <returns>A ratio-preserved lightened <see cref="Color32"/>.</returns>
|
||||
public static Color32 LightenRelative(this Color32 color, float percentage)
|
||||
{
|
||||
var multiplier = 1f + percentage;
|
||||
return new Color32(
|
||||
Math.Min(255f, color.r * 255f * multiplier / 255f).ToByte(),
|
||||
Math.Min(255f, color.g * 255f * multiplier / 255f).ToByte(),
|
||||
Math.Min(255f, color.b * 255f * multiplier / 255f).ToByte(),
|
||||
color.a);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ using System;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Utilities;
|
||||
namespace MiraAPI.Utilities.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A spinning ring used to indicate that content is loading.
|
||||
@@ -26,6 +26,8 @@ namespace MiraAPI.Utilities;
|
||||
/// </summary>
|
||||
public static class Extensions
|
||||
{
|
||||
private static readonly Regex DisplayRegex = new(@"([^\^])([A-Z][a-z$])", default, Regex.InfiniteMatchTimeout);
|
||||
|
||||
internal static NetData GetNetData(this ICustomRole role)
|
||||
{
|
||||
var count = role.GetCount();
|
||||
@@ -492,14 +494,25 @@ public static class Extensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a proper string for an <see cref="Enum"/>. (with spaces).
|
||||
/// Gets a proper string for an <see cref="Enum"/> (with spaces).
|
||||
/// </summary>
|
||||
/// <param name="enum">The <see cref="Enum"/> you would like to change.</param>
|
||||
/// <returns>A proper string for the <see cref="Enum"/>.</returns>
|
||||
/// <returns>A proper string for the provided <see cref="Enum"/>.</returns>
|
||||
public static string ToDisplayString(this Enum @enum)
|
||||
{
|
||||
var regex = new Regex(@"([^\^])([A-Z][a-z$])", default, Regex.InfiniteMatchTimeout);
|
||||
return regex.Replace(@enum.ToString(), m => $"{m.Groups[1].Value} {m.Groups[2].Value}");
|
||||
return DisplayRegex.Replace(@enum.ToString(), m => $"{m.Groups[1].Value} {m.Groups[2].Value}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a proper string for a(n) <typeparamref name="T"/> value (with spaces).
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the enum value.</typeparam>
|
||||
/// <param name="enum">The <typeparamref name="T"/> enum value you would like to change.</param>
|
||||
/// <returns>A proper string for the provided <typeparamref name="T"/> value.</returns>
|
||||
public static string ToDisplayString<T>(this T @enum)
|
||||
where T : struct, Enum
|
||||
{
|
||||
return DisplayRegex.Replace(@enum.ToString(), m => $"{m.Groups[1].Value} {m.Groups[2].Value}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -526,17 +539,46 @@ public static class Extensions
|
||||
/// <returns>The distance between the types.</returns>
|
||||
public static int GetInheritanceDistance(Type from, Type to)
|
||||
{
|
||||
if (!from.IsAssignableFrom(to)) return int.MaxValue;
|
||||
if (from == null || to == null)
|
||||
throw new ArgumentNullException(from == null ? nameof(from) : nameof(to));
|
||||
|
||||
var type = from;
|
||||
var distance = 0;
|
||||
while (type != null && type != to)
|
||||
if (from == to)
|
||||
return 0;
|
||||
|
||||
if (!to.IsGenericTypeDefinition && !to.IsAssignableFrom(from))
|
||||
return int.MaxValue;
|
||||
|
||||
var queue = new Queue<(Type Type, int Distance)>();
|
||||
var visited = new HashSet<Type>();
|
||||
|
||||
queue.Enqueue((from, 0));
|
||||
visited.Add(from);
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
type = type.BaseType;
|
||||
distance++;
|
||||
var (current, dist) = queue.Dequeue();
|
||||
|
||||
if (current == to || (current.IsGenericType && current.GetGenericTypeDefinition() == to))
|
||||
return dist;
|
||||
|
||||
if (current.BaseType != null && visited.Add(current.BaseType))
|
||||
queue.Enqueue((current.BaseType, dist + 1));
|
||||
|
||||
var allInterfaces = current.GetInterfaces();
|
||||
var baseInterfaces = current.BaseType?.GetInterfaces() ?? Type.EmptyTypes;
|
||||
|
||||
var directInterfaces = allInterfaces.Except(baseInterfaces).ToList();
|
||||
var inheritedInterfaces = directInterfaces.SelectMany(i => i.GetInterfaces());
|
||||
var immediateInterfaces = directInterfaces.Except(inheritedInterfaces);
|
||||
|
||||
foreach (var iFace in immediateInterfaces)
|
||||
{
|
||||
if (visited.Add(iFace))
|
||||
queue.Enqueue((iFace, dist + 1));
|
||||
}
|
||||
}
|
||||
|
||||
return type == to ? distance : int.MaxValue;
|
||||
return to.IsAssignableFrom(from) ? int.MaxValue - 1 : int.MaxValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -565,14 +607,10 @@ public static class Extensions
|
||||
/// <param name="color">The original <see cref="Color32"/>.</param>
|
||||
/// <param name="darknessAmount">A darkness amount between 0 and 255.</param>
|
||||
/// <returns>The darkened <see cref="Color32"/>.</returns>
|
||||
[Obsolete("Use ColorExtensions.DarkenAbsolute instead. This will be removed in a future version.")]
|
||||
public static Color32 GetShadowColor(this Color32 color, byte darknessAmount)
|
||||
{
|
||||
return
|
||||
new Color32(
|
||||
(byte)Mathf.Clamp(color.r - darknessAmount, 0, 255),
|
||||
(byte)Mathf.Clamp(color.g - darknessAmount, 0, 255),
|
||||
(byte)Mathf.Clamp(color.b - darknessAmount, 0, 255),
|
||||
color.a);
|
||||
return color.DarkenAbsolute(darknessAmount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -581,14 +619,10 @@ public static class Extensions
|
||||
/// <param name="color">The original <see cref="Color"/>.</param>
|
||||
/// <param name="darknessAmount">A darkness amount between 0 and 1.</param>
|
||||
/// <returns>The darkened <see cref="Color"/>.</returns>
|
||||
[Obsolete("Use ColorExtensions.DarkenAbsolute instead. This will be removed in a future version.")]
|
||||
public static Color GetShadowColor(this Color color, float darknessAmount)
|
||||
{
|
||||
return
|
||||
new Color(
|
||||
MathUtilities.Clamp01(color.r - darknessAmount),
|
||||
MathUtilities.Clamp01(color.g - darknessAmount),
|
||||
MathUtilities.Clamp01(color.b - darknessAmount),
|
||||
color.a);
|
||||
return color.DarkenAbsolute(darknessAmount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -677,9 +711,10 @@ public static class Extensions
|
||||
/// <param name="color">The original <see cref="Color"/>.</param>
|
||||
/// <param name="amount">A <see langword="float"/> amount between 0 and 1.</param>
|
||||
/// <returns>The darkened <see cref="Color"/>.</returns>
|
||||
[Obsolete("Use ColorExtensions.DarkenAbsolute instead. This will be removed in a future version.")]
|
||||
public static Color DarkenColor(this Color color, float amount = 0.45f)
|
||||
{
|
||||
return new Color(color.r - amount, color.g - amount, color.b - amount);
|
||||
return color.DarkenAbsolute(amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -688,9 +723,10 @@ public static class Extensions
|
||||
/// <param name="color">The original <see cref="Color"/>.</param>
|
||||
/// <param name="amount">A <see langword="float"/> amount between 0.0 and 1.0.</param>
|
||||
/// <returns>The lightened <see cref="Color"/>.</returns>
|
||||
[Obsolete("Use ColorExtensions.LightenAbsolute instead. This will be removed in a future version.")]
|
||||
public static Color LightenColor(this Color color, float amount = 0.45f)
|
||||
{
|
||||
return new Color(color.r + amount, color.g + amount, color.b + amount);
|
||||
return color.LightenAbsolute(amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using MiraAPI.GameModes;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Translation;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Utilities;
|
||||
@@ -57,9 +54,7 @@ internal sealed class GuideOptionPage(
|
||||
|
||||
internal static class GuideOptionPages
|
||||
{
|
||||
private static readonly Regex TrailingZeroRegex = new(@"\.0+(?!\d)", RegexOptions.Compiled, TimeSpan.FromMilliseconds(100));
|
||||
|
||||
public static List<GuideOptionPage> BuildPages(Func<bool, string> formatBool)
|
||||
public static List<GuideOptionPage> BuildPages()
|
||||
{
|
||||
var pages = new List<GuideOptionPage>();
|
||||
var combinedPages = new Dictionary<string, GuideOptionPage>(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -95,48 +90,49 @@ internal static class GuideOptionPages
|
||||
item.Group,
|
||||
groupName,
|
||||
item.ModAbbreviation,
|
||||
modSearchTerms,
|
||||
formatBool);
|
||||
modSearchTerms);
|
||||
if (entry != null)
|
||||
{
|
||||
entries.Add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (entries.Count > 0)
|
||||
if (entries.Count <= 0)
|
||||
{
|
||||
var pageKey = item.Group.MatchInfoGuidePageKey?.Trim();
|
||||
var combinedKey = string.IsNullOrEmpty(pageKey) ? null : $"{item.Plugin.PluginId}\0{pageKey}";
|
||||
if (combinedKey != null && combinedPages.TryGetValue(combinedKey, out var combinedPage))
|
||||
continue;
|
||||
}
|
||||
|
||||
var pageKey = item.Group.MatchInfoGuidePageKey?.Trim();
|
||||
var combinedKey = string.IsNullOrEmpty(pageKey) ? null : $"{item.Plugin.PluginId}\0{pageKey}";
|
||||
if (combinedKey != null && combinedPages.TryGetValue(combinedKey, out var combinedPage))
|
||||
{
|
||||
combinedPage.Groups.Add(item.Group);
|
||||
combinedPage.Entries.AddRange(entries);
|
||||
combinedPage.ModSearchTerms.UnionWith(modSearchTerms);
|
||||
combinedPage.Order = Math.Min(combinedPage.Order, item.Group.MatchInfoGuidePageOrder);
|
||||
combinedPage.MenuOrder = Math.Min(
|
||||
combinedPage.MenuOrder,
|
||||
GetMenuOrder(item.Group.ParentMenu));
|
||||
combinedPage.GroupPriority = Math.Min(
|
||||
combinedPage.GroupPriority,
|
||||
item.Group.GroupPriority);
|
||||
}
|
||||
else
|
||||
{
|
||||
var page = new GuideOptionPage(
|
||||
item.Group,
|
||||
item.Group.MatchInfoGuidePageTitle,
|
||||
item.Group.MatchInfoGuidePageOrder,
|
||||
GetMenuOrder(item.Group.ParentMenu),
|
||||
item.Group.GroupPriority,
|
||||
Mathf.Clamp(item.Group.MatchInfoGuidePageColumns, 1, 6),
|
||||
item.ModAbbreviation,
|
||||
modSearchTerms,
|
||||
entries);
|
||||
pages.Add(page);
|
||||
if (combinedKey != null)
|
||||
{
|
||||
combinedPage.Groups.Add(item.Group);
|
||||
combinedPage.Entries.AddRange(entries);
|
||||
combinedPage.ModSearchTerms.UnionWith(modSearchTerms);
|
||||
combinedPage.Order = Math.Min(combinedPage.Order, item.Group.MatchInfoGuidePageOrder);
|
||||
combinedPage.MenuOrder = Math.Min(
|
||||
combinedPage.MenuOrder,
|
||||
GetMenuOrder(item.Group.ParentMenu));
|
||||
combinedPage.GroupPriority = Math.Min(
|
||||
combinedPage.GroupPriority,
|
||||
item.Group.GroupPriority);
|
||||
}
|
||||
else
|
||||
{
|
||||
var page = new GuideOptionPage(
|
||||
item.Group,
|
||||
item.Group.MatchInfoGuidePageTitle,
|
||||
item.Group.MatchInfoGuidePageOrder,
|
||||
GetMenuOrder(item.Group.ParentMenu),
|
||||
item.Group.GroupPriority,
|
||||
Mathf.Clamp(item.Group.MatchInfoGuidePageColumns, 1, 6),
|
||||
item.ModAbbreviation,
|
||||
modSearchTerms,
|
||||
entries);
|
||||
pages.Add(page);
|
||||
if (combinedKey != null)
|
||||
{
|
||||
combinedPages.Add(combinedKey, page);
|
||||
}
|
||||
combinedPages.Add(combinedKey, page);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,7 +265,7 @@ internal static class GuideOptionPages
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning($"GroupVisible threw for {group.GetType().Name}: {e.Message}");
|
||||
Warning($"GroupVisible threw for {group.GetType().Name}: {e.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -282,7 +278,7 @@ internal static class GuideOptionPages
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning($"Visible threw for option '{option.Title}': {e.Message}");
|
||||
Warning($"Visible threw for option '{option.Title}': {e.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -292,51 +288,18 @@ internal static class GuideOptionPages
|
||||
AbstractOptionGroup group,
|
||||
string groupName,
|
||||
string modAbbreviation,
|
||||
IReadOnlyList<string> modSearchTerms,
|
||||
Func<bool, string> formatBool)
|
||||
IReadOnlyList<string> modSearchTerms)
|
||||
{
|
||||
try
|
||||
{
|
||||
var title = TranslationController.Instance.GetString(option.StringName);
|
||||
var value = FormatValue(option, formatBool);
|
||||
var value = option.GetDisplayValue();
|
||||
return new GuideOptionEntry(title, value, group, groupName, modAbbreviation, modSearchTerms);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning($"Could not format option '{option.Title}' for the guide: {e.Message}");
|
||||
Warning($"Could not format option '{option.Title}' for the guide: {e.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatValue(IModdedOption option, Func<bool, string> formatBool)
|
||||
{
|
||||
switch (option)
|
||||
{
|
||||
case ModdedToggleOption toggle:
|
||||
return formatBool(toggle.Value);
|
||||
|
||||
case ModdedEnumOption enumOption:
|
||||
return MiraLocaleManager.Get(
|
||||
enumOption.Values[enumOption.Value],
|
||||
enumOption.Values[enumOption.Value]);
|
||||
|
||||
case ModdedNumberOption number:
|
||||
if (number is { NegativeWordValue: not "#", Value: -1 })
|
||||
{
|
||||
return number.NegativeWordValue;
|
||||
}
|
||||
|
||||
if (number is { ZeroWordValue: not "#", Value: 0 })
|
||||
{
|
||||
return number.ZeroWordValue;
|
||||
}
|
||||
|
||||
return TrailingZeroRegex.Replace(number.Data.GetValueString(number.Value), string.Empty);
|
||||
|
||||
default:
|
||||
return option.Data.Type == OptionTypes.Checkbox
|
||||
? formatBool(Math.Abs(option.GetFloatData() - 1f) < 0.0001f)
|
||||
: option.Data.GetValueString(option.GetFloatData());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,12 @@ using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Translation;
|
||||
@@ -20,7 +19,6 @@ using Rewired;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using MethodBase = System.Reflection.MethodBase;
|
||||
using Object = UnityEngine.Object;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
@@ -34,7 +32,6 @@ internal readonly record struct AdvancedSettingsRow(string Title, string Value,
|
||||
public static class Helpers
|
||||
{
|
||||
internal const float MatchInfoGuideBaseScale = 1.3f;
|
||||
private static readonly Regex TrailingZeroRegex = new(@"\.0+(?!\d)", RegexOptions.Compiled, TimeSpan.FromMilliseconds(100));
|
||||
private static NumberOption? _advancedSettingsRowPrefab;
|
||||
|
||||
/// <summary>
|
||||
@@ -57,111 +54,54 @@ public static class Helpers
|
||||
public static string GetOptionsText(Type classType)
|
||||
{
|
||||
var group = ModdedOptionsManager.Groups.FirstOrDefault(x => x.OptionableType == classType);
|
||||
var options = group?.Children;
|
||||
|
||||
if (group == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var headerAdded = false;
|
||||
var options = group.Children;
|
||||
var builder = new StringBuilder();
|
||||
var summaryProvider = group as ISummarizedOptions;
|
||||
var hiddenKeys = summaryProvider?.WikiHiddenOptionKeys;
|
||||
|
||||
if (options == null)
|
||||
void EnsureHeader()
|
||||
{
|
||||
return string.Empty;
|
||||
if (headerAdded)
|
||||
return;
|
||||
|
||||
builder.AppendLine(MiraApiPlugin.Culture, $"\n<size=50%> \n</size><b><color=#29D7B5>{MiraLocaleManager.Get("Options")}</color></b>");
|
||||
headerAdded = true;
|
||||
}
|
||||
|
||||
if (summaryProvider != null)
|
||||
{
|
||||
foreach (var line in summaryProvider.GetWikiOptionSummaryLines())
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
|
||||
EnsureHeader();
|
||||
builder.AppendLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
var lines = new List<string>();
|
||||
var insertedSummary = false;
|
||||
foreach (var option in options)
|
||||
{
|
||||
StringNames? key = option switch
|
||||
{
|
||||
ModdedToggleOption t => t.StringName,
|
||||
ModdedEnumOption e => e.StringName,
|
||||
ModdedNumberOption n => n.StringName,
|
||||
_ => null,
|
||||
};
|
||||
var key = option.StringName;
|
||||
|
||||
if (!insertedSummary && summaryProvider != null && hiddenKeys != null &&
|
||||
key.HasValue && hiddenKeys.Contains(key.Value))
|
||||
{
|
||||
foreach (var line in summaryProvider.GetWikiOptionSummaryLines())
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
lines.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
insertedSummary = true;
|
||||
}
|
||||
|
||||
if (!option.Visible())
|
||||
{
|
||||
if (hiddenKeys?.Contains(key) == true || !option.Visible())
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key.HasValue && hiddenKeys?.Contains(key.Value) == true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
EnsureHeader();
|
||||
|
||||
switch (option)
|
||||
{
|
||||
case ModdedToggleOption toggleOption:
|
||||
lines.Add(
|
||||
TranslationController.Instance.GetString(toggleOption.StringName) + ": " +
|
||||
toggleOption.Value);
|
||||
break;
|
||||
/*case ModdedMultiSelectOption<Enum> enumOption:
|
||||
if (!enumOption.Visible())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.AppendLine(enumOption.Title + ": " + enumOption.Values[enumOption.Value]);
|
||||
break;*/
|
||||
case ModdedEnumOption enumOption:
|
||||
lines.Add(
|
||||
TranslationController.Instance.GetString(enumOption.StringName) + ": " +
|
||||
MiraLocaleManager.Get(
|
||||
enumOption.Values[enumOption.Value],
|
||||
enumOption.Values[enumOption.Value]));
|
||||
break;
|
||||
case ModdedNumberOption numberOption:
|
||||
var optionStr = TrailingZeroRegex.Replace(
|
||||
numberOption.Data.GetValueString(numberOption.Value),
|
||||
string.Empty);
|
||||
|
||||
var title = TranslationController.Instance.GetString(numberOption.StringName);
|
||||
if (numberOption is { NegativeWordValue: not "#", Value: -1 })
|
||||
{
|
||||
lines.Add(title + $": {numberOption.NegativeWordValue}");
|
||||
}
|
||||
else if (numberOption is { ZeroWordValue: not "#", Value: 0 })
|
||||
{
|
||||
lines.Add(title + $": {numberOption.ZeroWordValue}");
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.Add(title + ": " + optionStr);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
builder.Append(TranslationController.Instance.GetString(key))
|
||||
.Append(": ")
|
||||
.AppendLine(option.GetDisplayValue());
|
||||
}
|
||||
|
||||
if (lines.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine(
|
||||
MiraApiPlugin.Culture,
|
||||
$"\n<size=50%> \n</size><b><color=#29D7B5>{MiraLocaleManager.Get("Options")}</color></b>");
|
||||
foreach (var line in lines)
|
||||
{
|
||||
builder.AppendLine(line);
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
return builder.Length == 0 ? string.Empty : builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in new issue
Block a user