mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-21 13:37:54 +00:00
Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5049d16ac9 | ||
|
|
dccd06eac3 | ||
|
|
2c2a2207c5 | ||
|
|
960573243c | ||
|
|
94bb5a7a37 | ||
|
|
5eba77dba9 | ||
|
|
ef329bfcbd | ||
|
|
a95cac055f | ||
|
|
7909b8f13d | ||
|
|
8ad2e220c9 | ||
|
|
40d88750da | ||
|
|
28636e961d | ||
|
|
5eba1c9d7f | ||
|
|
7cbf9ffe1b | ||
|
|
38ec50bcc7 |
No files matched your search
@@ -7,3 +7,4 @@ bin/
|
|||||||
|
|
||||||
*.user
|
*.user
|
||||||
*.lock.json
|
*.lock.json
|
||||||
|
.qodo
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||||
|
|
||||||
<VersionPrefix>0.3.8</VersionPrefix>
|
<VersionPrefix>0.3.3</VersionPrefix>
|
||||||
<VersionSuffix>dev</VersionSuffix>
|
<VersionSuffix>dev</VersionSuffix>
|
||||||
|
|
||||||
<Authors>All Of Us, XtraCube, Angxl</Authors>
|
<Authors>All Of Us, XtraCube, Angxl</Authors>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Globalization;
|
using System.Linq;
|
||||||
using System.Linq;
|
|
||||||
using MiraAPI.Events;
|
using MiraAPI.Events;
|
||||||
using MiraAPI.Events.Mira;
|
using MiraAPI.Events.Mira;
|
||||||
using MiraAPI.Events.Vanilla.Gameplay;
|
using MiraAPI.Events.Vanilla.Gameplay;
|
||||||
@@ -18,7 +17,7 @@ public static class ExampleEventHandlers
|
|||||||
{
|
{
|
||||||
public static void Initialize()
|
public static void Initialize()
|
||||||
{
|
{
|
||||||
// You can register event handlers with the MiraEventManager class
|
// You can register event handlers with the MiraEventManager class.
|
||||||
var handle = MiraEventManager.RegisterEventHandler<BeforeMurderEvent>(@event =>
|
var handle = MiraEventManager.RegisterEventHandler<BeforeMurderEvent>(@event =>
|
||||||
{
|
{
|
||||||
Logger<ExamplePlugin>.Info($"{@event.Source.Data.PlayerName} is about to kill {@event.Target.Data.PlayerName}");
|
Logger<ExamplePlugin>.Info($"{@event.Source.Data.PlayerName} is about to kill {@event.Target.Data.PlayerName}");
|
||||||
@@ -38,21 +37,6 @@ public static class ExampleEventHandlers
|
|||||||
MiraEventManager.UnregisterEventHandler(handle);
|
MiraEventManager.UnregisterEventHandler(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If you want to check vanilla buttons, use something like this!
|
|
||||||
[RegisterEvent]
|
|
||||||
public static void VanillaButtonClickHandler(VanillaButtonClickEvent @event)
|
|
||||||
{
|
|
||||||
Logger<ExamplePlugin>.Info($"{@event.Button.buttonLabelText.text} is being used!");
|
|
||||||
if (@event.PlayerTarget != null)
|
|
||||||
{
|
|
||||||
Logger<ExamplePlugin>.Info($"{@event.PlayerTarget.Data.PlayerName} is being targeted!");
|
|
||||||
}
|
|
||||||
else if (@event.VentTarget != null)
|
|
||||||
{
|
|
||||||
Logger<ExamplePlugin>.Info($"Vent with id {@event.VentTarget.Id.ToString(CultureInfo.InvariantCulture)} is being targeted!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If you want to add extra votes to a player, do something like this.
|
// If you want to add extra votes to a player, do something like this.
|
||||||
[RegisterEvent]
|
[RegisterEvent]
|
||||||
public static void StartMeetingEvent(StartMeetingEvent _)
|
public static void StartMeetingEvent(StartMeetingEvent _)
|
||||||
|
|||||||
@@ -19,11 +19,14 @@ public class ExampleOptions : AbstractOptionGroup
|
|||||||
[ModdedNumberOption("Number Opt", min: 0, max: 10, increment: .25f, formatString: "0.00", suffixType: MiraNumberSuffixes.Percent)]
|
[ModdedNumberOption("Number Opt", min: 0, max: 10, increment: .25f, formatString: "0.00", suffixType: MiraNumberSuffixes.Percent)]
|
||||||
public float NumberOpt { get; set; } = 4f;
|
public float NumberOpt { get; set; } = 4f;
|
||||||
|
|
||||||
[ModdedEnumOption("Best API", typeof(BestApi), ["Mira API", "Mitochondria", "Reactor"])]
|
[ModdedEnumOption("Best API", typeof(AuApis), ["Mira API", "Mitochondria", "Reactor"])]
|
||||||
public BestApi Opt { get; set; } = BestApi.MiraAPI;
|
public AuApis BestApiOpt { get; set; } = AuApis.MiraAPI;
|
||||||
|
|
||||||
|
[ModdedEnumOption<AuApis>("Worst API", ["Mira API", "Mitochondria", "Reactor"])]
|
||||||
|
public AuApis WorstApiOpt { get; set; } = AuApis.Mitochondria;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum BestApi
|
public enum AuApis
|
||||||
{
|
{
|
||||||
MiraAPI,
|
MiraAPI,
|
||||||
Mitochondria,
|
Mitochondria,
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ public class ExampleOptions2 : AbstractOptionGroup
|
|||||||
{
|
{
|
||||||
ChangedEvent = x => Logger<ExamplePlugin>.Info($"changed Enum Opt to {x}"),
|
ChangedEvent = x => Logger<ExamplePlugin>.Info($"changed Enum Opt to {x}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public ModdedMultiSelectOption<TestingData> MultiSelectOption { get; } = new(
|
||||||
|
"Emotions",
|
||||||
|
TestingData.Happy,
|
||||||
|
TestingData.All,
|
||||||
|
TestingData.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum TestingData : ulong
|
public enum TestingData : ulong
|
||||||
@@ -30,4 +36,6 @@ public enum TestingData : ulong
|
|||||||
Happy,
|
Happy,
|
||||||
Sad,
|
Sad,
|
||||||
Neutral,
|
Neutral,
|
||||||
|
All,
|
||||||
|
None,
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
using MiraAPI.Networking;
|
namespace MiraAPI.Events.Vanilla.Gameplay;
|
||||||
|
|
||||||
namespace MiraAPI.Events.Vanilla.Gameplay;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that is invoked before a player is murdered. This event is cancelable.
|
/// Event that is invoked before a player is murdered. This event is cancelable.
|
||||||
@@ -17,24 +15,6 @@ public sealed class BeforeMurderEvent : MiraCancelableEvent
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public PlayerControl Target { get; }
|
public PlayerControl Target { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets whether the murder was meant to be done in a meeting via an enum.
|
|
||||||
/// </summary>
|
|
||||||
public MeetingCheck InMeeting { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="BeforeMurderEvent"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">The player that is killing the target.</param>
|
|
||||||
/// <param name="target">The player that is being killed.</param>
|
|
||||||
/// <param name="inMeeting">Whether the murder is intended to be triggered in a meeting.</param>
|
|
||||||
public BeforeMurderEvent(PlayerControl source, PlayerControl target, MeetingCheck inMeeting)
|
|
||||||
{
|
|
||||||
Source = source;
|
|
||||||
Target = target;
|
|
||||||
InMeeting = inMeeting;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BeforeMurderEvent"/> class.
|
/// Initializes a new instance of the <see cref="BeforeMurderEvent"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -44,6 +24,5 @@ public sealed class BeforeMurderEvent : MiraCancelableEvent
|
|||||||
{
|
{
|
||||||
Source = source;
|
Source = source;
|
||||||
Target = target;
|
Target = target;
|
||||||
InMeeting = MeetingCheck.Ignore;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
using MiraAPI.Hud;
|
|
||||||
|
|
||||||
namespace MiraAPI.Events.Vanilla.Gameplay;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoked when a Vanilla Button click is cancelled. Do not use for custom buttons.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The Vanilla Button type.</typeparam>
|
|
||||||
public sealed class VanillaButtonCancelledEvent<T> : MiraEvent where T : AbilityButton
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets Vanilla Button whose click was cancelled.
|
|
||||||
/// </summary>
|
|
||||||
public T Button { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="VanillaButtonCancelledEvent{T}"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="button">The Mira Button whose click was cancelled.</param>
|
|
||||||
public VanillaButtonCancelledEvent(T button)
|
|
||||||
{
|
|
||||||
Button = button;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoked when a Vanilla Button click is cancelled. Do not use for custom buttons.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class VanillaButtonCancelledEvent : MiraEvent
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets Vanilla Button whose click was cancelled.
|
|
||||||
/// </summary>
|
|
||||||
public AbilityButton Button { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="VanillaButtonCancelledEvent"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="button">The Vanilla Button whose click was cancelled.</param>
|
|
||||||
public VanillaButtonCancelledEvent(AbilityButton button)
|
|
||||||
{
|
|
||||||
Button = button;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
using MiraAPI.Hud;
|
|
||||||
|
|
||||||
namespace MiraAPI.Events.Vanilla.Gameplay;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Button click event for Vanilla Buttons only. Do not use for custom buttons.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The Vanilla Button type.</typeparam>
|
|
||||||
public sealed class VanillaButtonClickEvent<T> : MiraCancelableEvent where T : AbilityButton
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets Vanilla Button that was clicked.
|
|
||||||
/// </summary>
|
|
||||||
public T Button { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the generic click event that is invoked before this button-specific event.
|
|
||||||
/// </summary>
|
|
||||||
public VanillaButtonClickEvent GenericClickEvent { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="VanillaButtonClickEvent{T}"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="button">The Mira Button that was clicked.</param>
|
|
||||||
/// <param name="genericClickEvent">The generic click event invoked before button-specific events.</param>
|
|
||||||
public VanillaButtonClickEvent(T button, VanillaButtonClickEvent genericClickEvent)
|
|
||||||
{
|
|
||||||
GenericClickEvent = genericClickEvent;
|
|
||||||
Button = button;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Button click event for Vanilla Buttons only. Do not use for custom buttons.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class VanillaButtonClickEvent : MiraCancelableEvent
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets Vanilla Button that was clicked.
|
|
||||||
/// </summary>
|
|
||||||
public AbilityButton Button { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the target player, if any, of the Vanilla Button that was clicked.
|
|
||||||
/// </summary>
|
|
||||||
public PlayerControl? PlayerTarget { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the target vent, if any, of the Vanilla Button that was clicked.
|
|
||||||
/// </summary>
|
|
||||||
public Vent? VentTarget { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="VanillaButtonClickEvent"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="button">The Vanilla Button that was clicked.</param>
|
|
||||||
/// <param name="playerTarget">The Player target, if any.</param>
|
|
||||||
/// <param name="ventTarget">The Vent target, if any.</param>
|
|
||||||
public VanillaButtonClickEvent(AbilityButton button, PlayerControl? playerTarget = null, Vent? ventTarget = null)
|
|
||||||
{
|
|
||||||
Button = button;
|
|
||||||
PlayerTarget = playerTarget;
|
|
||||||
VentTarget = ventTarget;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
namespace MiraAPI.Events.Vanilla.Player;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The event that is invoked when a player completes a task within Hide and Seek. Non-cancelable.
|
|
||||||
/// </summary>
|
|
||||||
public class CompleteHnsTaskEvent : MiraEvent
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the instance of the <see cref="PlayerControl"/>.
|
|
||||||
/// </summary>
|
|
||||||
public PlayerControl Player { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the instance of the <see cref="PlayerTask"/> that the player completed.
|
|
||||||
/// </summary>
|
|
||||||
public PlayerTask Task { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the instance of the <see cref="HnsManager"/> attached to the game.
|
|
||||||
/// </summary>
|
|
||||||
public HideAndSeekManager HnsManager { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="CompleteHnsTaskEvent"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="hnsManager">The Hide and Seek Manager currently active.</param>
|
|
||||||
/// <param name="player">The player who completed the task.</param>
|
|
||||||
/// <param name="task">The task that the player completed.</param>
|
|
||||||
public CompleteHnsTaskEvent(HideAndSeekManager hnsManager, PlayerControl player, PlayerTask task)
|
|
||||||
{
|
|
||||||
HnsManager = hnsManager;
|
|
||||||
Player = player;
|
|
||||||
Task = task;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,3 +32,33 @@ public class ModdedEnumOptionAttribute(string title, Type enumType, string[]? va
|
|||||||
: throw new InvalidOperationException($"HolderOption for option \"{Title}\" with EnumType ${enumType.FullName} is not a ModdedEnumOption");
|
: throw new InvalidOperationException($"HolderOption for option \"{Title}\" with EnumType ${enumType.FullName} is not a ModdedEnumOption");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generic attribute for creating an enum option.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">An enum type.</typeparam>
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
public class ModdedEnumOptionAttribute<T>(string title, string[]? values = null)
|
||||||
|
: ModdedOptionAttribute(title) where T : struct, Enum
|
||||||
|
{
|
||||||
|
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
|
||||||
|
{
|
||||||
|
var opt = new ModdedEnumOption<T>(Title, (T)(value ?? 0), values);
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void SetValue(object value)
|
||||||
|
{
|
||||||
|
var opt = HolderOption as ModdedEnumOption;
|
||||||
|
opt?.SetValue((int)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override object GetValue()
|
||||||
|
{
|
||||||
|
return HolderOption is ModdedEnumOption<T> opt
|
||||||
|
? Enum.ToObject(typeof(T), opt.Value)
|
||||||
|
: throw new InvalidOperationException($"HolderOption for option \"{Title}\" is not a ModdedEnumOption<{typeof(T).Name}>");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,9 +17,9 @@ public interface IModdedOption
|
|||||||
uint Id { get; }
|
uint Id { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the title of the option.
|
/// Gets the title of the option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string Title { get; set; }
|
string Title { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the StringName for the option, used for localization.
|
/// Gets the StringName for the option, used for localization.
|
||||||
@@ -92,6 +92,11 @@ public interface IModdedOption
|
|||||||
/// <param name="saveDefault">Indicates whether to save the default value instead of the current value.</param>
|
/// <param name="saveDefault">Indicates whether to save the default value instead of the current value.</param>
|
||||||
void SaveToPreset(ConfigFile presetConfig, bool saveDefault = false);
|
void SaveToPreset(ConfigFile presetConfig, bool saveDefault = false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the scales and positions of the option if it's attached to the game options menu.
|
||||||
|
/// </summary>
|
||||||
|
void ChangeGameSetting();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Binds the option to a configuration file.
|
/// Binds the option to a configuration file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using Il2CppInterop.Runtime.Attributes;
|
|
||||||
using MiraAPI.GameOptions.OptionTypes;
|
using MiraAPI.GameOptions.OptionTypes;
|
||||||
using Reactor.Utilities.Attributes;
|
using Reactor.Utilities.Attributes;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -15,7 +14,6 @@ public class MiraNumberOptionComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the modded option associated with the object.
|
/// Gets or sets the modded option associated with the object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HideFromIl2Cpp]
|
|
||||||
public ModdedNumberOption NumberOption { get; set; }
|
public ModdedNumberOption NumberOption { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -24,17 +22,7 @@ public class MiraNumberOptionComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
|||||||
public float DefaultIncrement { get; set; }
|
public float DefaultIncrement { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether holding shift will split the increment in half.
|
/// Gets or sets the value of whether or not holding shift will split the increment in half.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ShiftIncrementToggle { get; set; }
|
public bool ShiftIncrementToggle { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating what Zero displays.
|
|
||||||
/// </summary>
|
|
||||||
public string ZeroValue { get; set; } = "#";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating what Negative One displays.
|
|
||||||
/// </summary>
|
|
||||||
public string NegativeValue { get; set; } = "#";
|
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,6 @@ public static class ModdedOptionsManager
|
|||||||
private static readonly Dictionary<Type, AbstractOptionGroup> TypeToGroup = [];
|
private static readonly Dictionary<Type, AbstractOptionGroup> TypeToGroup = [];
|
||||||
|
|
||||||
internal static readonly Dictionary<OptionBehaviour, ModdedPlayerOption> CreatedPlayerOptions = [];
|
internal static readonly Dictionary<OptionBehaviour, ModdedPlayerOption> CreatedPlayerOptions = [];
|
||||||
internal static readonly Dictionary<OptionBehaviour, ModdedStringOption> CreatedStringOptions = [];
|
|
||||||
internal static readonly Dictionary<uint, IModdedOption> ModdedOptions = [];
|
internal static readonly Dictionary<uint, IModdedOption> ModdedOptions = [];
|
||||||
internal static readonly List<AbstractOptionGroup> Groups = [];
|
internal static readonly List<AbstractOptionGroup> Groups = [];
|
||||||
|
|
||||||
@@ -48,11 +47,13 @@ public static class ModdedOptionsManager
|
|||||||
TypeToGroup.Add(type, group);
|
TypeToGroup.Add(type, group);
|
||||||
pluginInfo.InternalOptionGroups.Add(group);
|
pluginInfo.InternalOptionGroups.Add(group);
|
||||||
|
|
||||||
|
#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
|
||||||
typeof(OptionGroupSingleton<>).MakeGenericType(type)
|
typeof(OptionGroupSingleton<>).MakeGenericType(type)
|
||||||
#pragma warning disable S3011
|
#pragma warning disable S3011
|
||||||
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
|
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
|
||||||
#pragma warning restore S3011
|
#pragma warning restore S3011
|
||||||
.SetValue(null, group);
|
.SetValue(null, group);
|
||||||
|
#pragma warning restore S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,11 @@ public class ModdedEnumOption : ModdedOption<int>
|
|||||||
stringOption.SetUpFromData(Data, 20);
|
stringOption.SetUpFromData(Data, 20);
|
||||||
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
||||||
|
|
||||||
// SetUpFromData method doesnt work correctly so we must set the values manually
|
// Changing the button text to make them more distinct from number options
|
||||||
|
stringOption.MinusBtn.ChangeButtonText("<");
|
||||||
|
stringOption.PlusBtn.ChangeButtonText(">");
|
||||||
|
|
||||||
|
// SetUpFromData method doesn't work correctly so we must set the values manually
|
||||||
stringOption.Title = StringName;
|
stringOption.Title = StringName;
|
||||||
stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray<StringNames>(0);
|
stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray<StringNames>(0);
|
||||||
stringOption.Value = Value;
|
stringOption.Value = Value;
|
||||||
@@ -63,6 +67,32 @@ public class ModdedEnumOption : ModdedOption<int>
|
|||||||
return stringOption;
|
return stringOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeGameSetting()
|
||||||
|
{
|
||||||
|
var stringOption = OptionBehaviour as StringOption;
|
||||||
|
|
||||||
|
stringOption!.MinusBtn.transform.localPosition += new Vector3(0.6f, 0f, 0f);
|
||||||
|
stringOption.PlusBtn.transform.localPosition += new Vector3(1.5f, 0f, 0f);
|
||||||
|
|
||||||
|
// The value box itself becomes the button
|
||||||
|
stringOption.PlusBtn.gameObject.SetActive(false);
|
||||||
|
stringOption.MinusBtn.gameObject.SetActive(false);
|
||||||
|
|
||||||
|
var background = stringOption.transform.GetChild(0);
|
||||||
|
background.localPosition += new Vector3(-0.8f, 0f, 0f);
|
||||||
|
background.localScale += new Vector3(1f, 0f, 0f);
|
||||||
|
|
||||||
|
var title = stringOption.TitleText;
|
||||||
|
title.transform.localPosition = new(-2.0466f, 0f, -2.9968f);
|
||||||
|
title.GetComponent<RectTransform>().sizeDelta = new(5.8f, 0.458f);
|
||||||
|
title.fontSize = 2.9f; // Why is it different for string options??
|
||||||
|
|
||||||
|
var valueBox = stringOption.transform.GetChild(5);
|
||||||
|
valueBox.localPosition += new Vector3(1.05f, 0f, 0f);
|
||||||
|
valueBox.localScale += new Vector3(0.2f, 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override float GetFloatData()
|
public override float GetFloatData()
|
||||||
{
|
{
|
||||||
@@ -107,7 +137,7 @@ public class ModdedEnumOption : ModdedOption<int>
|
|||||||
/// An option for selecting an enum value.
|
/// An option for selecting an enum value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The enum type.</typeparam>
|
/// <typeparam name="T">The enum type.</typeparam>
|
||||||
public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
public class ModdedEnumOption<T> : ModdedOption<T> where T : struct, Enum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the string values of the enum.
|
/// Gets the string values of the enum.
|
||||||
@@ -118,12 +148,12 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
|||||||
/// Initializes a new instance of the <see cref="ModdedEnumOption{T}"/> class.
|
/// Initializes a new instance of the <see cref="ModdedEnumOption{T}"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="title">The title of the option.</param>
|
/// <param name="title">The title of the option.</param>
|
||||||
/// <param name="defaultValue">The default value as an int.</param>
|
/// <param name="defaultValue">The default value.</param>
|
||||||
/// <param name="values">An option list of string values to use in place of the enum name.</param>
|
/// <param name="values">An option list of string values to use in place of the enum name.</param>
|
||||||
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
|
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
|
||||||
public ModdedEnumOption(string title, T defaultValue, string[]? values = null, bool includeInPreset=true) : base(title, defaultValue, includeInPreset)
|
public ModdedEnumOption(string title, T defaultValue, string[]? values = null, bool includeInPreset=true) : base(title, defaultValue, includeInPreset)
|
||||||
{
|
{
|
||||||
Values = values ?? Enum.GetNames(typeof(T));
|
Values = values ?? Enum.GetNames<T>();
|
||||||
Data = ScriptableObject.CreateInstance<StringGameSetting>();
|
Data = ScriptableObject.CreateInstance<StringGameSetting>();
|
||||||
var data = (StringGameSetting)Data;
|
var data = (StringGameSetting)Data;
|
||||||
|
|
||||||
@@ -144,9 +174,13 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
|||||||
$"{ParentMod!.OptionsTitleText}.EnumOption.{TranslationController.Instance.GetString(StringName)}";
|
$"{ParentMod!.OptionsTitleText}.EnumOption.{TranslationController.Instance.GetString(StringName)}";
|
||||||
|
|
||||||
stringOption.SetUpFromData(Data, 20);
|
stringOption.SetUpFromData(Data, 20);
|
||||||
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
stringOption.OnValueChanged = (Action<OptionBehaviour>)ValueChanged;
|
||||||
|
|
||||||
// SetUpFromData method doesnt work correctly so we must set the values manually
|
// Changing the button text to make them more distinct from number options
|
||||||
|
stringOption.MinusBtn.ChangeButtonText("<");
|
||||||
|
stringOption.PlusBtn.ChangeButtonText(">");
|
||||||
|
|
||||||
|
// SetUpFromData method doesn't work correctly so we must set the values manually
|
||||||
stringOption.Title = StringName;
|
stringOption.Title = StringName;
|
||||||
stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray<StringNames>(0);
|
stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray<StringNames>(0);
|
||||||
stringOption.Value = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo);
|
stringOption.Value = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo);
|
||||||
@@ -156,6 +190,32 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
|||||||
return stringOption;
|
return stringOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeGameSetting()
|
||||||
|
{
|
||||||
|
var stringOption = OptionBehaviour as StringOption;
|
||||||
|
|
||||||
|
stringOption!.MinusBtn.transform.localPosition += new Vector3(0.6f, 0f, 0f);
|
||||||
|
stringOption.PlusBtn.transform.localPosition += new Vector3(1.5f, 0f, 0f);
|
||||||
|
|
||||||
|
// The value box itself becomes the button
|
||||||
|
stringOption.PlusBtn.gameObject.SetActive(false);
|
||||||
|
stringOption.MinusBtn.gameObject.SetActive(false);
|
||||||
|
|
||||||
|
var background = stringOption.transform.GetChild(0);
|
||||||
|
background.localPosition += new Vector3(-0.8f, 0f, 0f);
|
||||||
|
background.localScale += new Vector3(1f, 0f, 0f);
|
||||||
|
|
||||||
|
var title = stringOption.TitleText;
|
||||||
|
title.transform.localPosition = new(-2.0466f, 0f, -2.9968f);
|
||||||
|
title.GetComponent<RectTransform>().sizeDelta = new(5.8f, 0.458f);
|
||||||
|
title.fontSize = 2.9f; // Why is it different for string options??
|
||||||
|
|
||||||
|
var valueBox = stringOption.transform.GetChild(5);
|
||||||
|
valueBox.localPosition += new Vector3(1.05f, 0f, 0f);
|
||||||
|
valueBox.localScale += new Vector3(0.2f, 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override float GetFloatData()
|
public override float GetFloatData()
|
||||||
{
|
{
|
||||||
@@ -171,19 +231,19 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void HandleNetData(byte[] data)
|
public override void HandleNetData(byte[] data)
|
||||||
{
|
{
|
||||||
SetValue((T)Enum.Parse(typeof(T), Encoding.Unicode.GetString(data)));
|
SetValue(Enum.Parse<T>(Encoding.Unicode.GetString(data)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
|
public override T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
|
||||||
{
|
{
|
||||||
return (T)Enum.Parse(typeof(T), optionBehaviour.GetInt().ToString(NumberFormatInfo.InvariantInfo));
|
return Enum.Parse<T>(optionBehaviour.GetInt().ToString(NumberFormatInfo.InvariantInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void OnValueChanged(T newValue)
|
protected override void OnValueChanged(T newValue)
|
||||||
{
|
{
|
||||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo)), false);
|
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data!.GetValueString(Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo)), false);
|
||||||
if (!OptionBehaviour)
|
if (!OptionBehaviour)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
using AmongUs.GameOptions;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||||
|
using MiraAPI.Modules;
|
||||||
|
using MiraAPI.Networking;
|
||||||
|
using MiraAPI.Utilities;
|
||||||
|
using MiraAPI.Utilities.Assets;
|
||||||
|
using Reactor.Utilities.Extensions;
|
||||||
|
using UnityEngine;
|
||||||
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
|
namespace MiraAPI.GameOptions.OptionTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An option for selecting an enum value.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The enum type.</typeparam>
|
||||||
|
public class ModdedMultiSelectOption<T> : ModdedOption<T> where T : struct, Enum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the string values of the enum.
|
||||||
|
/// </summary>
|
||||||
|
public string[]? Values { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating "all of the above".
|
||||||
|
/// </summary>
|
||||||
|
public T All { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating "none of the above".
|
||||||
|
/// </summary>
|
||||||
|
public T None { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the list of buttons that relate to the current instance.
|
||||||
|
/// </summary>
|
||||||
|
public List<MonoBehaviour> Buttons { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ModdedMultiSelectOption{T}"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="title">The title of the option.</param>
|
||||||
|
/// <param name="defaultValue">The default value as an int.</param>
|
||||||
|
/// <param name="all">The all value.</param>
|
||||||
|
/// <param name="none">The none value.</param>
|
||||||
|
/// <param name="values">An option list of string values to use in place of the enum name.</param>
|
||||||
|
/// <exception cref="ArgumentException">The enum type doesn't have the flags attribute.</exception>
|
||||||
|
public ModdedMultiSelectOption(string title, T defaultValue, T all, T none, string[]? values = null) : base(title, defaultValue)
|
||||||
|
{
|
||||||
|
var tType = typeof(T);
|
||||||
|
|
||||||
|
if (tType.GetCustomAttribute<FlagsAttribute>() == null)
|
||||||
|
throw new ArgumentException($"{tType.Name} is not a flag enum type");
|
||||||
|
|
||||||
|
Values = values ?? Enum.GetNames<T>();
|
||||||
|
All = all;
|
||||||
|
None = none;
|
||||||
|
Data = ScriptableObject.CreateInstance<MultiSelectGameSetting>();
|
||||||
|
Buttons = [];
|
||||||
|
|
||||||
|
var data = (MultiSelectGameSetting)Data;
|
||||||
|
data.Title = StringName;
|
||||||
|
data.OptionName = Int32OptionNames.Invalid;
|
||||||
|
data.Type = global::OptionTypes.MultipleChoice; // How nice of the game to provide an enum value themselves
|
||||||
|
data.EnumNames = []; // We don't initialise the translation here, we just add one as and when needed
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override OptionBehaviour CreateOption(
|
||||||
|
ToggleOption toggleOpt,
|
||||||
|
NumberOption numberOpt,
|
||||||
|
StringOption stringOpt,
|
||||||
|
PlayerOption playerOpt,
|
||||||
|
Transform container)
|
||||||
|
{
|
||||||
|
var stringOption = Object.Instantiate(stringOpt, container);
|
||||||
|
|
||||||
|
stringOption.SetUpFromData(Data, 20);
|
||||||
|
stringOption.OnValueChanged = (Action<OptionBehaviour>)ValueChanged;
|
||||||
|
|
||||||
|
var toggle = Object.Instantiate(toggleOpt.GetComponentInChildren<PassiveButton>(), stringOption.transform);
|
||||||
|
toggle.name = "Button";
|
||||||
|
toggle.transform.DestroyChildren();
|
||||||
|
toggle.OverrideOnClickListeners(ToggleButtons);
|
||||||
|
|
||||||
|
var box = toggle.GetComponent<BoxCollider2D>();
|
||||||
|
var prevColliderSize = box.size;
|
||||||
|
prevColliderSize.x *= 4.97f;
|
||||||
|
box.size = prevColliderSize;
|
||||||
|
|
||||||
|
var rend = stringOption.transform.GetChild(5).GetComponent<SpriteRenderer>();
|
||||||
|
toggle.OverrideOnMouseOverListeners(() => rend.color = Buttons.Count > 0 ? Color.white : MiraAssets.AcceptedTeal);
|
||||||
|
toggle.OverrideOnMouseOutListeners(() => rend.color = Buttons.Count > 0 ? MiraAssets.AcceptedTeal : Color.white);
|
||||||
|
|
||||||
|
// SetUpFromData method doesn't work correctly so we must set the values manually
|
||||||
|
stringOption.Title = StringName;
|
||||||
|
stringOption.Values = (Data as MultiSelectGameSetting)?.EnumNames?.Values?.ToArray() ?? new Il2CppStructArray<StringNames>(0);
|
||||||
|
stringOption.Value = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo);
|
||||||
|
|
||||||
|
OptionBehaviour = stringOption;
|
||||||
|
|
||||||
|
return stringOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeGameSetting()
|
||||||
|
{
|
||||||
|
var stringOption = OptionBehaviour as StringOption;
|
||||||
|
|
||||||
|
// The value box itself becomes the button
|
||||||
|
stringOption!.PlusBtn.gameObject.SetActive(false);
|
||||||
|
stringOption.MinusBtn.gameObject.SetActive(false);
|
||||||
|
|
||||||
|
var background = stringOption.transform.GetChild(0);
|
||||||
|
background.localPosition += new Vector3(-0.8f, 0f, 0f);
|
||||||
|
background.localScale += new Vector3(1f, 0f, 0f);
|
||||||
|
|
||||||
|
var title = stringOption.TitleText;
|
||||||
|
title.transform.localPosition = new(-2.0466f, 0f, -2.9968f);
|
||||||
|
title.GetComponent<RectTransform>().sizeDelta = new(5.8f, 0.458f);
|
||||||
|
title.fontSize = 2.9f; // Why is it different for string options??
|
||||||
|
|
||||||
|
var valueBox = stringOption.transform.GetChild(5);
|
||||||
|
valueBox.localPosition += new Vector3(1.05f, 0f, 0f);
|
||||||
|
valueBox.localScale += new Vector3(0.2f, 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleButtons()
|
||||||
|
{
|
||||||
|
if (Buttons.Count > 0)
|
||||||
|
{
|
||||||
|
Buttons.ForEach(x => x.gameObject.Destroy());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement this
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override float GetFloatData()
|
||||||
|
{
|
||||||
|
return Convert.ToSingle(Value, NumberFormatInfo.InvariantInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override NetData GetNetData()
|
||||||
|
{
|
||||||
|
return new NetData(Id, EnumSerializer.EnumToBytes(Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void HandleNetData(byte[] data)
|
||||||
|
{
|
||||||
|
SetValue(EnumSerializer.EnumFromBytes<T>(data, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
|
||||||
|
{
|
||||||
|
return Enum.Parse<T>(optionBehaviour.GetInt().ToString(NumberFormatInfo.InvariantInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnValueChanged(T newValue)
|
||||||
|
{
|
||||||
|
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data!.GetValueString(Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo)), false);
|
||||||
|
if (!OptionBehaviour)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OptionBehaviour is StringOption opt)
|
||||||
|
{
|
||||||
|
opt.Value = Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,83 +32,16 @@ public class ModdedNumberOption : ModdedOption<float>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public MiraNumberSuffixes SuffixType { get; }
|
public MiraNumberSuffixes SuffixType { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating what zero appears as.
|
|
||||||
/// </summary>
|
|
||||||
public string ZeroWordValue { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating what a negative one appears as.
|
|
||||||
/// </summary>
|
|
||||||
public string NegativeWordValue { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether zero is infinity.
|
/// Gets a value indicating whether zero is infinity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ZeroInfinity => ZeroWordValue == "∞";
|
public bool ZeroInfinity { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether holding shift will divide the increment.
|
/// Gets a value indicating whether holding shift will divide the increment.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ShiftIncrement { get; }
|
public bool ShiftIncrement { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="ModdedNumberOption"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="title">The title of the option.</param>
|
|
||||||
/// <param name="defaultValue">The default value as a float.</param>
|
|
||||||
/// <param name="min">The minimum value.</param>
|
|
||||||
/// <param name="max">The maximum value.</param>
|
|
||||||
/// <param name="increment">The increment.</param>
|
|
||||||
/// <param name="suffixType">The suffix type.</param>
|
|
||||||
/// <param name="formatString">Optional format string for the option screen.</param>
|
|
||||||
/// <param name="zeroBehavior">Determines what is shown for zero. Options: ∞, #, or a word value (such as Off).</param>
|
|
||||||
/// <param name="negativeBehavior">Determines what is shown for negative 1. Options: ∞, #, or a word value (such as Off).</param>
|
|
||||||
/// <param name="halfIncrements">Whether increments can be split in half.</param>
|
|
||||||
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
|
|
||||||
public ModdedNumberOption(
|
|
||||||
string title,
|
|
||||||
float defaultValue,
|
|
||||||
float min,
|
|
||||||
float max,
|
|
||||||
float increment,
|
|
||||||
string zeroBehavior,
|
|
||||||
string negativeBehavior,
|
|
||||||
MiraNumberSuffixes suffixType,
|
|
||||||
string? formatString = null,
|
|
||||||
bool halfIncrements = false,
|
|
||||||
bool includeInPreset = true) : base(title, defaultValue, includeInPreset)
|
|
||||||
{
|
|
||||||
Min = min;
|
|
||||||
Max = max;
|
|
||||||
Increment = increment;
|
|
||||||
SuffixType = suffixType;
|
|
||||||
ZeroWordValue = zeroBehavior;
|
|
||||||
NegativeWordValue = negativeBehavior;
|
|
||||||
ShiftIncrement = halfIncrements;
|
|
||||||
|
|
||||||
Value = Mathf.Clamp(defaultValue, min, max);
|
|
||||||
|
|
||||||
Data = ScriptableObject.CreateInstance<FloatGameSetting>();
|
|
||||||
|
|
||||||
var data = (FloatGameSetting)Data;
|
|
||||||
data.Type = global::OptionTypes.Float;
|
|
||||||
data.Title = StringName;
|
|
||||||
data.Value = Value;
|
|
||||||
data.Increment = Increment;
|
|
||||||
data.ValidRange = new FloatRange(Min, Max);
|
|
||||||
data.FormatString = formatString ??
|
|
||||||
(defaultValue.IsInteger() &&
|
|
||||||
Increment.IsInteger() &&
|
|
||||||
Value.IsInteger() &&
|
|
||||||
Min.IsInteger() &&
|
|
||||||
Max.IsInteger() ? "0" : "0.0");
|
|
||||||
|
|
||||||
data.ZeroIsInfinity = ZeroInfinity;
|
|
||||||
data.SuffixType = (NumberSuffixes)SuffixType;
|
|
||||||
data.OptionName = FloatOptionNames.Invalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ModdedNumberOption"/> class.
|
/// Initializes a new instance of the <see cref="ModdedNumberOption"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -121,6 +54,7 @@ public class ModdedNumberOption : ModdedOption<float>
|
|||||||
/// <param name="formatString">Optional format string for the option screen.</param>
|
/// <param name="formatString">Optional format string for the option screen.</param>
|
||||||
/// <param name="zeroInfinity">Whether zero is infinity or not.</param>
|
/// <param name="zeroInfinity">Whether zero is infinity or not.</param>
|
||||||
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
|
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
|
||||||
|
/// <param name="shiftIncrement">Whether holding shift will divide the increment or not.</param>
|
||||||
public ModdedNumberOption(
|
public ModdedNumberOption(
|
||||||
string title,
|
string title,
|
||||||
float defaultValue,
|
float defaultValue,
|
||||||
@@ -136,8 +70,7 @@ public class ModdedNumberOption : ModdedOption<float>
|
|||||||
Max = max;
|
Max = max;
|
||||||
Increment = increment;
|
Increment = increment;
|
||||||
SuffixType = suffixType;
|
SuffixType = suffixType;
|
||||||
ZeroWordValue = zeroInfinity ? "∞" : "#";
|
ZeroInfinity = zeroInfinity;
|
||||||
NegativeWordValue = "#";
|
|
||||||
ShiftIncrement = increment != 1f;
|
ShiftIncrement = increment != 1f;
|
||||||
|
|
||||||
Value = Mathf.Clamp(defaultValue, min, max);
|
Value = Mathf.Clamp(defaultValue, min, max);
|
||||||
@@ -177,8 +110,6 @@ public class ModdedNumberOption : ModdedOption<float>
|
|||||||
optionComponent.NumberOption = this;
|
optionComponent.NumberOption = this;
|
||||||
optionComponent.DefaultIncrement = Increment;
|
optionComponent.DefaultIncrement = Increment;
|
||||||
optionComponent.ShiftIncrementToggle = ShiftIncrement;
|
optionComponent.ShiftIncrementToggle = ShiftIncrement;
|
||||||
optionComponent.NegativeValue = NegativeWordValue;
|
|
||||||
optionComponent.ZeroValue = ZeroWordValue;
|
|
||||||
|
|
||||||
numberOption.SetUpFromData(Data, 20);
|
numberOption.SetUpFromData(Data, 20);
|
||||||
numberOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
numberOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
||||||
@@ -197,6 +128,27 @@ public class ModdedNumberOption : ModdedOption<float>
|
|||||||
return numberOption;
|
return numberOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeGameSetting()
|
||||||
|
{
|
||||||
|
var numberOption = OptionBehaviour as NumberOption;
|
||||||
|
|
||||||
|
numberOption!.PlusBtn.transform.localPosition += new Vector3(0.6f, 0f, 0f);
|
||||||
|
numberOption.MinusBtn.transform.localPosition += new Vector3(1.5f, 0f, 0f);
|
||||||
|
|
||||||
|
var background = numberOption.transform.GetChild(0);
|
||||||
|
background.localPosition += new Vector3(-0.8f, 0f, 0f);
|
||||||
|
background.localScale += new Vector3(1f, 0f, 0f);
|
||||||
|
|
||||||
|
var title = numberOption.TitleText;
|
||||||
|
title.transform.localPosition = new(-2.0466f, 0f, -2.9968f);
|
||||||
|
title.GetComponent<RectTransform>().sizeDelta = new(5.8f, 0.458f);
|
||||||
|
|
||||||
|
var valueBox = numberOption.transform.GetChild(5);
|
||||||
|
valueBox.localPosition += new Vector3(1.05f, 0f, 0f);
|
||||||
|
valueBox.localScale += new Vector3(0.2f, 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override float GetFloatData()
|
public override float GetFloatData()
|
||||||
{
|
{
|
||||||
@@ -227,7 +179,7 @@ public class ModdedNumberOption : ModdedOption<float>
|
|||||||
Value = Mathf.Clamp(newValue, Min, Max);
|
Value = Mathf.Clamp(newValue, Min, Max);
|
||||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(
|
HudManager.Instance.Notifier.AddSettingsChangeMessage(
|
||||||
StringName,
|
StringName,
|
||||||
Data.GetValueString(Value),
|
Data!.GetValueString(Value),
|
||||||
false);
|
false);
|
||||||
|
|
||||||
if (OptionBehaviour is NumberOption opt)
|
if (OptionBehaviour is NumberOption opt)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public abstract class ModdedOption<T> : IModdedOption
|
|||||||
public uint Id { get; }
|
public uint Id { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string Title { get; set; }
|
public string Title { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public StringNames StringName { get; }
|
public StringNames StringName { get; }
|
||||||
@@ -181,6 +181,23 @@ public abstract class ModdedOption<T> : IModdedOption
|
|||||||
PlayerOption playerOpt,
|
PlayerOption playerOpt,
|
||||||
Transform container);
|
Transform container);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Visually adjust the game option as needed.
|
||||||
|
/// </summary>
|
||||||
|
public abstract void ChangeGameSetting();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binds the option to the configuration file of the parent mod, if available.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void BindConfig()
|
||||||
|
{
|
||||||
|
var entry = ParentMod?.GetConfigFile().Bind(ConfigDefinition, DefaultValue);
|
||||||
|
if (entry != null)
|
||||||
|
{
|
||||||
|
Value = entry.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implicitly converts the option to type of <typeparamref name="T"/>.
|
/// Implicitly converts the option to type of <typeparamref name="T"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -28,6 +28,27 @@ public class ModdedPlayerOption : ModdedOption<int>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AllowNone { get; set; }
|
public bool AllowNone { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeGameSetting()
|
||||||
|
{
|
||||||
|
var numberOption = OptionBehaviour as PlayerOption;
|
||||||
|
|
||||||
|
numberOption!.PlusBtn.transform.localPosition += new Vector3(0.6f, 0f, 0f);
|
||||||
|
numberOption.MinusBtn.transform.localPosition += new Vector3(1.5f, 0f, 0f);
|
||||||
|
|
||||||
|
var background = numberOption.transform.GetChild(0);
|
||||||
|
background.localPosition += new Vector3(-0.8f, 0f, 0f);
|
||||||
|
background.localScale += new Vector3(1f, 0f, 0f);
|
||||||
|
|
||||||
|
var title = numberOption.TitleText;
|
||||||
|
title.transform.localPosition = new(-2.0466f, 0f, -2.9968f);
|
||||||
|
title.GetComponent<RectTransform>().sizeDelta = new(5.8f, 0.458f);
|
||||||
|
|
||||||
|
var valueBox = numberOption.transform.GetChild(5);
|
||||||
|
valueBox.localPosition += new Vector3(1.05f, 0f, 0f);
|
||||||
|
valueBox.localScale += new Vector3(0.2f, 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ModdedPlayerOption"/> class.
|
/// Initializes a new instance of the <see cref="ModdedPlayerOption"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,177 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
|
||||||
using MiraAPI.Networking;
|
|
||||||
using Reactor.Localization.Utilities;
|
|
||||||
using UnityEngine;
|
|
||||||
using Object = UnityEngine.Object;
|
|
||||||
|
|
||||||
namespace MiraAPI.GameOptions.OptionTypes;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// An option for selecting an enum value.
|
|
||||||
/// </summary>
|
|
||||||
public class ModdedStringOption : ModdedOption<string>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the string values of the option.
|
|
||||||
/// </summary>
|
|
||||||
public string[] Values { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the String Name values of the option.
|
|
||||||
/// </summary>
|
|
||||||
public StringNames[] StringNameValues { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the string's index
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">The option's string value.</param>
|
|
||||||
/// <returns>The option's value in the index.</returns>
|
|
||||||
public int GetIndex(string value, int location)
|
|
||||||
{
|
|
||||||
if (Values.Contains(value))
|
|
||||||
{
|
|
||||||
return Values.ToList().IndexOf(value);
|
|
||||||
}
|
|
||||||
Info($"Cannot find a valid index for {value} in {location}. Returning 0 instead. Values: {string.Join(", ", Values)}");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a value to the option.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">The option's string value.</param>
|
|
||||||
/// <param name="index">The additional value's index on the list.</param>
|
|
||||||
public void RegisterExtraValue(string value, int index = -1)
|
|
||||||
{
|
|
||||||
if (!Values.Contains(value))
|
|
||||||
{
|
|
||||||
if (index <= -1)
|
|
||||||
index = Values.Length;
|
|
||||||
|
|
||||||
var newVals = Values.ToList();
|
|
||||||
newVals.Insert(index, value);
|
|
||||||
Values = newVals.ToArray();
|
|
||||||
var newStrVals = StringNameValues.ToList();
|
|
||||||
newStrVals.Insert(index, CustomStringName.CreateAndRegister(value));
|
|
||||||
StringNameValues = newStrVals.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Data != null)
|
|
||||||
{
|
|
||||||
var data = (StringGameSetting)Data;
|
|
||||||
data.Values = StringNameValues;
|
|
||||||
|
|
||||||
data.Index = GetIndex(Value, 1);
|
|
||||||
if (OptionBehaviour != null)
|
|
||||||
{
|
|
||||||
var strOpt = OptionBehaviour.TryCast<StringOption>();
|
|
||||||
if (strOpt != null)
|
|
||||||
{
|
|
||||||
strOpt.Values = StringNameValues;
|
|
||||||
strOpt.Value = GetIndex(Value, 2);
|
|
||||||
Warning("StringOption is valid");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Error("StringOption is null.");
|
|
||||||
}
|
|
||||||
Warning("OptionBehaviour is valid");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Error("OptionBehaviour is null.");
|
|
||||||
}
|
|
||||||
Warning("Data is valid");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Error("Data is null.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="ModdedStringOption"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="title">The title of the option.</param>
|
|
||||||
/// <param name="defaultValue">The default value as a string.</param>
|
|
||||||
/// <param name="values">An option list of string values to use.</param>
|
|
||||||
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
|
|
||||||
public ModdedStringOption(string title, string defaultValue, string[] values, bool includeInPreset=true) : base(title, defaultValue, includeInPreset)
|
|
||||||
{
|
|
||||||
Values = values;
|
|
||||||
Data = ScriptableObject.CreateInstance<StringGameSetting>();
|
|
||||||
var data = (StringGameSetting)Data;
|
|
||||||
|
|
||||||
data.Title = StringName;
|
|
||||||
data.Type = global::OptionTypes.String;
|
|
||||||
StringNameValues = values.Select(CustomStringName.CreateAndRegister).ToArray();
|
|
||||||
data.Values = StringNameValues;
|
|
||||||
|
|
||||||
data.Index = GetIndex(Value, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
|
|
||||||
{
|
|
||||||
var stringOption = Object.Instantiate(stringOpt, container);
|
|
||||||
stringOption.name =
|
|
||||||
$"{ParentMod!.OptionsTitleText}.StringOption.{TranslationController.Instance.GetString(StringName)}";
|
|
||||||
|
|
||||||
stringOption.SetUpFromData(Data, 20);
|
|
||||||
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
|
||||||
|
|
||||||
// SetUpFromData method doesnt work correctly so we must set the values manually
|
|
||||||
stringOption.Title = StringName;
|
|
||||||
stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray<StringNames>(0);
|
|
||||||
stringOption.Value = GetIndex(Value, 4);
|
|
||||||
|
|
||||||
OptionBehaviour = stringOption;
|
|
||||||
ModdedOptionsManager.CreatedStringOptions.TryAdd(stringOption, this);
|
|
||||||
|
|
||||||
return stringOption;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override float GetFloatData()
|
|
||||||
{
|
|
||||||
return Values.IndexOf(Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override NetData GetNetData()
|
|
||||||
{
|
|
||||||
return new NetData(Id, Encoding.Unicode.GetBytes(Convert.ToString(Value, NumberFormatInfo.InvariantInfo)!));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void HandleNetData(byte[] data)
|
|
||||||
{
|
|
||||||
SetValue(Encoding.Unicode.GetString(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
|
|
||||||
{
|
|
||||||
return Values[optionBehaviour.GetInt()];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void OnValueChanged(string newValue)
|
|
||||||
{
|
|
||||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(GetIndex(newValue, 5)), false);
|
|
||||||
if (!OptionBehaviour)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (OptionBehaviour is StringOption opt)
|
|
||||||
{
|
|
||||||
opt.Value = GetIndex(newValue, 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -44,6 +44,21 @@ public class ModdedToggleOption : ModdedOption<bool>
|
|||||||
return toggleOption;
|
return toggleOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeGameSetting()
|
||||||
|
{
|
||||||
|
var toggleOption = OptionBehaviour as ToggleOption;
|
||||||
|
toggleOption!.transform.GetChild(1).localPosition += new Vector3(2.2f, 0f, 0f); // Checkbox
|
||||||
|
|
||||||
|
var title = toggleOption.TitleText;
|
||||||
|
title.transform.localPosition = new(-2.0466f, 0f, -2.9968f);
|
||||||
|
title.GetComponent<RectTransform>().sizeDelta = new(5.8f, 0.458f);
|
||||||
|
|
||||||
|
var background = toggleOption.transform.GetChild(2);
|
||||||
|
background.localPosition += new Vector3(-0.8f, 0f, 0f);
|
||||||
|
background.localScale += new Vector3(1f, 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override float GetFloatData()
|
public override float GetFloatData()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -64,14 +64,9 @@ public abstract class CustomActionButton
|
|||||||
public virtual float EffectDuration => 0;
|
public virtual float EffectDuration => 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the button has limited uses.
|
/// Gets the maximum amount of uses the button has. If the button has infinite uses, set to 0.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool LimitedUses => ZeroIsInfinite ? MaxUses > 0 : MaxUses >= 0;
|
public virtual int MaxUses => 0;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the maximum number of uses the button has. If the button has infinite uses, set to 0 or -1 based on what value that ZeroIsInfinite is set to.
|
|
||||||
/// </summary>
|
|
||||||
public virtual int MaxUses => ZeroIsInfinite ? 0 : -1;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the value indicating uses mode.
|
/// Gets the value indicating uses mode.
|
||||||
@@ -94,9 +89,9 @@ public abstract class CustomActionButton
|
|||||||
public virtual ButtonLocation Location { get; set; } = ButtonLocation.BottomLeft;
|
public virtual ButtonLocation Location { get; set; } = ButtonLocation.BottomLeft;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether limited uses are determined via zero or a negative number of uses.
|
/// Gets a value indicating whether the button has limited uses.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool ZeroIsInfinite { get; set; } = true;
|
public bool LimitedUses => MaxUses > 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether the effect is currently active, if there is one.
|
/// Gets or sets a value indicating whether the effect is currently active, if there is one.
|
||||||
@@ -210,51 +205,19 @@ public abstract class CustomActionButton
|
|||||||
Keybind.OnActivate(() =>
|
Keybind.OnActivate(() =>
|
||||||
{
|
{
|
||||||
if (Enabled(PlayerControl.LocalPlayer.Data.Role))
|
if (Enabled(PlayerControl.LocalPlayer.Data.Role))
|
||||||
{
|
|
||||||
// Invoke the generic button click event.
|
|
||||||
var genericEvent = new MiraButtonClickEvent(this);
|
|
||||||
MiraEventManager.InvokeEvent(genericEvent);
|
|
||||||
if (genericEvent.IsCancelled)
|
|
||||||
{
|
|
||||||
MiraEventManager.InvokeEvent(new MiraButtonCancelledEvent(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invoke the button click event for specific button.
|
|
||||||
var eventType = CustomButtonManager.ButtonEventTypes[GetType()];
|
|
||||||
var @event = (MiraCancelableEvent)Activator.CreateInstance(eventType, this, genericEvent)!;
|
|
||||||
var specificInvoked = MiraEventManager.InvokeEvent(@event, eventType);
|
|
||||||
if (@event.IsCancelled)
|
|
||||||
{
|
|
||||||
var cancelEventType = CustomButtonManager.ButtonCancelledEventTypes[GetType()];
|
|
||||||
var cancelEvent = (MiraEvent)Activator.CreateInstance(cancelEventType, this)!;
|
|
||||||
MiraEventManager.InvokeEvent(cancelEvent, cancelEventType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (specificInvoked)
|
|
||||||
{
|
|
||||||
if (!@event.IsCancelled)
|
|
||||||
{
|
{
|
||||||
ClickHandler();
|
ClickHandler();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!genericEvent.IsCancelled)
|
|
||||||
{
|
|
||||||
ClickHandler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
KeybindIcon =
|
KeybindIcon =
|
||||||
Helpers.CreateKeybindIcon(
|
Helpers.CreateKeybindIcon(
|
||||||
Button.gameObject,
|
Button.gameObject,
|
||||||
Keybind.CurrentKey,
|
Keybind.CurrentKey,
|
||||||
new Vector3(0.4f, 0.45f, -9f)
|
new Vector3(MaxUses <= 0 ? -0.4f : 0.4f, 0.45f, -9f)
|
||||||
);
|
);
|
||||||
KeybindText = KeybindIcon.transform.GetChild(0).GetComponent<TextMeshPro>();
|
KeybindText = KeybindIcon.transform.GetChild(0).GetComponent<TextMeshPro>();
|
||||||
HudManagerPatches.ModdedKeybindIcons.Add(KeybindText);
|
HudManagerPatches.moddedKeybindIcons.Add(KeybindText);
|
||||||
Button.usesRemainingSprite.transform.localPosition = new(-0.341f, 0.45f, -0.1f);
|
Button.usesRemainingSprite.transform.localPosition = new(-0.341f, 0.45f, -0.1f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace MiraAPI.Keybinds;
|
|||||||
public class MiraKeybind : BaseKeybind
|
public class MiraKeybind : BaseKeybind
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets name of the keybind.
|
/// Gets name of the keybind
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ public class VanillaKeybind : BaseKeybind
|
|||||||
public VanillaKeybind(int id)
|
public VanillaKeybind(int id)
|
||||||
{
|
{
|
||||||
RewiredInputAction = KeybindUtils.GetInputActionById(id);
|
RewiredInputAction = KeybindUtils.GetInputActionById(id);
|
||||||
Id = RewiredInputAction != null ? RewiredInputAction.name : id.ToString();
|
Id = RewiredInputAction?.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
global using static Reactor.Utilities.Logger<MiraAPI.MiraApiPlugin>;
|
global using static Reactor.Utilities.Logger<MiraAPI.MiraApiPlugin>;
|
||||||
using System;
|
|
||||||
using BepInEx;
|
using BepInEx;
|
||||||
|
using BepInEx.Configuration;
|
||||||
using BepInEx.Unity.IL2CPP;
|
using BepInEx.Unity.IL2CPP;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using MiraAPI.PluginLoading;
|
using MiraAPI.PluginLoading;
|
||||||
@@ -22,11 +22,6 @@ namespace MiraAPI;
|
|||||||
[ReactorModFlags(ModFlags.RequireOnAllClients)]
|
[ReactorModFlags(ModFlags.RequireOnAllClients)]
|
||||||
public partial class MiraApiPlugin : BasePlugin
|
public partial class MiraApiPlugin : BasePlugin
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the current device is running Starlight (on mobile).
|
|
||||||
/// </summary>
|
|
||||||
public static bool IsMobile => Constants.GetPlatformType() is Platforms.Android or Platforms.IPhone;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the branding Mira API color.
|
/// Gets the branding Mira API color.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -37,11 +32,7 @@ public partial class MiraApiPlugin : BasePlugin
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static Color DefaultHeaderColor { get; } = new Color32(77, 77, 77, 255);
|
public static Color DefaultHeaderColor { get; } = new Color32(77, 77, 77, 255);
|
||||||
|
|
||||||
/// <summary>
|
internal static ConfigEntry<string>? DistanceSuffix { get; private set; }
|
||||||
/// Gets a value indicating whether the current build is a development build or not. This mostly avoids confusion for users asking why the mod appears red.
|
|
||||||
/// </summary>
|
|
||||||
public static bool IsDevBuild => Version.Contains("ci", StringComparison.OrdinalIgnoreCase) || Version.Contains("dev", StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
private static MiraPluginManager? PluginManager { get; set; }
|
private static MiraPluginManager? PluginManager { get; set; }
|
||||||
internal Harmony Harmony { get; } = new(Id);
|
internal Harmony Harmony { get; } = new(Id);
|
||||||
|
|
||||||
@@ -50,7 +41,9 @@ public partial class MiraApiPlugin : BasePlugin
|
|||||||
{
|
{
|
||||||
Harmony.PatchAll();
|
Harmony.PatchAll();
|
||||||
|
|
||||||
ReactorCredits.Register("Mira API", Version, IsDevBuild, ReactorCredits.AlwaysShow);
|
ReactorCredits.Register("Mira API", Version, true, ReactorCredits.AlwaysShow);
|
||||||
|
|
||||||
|
DistanceSuffix = Config.Bind("Display", "Distance", "unit(s)", "Dictates how a distance is shown in numeric option displays");
|
||||||
|
|
||||||
PluginManager = new MiraPluginManager();
|
PluginManager = new MiraPluginManager();
|
||||||
PluginManager.Initialize();
|
PluginManager.Initialize();
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
using BepInEx.Configuration;
|
using BepInEx.Configuration;
|
||||||
using MiraAPI.LocalSettings;
|
using MiraAPI.LocalSettings;
|
||||||
using MiraAPI.LocalSettings.Attributes;
|
using MiraAPI.LocalSettings.Attributes;
|
||||||
using MiraAPI.Patches;
|
|
||||||
using MiraAPI.Utilities;
|
|
||||||
using MiraAPI.Utilities.Assets;
|
using MiraAPI.Utilities.Assets;
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace MiraAPI;
|
namespace MiraAPI;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mira API Config File Handler.
|
/// Mira API Config File Handler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
|
public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
|
||||||
{
|
{
|
||||||
@@ -24,78 +21,22 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
|
|||||||
HideIconOnHover = false,
|
HideIconOnHover = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the value stored for scaling buttons properly.
|
|
||||||
/// </summary>
|
|
||||||
public static float OldButtonScaleFactor { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Open()
|
|
||||||
{
|
|
||||||
base.Open();
|
|
||||||
OldButtonScaleFactor = ButtonUIFactorSlider.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void OnOptionChanged(ConfigEntryBase configEntry)
|
|
||||||
{
|
|
||||||
base.OnOptionChanged(configEntry);
|
|
||||||
if (configEntry == ButtonUIFactorSlider)
|
|
||||||
{
|
|
||||||
if (HudManager.InstanceExists)
|
|
||||||
{
|
|
||||||
HudManagerPatches.ResizeUI(1f / OldButtonScaleFactor);
|
|
||||||
HudManagerPatches.ResizeUI(ButtonUIFactorSlider.Value);
|
|
||||||
}
|
|
||||||
OldButtonScaleFactor = ButtonUIFactorSlider.Value;
|
|
||||||
}
|
|
||||||
else if (configEntry == SetFpsSlider)
|
|
||||||
{
|
|
||||||
Application.targetFrameRate = (int)SetFpsSlider.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether the modifiers hud should be on the left side of the screen (under roles/task tab). Recommended for streamers.
|
/// Gets whether the modifiers hud should be on the left side of the screen (under roles/task tab). Recommended for streamers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[LocalToggleSetting]
|
[LocalToggleSetting]
|
||||||
public ConfigEntry<bool> ModifiersHudLeftSide { get; private set; } = config.Bind("Visuals/UI", "Show Modifiers HUD on Left Side", false);
|
public ConfigEntry<bool> ModifiersHudLeftSide { get; private set; } = config.Bind("Displays", "Show Modifiers HUD on Left Side", false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether to show keybinds on buttons.
|
/// Gets whether to show keybinds on buttons.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[LocalToggleSetting]
|
[LocalToggleSetting]
|
||||||
public ConfigEntry<bool> ShowKeybinds { get; private set; } = config.Bind("Visuals/UI", "Show Keybinds on Buttons", true);
|
public ConfigEntry<bool> ShowKeybinds { get; private set; } = config.Bind("Keybinds", "Show Keybinds on Buttons", true);
|
||||||
|
// This would be placed in the keybinds menu, but it crashes for Epic Games users. - Atony
|
||||||
/// <summary>
|
|
||||||
/// Gets the scale of the buttons.
|
|
||||||
/// </summary>
|
|
||||||
[LocalSliderSetting(min: 0.5f, max: 1.5f, suffixType: MiraNumberSuffixes.Multiplier, formatString: "0.00", displayValue: true)]
|
|
||||||
public ConfigEntry<float> ButtonUIFactorSlider { get; private set; } =
|
|
||||||
config.Bind("Visuals/UI", "Button Scale Factor", 0.75f);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the fps specified by the player.
|
|
||||||
/// </summary>
|
|
||||||
[LocalSliderSetting(min: 60f, max: 240f, suffixType: MiraNumberSuffixes.None, formatString: "0", displayValue: true, roundValue: true)]
|
|
||||||
public ConfigEntry<float> SetFpsSlider { get; private set; } =
|
|
||||||
config.Bind("Visuals/UI", "Max FPS", 120f);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether to apply cosmetic changes to the TaskAdder.
|
/// Gets whether to apply cosmetic changes to the TaskAdder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[LocalToggleSetting]
|
[LocalToggleSetting]
|
||||||
public ConfigEntry<bool> PrettyTaskAdder { get; private set; } = config.Bind("Freeplay", "Pretty Task Laptop", true);
|
public ConfigEntry<bool> PrettyTaskAdder { get; private set; } = config.Bind("Freeplay", "Pretty Task Laptop", true);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets whether to show the red flash from sabotages.
|
|
||||||
/// </summary>
|
|
||||||
[LocalToggleSetting]
|
|
||||||
public ConfigEntry<bool> EnableSabotageFlashes { get; private set; } = config.Bind("Accessibility", "Enable Sabotage Flashes", true);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets whether to enable the sabotage sound effects or not.
|
|
||||||
/// </summary>
|
|
||||||
[LocalToggleSetting]
|
|
||||||
public ConfigEntry<bool> EnableSabotageBlares { get; private set; } = config.Bind("Accessibility", "Enable Sabotage Blare", true);
|
|
||||||
}
|
}
|
||||||
@@ -179,7 +179,6 @@ public static class ModifierManager
|
|||||||
private static bool IsGameModifierValid(PlayerControl player, GameModifier modifier, uint modifierId)
|
private static bool IsGameModifierValid(PlayerControl player, GameModifier modifier, uint modifierId)
|
||||||
{
|
{
|
||||||
return (player.Data.Role is not ICustomRole role || role.IsModifierApplicable(modifier)) &&
|
return (player.Data.Role is not ICustomRole role || role.IsModifierApplicable(modifier)) &&
|
||||||
modifier.IsModifierValidOn(player.Data.Role) && !player.HasModifier(modifierId) &&
|
modifier.IsModifierValidOn(player.Data.Role) && !player.HasModifier(modifierId);
|
||||||
modifier.CanSpawnOnCurrentMode();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,10 +39,4 @@ public abstract class GameModifier : BaseModifier
|
|||||||
/// <param name="reason">The reason why the game ended.</param>
|
/// <param name="reason">The reason why the game ended.</param>
|
||||||
/// <returns>True if the player won, false if they lost. Return null to use the player's default win condition.</returns>
|
/// <returns>True if the player won, false if they lost. Return null to use the player's default win condition.</returns>
|
||||||
public virtual bool? DidWin(GameOverReason reason) => null;
|
public virtual bool? DidWin(GameOverReason reason) => null;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether the modifier can spawn in general, accounting for gamemodes and everything else.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if the modifier is able to spawn, otherwise false.</returns>
|
|
||||||
public virtual bool CanSpawnOnCurrentMode() => !GameManager.Instance.IsHideAndSeek();
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MiraAPI.Modules;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Denotes a failure in conversion of values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message to log.</param>
|
||||||
|
public class ConversionFailException(string message) : Exception(message);
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// TODO: Remove later before completing the pr
|
||||||
|
#pragma warning disable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AmongUs.GameOptions;
|
||||||
|
using Il2CppInterop.Runtime.Attributes;
|
||||||
|
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||||
|
using Reactor.Utilities.Attributes;
|
||||||
|
|
||||||
|
namespace MiraAPI.Modules;
|
||||||
|
|
||||||
|
[RegisterInIl2Cpp, Serializable]
|
||||||
|
public class MultiSelectGameSetting : BaseGameSetting
|
||||||
|
{
|
||||||
|
public Int32OptionNames OptionName;
|
||||||
|
public Dictionary<int, StringNames> EnumNames;
|
||||||
|
|
||||||
|
public override string GetValueString(float value) => TranslationController.Instance.GetString(EnumNames[(int)value]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using MiraAPI.Utilities;
|
||||||
|
|
||||||
|
namespace MiraAPI.Modules;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a collection of enum values that can be stored and manipulated as a comma-separated string.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The enum type that this collection will store.</typeparam>
|
||||||
|
/// <param name="values">One or more enum values to initialize the collection with.</param>
|
||||||
|
[Serializable]
|
||||||
|
public struct MultiSelectValue<T>(params T[] values) : IEnumerable<T>, IEquatable<MultiSelectValue<T>>, IDisposable where T : struct, Enum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the underlying comma-separated string representation of enum values.
|
||||||
|
/// </summary>
|
||||||
|
public string Values
|
||||||
|
{
|
||||||
|
readonly get => Helpers.Join(',', _values);
|
||||||
|
set => _values = [.. value.TrueSplit(',').Select(Enum.Parse<T>)];
|
||||||
|
}
|
||||||
|
private HashSet<T> _values = [.. values];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of values contained within.
|
||||||
|
/// </summary>
|
||||||
|
public readonly int Count => _values.Count;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a single enum value to the end of the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The enum value to add.</param>
|
||||||
|
/// <returns><c>true</c> if successfully added; otherwise, <c>false</c>.</returns>
|
||||||
|
public readonly bool Add(T item) => _values.Add(item);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the first occurrence of the specified enum value.<br/>
|
||||||
|
/// Returns <c>true</c> if the value was found and removed, <c>false</c> otherwise.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The enum value to remove.</param>
|
||||||
|
/// <returns><c>true</c> if successfully removed; otherwise, <c>false</c>.</returns>
|
||||||
|
public readonly bool Remove(T item) => _values.Remove(item);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds multiple enum values to the end of the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="items">The collection of enum values to add.</param>
|
||||||
|
public readonly void AddRange(IEnumerable<T> items)
|
||||||
|
{
|
||||||
|
foreach (var item in items)
|
||||||
|
_values.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes multiple enum values from the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="items">The enum values to remove.</param>
|
||||||
|
/// <returns>The number of items successfully removed.</returns>
|
||||||
|
public readonly int RemoveRange(params T[] items) => RemoveRange((IEnumerable<T>)items);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes multiple enum values from the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="items">The collection of enum values to remove.</param>
|
||||||
|
/// <returns>The number of items successfully removed.</returns>
|
||||||
|
public readonly int RemoveRange(IEnumerable<T> items) => items.Count(Remove);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the collection contains the specified enum value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The enum value to locate.</param>
|
||||||
|
/// <returns>true if the item is found in the collection; otherwise, false.</returns>
|
||||||
|
public readonly bool Contains(T item) => _values.Contains(item);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all enum values that match the specified predicate condition.<br/>
|
||||||
|
/// Iterates through the collection and removes each matching value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="predicate">A function that defines the condition for removing items.</param>
|
||||||
|
/// <returns>The number of items removed from the collection.</returns>
|
||||||
|
public readonly int RemoveAll(Func<T, bool> predicate)
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
foreach (var item in this)
|
||||||
|
{
|
||||||
|
if (predicate(item) && Remove(item))
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all enum values from the collection.
|
||||||
|
/// </summary>
|
||||||
|
public readonly void Clear() => _values.Clear();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the first value in the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns the first value.</returns>
|
||||||
|
public readonly T First() => _values.First();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes the current instance to an array of bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An array of bytes representing the current instance.</returns>
|
||||||
|
public readonly byte[] ToBytes() => EnumSerializer.EnumsToBytes(this);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the collection to its string representation.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A comma-separated string of enum values.</returns>
|
||||||
|
public readonly override string ToString() => Values;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public readonly override bool Equals(object? obj) => obj is MultiSelectValue<T> other && Equals(other);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public readonly bool Equals(MultiSelectValue<T> other) => _values.SetEquals(other._values);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public readonly override int GetHashCode() => Values.GetHashCode();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public readonly IEnumerator<T> GetEnumerator() => ((IEnumerable<T>)_values).GetEnumerator();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public readonly void Dispose() => _values.Clear();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an instance of <see cref="MultiSelectValue{T}"/> from the provided byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes">The bytes to deserialize from.</param>
|
||||||
|
/// <returns>An instance of <see cref="MultiSelectValue{T}"/>.</returns>
|
||||||
|
#pragma warning disable CA1000 // Do not declare static members on generic types
|
||||||
|
public static MultiSelectValue<T> FromBytes(byte[] bytes) => new([.. EnumSerializer.EnumsFromBytes<T>(bytes)]);
|
||||||
|
#pragma warning restore CA1000 // Do not declare static members on generic types
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the current instance to its string representation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to convert.</param>
|
||||||
|
public static implicit operator string(MultiSelectValue<T> value) => value.Values;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the current instance to its array representation of values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to convert.</param>
|
||||||
|
public static implicit operator T[](MultiSelectValue<T> value) => [.. value._values];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the current instance to one singular value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to convert.</param>
|
||||||
|
/// <exception cref="ConversionFailException">There were either no values, or more than one value.</exception>
|
||||||
|
#pragma warning disable S3877 // Exceptions should not be thrown from unexpected methods
|
||||||
|
public static implicit operator T(MultiSelectValue<T> value) =>
|
||||||
|
value.Count == 1
|
||||||
|
? value._values.First()
|
||||||
|
: throw new ConversionFailException($"Tried to convert multiple or no values ({value.Values}) to a singular one");
|
||||||
|
#pragma warning restore S3877 // Exceptions should not be thrown from unexpected methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the value to an instance of MultiSelectValue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to convert.</param>
|
||||||
|
public static implicit operator MultiSelectValue<T>(T value) => new(value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the array of values to an instance of MultiSelectValue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values">The values to convert.</param>
|
||||||
|
public static implicit operator MultiSelectValue<T>(T[] values) => new(values);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the value string to an instance of MultiSelectValue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values">The values to convert.</param>
|
||||||
|
public static implicit operator MultiSelectValue<T>(string values) => new([.. values.TrueSplit(',').Select(Enum.Parse<T>)]);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Equality comparison.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">Left.</param>
|
||||||
|
/// <param name="right">Right.</param>
|
||||||
|
public static bool operator ==(MultiSelectValue<T> left, MultiSelectValue<T> right) => left.Equals(right);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inequality comparison.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">Left.</param>
|
||||||
|
/// <param name="right">Right.</param>
|
||||||
|
public static bool operator !=(MultiSelectValue<T> left, MultiSelectValue<T> right) => !left.Equals(right);
|
||||||
|
}
|
||||||
@@ -7,12 +7,10 @@ using Assets.CoreScripts;
|
|||||||
using BepInEx.Unity.IL2CPP.Utils;
|
using BepInEx.Unity.IL2CPP.Utils;
|
||||||
using MiraAPI.Events;
|
using MiraAPI.Events;
|
||||||
using MiraAPI.Events.Vanilla.Gameplay;
|
using MiraAPI.Events.Vanilla.Gameplay;
|
||||||
using MiraAPI.Utilities;
|
|
||||||
using Reactor.Networking.Attributes;
|
using Reactor.Networking.Attributes;
|
||||||
using Reactor.Networking.Rpc;
|
using Reactor.Networking.Rpc;
|
||||||
using Reactor.Utilities;
|
using Reactor.Utilities;
|
||||||
using Reactor.Utilities.Extensions;
|
using Reactor.Utilities.Extensions;
|
||||||
using Rewired;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
@@ -24,7 +22,7 @@ namespace MiraAPI.Networking;
|
|||||||
public static class CustomMurderRpc
|
public static class CustomMurderRpc
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Networked Custom Murder method, ran by clients for the host to confirm kills.
|
/// Networked Custom Murder method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="source">The killer.</param>
|
/// <param name="source">The killer.</param>
|
||||||
/// <param name="target">The player to murder.</param>
|
/// <param name="target">The player to murder.</param>
|
||||||
@@ -34,44 +32,10 @@ public static class CustomMurderRpc
|
|||||||
/// <param name="teleportMurderer">Should the killer be snapped to the dead player.</param>
|
/// <param name="teleportMurderer">Should the killer be snapped to the dead player.</param>
|
||||||
/// <param name="showKillAnim">Should the kill animation be shown.</param>
|
/// <param name="showKillAnim">Should the kill animation be shown.</param>
|
||||||
/// <param name="playKillSound">Should the kill sound be played.</param>
|
/// <param name="playKillSound">Should the kill sound be played.</param>
|
||||||
public static void RpcCustomMurder(
|
|
||||||
this PlayerControl source,
|
|
||||||
PlayerControl target,
|
|
||||||
bool didSucceed = true,
|
|
||||||
bool resetKillTimer = true,
|
|
||||||
bool createDeadBody = true,
|
|
||||||
bool teleportMurderer = true,
|
|
||||||
bool showKillAnim = true,
|
|
||||||
bool playKillSound = true)
|
|
||||||
{
|
|
||||||
source.RpcCustomMurder(
|
|
||||||
target,
|
|
||||||
MeetingCheck.Ignore,
|
|
||||||
didSucceed,
|
|
||||||
resetKillTimer,
|
|
||||||
createDeadBody,
|
|
||||||
teleportMurderer,
|
|
||||||
showKillAnim,
|
|
||||||
playKillSound);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Networked Custom Murder method, which checks for meetings as well, ran by clients for the host to confirm kills.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">The killer.</param>
|
|
||||||
/// <param name="target">The player to murder.</param>
|
|
||||||
/// <param name="inMeeting">Whether the murder is intended to be triggered in a meeting.</param>
|
|
||||||
/// <param name="didSucceed">Whether the murder was successful or not.</param>
|
|
||||||
/// <param name="resetKillTimer">Should the kill timer be reset.</param>
|
|
||||||
/// <param name="createDeadBody">Should a dead body be created.</param>
|
|
||||||
/// <param name="teleportMurderer">Should the killer be snapped to the dead player.</param>
|
|
||||||
/// <param name="showKillAnim">Should the kill animation be shown.</param>
|
|
||||||
/// <param name="playKillSound">Should the kill sound be played.</param>
|
|
||||||
[MethodRpc((uint)MiraRpc.CustomMurder, LocalHandling = RpcLocalHandling.Before)]
|
[MethodRpc((uint)MiraRpc.CustomMurder, LocalHandling = RpcLocalHandling.Before)]
|
||||||
public static void RpcCustomMurder(
|
public static void RpcCustomMurder(
|
||||||
this PlayerControl source,
|
this PlayerControl source,
|
||||||
PlayerControl target,
|
PlayerControl target,
|
||||||
MeetingCheck inMeeting,
|
|
||||||
bool didSucceed = true,
|
bool didSucceed = true,
|
||||||
bool resetKillTimer = true,
|
bool resetKillTimer = true,
|
||||||
bool createDeadBody = true,
|
bool createDeadBody = true,
|
||||||
@@ -79,71 +43,16 @@ public static class CustomMurderRpc
|
|||||||
bool showKillAnim = true,
|
bool showKillAnim = true,
|
||||||
bool playKillSound = true)
|
bool playKillSound = true)
|
||||||
{
|
{
|
||||||
if (LobbyBehaviour.Instance)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError;
|
var murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError;
|
||||||
|
|
||||||
var beforeMurderEvent = new BeforeMurderEvent(source, target, inMeeting);
|
var beforeMurderEvent = new BeforeMurderEvent(source, target);
|
||||||
MiraEventManager.InvokeEvent(beforeMurderEvent);
|
MiraEventManager.InvokeEvent(beforeMurderEvent);
|
||||||
var isMeetingActive = MeetingHud.Instance != null || ExileController.Instance != null;
|
|
||||||
if ((inMeeting is MeetingCheck.ForMeeting && !isMeetingActive) || (inMeeting is MeetingCheck.OutsideMeeting && isMeetingActive))
|
|
||||||
{
|
|
||||||
beforeMurderEvent.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (beforeMurderEvent.IsCancelled)
|
if (beforeMurderEvent.IsCancelled)
|
||||||
{
|
{
|
||||||
murderResultFlags = MurderResultFlags.FailedError;
|
murderResultFlags = MurderResultFlags.FailedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!PlayerControl.LocalPlayer.IsHost())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RpcConfirmCustomMurder(
|
|
||||||
PlayerControl.LocalPlayer,
|
|
||||||
source,
|
|
||||||
target,
|
|
||||||
murderResultFlags,
|
|
||||||
resetKillTimer,
|
|
||||||
createDeadBody,
|
|
||||||
teleportMurderer,
|
|
||||||
showKillAnim,
|
|
||||||
playKillSound);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Networked Custom Murder method, which checks for meetings as well, ran by clients for the host to confirm kills.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="host">The game host.</param>
|
|
||||||
/// <param name="source">The killer.</param>
|
|
||||||
/// <param name="target">The player to murder.</param>
|
|
||||||
/// <param name="murderResultFlags">End result for the murder.</param>
|
|
||||||
/// <param name="resetKillTimer">Should the kill timer be reset.</param>
|
|
||||||
/// <param name="createDeadBody">Should a dead body be created.</param>
|
|
||||||
/// <param name="teleportMurderer">Should the killer be snapped to the dead player.</param>
|
|
||||||
/// <param name="showKillAnim">Should the kill animation be shown.</param>
|
|
||||||
/// <param name="playKillSound">Should the kill sound be played.</param>
|
|
||||||
[MethodRpc((uint)MiraRpc.ConfirmCustomMurder, LocalHandling = RpcLocalHandling.Before)]
|
|
||||||
public static void RpcConfirmCustomMurder(
|
|
||||||
this PlayerControl host,
|
|
||||||
PlayerControl source,
|
|
||||||
PlayerControl target,
|
|
||||||
MurderResultFlags murderResultFlags,
|
|
||||||
bool resetKillTimer = true,
|
|
||||||
bool createDeadBody = true,
|
|
||||||
bool teleportMurderer = true,
|
|
||||||
bool showKillAnim = true,
|
|
||||||
bool playKillSound = true)
|
|
||||||
{
|
|
||||||
if (LobbyBehaviour.Instance || !host.IsHost() || target.Data.IsDead || target.Data.Disconnected)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var murderResultFlags2 = MurderResultFlags.DecisionByHost | murderResultFlags;
|
var murderResultFlags2 = MurderResultFlags.DecisionByHost | murderResultFlags;
|
||||||
|
|
||||||
source.CustomMurder(
|
source.CustomMurder(
|
||||||
@@ -178,7 +87,8 @@ public static class CustomMurderRpc
|
|||||||
bool playKillSound = true)
|
bool playKillSound = true)
|
||||||
{
|
{
|
||||||
source.isKilling = false;
|
source.isKilling = false;
|
||||||
if (resultFlags.HasFlag(MurderResultFlags.FailedError) || LobbyBehaviour.Instance)
|
var data = target.Data;
|
||||||
|
if (resultFlags.HasFlag(MurderResultFlags.FailedError))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -264,7 +174,7 @@ public static class CustomMurderRpc
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
HudManager.Instance.KillOverlay.ShowKillAnimation(source.Data, target.Data);
|
HudManager.Instance.KillOverlay.ShowKillAnimation(source.Data, data);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -301,10 +211,6 @@ public static class CustomMurderRpc
|
|||||||
bool createDeadBody = true,
|
bool createDeadBody = true,
|
||||||
bool teleportMurderer = true)
|
bool teleportMurderer = true)
|
||||||
{
|
{
|
||||||
if (LobbyBehaviour.Instance)
|
|
||||||
{
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
var cam = Camera.main?.GetComponent<FollowerCamera>();
|
var cam = Camera.main?.GetComponent<FollowerCamera>();
|
||||||
var isParticipant = source.AmOwner || target.AmOwner;
|
var isParticipant = source.AmOwner || target.AmOwner;
|
||||||
var sourcePhys = source.MyPhysics;
|
var sourcePhys = source.MyPhysics;
|
||||||
@@ -392,24 +298,3 @@ public static class CustomMurderRpc
|
|||||||
source.isKilling = false;
|
source.isKilling = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks for custom murders.
|
|
||||||
/// </summary>
|
|
||||||
public enum MeetingCheck
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Checks for meetings.
|
|
||||||
/// </summary>
|
|
||||||
ForMeeting,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks for a meeting to not be active.
|
|
||||||
/// </summary>
|
|
||||||
OutsideMeeting,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Doesn't check for a meeting at all.
|
|
||||||
/// </summary>
|
|
||||||
Ignore,
|
|
||||||
}
|
|
||||||
@@ -35,11 +35,6 @@ public enum MiraRpc : uint
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
CustomMurder,
|
CustomMurder,
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Custom Murder RPC Confirmation.
|
|
||||||
/// </summary>
|
|
||||||
ConfirmCustomMurder,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Custom Game Over RPC.
|
/// Custom Game Over RPC.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
using System.Collections;
|
|
||||||
using BepInEx.Unity.IL2CPP.Utils.Collections;
|
|
||||||
using HarmonyLib;
|
|
||||||
using MiraAPI.LocalSettings;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace MiraAPI.Patches.Accessibility;
|
|
||||||
[HarmonyPatch]
|
|
||||||
public static class HudManagerFlashPatches
|
|
||||||
{
|
|
||||||
[HarmonyPatch(typeof(HudManager), nameof(HudManager.StartReactorFlash))]
|
|
||||||
[HarmonyPrefix]
|
|
||||||
public static bool ReactorFlashPrefix(HudManager __instance)
|
|
||||||
{
|
|
||||||
if (__instance.ReactorFlash == null)
|
|
||||||
{
|
|
||||||
__instance.ReactorFlash = __instance.StartCoroutine(CoReactorFlash().WrapToIl2Cpp());
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
[HarmonyPatch(typeof(HudManager), nameof(HudManager.StartOxyFlash))]
|
|
||||||
[HarmonyPrefix]
|
|
||||||
public static bool OxygenFlashPrefix(HudManager __instance)
|
|
||||||
{
|
|
||||||
if (__instance.OxyFlash == null)
|
|
||||||
{
|
|
||||||
__instance.OxyFlash = __instance.StartCoroutine(CoReactorFlash().WrapToIl2Cpp());
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
public static IEnumerator CoReactorFlash()
|
|
||||||
{
|
|
||||||
if (!HudManager.InstanceExists)
|
|
||||||
{
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
var hudManager = HudManager.Instance;
|
|
||||||
WaitForSeconds wait = new WaitForSeconds(1f);
|
|
||||||
bool light = false;
|
|
||||||
|
|
||||||
hudManager.FullScreen.color = new Color(1f, 0f, 0f, 0.37254903f);
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var settins = LocalSettingsTabSingleton<MiraApiSettings>.Instance;
|
|
||||||
hudManager.FullScreen.gameObject.SetActive(settins.EnableSabotageFlashes.Value && !hudManager.FullScreen.gameObject.activeSelf);
|
|
||||||
if (settins.EnableSabotageBlares.Value)
|
|
||||||
{
|
|
||||||
SoundManager.Instance.PlaySound(ShipStatus.Instance.SabotageSound, false, 1f, null);
|
|
||||||
}
|
|
||||||
light = !light;
|
|
||||||
|
|
||||||
/*if (!MiraApiPlugin.IsMobile)
|
|
||||||
{
|
|
||||||
if (hudManager.lightFlashHandle == null)
|
|
||||||
{
|
|
||||||
hudManager.lightFlashHandle = DualshockLightManager.Instance.AllocateLight();
|
|
||||||
hudManager.lightFlashHandle.color = new Color(1f, 0f, 0f, 1f);
|
|
||||||
hudManager.lightFlashHandle.intensity = 1f;
|
|
||||||
}
|
|
||||||
hudManager.lightFlashHandle.color.SetAlpha(light ? 1f : 0f);
|
|
||||||
}*/
|
|
||||||
yield return wait;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using HarmonyLib;
|
|
||||||
using MiraAPI.Events;
|
|
||||||
using MiraAPI.Events.Vanilla.Player;
|
|
||||||
|
|
||||||
namespace MiraAPI.Patches.Events;
|
|
||||||
|
|
||||||
[HarmonyPatch(typeof(HideAndSeekManager))]
|
|
||||||
public static class HnsFinishTaskPatch
|
|
||||||
{
|
|
||||||
[HarmonyPostfix]
|
|
||||||
[HarmonyPatch(nameof(HideAndSeekManager.FinishTask))]
|
|
||||||
public static void FinishTaskPostfix(HideAndSeekManager __instance, PlayerTask task)
|
|
||||||
{
|
|
||||||
var completeTaskEvent = new CompleteHnsTaskEvent(__instance, task.Owner, task);
|
|
||||||
MiraEventManager.InvokeEvent(completeTaskEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
/*
|
using HarmonyLib;
|
||||||
using HarmonyLib;
|
|
||||||
using MiraAPI.Events;
|
using MiraAPI.Events;
|
||||||
using MiraAPI.Events.Vanilla.Gameplay;
|
using MiraAPI.Events.Vanilla.Gameplay;
|
||||||
|
|
||||||
@@ -19,4 +18,3 @@ internal static class SpawnInMinigamePatch
|
|||||||
MiraEventManager.InvokeEvent(@event2);
|
MiraEventManager.InvokeEvent(@event2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
/*
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using MiraAPI.Events;
|
using MiraAPI.Events;
|
||||||
@@ -35,4 +34,3 @@ internal static class SubmergedSpawnGamePatch
|
|||||||
MiraEventManager.InvokeEvent(@event2);
|
MiraEventManager.InvokeEvent(@event2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using AmongUs.GameOptions;
|
|
||||||
using HarmonyLib;
|
|
||||||
using MiraAPI.Events;
|
|
||||||
using MiraAPI.Events.Vanilla.Gameplay;
|
|
||||||
using MiraAPI.Events.Vanilla.Usables;
|
|
||||||
|
|
||||||
namespace MiraAPI.Patches.Events;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Patches to invoke vent related MiraEvents.
|
|
||||||
/// </summary>
|
|
||||||
[HarmonyPatch]
|
|
||||||
public static class VanillaButtonPatches
|
|
||||||
{
|
|
||||||
[HarmonyPrefix]
|
|
||||||
[HarmonyPatch(typeof(AbilityButton), nameof(AbilityButton.DoClick))]
|
|
||||||
public static bool DoClickPrefix(AbilityButton __instance)
|
|
||||||
{
|
|
||||||
// Invoke the generic button click event.
|
|
||||||
PlayerControl? playerTarget = null;
|
|
||||||
Vent? ventTarget = null;
|
|
||||||
var role = PlayerControl.LocalPlayer.Data.Role;
|
|
||||||
if (role.Role is RoleTypes.Tracker)
|
|
||||||
{
|
|
||||||
playerTarget = role.Cast<TrackerRole>().currentTarget;
|
|
||||||
}
|
|
||||||
else if (role.Role is RoleTypes.GuardianAngel)
|
|
||||||
{
|
|
||||||
playerTarget = role.Cast<GuardianAngelRole>().currentTarget;
|
|
||||||
}
|
|
||||||
else if (role.Role is RoleTypes.Engineer)
|
|
||||||
{
|
|
||||||
ventTarget = role.Cast<EngineerRole>().currentTarget;
|
|
||||||
}
|
|
||||||
var genericEvent = new VanillaButtonClickEvent(__instance, playerTarget, ventTarget);
|
|
||||||
MiraEventManager.InvokeEvent(genericEvent);
|
|
||||||
if (genericEvent.IsCancelled)
|
|
||||||
{
|
|
||||||
MiraEventManager.InvokeEvent(new VanillaButtonCancelledEvent(__instance));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!genericEvent.IsCancelled)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
[HarmonyPrefix]
|
|
||||||
[HarmonyPatch(typeof(SecondaryAbilityButton), nameof(SecondaryAbilityButton.DoClick))]
|
|
||||||
public static bool SecondaryDoClickPrefix(AbilityButton __instance)
|
|
||||||
{
|
|
||||||
// Invoke the generic button click event.
|
|
||||||
PlayerControl? playerTarget = null;
|
|
||||||
var role = PlayerControl.LocalPlayer.Data.Role;
|
|
||||||
if (role.Role is RoleTypes.Detective)
|
|
||||||
{
|
|
||||||
playerTarget = role.Cast<DetectiveRole>().currentTarget;
|
|
||||||
}
|
|
||||||
var genericEvent = new VanillaButtonClickEvent(__instance, playerTarget);
|
|
||||||
MiraEventManager.InvokeEvent(genericEvent);
|
|
||||||
if (genericEvent.IsCancelled)
|
|
||||||
{
|
|
||||||
MiraEventManager.InvokeEvent(new VanillaButtonCancelledEvent(__instance));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!genericEvent.IsCancelled)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,14 +21,10 @@ using Object = UnityEngine.Object;
|
|||||||
namespace MiraAPI.Patches.Freeplay;
|
namespace MiraAPI.Patches.Freeplay;
|
||||||
|
|
||||||
[HarmonyPatch(typeof(TaskAdderGame))]
|
[HarmonyPatch(typeof(TaskAdderGame))]
|
||||||
public static class TaskAdderPatches
|
internal static class TaskAdderPatches
|
||||||
{
|
{
|
||||||
private static Scroller _scroller = null!;
|
private static Scroller _scroller = null!;
|
||||||
private static Dictionary<string, TaskFolder> folders = new();
|
private static Dictionary<string, TaskFolder> folders = new();
|
||||||
public static string CrewmateName => TranslationController.Instance.GetString(StringNames.Crewmate);
|
|
||||||
public static string ImpostorName => TranslationController.Instance.GetString(StringNames.Impostor);
|
|
||||||
public static string NeutralName => "Neutral";
|
|
||||||
public static string ModifiersName => "Modifiers";
|
|
||||||
|
|
||||||
[HarmonyPostfix]
|
[HarmonyPostfix]
|
||||||
[HarmonyPatch(nameof(TaskAdderGame.Begin))]
|
[HarmonyPatch(nameof(TaskAdderGame.Begin))]
|
||||||
@@ -70,16 +66,19 @@ public static class TaskAdderPatches
|
|||||||
__instance.RootFolderPrefab.GetComponent<PassiveButton>().ClickMask = collider;
|
__instance.RootFolderPrefab.GetComponent<PassiveButton>().ClickMask = collider;
|
||||||
__instance.RootFolderPrefab.gameObject.SetActive(false);
|
__instance.RootFolderPrefab.gameObject.SetActive(false);
|
||||||
__instance.TaskParent = inner.transform;
|
__instance.TaskParent = inner.transform;
|
||||||
var crewmateFolder = __instance.Root.SubFolders.ToArray().FirstOrDefault(x => x.FolderName == CrewmateName)!;
|
var crewLocale = TranslationController.Instance.GetString(StringNames.Crewmate);
|
||||||
var impostorFolder = __instance.Root.SubFolders.ToArray().FirstOrDefault(x => x.FolderName == ImpostorName)!;
|
var impLocale = TranslationController.Instance.GetString(StringNames.Impostor);
|
||||||
|
|
||||||
|
var crewmateFolder = __instance.Root.SubFolders.ToArray().FirstOrDefault(x => x.FolderName == crewLocale)!;
|
||||||
|
var impostorFolder = __instance.Root.SubFolders.ToArray().FirstOrDefault(x => x.FolderName == impLocale)!;
|
||||||
//var neutralFolder = __instance.CreateFolder("Neutral", __instance.Root, 2, Color.gray);
|
//var neutralFolder = __instance.CreateFolder("Neutral", __instance.Root, 2, Color.gray);
|
||||||
var modifiersFolder = __instance.CreateFolder(ModifiersName, __instance.Root, 0, Color.blue);
|
var modifiersFolder = __instance.CreateFolder("Modifiers", __instance.Root, 0, Color.blue);
|
||||||
|
|
||||||
folders.Clear();
|
folders.Clear();
|
||||||
folders.Add(crewmateFolder.FolderName, crewmateFolder);
|
folders.Add(crewmateFolder.FolderName, crewmateFolder);
|
||||||
folders.Add(impostorFolder.FolderName, impostorFolder);
|
folders.Add(impostorFolder.FolderName, impostorFolder);
|
||||||
//folders.Add("Neutrals", neutralFolder);
|
//folders.Add("Neutrals", neutralFolder);
|
||||||
folders.Add(ModifiersName, modifiersFolder);
|
folders.Add("Modifiers", modifiersFolder);
|
||||||
|
|
||||||
int folderIdx = 2;
|
int folderIdx = 2;
|
||||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
|
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
|
||||||
@@ -138,8 +137,8 @@ public static class TaskAdderPatches
|
|||||||
Object.Instantiate(__instance.RootFolderPrefab, __instance.transform);
|
Object.Instantiate(__instance.RootFolderPrefab, __instance.transform);
|
||||||
impFolder.gameObject.SetActive(false);
|
impFolder.gameObject.SetActive(false);
|
||||||
impFolder.SetFolderColor(TaskFolder.FolderColor.Red);
|
impFolder.SetFolderColor(TaskFolder.FolderColor.Red);
|
||||||
crewFolder.FolderName = CrewmateName;
|
crewFolder.FolderName = TranslationController.Instance.GetString(StringNames.Crewmate);
|
||||||
impFolder.FolderName = ImpostorName;
|
impFolder.FolderName = TranslationController.Instance.GetString(StringNames.Impostor);
|
||||||
rootFolder.SubFolders.Insert(0, crewFolder);
|
rootFolder.SubFolders.Insert(0, crewFolder);
|
||||||
rootFolder.SubFolders.Insert(0, impFolder);
|
rootFolder.SubFolders.Insert(0, impFolder);
|
||||||
Il2CppSystem.Collections.Generic.List<RoleBehaviour> impRoles = new();
|
Il2CppSystem.Collections.Generic.List<RoleBehaviour> impRoles = new();
|
||||||
@@ -441,7 +440,7 @@ public static class TaskAdderPatches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (folders.TryGetValue(ModifiersName, out var modifiersFolder) && taskFolder.IsChildOf(modifiersFolder))
|
if (folders.TryGetValue("Modifiers", out var modifiersFolder) && taskFolder.IsChildOf(modifiersFolder))
|
||||||
{
|
{
|
||||||
var plugin = MiraPluginManager.GetPluginByGuid(taskFolder.name.Replace("(Clone)", string.Empty));
|
var plugin = MiraPluginManager.GetPluginByGuid(taskFolder.name.Replace("(Clone)", string.Empty));
|
||||||
if (plugin != null)
|
if (plugin != null)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.Linq;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using MiraAPI.Hud;
|
using MiraAPI.Hud;
|
||||||
using MiraAPI.Keybinds;
|
using MiraAPI.Keybinds;
|
||||||
@@ -25,87 +25,9 @@ public static class HudManagerPatches
|
|||||||
public static Transform? BottomRight { get; private set; }
|
public static Transform? BottomRight { get; private set; }
|
||||||
public static Transform? Buttons { get; private set; }
|
public static Transform? Buttons { get; private set; }
|
||||||
|
|
||||||
private static Dictionary<TextMeshPro, int> vanillaKeybindIcons = [];
|
private static Dictionary<TextMeshPro, int> vanillaKeybindIcons = new();
|
||||||
|
|
||||||
internal static List<TextMeshPro> ModdedKeybindIcons = [];
|
internal static List<TextMeshPro> moddedKeybindIcons = new();
|
||||||
|
|
||||||
public static IEnumerator CoResizeUI()
|
|
||||||
{
|
|
||||||
while (!HudManager.Instance)
|
|
||||||
{
|
|
||||||
yield return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return new WaitForSeconds(0.01f);
|
|
||||||
ResizeUI(LocalSettingsTabSingleton<MiraApiSettings>.Instance.ButtonUIFactorSlider.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ResizeUI(float scaleFactor)
|
|
||||||
{
|
|
||||||
var baseButtons = HudManager.Instance.transform.FindChild("Buttons");
|
|
||||||
if (baseButtons != null)
|
|
||||||
{
|
|
||||||
foreach (var aspect in baseButtons.GetComponentsInChildren<AspectPosition>(true))
|
|
||||||
{
|
|
||||||
if (aspect.gameObject == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aspect.gameObject.transform.parent.name == "TopRight")
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aspect.gameObject.transform.parent.transform.parent.name == "TopRight")
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
aspect.gameObject.SetActive(!aspect.isActiveAndEnabled);
|
|
||||||
aspect.DistanceFromEdge *= new Vector2(scaleFactor, scaleFactor);
|
|
||||||
aspect.gameObject.SetActive(!aspect.isActiveAndEnabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var button in HudManager.Instance.GetComponentsInChildren<ActionButton>(true))
|
|
||||||
{
|
|
||||||
if (button.gameObject == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.gameObject.SetActive(!button.isActiveAndEnabled);
|
|
||||||
button.gameObject.transform.localScale *= scaleFactor;
|
|
||||||
button.gameObject.SetActive(!button.isActiveAndEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseButtons != null)
|
|
||||||
{
|
|
||||||
foreach (var arrange in baseButtons.GetComponentsInChildren<GridArrange>(true))
|
|
||||||
{
|
|
||||||
if (!arrange.gameObject || !arrange.transform)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
arrange.gameObject.SetActive(!arrange.isActiveAndEnabled);
|
|
||||||
arrange.CellSize = new Vector2(scaleFactor, scaleFactor);
|
|
||||||
arrange.gameObject.SetActive(!arrange.isActiveAndEnabled);
|
|
||||||
if (arrange.isActiveAndEnabled && arrange.gameObject.transform.childCount != 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
arrange.ArrangeChilds();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Error($"Error arranging child objects in GridArrange: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -168,7 +90,7 @@ public static class HudManagerPatches
|
|||||||
fakeButton.ToggleVisible(true);
|
fakeButton.ToggleVisible(true);
|
||||||
fakeButton.Destroy();
|
fakeButton.Destroy();
|
||||||
}
|
}
|
||||||
ModdedKeybindIcons = [];
|
moddedKeybindIcons = [];
|
||||||
|
|
||||||
foreach (var button in CustomButtonManager.CustomButtons)
|
foreach (var button in CustomButtonManager.CustomButtons)
|
||||||
{
|
{
|
||||||
@@ -201,7 +123,7 @@ public static class HudManagerPatches
|
|||||||
aspectPosition.AdjustPosition();
|
aspectPosition.AdjustPosition();
|
||||||
|
|
||||||
vanillaKeybindIcons = [];
|
vanillaKeybindIcons = [];
|
||||||
var keybindIconPos = new Vector3(0.4f, 0.4f, -9.5f);
|
var keybindIconPos = new Vector3(-0.4f, 0.4f, -9.5f);
|
||||||
var vanillaButtons = new Dictionary<GameObject, int>
|
var vanillaButtons = new Dictionary<GameObject, int>
|
||||||
{
|
{
|
||||||
{ __instance.KillButton.gameObject, 8 },
|
{ __instance.KillButton.gameObject, 8 },
|
||||||
@@ -224,16 +146,10 @@ public static class HudManagerPatches
|
|||||||
}
|
}
|
||||||
var icon = Helpers.CreateKeybindIcon(buttonObj, key, keybindIconPos);
|
var icon = Helpers.CreateKeybindIcon(buttonObj, key, keybindIconPos);
|
||||||
vanillaKeybindIcons.Add(icon.transform.GetChild(0).GetComponent<TextMeshPro>(), actionId);
|
vanillaKeybindIcons.Add(icon.transform.GetChild(0).GetComponent<TextMeshPro>(), actionId);
|
||||||
if (!MiraApiPlugin.IsMobile)
|
|
||||||
{
|
|
||||||
var comp = buttonObj.GetComponent<ActionButton>();
|
var comp = buttonObj.GetComponent<ActionButton>();
|
||||||
KeybindManager.VanillaKeybinds[comp.GetType()].Button = comp;
|
KeybindManager.VanillaKeybinds[comp.GetType()].Button = comp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MiraApiSettings.OldButtonScaleFactor =
|
|
||||||
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ButtonUIFactorSlider.Value;
|
|
||||||
Coroutines.Start(CoResizeUI());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set the custom buttons active when the hud is active.
|
/// Set the custom buttons active when the hud is active.
|
||||||
@@ -271,54 +187,31 @@ public static class HudManagerPatches
|
|||||||
{
|
{
|
||||||
var canSeeBinds = ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard &&
|
var canSeeBinds = ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard &&
|
||||||
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value;
|
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value;
|
||||||
|
|
||||||
foreach (var btnIcon in vanillaKeybindIcons)
|
foreach (var btnIcon in vanillaKeybindIcons)
|
||||||
{
|
{
|
||||||
btnIcon.Key.text = KeybindUtils.GetKeycodeByActionId(btnIcon.Value).ToString();
|
btnIcon.Key.text = KeybindUtils.GetKeycodeByActionId(btnIcon.Value).ToString();
|
||||||
btnIcon.Key.transform.parent.gameObject.SetActive(canSeeBinds);
|
btnIcon.Key.transform.parent.gameObject.SetActive(canSeeBinds);
|
||||||
}
|
}
|
||||||
|
foreach (var btnIcon in moddedKeybindIcons)
|
||||||
foreach (var btnIcon in ModdedKeybindIcons)
|
|
||||||
{
|
{
|
||||||
btnIcon.transform.parent.gameObject.SetActive(canSeeBinds);
|
btnIcon.transform.parent.gameObject.SetActive(canSeeBinds);
|
||||||
}
|
}
|
||||||
|
|
||||||
var player = ReInput.players.GetPlayer(0);
|
var player = ReInput.players.GetPlayer(0);
|
||||||
var keyboard = player.controllers.Keyboard;
|
|
||||||
foreach (var entry in KeybindManager.Keybinds)
|
foreach (var entry in KeybindManager.Keybinds)
|
||||||
{
|
{
|
||||||
var modKeys = entry.ModifierKeys;
|
var keyboard = player.controllers.Keyboard;
|
||||||
bool modifierKeysPressed;
|
bool modifierKeysPressed = entry.ModifierKeys.All(k => keyboard.GetModifierKey(k)) ||
|
||||||
if (modKeys.Length == 0)
|
entry.ModifierKeys.Length <= 0;
|
||||||
{
|
|
||||||
modifierKeysPressed = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
modifierKeysPressed = true;
|
|
||||||
foreach (var key in modKeys)
|
|
||||||
{
|
|
||||||
if (keyboard.GetModifierKey(key)) continue;
|
|
||||||
modifierKeysPressed = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (player.GetButtonDown(entry.Id) && modifierKeysPressed)
|
if (player.GetButtonDown(entry.Id) && modifierKeysPressed)
|
||||||
{
|
{
|
||||||
entry.Invoke();
|
entry.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// suppressed warning because we want minimum allocations here
|
foreach (var entry in KeybindManager.VanillaKeybinds.Values.Where(x => player.GetButtonDown(x.Id)))
|
||||||
#pragma warning disable S3267
|
|
||||||
foreach (var entry in KeybindManager.VanillaKeybinds.Values)
|
|
||||||
#pragma warning restore S3267
|
|
||||||
{
|
|
||||||
if (player.GetButtonDown(entry.Id))
|
|
||||||
{
|
{
|
||||||
entry.Invoke();
|
entry.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using MiraAPI.LocalSettings;
|
|
||||||
using MiraAPI.Utilities.Assets;
|
using MiraAPI.Utilities.Assets;
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace MiraAPI.Patches.Menu;
|
namespace MiraAPI.Patches.Menu;
|
||||||
|
|
||||||
@@ -19,6 +17,5 @@ public static class MainMenuManagerPatches
|
|||||||
public static void AwakePostfix()
|
public static void AwakePostfix()
|
||||||
{
|
{
|
||||||
AddressablesLoader.LoadAll();
|
AddressablesLoader.LoadAll();
|
||||||
Application.targetFrameRate = (int)LocalSettingsTabSingleton<MiraApiSettings>.Instance.SetFpsSlider.Value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AmongUs.GameOptions;
|
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using Il2CppInterop.Runtime.Attributes;
|
|
||||||
using MiraAPI.GameOptions;
|
using MiraAPI.GameOptions;
|
||||||
using MiraAPI.Modifiers;
|
using MiraAPI.Modifiers;
|
||||||
using MiraAPI.Presets;
|
|
||||||
using MiraAPI.Utilities;
|
using MiraAPI.Utilities;
|
||||||
using MiraAPI.Utilities.Assets;
|
using MiraAPI.Utilities.Assets;
|
||||||
using Reactor.Localization.Utilities;
|
using Reactor.Localization.Utilities;
|
||||||
@@ -13,7 +10,6 @@ using Reactor.Utilities.Extensions;
|
|||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Events;
|
using UnityEngine.Events;
|
||||||
using UnityEngine.UI;
|
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
namespace MiraAPI.Patches.Options;
|
namespace MiraAPI.Patches.Options;
|
||||||
@@ -21,20 +17,6 @@ namespace MiraAPI.Patches.Options;
|
|||||||
[HarmonyPatch(typeof(GameOptionsMenu))]
|
[HarmonyPatch(typeof(GameOptionsMenu))]
|
||||||
internal static class GameOptionsMenuPatch
|
internal static class GameOptionsMenuPatch
|
||||||
{
|
{
|
||||||
[HarmonyPostfix]
|
|
||||||
[HarmonyPatch(nameof(GameOptionsMenu.OnEnable))]
|
|
||||||
public static void OpenPatch(GameOptionsMenu __instance)
|
|
||||||
{
|
|
||||||
HudManager.Instance.PlayerCam.OverrideScreenShakeEnabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HarmonyPostfix]
|
|
||||||
[HarmonyPatch(nameof(GameOptionsMenu.OnDisable))]
|
|
||||||
public static void ClosePatch(GameOptionsMenu __instance)
|
|
||||||
{
|
|
||||||
HudManager.Instance.PlayerCam.OverrideScreenShakeEnabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HarmonyPostfix]
|
[HarmonyPostfix]
|
||||||
[HarmonyPatch(nameof(GameOptionsMenu.Update))]
|
[HarmonyPatch(nameof(GameOptionsMenu.Update))]
|
||||||
// ReSharper disable once InconsistentNaming
|
// ReSharper disable once InconsistentNaming
|
||||||
@@ -275,9 +257,7 @@ internal static class GameOptionsMenuPatch
|
|||||||
categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister(group.GroupName), 20);
|
categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister(group.GroupName), 20);
|
||||||
categoryHeaderMasked.Background.color = group.GroupColor;
|
categoryHeaderMasked.Background.color = group.GroupColor;
|
||||||
categoryHeaderMasked.Divider.color = group.GroupColor;
|
categoryHeaderMasked.Divider.color = group.GroupColor;
|
||||||
categoryHeaderMasked.Title.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor)
|
categoryHeaderMasked.Title.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) ? Color.white : group.GroupColor.FindAlternateColor();
|
||||||
? Color.white
|
|
||||||
: group.GroupColor.FindAlternateColor();
|
|
||||||
|
|
||||||
categoryHeaderMasked.Background.sprite = MiraAssets.CategoryHeader.LoadAsset();
|
categoryHeaderMasked.Background.sprite = MiraAssets.CategoryHeader.LoadAsset();
|
||||||
categoryHeaderMasked.Background.sprite.texture.filterMode = FilterMode.Bilinear;
|
categoryHeaderMasked.Background.sprite.texture.filterMode = FilterMode.Bilinear;
|
||||||
@@ -296,20 +276,16 @@ internal static class GameOptionsMenuPatch
|
|||||||
newText.transform.localPosition = new Vector3(2.6249f, -0.165f, 0f);
|
newText.transform.localPosition = new Vector3(2.6249f, -0.165f, 0f);
|
||||||
newText.gameObject.GetComponent<TextTranslatorTMP>().Destroy();
|
newText.gameObject.GetComponent<TextTranslatorTMP>().Destroy();
|
||||||
|
|
||||||
var options = group.Options.Select(opt => opt.CreateOption(
|
var options = group.Options.Select(
|
||||||
|
opt => opt.CreateOption(
|
||||||
menu.checkboxOrigin,
|
menu.checkboxOrigin,
|
||||||
menu.numberOptionOrigin,
|
menu.numberOptionOrigin,
|
||||||
menu.stringOptionOrigin,
|
menu.stringOptionOrigin,
|
||||||
menu.playerOptionOrigin,
|
menu.playerOptionOrigin,
|
||||||
menu.settingsContainer));
|
menu.settingsContainer));
|
||||||
|
|
||||||
OptionPreset? defaultPreset = null;
|
// TODO: Reimplement this without breaking the menu.
|
||||||
if (GameSettingMenuPatches.SelectedMod != null && PresetManager.DefaultPresets.TryGetValue(
|
// group.Options.ForEach(x => x.ChangeGameSetting());
|
||||||
GameSettingMenuPatches.SelectedMod,
|
|
||||||
out var preset))
|
|
||||||
{
|
|
||||||
defaultPreset = preset;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var newOpt in options)
|
foreach (var newOpt in options)
|
||||||
{
|
{
|
||||||
@@ -345,79 +321,14 @@ internal static class GameOptionsMenuPatch
|
|||||||
if (newOpt is ToggleOption toggle)
|
if (newOpt is ToggleOption toggle)
|
||||||
{
|
{
|
||||||
toggle.CheckMark.sprite = MiraAssets.Checkmark.LoadAsset();
|
toggle.CheckMark.sprite = MiraAssets.Checkmark.LoadAsset();
|
||||||
toggle.CheckMark.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor
|
toggle.CheckMark.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor ? group.GroupColor : MiraAssets.AcceptedTeal;
|
||||||
? group.GroupColor
|
|
||||||
: MiraAssets.AcceptedTeal;
|
|
||||||
var rend = toggle.CheckMark.transform.parent.FindChild("ActiveSprite")
|
var rend = toggle.CheckMark.transform.parent.FindChild("ActiveSprite")
|
||||||
.GetComponent<SpriteRenderer>();
|
.GetComponent<SpriteRenderer>();
|
||||||
rend.sprite = MiraAssets.CheckmarkBox.LoadAsset();
|
rend.sprite = MiraAssets.CheckmarkBox.LoadAsset();
|
||||||
rend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor
|
rend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor ? group.GroupColor : MiraAssets.AcceptedTeal;
|
||||||
? group.GroupColor
|
|
||||||
: MiraAssets.AcceptedTeal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
menu.Children.Add(newOpt);
|
menu.Children.Add(newOpt);
|
||||||
var resetBtn = new GameObject("ResetOption");
|
|
||||||
resetBtn.transform.parent = newOpt.transform;
|
|
||||||
resetBtn.transform.localScale = new(.5f, .5f, 1);
|
|
||||||
resetBtn.layer = LayerMask.NameToLayer("UI");
|
|
||||||
resetBtn.transform.localPosition = new Vector3(-3.1f, 0f, -2f);
|
|
||||||
var resetRend = resetBtn.AddComponent<SpriteRenderer>();
|
|
||||||
resetRend.sprite = MiraAssets.ResetButton.LoadAsset();
|
|
||||||
resetRend.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
|
|
||||||
resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor)
|
|
||||||
? Color.white
|
|
||||||
: group.GroupColor.FindAlternateColor();
|
|
||||||
var resetBoxCol = resetBtn.gameObject.AddComponent<BoxCollider2D>();
|
|
||||||
resetBoxCol.size = new Vector2(1f, 1f);
|
|
||||||
resetBoxCol.offset = new Vector2(0, 0);
|
|
||||||
var passiveButton = resetBtn.AddComponent<PassiveButton>();
|
|
||||||
passiveButton.OnClick = new Button.ButtonClickedEvent();
|
|
||||||
passiveButton.ClickSound = menu.BackButton.GetComponent<PassiveButton>().ClickSound;
|
|
||||||
passiveButton.OnMouseOver = new UnityEvent();
|
|
||||||
passiveButton.OnMouseOver.AddListener(
|
|
||||||
(UnityAction)(() =>
|
|
||||||
{
|
|
||||||
resetRend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor
|
|
||||||
? group.GroupColor
|
|
||||||
: MiraAssets.AcceptedTeal;
|
|
||||||
}));
|
|
||||||
passiveButton.OnMouseOut = new UnityEvent();
|
|
||||||
passiveButton.OnMouseOut.AddListener(
|
|
||||||
(UnityAction)(() =>
|
|
||||||
{
|
|
||||||
resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor)
|
|
||||||
? Color.white
|
|
||||||
: group.GroupColor.FindAlternateColor();
|
|
||||||
}));
|
|
||||||
if (newOpt is ToggleOption toggleOpt)
|
|
||||||
{
|
|
||||||
passiveButton.OnClick.AddListener(
|
|
||||||
(UnityAction)(() =>
|
|
||||||
{
|
|
||||||
defaultPreset!.ResetOption(toggleOpt);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
else if (newOpt is NumberOption numOpt)
|
|
||||||
{
|
|
||||||
passiveButton.OnClick.AddListener(
|
|
||||||
(UnityAction)(() =>
|
|
||||||
{
|
|
||||||
defaultPreset!.ResetOption(numOpt);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
else if (newOpt is StringOption strOpt)
|
|
||||||
{
|
|
||||||
passiveButton.OnClick.AddListener(
|
|
||||||
(UnityAction)(() =>
|
|
||||||
{
|
|
||||||
defaultPreset!.ResetOption(strOpt);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (!defaultPreset!.IsOptionInPreset(newOpt))
|
|
||||||
{
|
|
||||||
resetBtn.Destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
newOpt.Initialize();
|
newOpt.Initialize();
|
||||||
newOpt.gameObject.SetActive(false);
|
newOpt.gameObject.SetActive(false);
|
||||||
|
|||||||
@@ -110,15 +110,12 @@ internal static class GameSettingMenuPatches
|
|||||||
{
|
{
|
||||||
case 3:
|
case 3:
|
||||||
_modifiersButton?.SelectButton(true);
|
_modifiersButton?.SelectButton(true);
|
||||||
__instance.MenuDescriptionText.text = SelectedMod != null ? SelectedMod.MiraPlugin.ModifierMenuDescription : "Configure modifiers and their settings here!";
|
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
_customOneButton?.SelectButton(true);
|
_customOneButton?.SelectButton(true);
|
||||||
__instance.MenuDescriptionText.text = SelectedMod != null ? SelectedMod.MiraPlugin.CustomOptionMenuOneDescription : "Apply game settings for this mod!";
|
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
_customTwoButton?.SelectButton(true);
|
_customTwoButton?.SelectButton(true);
|
||||||
__instance.MenuDescriptionText.text = SelectedMod != null ? SelectedMod.MiraPlugin.CustomOptionMenuTwoDescription : "Apply game settings for this mod!";
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,9 +185,7 @@ public static class LobbyViewPanePatches
|
|||||||
__instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject);
|
__instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject);
|
||||||
num -= 0.696f;
|
num -= 0.696f;
|
||||||
var roles = RoleManager.Instance.AllRoles.ToArray().Where(x =>
|
var roles = RoleManager.Instance.AllRoles.ToArray().Where(x =>
|
||||||
x is not ICustomRole && !x.IsRoleBlacklisted() && x.Role != RoleTypes.Crewmate &&
|
x is not ICustomRole && x.Role != RoleTypes.Crewmate && x.Role != RoleTypes.Impostor &&
|
||||||
x.Role != RoleTypes.Impostor && ((i == 0 && x.TeamType is RoleTeamTypes.Crewmate) ||
|
|
||||||
(i == 1 && x.TeamType is RoleTeamTypes.Impostor)) &&
|
|
||||||
x.Role != RoleTypes.CrewmateGhost && x.Role != RoleTypes.ImpostorGhost).ToList();
|
x.Role != RoleTypes.CrewmateGhost && x.Role != RoleTypes.ImpostorGhost).ToList();
|
||||||
for (int j = 0; j < roles.Count; j++)
|
for (int j = 0; j < roles.Count; j++)
|
||||||
{
|
{
|
||||||
@@ -382,7 +380,7 @@ public static class LobbyViewPanePatches
|
|||||||
|
|
||||||
foreach (var grouping in sortedRoleGroups)
|
foreach (var grouping in sortedRoleGroups)
|
||||||
{
|
{
|
||||||
if (!grouping.Any() || grouping.All(x => x.Configuration.HideSettings || !x.VisibleInSettings()))
|
if (!grouping.Any() || grouping.All(x => x.Configuration.HideSettings))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -413,7 +411,7 @@ public static class LobbyViewPanePatches
|
|||||||
|
|
||||||
foreach (var customRole in grouping)
|
foreach (var customRole in grouping)
|
||||||
{
|
{
|
||||||
if (customRole.Configuration.HideSettings || !customRole.VisibleInSettings())
|
if (customRole.Configuration.HideSettings)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,19 +35,6 @@ public static class RoleSettingMenuPatches
|
|||||||
private static RoleBehaviour? CurrentRole { get; set; }
|
private static RoleBehaviour? CurrentRole { get; set; }
|
||||||
private static List<IModdedOption>? CurrentRoleOptions { get; set; }
|
private static List<IModdedOption>? CurrentRoleOptions { get; set; }
|
||||||
|
|
||||||
[HarmonyPostfix]
|
|
||||||
[HarmonyPatch(nameof(RolesSettingsMenu.OnEnable))]
|
|
||||||
public static void OpenPatch(RolesSettingsMenu __instance)
|
|
||||||
{
|
|
||||||
HudManager.Instance.PlayerCam.OverrideScreenShakeEnabled = false;
|
|
||||||
}
|
|
||||||
[HarmonyPostfix]
|
|
||||||
[HarmonyPatch(nameof(RolesSettingsMenu.OnDisable))]
|
|
||||||
public static void ClosePatch(RolesSettingsMenu __instance)
|
|
||||||
{
|
|
||||||
HudManager.Instance.PlayerCam.OverrideScreenShakeEnabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HarmonyPostfix]
|
[HarmonyPostfix]
|
||||||
[HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))]
|
[HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))]
|
||||||
public static void SetQuotaTabPostfix(RolesSettingsMenu __instance)
|
public static void SetQuotaTabPostfix(RolesSettingsMenu __instance)
|
||||||
@@ -87,10 +74,10 @@ public static class RoleSettingMenuPatches
|
|||||||
var num = 0.662f;
|
var num = 0.662f;
|
||||||
|
|
||||||
var list = CustomRoleManager.AllRoles.Where(
|
var list = CustomRoleManager.AllRoles.Where(
|
||||||
r => !r.IsCustomRole() && !r.IsRoleBlacklisted() && r.TeamType == RoleTeamTypes.Crewmate && r.Role != RoleTypes.Crewmate &&
|
r => !r.IsCustomRole() && r.TeamType == RoleTeamTypes.Crewmate && r.Role != RoleTypes.Crewmate &&
|
||||||
r.Role != RoleTypes.CrewmateGhost).ToList();
|
r.Role != RoleTypes.CrewmateGhost).ToList();
|
||||||
var list2 = CustomRoleManager.AllRoles.Where(
|
var list2 = CustomRoleManager.AllRoles.Where(
|
||||||
r => !r.IsCustomRole() && !r.IsRoleBlacklisted() && r.TeamType == RoleTeamTypes.Impostor && r.Role != RoleTypes.Impostor &&
|
r => !r.IsCustomRole() && r.TeamType == RoleTeamTypes.Impostor && r.Role != RoleTypes.Impostor &&
|
||||||
r.Role != RoleTypes.ImpostorGhost).ToList();
|
r.Role != RoleTypes.ImpostorGhost).ToList();
|
||||||
|
|
||||||
if (__instance.roleTabs == null || __instance.roleTabs.Count == 0)
|
if (__instance.roleTabs == null || __instance.roleTabs.Count == 0)
|
||||||
@@ -173,7 +160,7 @@ public static class RoleSettingMenuPatches
|
|||||||
|
|
||||||
foreach (var grouping in sortedRoleGroups)
|
foreach (var grouping in sortedRoleGroups)
|
||||||
{
|
{
|
||||||
if (!grouping.Any() || grouping.All(x=> x.Configuration.HideSettings || !x.VisibleInSettings()))
|
if (!grouping.Any() || grouping.All(x=>x.Configuration.HideSettings))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using MiraAPI.GameOptions;
|
|
||||||
using MiraAPI.GameOptions.OptionTypes;
|
|
||||||
using MiraAPI.Utilities;
|
using MiraAPI.Utilities;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
@@ -31,33 +28,7 @@ public static class SettingPatches
|
|||||||
string result;
|
string result;
|
||||||
var suffix = (MiraNumberSuffixes)__instance.SuffixType;
|
var suffix = (MiraNumberSuffixes)__instance.SuffixType;
|
||||||
|
|
||||||
var custom =
|
if (__instance.ZeroIsInfinity && Mathf.Abs(value) < 0.0001f)
|
||||||
ModdedOptionsManager.ModdedOptions.Values.FirstOrDefault(opt =>
|
|
||||||
opt.OptionBehaviour != null && opt.OptionBehaviour.Data == __instance);
|
|
||||||
if (custom is ModdedNumberOption moddedNumberOption)
|
|
||||||
{
|
|
||||||
if (moddedNumberOption.NegativeWordValue != "#" && (int)value == -1)
|
|
||||||
{
|
|
||||||
result = $"<b>{moddedNumberOption.NegativeWordValue}</b>";
|
|
||||||
}
|
|
||||||
else if (moddedNumberOption.ZeroWordValue != "#" && Mathf.Abs(value) < 0.0001f)
|
|
||||||
{
|
|
||||||
result = $"<b>{moddedNumberOption.ZeroWordValue}</b>";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = suffix switch
|
|
||||||
{
|
|
||||||
MiraNumberSuffixes.None => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo),
|
|
||||||
MiraNumberSuffixes.Multiplier => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + "x",
|
|
||||||
MiraNumberSuffixes.Percent => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + "%",
|
|
||||||
_ => TranslationController.Instance.GetString(
|
|
||||||
StringNames.GameSecondsAbbrev,
|
|
||||||
(Il2CppSystem.Object[])[value.ToString(__instance.FormatString, CultureInfo.InvariantCulture)]),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (__instance.ZeroIsInfinity && Mathf.Abs(value) < 0.0001f)
|
|
||||||
{
|
{
|
||||||
result = "<b>∞</b>";
|
result = "<b>∞</b>";
|
||||||
}
|
}
|
||||||
@@ -68,6 +39,7 @@ public static class SettingPatches
|
|||||||
MiraNumberSuffixes.None => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo),
|
MiraNumberSuffixes.None => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo),
|
||||||
MiraNumberSuffixes.Multiplier => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + "x",
|
MiraNumberSuffixes.Multiplier => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + "x",
|
||||||
MiraNumberSuffixes.Percent => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + "%",
|
MiraNumberSuffixes.Percent => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + "%",
|
||||||
|
MiraNumberSuffixes.Distance => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + MiraApiPlugin.DistanceSuffix.Value,
|
||||||
_ => TranslationController.Instance.GetString(
|
_ => TranslationController.Instance.GetString(
|
||||||
StringNames.GameSecondsAbbrev,
|
StringNames.GameSecondsAbbrev,
|
||||||
(Il2CppSystem.Object[])[value.ToString(__instance.FormatString, CultureInfo.InvariantCulture)]),
|
(Il2CppSystem.Object[])[value.ToString(__instance.FormatString, CultureInfo.InvariantCulture)]),
|
||||||
|
|||||||
@@ -117,20 +117,6 @@ internal static class PlayerControlPatches
|
|||||||
__instance.RpcMurderPlayer(target, true);
|
__instance.RpcMurderPlayer(target, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HarmonyPrefix]
|
|
||||||
[HarmonyPatch(nameof(PlayerControl.RpcMurderPlayer))]
|
|
||||||
[HarmonyPatch(nameof(PlayerControl.MurderPlayer))]
|
|
||||||
// ReSharper disable once InconsistentNaming
|
|
||||||
public static bool MurderPlayerPrefix(PlayerControl __instance)
|
|
||||||
{
|
|
||||||
if (LobbyBehaviour.Instance)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HarmonyPostfix]
|
[HarmonyPostfix]
|
||||||
[HarmonyPatch(nameof(PlayerControl.FixedUpdate))]
|
[HarmonyPatch(nameof(PlayerControl.FixedUpdate))]
|
||||||
// ReSharper disable once InconsistentNaming
|
// ReSharper disable once InconsistentNaming
|
||||||
|
|||||||
@@ -13,10 +13,6 @@ internal static class HowToPlayPatch
|
|||||||
// yes i patched the entire method
|
// yes i patched the entire method
|
||||||
private static void Prefix(HowToPlayScene __instance)
|
private static void Prefix(HowToPlayScene __instance)
|
||||||
{
|
{
|
||||||
if (RoleManager.Instance.AllRoles.ToArray().All(x => !x.IsCustomRole()))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
__instance.sceneIndex = 0;
|
__instance.sceneIndex = 0;
|
||||||
__instance.category = HowToPlayScene.HowToPlayCategory.RolesSelection;
|
__instance.category = HowToPlayScene.HowToPlayCategory.RolesSelection;
|
||||||
__instance.startPage.SetActive(false);
|
__instance.startPage.SetActive(false);
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public static class KillButtonPatches
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Use Custom Murder instead of Murder Player from vanilla.
|
/// Use Custom Murder if player is custom role.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HarmonyPrefix]
|
[HarmonyPrefix]
|
||||||
[HarmonyPatch(nameof(KillButton.DoClick))]
|
[HarmonyPatch(nameof(KillButton.DoClick))]
|
||||||
@@ -58,7 +58,12 @@ public static class KillButtonPatches
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerControl.LocalPlayer.RpcCustomMurder(__instance.currentTarget, MeetingCheck.OutsideMeeting);
|
if (PlayerControl.LocalPlayer.Data.Role is not ICustomRole)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerControl.LocalPlayer.RpcCustomMurder(__instance.currentTarget);
|
||||||
__instance.SetTarget(null);
|
__instance.SetTarget(null);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,183 +0,0 @@
|
|||||||
using AmongUs.GameOptions;
|
|
||||||
using HarmonyLib;
|
|
||||||
using Il2CppSystem.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using MiraAPI.Roles;
|
|
||||||
|
|
||||||
namespace MiraAPI.Patches.Roles;
|
|
||||||
|
|
||||||
[HarmonyPatch(typeof(LogicRoleSelectionHnS))]
|
|
||||||
public static class LogicRoleSelectionHnsPatch
|
|
||||||
{
|
|
||||||
[HarmonyPrefix]
|
|
||||||
[HarmonyPatch(nameof(LogicRoleSelectionHnS.AssignRolesForTeam))]
|
|
||||||
public static bool AssignRolesForTeam(
|
|
||||||
LogicRoleSelectionHnS __instance,
|
|
||||||
List<NetworkedPlayerInfo> players,
|
|
||||||
IGameOptions opts,
|
|
||||||
RoleTeamTypes team,
|
|
||||||
int teamMax,
|
|
||||||
Il2CppSystem.Nullable<RoleTypes> defaultRole)
|
|
||||||
{
|
|
||||||
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Team: {team}, Max: {teamMax}, Players: {players.Count}, DefaultRole: {defaultRole}");
|
|
||||||
int num = 0;
|
|
||||||
IRoleOptionsCollection roleOptions = opts.RoleOptions;
|
|
||||||
var source = RoleManager.Instance.AllRoles.ToArray()
|
|
||||||
.Where(role => role.TeamType == team && !RoleManager.IsGhostRole(role.Role) &&
|
|
||||||
CustomRoleUtils.CanSpawnOnCurrentMode(role));
|
|
||||||
var assignmentData = source.Where(x => !x.IsDead).Select(role =>
|
|
||||||
new RoleManager.RoleAssignmentData(
|
|
||||||
role,
|
|
||||||
roleOptions.GetNumPerGame(role.Role),
|
|
||||||
roleOptions.GetChancePerGame(role.Role))).ToList();
|
|
||||||
var source2 = CustomRoleUtils.GetPossibleRoles(assignmentData, x => x.Chance == 100);
|
|
||||||
var guaranteedRoles = source.Where(x => source2.Contains(((ushort)x.Role, 100)));
|
|
||||||
List<RoleTypes> list = new List<RoleTypes>();
|
|
||||||
if (team == RoleTeamTypes.Crewmate)
|
|
||||||
{
|
|
||||||
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Before Guaranteed Assignment");
|
|
||||||
foreach (RoleManager.RoleAssignmentData roleAssignmentData in guaranteedRoles.Select((x) =>
|
|
||||||
new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100)))
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
RoleManager.RoleAssignmentData roleAssignmentData2 = roleAssignmentData;
|
|
||||||
int count = roleAssignmentData2.Count;
|
|
||||||
roleAssignmentData2.Count = count - 1;
|
|
||||||
if (count <= 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
list.Add(roleAssignmentData.Role.Role);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Guaranteed Assignment");
|
|
||||||
AssignRolesFromList(players, teamMax, list, ref num);
|
|
||||||
|
|
||||||
var list2 = source.Where(x => !x.IsDead).Select(role =>
|
|
||||||
new RoleManager.RoleAssignmentData(
|
|
||||||
role,
|
|
||||||
roleOptions.GetNumPerGame(role.Role),
|
|
||||||
roleOptions.GetChancePerGame(role.Role))).ToList();
|
|
||||||
|
|
||||||
list.Clear();
|
|
||||||
foreach (RoleManager.RoleAssignmentData roleAssignmentData3 in list2)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < roleAssignmentData3.Count; i++)
|
|
||||||
{
|
|
||||||
if (HashRandom.Next(101) < roleAssignmentData3.Chance)
|
|
||||||
{
|
|
||||||
list.Add(roleAssignmentData3.Role.Role);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Potential Assignment");
|
|
||||||
|
|
||||||
AssignRolesFromList(players, teamMax, list, ref num);
|
|
||||||
var basicRole = RoleTypes.Engineer;
|
|
||||||
while (list.Count < players.Count && list.Count + num < teamMax)
|
|
||||||
{
|
|
||||||
list.Add(basicRole);
|
|
||||||
}
|
|
||||||
|
|
||||||
AssignRolesFromList(players, teamMax, list, ref num);
|
|
||||||
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Fallback Assignment");
|
|
||||||
}
|
|
||||||
else if (team == RoleTeamTypes.Impostor)
|
|
||||||
{
|
|
||||||
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Before Guaranteed Assignment");
|
|
||||||
var newImpostors = new List<NetworkedPlayerInfo>();
|
|
||||||
// Specified Seeker
|
|
||||||
if (__instance.hnsManager.LogicOptionsHnS.HasImpostorPlayerID() &&
|
|
||||||
__instance.hnsManager.LogicOptionsHnS.ValidateImpostorPlayerID(players) &&
|
|
||||||
!AmongUsClient.Instance.IsGamePublic)
|
|
||||||
{
|
|
||||||
NetworkedPlayerInfo networkedPlayerInfo = players.ToArray()
|
|
||||||
.First(p => p.PlayerId == __instance.hnsManager.LogicOptionsHnS.ImpostorPlayerID());
|
|
||||||
players.Remove(networkedPlayerInfo);
|
|
||||||
newImpostors.Add(networkedPlayerInfo);
|
|
||||||
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Seeker is {networkedPlayerInfo.PlayerName}, ID: {networkedPlayerInfo.PlayerId}");
|
|
||||||
}
|
|
||||||
// Random Seeker
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int num2 = 0;
|
|
||||||
while (num2 < teamMax && players.Count > 0)
|
|
||||||
{
|
|
||||||
PseudoRandomList<NetworkedPlayerInfo> pseudoRandomList = new PseudoRandomList<NetworkedPlayerInfo>(AmongUsClient.Instance.GameId);
|
|
||||||
players._items.Do(x => pseudoRandomList.Add(x));
|
|
||||||
for (int i = 0; i < GameData.RoundsPlayedInSession; i++)
|
|
||||||
{
|
|
||||||
pseudoRandomList.PickRandom();
|
|
||||||
}
|
|
||||||
NetworkedPlayerInfo networkedPlayerInfo = pseudoRandomList.PickRandom();
|
|
||||||
players.Remove(networkedPlayerInfo);
|
|
||||||
newImpostors.Add(networkedPlayerInfo);
|
|
||||||
num2++;
|
|
||||||
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Seeker is {networkedPlayerInfo.PlayerName}, ID: {networkedPlayerInfo.PlayerId}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Guaranteed Assignment");
|
|
||||||
foreach (RoleManager.RoleAssignmentData roleAssignmentData in guaranteedRoles.Select((x) =>
|
|
||||||
new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100)))
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
RoleManager.RoleAssignmentData roleAssignmentData2 = roleAssignmentData;
|
|
||||||
int count = roleAssignmentData2.Count;
|
|
||||||
roleAssignmentData2.Count = count - 1;
|
|
||||||
if (count <= 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
list.Add(roleAssignmentData.Role.Role);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AssignRolesFromList(newImpostors, teamMax, list, ref num);
|
|
||||||
|
|
||||||
var list2 = source.Where(x => !x.IsDead).Select(role =>
|
|
||||||
new RoleManager.RoleAssignmentData(
|
|
||||||
role,
|
|
||||||
roleOptions.GetNumPerGame(role.Role),
|
|
||||||
roleOptions.GetChancePerGame(role.Role))).ToList();
|
|
||||||
|
|
||||||
list.Clear();
|
|
||||||
foreach (RoleManager.RoleAssignmentData roleAssignmentData3 in list2)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < roleAssignmentData3.Count; i++)
|
|
||||||
{
|
|
||||||
if (HashRandom.Next(101) < roleAssignmentData3.Chance)
|
|
||||||
{
|
|
||||||
list.Add(roleAssignmentData3.Role.Role);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AssignRolesFromList(newImpostors, teamMax, list, ref num);
|
|
||||||
var basicRole = RoleTypes.Impostor;
|
|
||||||
while (list.Count < newImpostors.Count && list.Count + num < teamMax)
|
|
||||||
{
|
|
||||||
list.Add(basicRole);
|
|
||||||
}
|
|
||||||
|
|
||||||
AssignRolesFromList(newImpostors, teamMax, list, ref num);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void AssignRolesFromList(List<NetworkedPlayerInfo> players, int teamMax, List<RoleTypes> roleList, ref int rolesAssigned)
|
|
||||||
{
|
|
||||||
while (roleList.Count > 0 && players.Count > 0 && rolesAssigned < teamMax)
|
|
||||||
{
|
|
||||||
int index = HashRandom.FastNext(roleList.Count);
|
|
||||||
RoleTypes roleType = roleList[index];
|
|
||||||
roleList.RemoveAt(index);
|
|
||||||
int index2 = HashRandom.FastNext(players.Count);
|
|
||||||
players[index2].Object.RpcSetRole(roleType, false);
|
|
||||||
players.RemoveAt(index2);
|
|
||||||
rolesAssigned++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
using AmongUs.GameOptions;
|
|
||||||
using HarmonyLib;
|
|
||||||
using Il2CppSystem.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using MiraAPI.Roles;
|
|
||||||
|
|
||||||
namespace MiraAPI.Patches.Roles;
|
|
||||||
|
|
||||||
[HarmonyPatch(typeof(LogicRoleSelectionNormal))]
|
|
||||||
public static class LogicRoleSelectionNormalPatch
|
|
||||||
{
|
|
||||||
[HarmonyPrefix]
|
|
||||||
[HarmonyPatch(nameof(LogicRoleSelectionNormal.AssignRolesForTeam))]
|
|
||||||
public static bool AssignRolesForTeam(
|
|
||||||
LogicRoleSelectionNormal __instance,
|
|
||||||
List<NetworkedPlayerInfo> players,
|
|
||||||
IGameOptions opts,
|
|
||||||
RoleTeamTypes team,
|
|
||||||
int teamMax,
|
|
||||||
Il2CppSystem.Nullable<RoleTypes> defaultRole)
|
|
||||||
{
|
|
||||||
int num = 0;
|
|
||||||
var source = RoleManager.Instance.AllRoles.ToArray()
|
|
||||||
.Where(role => role.TeamType == team && !RoleManager.IsGhostRole(role.Role) &&
|
|
||||||
CustomRoleUtils.CanSpawnOnCurrentMode(role));
|
|
||||||
List<RoleTypes> list = new List<RoleTypes>();
|
|
||||||
IRoleOptionsCollection roleOptions = opts.RoleOptions;
|
|
||||||
|
|
||||||
var assignmentData = source.Where(x => !x.IsDead).Select(role =>
|
|
||||||
new RoleManager.RoleAssignmentData(
|
|
||||||
role,
|
|
||||||
roleOptions.GetNumPerGame(role.Role),
|
|
||||||
roleOptions.GetChancePerGame(role.Role))).ToList();
|
|
||||||
var source2 = CustomRoleUtils.GetPossibleRoles(assignmentData, x => x.Chance == 100);
|
|
||||||
var guaranteedRoles = source.Where(x => source2.Contains(((ushort)x.Role, 100)));
|
|
||||||
foreach (RoleManager.RoleAssignmentData roleAssignmentData in guaranteedRoles.Select((x) =>
|
|
||||||
new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100)))
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
RoleManager.RoleAssignmentData roleAssignmentData2 = roleAssignmentData;
|
|
||||||
int count = roleAssignmentData2.Count;
|
|
||||||
roleAssignmentData2.Count = count - 1;
|
|
||||||
if (count <= 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
list.Add(roleAssignmentData.Role.Role);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
__instance.AssignRolesFromList(players, teamMax, list, ref num);
|
|
||||||
|
|
||||||
var list2 = source.Where(x => !x.IsDead).Select(role =>
|
|
||||||
new RoleManager.RoleAssignmentData(
|
|
||||||
role,
|
|
||||||
roleOptions.GetNumPerGame(role.Role),
|
|
||||||
roleOptions.GetChancePerGame(role.Role))).ToList();
|
|
||||||
|
|
||||||
list.Clear();
|
|
||||||
foreach (RoleManager.RoleAssignmentData roleAssignmentData3 in list2)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < roleAssignmentData3.Count; i++)
|
|
||||||
{
|
|
||||||
if (HashRandom.Next(101) < roleAssignmentData3.Chance)
|
|
||||||
{
|
|
||||||
list.Add(roleAssignmentData3.Role.Role);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
__instance.AssignRolesFromList(players, teamMax, list, ref num);
|
|
||||||
var defaultRole2 = team is RoleTeamTypes.Crewmate ? RoleTypes.Crewmate : RoleTypes.Impostor;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
defaultRole2 = defaultRole.Value;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
while (list.Count < players.Count && list.Count + num < teamMax)
|
|
||||||
{
|
|
||||||
list.Add(defaultRole2);
|
|
||||||
}
|
|
||||||
|
|
||||||
__instance.AssignRolesFromList(players, teamMax, list, ref num);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,7 @@ public static class NameTagPatch
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PlayerControl.LocalPlayer.Data?.Role?.IsImpostor == true && otherPlayerRole.IsImpostor)
|
if (PlayerControl.LocalPlayer.Data.Role.IsImpostor && otherPlayerRole.IsImpostor)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ public static class NameTagPatch
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!PlayerControl.LocalPlayer || !PlayerControl.LocalPlayer.Data || !PlayerControl.LocalPlayer.Data?.Role || !otherPlayerRole)
|
if (!PlayerControl.LocalPlayer || !PlayerControl.LocalPlayer.Data || !PlayerControl.LocalPlayer.Data.Role || !otherPlayerRole)
|
||||||
{
|
{
|
||||||
__result = Color.white;
|
__result = Color.white;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AmongUs.GameOptions;
|
using AmongUs.GameOptions;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using InnerNet;
|
|
||||||
using MiraAPI.Events;
|
using MiraAPI.Events;
|
||||||
using MiraAPI.Events.Vanilla.Gameplay;
|
using MiraAPI.Events.Vanilla.Gameplay;
|
||||||
using MiraAPI.Modifiers;
|
using MiraAPI.Modifiers;
|
||||||
|
|||||||
@@ -22,21 +22,6 @@ public interface IMiraPlugin
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual string CustomOptionMenuNameTwo => "Custom Category 2";
|
public virtual string CustomOptionMenuNameTwo => "Custom Category 2";
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the description for the second custom category in the game options menu, if any.
|
|
||||||
/// </summary>
|
|
||||||
public virtual string ModifierMenuDescription => "Configure modifiers and their settings here!";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the description for the first custom category in the game options menu, if any.
|
|
||||||
/// </summary>
|
|
||||||
public virtual string CustomOptionMenuOneDescription => "Apply game settings for this mod!";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the description for the second custom category in the game options menu, if any.
|
|
||||||
/// </summary>
|
|
||||||
public virtual string CustomOptionMenuTwoDescription => "Apply game settings for this mod!";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the BepInEx configuration file for the plugin.
|
/// Gets the BepInEx configuration file for the plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class MiraPluginInfo
|
|||||||
public IReadOnlyCollection<CustomActionButton> Buttons { get; private set; } = null!;
|
public IReadOnlyCollection<CustomActionButton> Buttons { get; private set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a read only dictionary of this plugin's custom presets.
|
/// Gets a read only dictionary of this plugin's custom game modes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyCollection<OptionPreset> Presets { get; internal set; } = null!;
|
public IReadOnlyCollection<OptionPreset> Presets { get; internal set; } = null!;
|
||||||
|
|
||||||
|
|||||||
@@ -71,26 +71,4 @@ public class OptionPreset
|
|||||||
|
|
||||||
CustomRoleManager.SyncAllRoleSettings();
|
CustomRoleManager.SyncAllRoleSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resets the specified option with the preset provided.
|
|
||||||
/// </summary>
|
|
||||||
public void ResetOption(OptionBehaviour baseOption)
|
|
||||||
{
|
|
||||||
var selectedOpt = Plugin.InternalOptions.First(x => x.OptionBehaviour == baseOption);
|
|
||||||
selectedOpt.LoadFromPreset(PresetConfig);
|
|
||||||
/*ModdedOptionsManager.SyncAllOptions();
|
|
||||||
|
|
||||||
CustomRoleManager.SyncAllRoleSettings();*/
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets whether the specified option is included in the preset.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>>The value of whether the option is in the preset.</returns>
|
|
||||||
public bool IsOptionInPreset(OptionBehaviour baseOption)
|
|
||||||
{
|
|
||||||
var selectedOpt = Plugin.InternalOptions.First(x => x.OptionBehaviour == baseOption);
|
|
||||||
return selectedOpt.ConfigDefinition != null && PresetConfig.ContainsKey(selectedOpt.ConfigDefinition);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.IO;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using BepInEx.Configuration;
|
using BepInEx.Configuration;
|
||||||
using MiraAPI.PluginLoading;
|
using MiraAPI.PluginLoading;
|
||||||
@@ -19,14 +18,6 @@ public static class PresetManager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static string PresetDirectory { get; } = Path.GetFullPath("mira_presets", Application.persistentDataPath);
|
public static string PresetDirectory { get; } = Path.GetFullPath("mira_presets", Application.persistentDataPath);
|
||||||
|
|
||||||
internal static Dictionary<MiraPluginInfo, OptionPreset> DefaultPresets { get; } = [];
|
|
||||||
internal static List<OptionPreset> InternalMasterPresets { get; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a read only dictionary of this plugin's custom presets.
|
|
||||||
/// </summary>
|
|
||||||
public static IReadOnlyCollection<OptionPreset> MasterPresets { get; internal set; } = null!;
|
|
||||||
|
|
||||||
internal static void CreateDefaultPreset(MiraPluginInfo plugin)
|
internal static void CreateDefaultPreset(MiraPluginInfo plugin)
|
||||||
{
|
{
|
||||||
var presetPath = Path.Combine(PresetDirectory, plugin.PluginId);
|
var presetPath = Path.Combine(PresetDirectory, plugin.PluginId);
|
||||||
@@ -48,7 +39,6 @@ public static class PresetManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
presetConfig.Save();
|
presetConfig.Save();
|
||||||
DefaultPresets.Add(plugin, new OptionPreset("Default", plugin, presetConfig));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -103,58 +93,4 @@ public static class PresetManager
|
|||||||
}
|
}
|
||||||
plugin.Presets = [..plugin.InternalPresets];
|
plugin.Presets = [..plugin.InternalPresets];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Loads the master presets by reading the configuration files from the preset directory.
|
|
||||||
/// </summary>
|
|
||||||
public static void LoadMasterPreset()
|
|
||||||
{
|
|
||||||
/*foreach (var btn in plugin.InternalPresets.Select(x=>x.PresetButton))
|
|
||||||
{
|
|
||||||
if (btn != null)
|
|
||||||
{
|
|
||||||
Object.DestroyImmediate(btn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin.InternalPresets.Clear();*/
|
|
||||||
if (!Directory.Exists(PresetDirectory))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(PresetDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
var pluginPresetPath = Path.Combine(PresetDirectory, "MasterPresets");
|
|
||||||
if (!Directory.Exists(pluginPresetPath))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(pluginPresetPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var file in Directory.GetFiles(pluginPresetPath, "*.cfg"))
|
|
||||||
{
|
|
||||||
var fileName = Path.GetFileName(file);
|
|
||||||
Info($"Loading preset file {fileName}");
|
|
||||||
var presetName = Path.GetFileNameWithoutExtension(file);
|
|
||||||
var presetConfig = new ConfigFile(file, false)
|
|
||||||
{
|
|
||||||
SaveOnConfigSet = false,
|
|
||||||
};
|
|
||||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
|
|
||||||
{
|
|
||||||
foreach (var option in plugin.InternalOptions.Where(x=>x.IncludeInPreset))
|
|
||||||
{
|
|
||||||
option.Bind(presetConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var role in plugin.InternalRoles.Values.OfType<ICustomRole>().Where(x=>!x.Configuration.HideSettings))
|
|
||||||
{
|
|
||||||
role.BindConfig(presetConfig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
presetConfig.Save();
|
|
||||||
|
|
||||||
// InternalMasterPresets.Add(new OptionPreset(presetName, plugin, presetConfig));
|
|
||||||
}
|
|
||||||
MasterPresets = [..InternalMasterPresets];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,9 +1,7 @@
|
|||||||
using AmongUs.GameOptions;
|
using AmongUs.GameOptions;
|
||||||
using System;
|
|
||||||
using Il2CppInterop.Runtime.Attributes;
|
|
||||||
using MiraAPI.Patches.Freeplay;
|
|
||||||
using MiraAPI.Utilities.Assets;
|
using MiraAPI.Utilities.Assets;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace MiraAPI.Roles;
|
namespace MiraAPI.Roles;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -11,16 +9,6 @@ namespace MiraAPI.Roles;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public record struct CustomRoleConfiguration
|
public record struct CustomRoleConfiguration
|
||||||
{
|
{
|
||||||
#pragma warning disable S1133
|
|
||||||
[Obsolete("Default constructor is not supported. Please use the constructor that takes an ICustomRole parameter.")]
|
|
||||||
#pragma warning restore S1133
|
|
||||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
|
||||||
public CustomRoleConfiguration()
|
|
||||||
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
|
|
||||||
{
|
|
||||||
throw new NotImplementedException("Default constructor is not supported. Please use the constructor that takes an ICustomRole parameter.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="CustomRoleConfiguration"/> struct.
|
/// Initializes a new instance of the <see cref="CustomRoleConfiguration"/> struct.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -48,7 +36,7 @@ public record struct CustomRoleConfiguration
|
|||||||
{
|
{
|
||||||
ModdedRoleTeams.Crewmate => TranslationController.Instance.GetString(StringNames.Crewmate),
|
ModdedRoleTeams.Crewmate => TranslationController.Instance.GetString(StringNames.Crewmate),
|
||||||
ModdedRoleTeams.Impostor => TranslationController.Instance.GetString(StringNames.Impostor),
|
ModdedRoleTeams.Impostor => TranslationController.Instance.GetString(StringNames.Impostor),
|
||||||
_ => TaskAdderPatches.NeutralName,
|
_ => "Neutral",
|
||||||
};
|
};
|
||||||
IntroSound = role.Team is ModdedRoleTeams.Crewmate
|
IntroSound = role.Team is ModdedRoleTeams.Crewmate
|
||||||
? CustomRoleManager.CrewmateIntroSound
|
? CustomRoleManager.CrewmateIntroSound
|
||||||
@@ -78,19 +66,16 @@ public record struct CustomRoleConfiguration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Sprite used for the Role Options menu screenshot.
|
/// Gets or sets the Sprite used for the Role Options menu screenshot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HideFromIl2Cpp]
|
|
||||||
public LoadableAsset<Sprite>? OptionsScreenshot { get; set; } = null;
|
public LoadableAsset<Sprite>? OptionsScreenshot { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Sprite used for the Role Icon.
|
/// Gets or sets the Sprite used for the Role Icon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HideFromIl2Cpp]
|
|
||||||
public LoadableAsset<Sprite>? Icon { get; set; } = null;
|
public LoadableAsset<Sprite>? Icon { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Intro sound for the Role.
|
/// Gets or sets the Intro sound for the Role.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HideFromIl2Cpp]
|
|
||||||
public LoadableAsset<AudioClip>? IntroSound { get; set; } = null;
|
public LoadableAsset<AudioClip>? IntroSound { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -88,8 +88,6 @@ public static class CustomRoleManager
|
|||||||
pluginInfo.PluginConfig.SaveOnConfigSet = false;
|
pluginInfo.PluginConfig.SaveOnConfigSet = false;
|
||||||
|
|
||||||
foreach (var roleType in roles)
|
foreach (var roleType in roles)
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
ClassInjector.RegisterTypeInIl2Cpp(roleType);
|
ClassInjector.RegisterTypeInIl2Cpp(roleType);
|
||||||
|
|
||||||
@@ -101,11 +99,6 @@ public static class CustomRoleManager
|
|||||||
|
|
||||||
pluginInfo.InternalRoles.Add((ushort)role.Role, role);
|
pluginInfo.InternalRoles.Add((ushort)role.Role, role);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Error($"Failed to register role type {roleType.Name}: {ex}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginInfo.PluginConfig.Save();
|
pluginInfo.PluginConfig.Save();
|
||||||
pluginInfo.PluginConfig.SaveOnConfigSet = oldConfigSetting;
|
pluginInfo.PluginConfig.SaveOnConfigSet = oldConfigSetting;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -14,62 +13,6 @@ namespace MiraAPI.Roles;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class CustomRoleUtils
|
public static class CustomRoleUtils
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Determines whether the specified role can spawn in general, accounting for gamemodes and everything else.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="role">The role you would like to check for.</param>
|
|
||||||
/// <returns>True if the role is able to spawn, otherwise false.</returns>
|
|
||||||
public static bool CanSpawnOnCurrentMode(RoleBehaviour role)
|
|
||||||
{
|
|
||||||
if (role is ICustomRole custom)
|
|
||||||
{
|
|
||||||
return custom.CanSpawnOnCurrentMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GameManager.Instance.IsHideAndSeek())
|
|
||||||
{
|
|
||||||
return role.Role is RoleTypes.Engineer || role.Role is RoleTypes.Impostor;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<(ushort RoleType, int Chance)> GetPossibleRoles(
|
|
||||||
List<RoleManager.RoleAssignmentData> assignmentData,
|
|
||||||
Func<RoleManager.RoleAssignmentData, bool>? predicate = null)
|
|
||||||
{
|
|
||||||
var roles = new List<(ushort, int)>();
|
|
||||||
|
|
||||||
assignmentData.Where(x => predicate == null || predicate(x)).ToList().ForEach(x =>
|
|
||||||
{
|
|
||||||
for (var i = 0; i < x.Count; i++)
|
|
||||||
{
|
|
||||||
roles.Add(((ushort)x.Role.Role, x.Chance));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RoleManager.RoleAssignmentData GetAssignData(RoleTypes roleType)
|
|
||||||
{
|
|
||||||
var currentGameOptions = GameOptionsManager.Instance.CurrentGameOptions;
|
|
||||||
var roleOptions = currentGameOptions.RoleOptions;
|
|
||||||
|
|
||||||
var role = GetRegisteredRole(roleType);
|
|
||||||
var assignmentData = new RoleManager.RoleAssignmentData(role, roleOptions.GetNumPerGame(role!.Role),
|
|
||||||
roleOptions.GetChancePerGame(role.Role));
|
|
||||||
|
|
||||||
return assignmentData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RoleBehaviour? GetRegisteredRole(RoleTypes roleType)
|
|
||||||
{
|
|
||||||
// we want to prioritize the custom roles because the role has the right RoleColour/TeamColor
|
|
||||||
var role = CustomRoleManager.AllRoles.FirstOrDefault(x => x.Role == roleType);
|
|
||||||
|
|
||||||
return role;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all active in-game roles.
|
/// Gets all active in-game roles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Text;
|
||||||
using System.Text;
|
|
||||||
using BepInEx.Configuration;
|
using BepInEx.Configuration;
|
||||||
using MiraAPI.GameOptions;
|
using MiraAPI.GameOptions;
|
||||||
using MiraAPI.Modifiers;
|
using MiraAPI.Modifiers;
|
||||||
@@ -240,15 +239,4 @@ public interface ICustomRole : IOptionable
|
|||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether the role can spawn in general, accounting for gamemodes and everything else.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if the role is able to spawn, otherwise false.</returns>
|
|
||||||
public virtual bool CanSpawnOnCurrentMode() => !GameManager.Instance.IsHideAndSeek();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the function that determines whether the role should be toggled on or off in the game settings.
|
|
||||||
/// </summary>
|
|
||||||
public virtual Func<bool> VisibleInSettings => () => true;
|
|
||||||
}
|
}
|
||||||
@@ -90,11 +90,6 @@ public static class MiraAssets
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LoadableResourceAsset CheckmarkBox { get; } = new("MiraAPI.Resources.CheckMarkBox.png");
|
public static LoadableResourceAsset CheckmarkBox { get; } = new("MiraAPI.Resources.CheckMarkBox.png");
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the Reset Box used in the Settings Menu.
|
|
||||||
/// </summary>
|
|
||||||
public static LoadableResourceAsset ResetButton { get; } = new("MiraAPI.Resources.ResetButton.png");
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the Checkmark used in the Settings Menu.
|
/// Gets the Checkmark used in the Settings Menu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace MiraAPI.Utilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enum serializer for converting enums to and from byte arrays.
|
||||||
|
/// </summary>
|
||||||
|
public static class EnumSerializer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes an enum to an array of bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The enum value to serialize.</param>
|
||||||
|
/// <typeparam name="T">The enum type.</typeparam>
|
||||||
|
/// <returns>A byte array representing the enum value.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the underlying type is not recognized.</exception>
|
||||||
|
public static byte[] EnumToBytes<T>(T value) where T : struct, Enum
|
||||||
|
{
|
||||||
|
var underlyingType = typeof(T).GetEnumUnderlyingType();
|
||||||
|
|
||||||
|
return underlyingType switch
|
||||||
|
{
|
||||||
|
not null when underlyingType == typeof(byte) => [(byte)(object)value],
|
||||||
|
not null when underlyingType == typeof(sbyte) => [(byte)(sbyte)(object)value],
|
||||||
|
not null when underlyingType == typeof(short) => BitConverter.GetBytes((short)(object)value),
|
||||||
|
not null when underlyingType == typeof(ushort) => BitConverter.GetBytes((ushort)(object)value),
|
||||||
|
not null when underlyingType == typeof(int) => BitConverter.GetBytes((int)(object)value),
|
||||||
|
not null when underlyingType == typeof(uint) => BitConverter.GetBytes((uint)(object)value),
|
||||||
|
not null when underlyingType == typeof(long) => BitConverter.GetBytes((long)(object)value),
|
||||||
|
not null when underlyingType == typeof(ulong) => BitConverter.GetBytes((ulong)(object)value),
|
||||||
|
_ => throw new ArgumentException("Unknown underlying type for " + typeof(T).Name),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts bytes to an enum using the underlying type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes">The byte array to convert.</param>
|
||||||
|
/// <param name="offset">The offset in the byte array to start reading from.</param>
|
||||||
|
/// <param name="underlyingType">The underlying type of the enum.</param>
|
||||||
|
/// <typeparam name="T">The enum type.</typeparam>
|
||||||
|
/// <returns>The enum value represented by the bytes.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the underlying type is not recognized.</exception>
|
||||||
|
public static T EnumFromBytes<T>(byte[] bytes, int offset, Type underlyingType) where T : struct, Enum
|
||||||
|
{
|
||||||
|
return underlyingType switch
|
||||||
|
{
|
||||||
|
{ } t when t == typeof(byte) || t == typeof(sbyte) => (T)(object)bytes[offset],
|
||||||
|
{ } t when t == typeof(short) => (T)(object)BitConverter.ToInt16(bytes, offset),
|
||||||
|
{ } t when t == typeof(ushort) => (T)(object)BitConverter.ToUInt16(bytes, offset),
|
||||||
|
{ } t when t == typeof(int) => (T)(object)BitConverter.ToInt32(bytes, offset),
|
||||||
|
{ } t when t == typeof(uint) => (T)(object)BitConverter.ToUInt32(bytes, offset),
|
||||||
|
{ } t when t == typeof(long) => (T)(object)BitConverter.ToInt64(bytes, offset),
|
||||||
|
{ } t when t == typeof(ulong) => (T)(object)BitConverter.ToUInt64(bytes, offset),
|
||||||
|
_ => throw new ArgumentException("Unknown underlying type for " + typeof(T).Name),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="EnumFromBytes{T}(byte[], int, Type)"/>
|
||||||
|
public static T EnumFromBytes<T>(byte[] bytes, int offset) where T : struct, Enum => EnumFromBytes<T>(bytes, offset, typeof(T).GetEnumUnderlyingType());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a byte array into a collection of enum values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes">The byte array to convert.</param>
|
||||||
|
/// <typeparam name="T">The enum type.</typeparam>
|
||||||
|
/// <returns>An enumerable collection of enum values.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the byte array length is not a multiple of the enum's underlying type size.</exception>
|
||||||
|
public static IEnumerable<T> EnumsFromBytes<T>(byte[] bytes) where T : struct, Enum
|
||||||
|
{
|
||||||
|
var underlyingType = typeof(T).GetEnumUnderlyingType();
|
||||||
|
var size = GetManagedSize(underlyingType);
|
||||||
|
|
||||||
|
if (bytes.Length % size != 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Byte array length must be a multiple of " + size + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
return EnumsFromBytesIterator<T>(bytes, underlyingType, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<T> EnumsFromBytesIterator<T>(byte[] bytes, Type underlyingType, int size) where T : struct, Enum
|
||||||
|
{
|
||||||
|
var count = bytes.Length / size;
|
||||||
|
var underlyingArray = Array.CreateInstance(underlyingType, count);
|
||||||
|
Buffer.BlockCopy(bytes, 0, underlyingArray, 0, bytes.Length);
|
||||||
|
|
||||||
|
foreach (var value in underlyingArray)
|
||||||
|
yield return (T)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a collection of enum values to a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values"> The collection of enum values to convert.</param>
|
||||||
|
/// <typeparam name="T">The enum type.</typeparam>
|
||||||
|
/// <returns>A byte array representing the collection of enum values.</returns>
|
||||||
|
public static byte[] EnumsToBytes<T>(IEnumerable<T> values) where T : struct, Enum
|
||||||
|
{
|
||||||
|
var underlyingType = typeof(T).GetEnumUnderlyingType();
|
||||||
|
var size = GetManagedSize(underlyingType);
|
||||||
|
var valuesArray = values.ToArray();
|
||||||
|
var count = valuesArray.Length;
|
||||||
|
|
||||||
|
var underlyingArray = Array.CreateInstance(underlyingType, count);
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
underlyingArray.SetValue(ConvertToUnderlyingType(valuesArray[i], underlyingType), i);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = new byte[count * size];
|
||||||
|
Buffer.BlockCopy(underlyingArray, 0, bytes, 0, bytes.Length);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object ConvertToUnderlyingType<T>(T value, Type underlyingType) where T : struct, Enum
|
||||||
|
{
|
||||||
|
return underlyingType switch
|
||||||
|
{
|
||||||
|
not null when underlyingType == typeof(byte) => (byte)(object)value,
|
||||||
|
not null when underlyingType == typeof(sbyte) => (sbyte)(object)value,
|
||||||
|
not null when underlyingType == typeof(short) => (short)(object)value,
|
||||||
|
not null when underlyingType == typeof(ushort) => (ushort)(object)value,
|
||||||
|
not null when underlyingType == typeof(int) => (int)(object)value,
|
||||||
|
not null when underlyingType == typeof(uint) => (uint)(object)value,
|
||||||
|
not null when underlyingType == typeof(long) => (long)(object)value,
|
||||||
|
not null when underlyingType == typeof(ulong) => (ulong)(object)value,
|
||||||
|
_ => throw new InvalidOperationException("Unsupported underlying type."),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetManagedSize(Type type)
|
||||||
|
{
|
||||||
|
return Type.GetTypeCode(type) switch
|
||||||
|
{
|
||||||
|
TypeCode.Byte or TypeCode.SByte => 1,
|
||||||
|
TypeCode.Int16 or TypeCode.UInt16 => 2,
|
||||||
|
TypeCode.Int32 or TypeCode.UInt32 => 4,
|
||||||
|
TypeCode.Int64 or TypeCode.UInt64 => 8,
|
||||||
|
_ => throw new ArgumentException("Unsupported underlying type: " + type.Name),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
using System.Collections;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AmongUs.GameOptions;
|
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||||
using MiraAPI.Roles;
|
using MiraAPI.Roles;
|
||||||
@@ -13,6 +13,7 @@ using TMPro;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using MethodBase = System.Reflection.MethodBase;
|
using MethodBase = System.Reflection.MethodBase;
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
using Random = UnityEngine.Random;
|
||||||
|
|
||||||
namespace MiraAPI.Utilities;
|
namespace MiraAPI.Utilities;
|
||||||
|
|
||||||
@@ -523,6 +524,79 @@ public static class Helpers
|
|||||||
(Il2CppSystem.Object[])[value.ToString(formatString, CultureInfo.InvariantCulture)]),
|
(Il2CppSystem.Object[])[value.ToString(formatString, CultureInfo.InvariantCulture)]),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Shorthand for <see cref="string.Split(char, StringSplitOptions)"/> that removes empty entries and trims leading and ending spaces.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The string to split.</param>
|
||||||
|
/// <param name="separator">The separator.</param>
|
||||||
|
/// <returns>An array of substrings.</returns>
|
||||||
|
public static string[] TrueSplit(this string value, char separator) => value.Split(separator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Joins a collection of items that are separated by a character.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="separator">The separator.</param>
|
||||||
|
/// <param name="items">The collection of items to string together.</param>
|
||||||
|
/// <typeparam name="T">The type of the items.</typeparam>
|
||||||
|
/// <returns>A string formed by concatenating the ToString results of each item, each separated by a separator.</returns>
|
||||||
|
public static string Join<T>(char separator, IEnumerable<T> items) => Join(separator.ToString(), items);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Join{T}(char, IEnumerable{T})"/>
|
||||||
|
public static string Join<T>(string separator, IEnumerable<T> items)
|
||||||
|
{
|
||||||
|
if (!items.Any())
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var result = string.Empty;
|
||||||
|
items.Do(x => result += $"{separator}{x}");
|
||||||
|
return result[separator.Length..];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shorthand helper for overriding the on click listeners for PassiveButton.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="passive">The PassiveButton being modified.</param>
|
||||||
|
/// <param name="action">The action that's replacing the original listeners.</param>
|
||||||
|
public static void OverrideOnClickListeners(this PassiveButton passive, Action action)
|
||||||
|
{
|
||||||
|
if (!passive)
|
||||||
|
return;
|
||||||
|
|
||||||
|
passive.OnClick?.RemoveAllListeners();
|
||||||
|
passive.OnClick = new();
|
||||||
|
passive.OnClick.AddListener(action);
|
||||||
|
passive.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shorthand helper for overriding the on mouse over listeners for PassiveButton.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="OverrideOnClickListeners"/>
|
||||||
|
public static void OverrideOnMouseOverListeners(this PassiveButton passive, Action action)
|
||||||
|
{
|
||||||
|
if (!passive)
|
||||||
|
return;
|
||||||
|
|
||||||
|
passive.OnMouseOver?.RemoveAllListeners();
|
||||||
|
passive.OnMouseOver = new();
|
||||||
|
passive.OnMouseOver.AddListener(action);
|
||||||
|
passive.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shorthand helper for overriding the on mouse out listeners for PassiveButton.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="OverrideOnClickListeners"/>
|
||||||
|
public static void OverrideOnMouseOutListeners(this PassiveButton passive, Action action)
|
||||||
|
{
|
||||||
|
if (!passive)
|
||||||
|
return;
|
||||||
|
|
||||||
|
passive.OnMouseOut?.RemoveAllListeners();
|
||||||
|
passive.OnMouseOut = new();
|
||||||
|
passive.OnMouseOut.AddListener(action);
|
||||||
|
passive.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the string of a role.
|
/// Returns the string of a role.
|
||||||
@@ -537,15 +611,4 @@ public static class Helpers
|
|||||||
}
|
}
|
||||||
return role.NiceName;
|
return role.NiceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns whether the role is blacklisted from appearing and spawning. (Only applicable to vanilla roles)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="role">The role to check.</param>
|
|
||||||
/// <returns>The role's blacklist status.</returns>
|
|
||||||
public static bool IsRoleBlacklisted(this RoleBehaviour role)
|
|
||||||
{
|
|
||||||
// This should be patchable by mods when a vanilla role is meant to be replaced by a custom role.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -24,4 +24,9 @@ public enum MiraNumberSuffixes
|
|||||||
/// Percentage suffix (%).
|
/// Percentage suffix (%).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Percent,
|
Percent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display in units.
|
||||||
|
/// </summary>
|
||||||
|
Distance,
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user