Compare commits

..
Author SHA1 Message Date
XtraCube 85431d3685 offset buttons for android 2025-05-22 22:38:51 -04:00
XtraCube 2ac6c55cf8 fix mira for new android 2025-05-22 20:29:11 -04:00
XtraCube db69446352 add android assetbundle 2025-05-22 15:07:05 -04:00
53 changed files with 350 additions and 1671 deletions

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@
<ItemGroup>
<PackageReference Include="Reactor" Version="2.3.1" />
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.735" Private="false" ExcludeAssets="runtime;native" />
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2025.4.15" PrivateAssets="all" />
<PackageReference Include="AmongUs.GameLibs.Android" Version="2025.4.20" PrivateAssets="all" />
<PackageReference Include="BepInEx.AutoPlugin" Version="1.1.0" PrivateAssets="all" />
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.1.0-rc.1" PrivateAssets="all" ExcludeAssets="runtime" />
@@ -1,21 +0,0 @@
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// Invoked before RpcEndGame is called, allowing cancellation of the game end entirely.
/// </summary>
public class BeforeGameEndEvent : MiraCancelableEvent
{
/// <summary>
/// Gets the reason for the game end.
/// </summary>
public GameOverReason Reason { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BeforeGameEndEvent"/> class.
/// </summary>
/// <param name="reason">The reason for the game end.</param>
public BeforeGameEndEvent(GameOverReason reason)
{
Reason = reason;
}
}
+1 -1
View File
@@ -63,7 +63,7 @@ public abstract class CustomGameOver
var reason = GameOverManager.GetGameOverId<T>();
var data = new GameOverData(reason, [.. winners]);
Rpc<CustomGameOverRpc>.Instance.Send(PlayerControl.LocalPlayer, data, true);
Rpc<CustomGameOverRpc>.Instance.Send(PlayerControl.LocalPlayer, data);
}
/// <summary>
-24
View File
@@ -46,11 +46,6 @@ public interface IModdedOption
/// </summary>
Func<bool> Visible { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the option should be included with presets.
/// </summary>
bool IncludeInPreset { get; set; }
/// <summary>
/// Gets or sets the ConfigDefinition for the option, used for BepInEx configuration.
/// </summary>
@@ -83,23 +78,4 @@ public interface IModdedOption
/// </summary>
/// <param name="data">>The byte array representing the network data.</param>
void HandleNetData(byte[] data);
/// <summary>
/// Saves the option to a preset configuration file.
/// </summary>
/// <param name="presetConfig">The ConfigFile representing the preset configuration.</param>
/// <param name="saveDefault">Indicates whether to save the default value instead of the current value.</param>
void SaveToPreset(ConfigFile presetConfig, bool saveDefault = false);
/// <summary>
/// Binds the option to a configuration file.
/// </summary>
/// <param name="config">The ConfigFile to bind the option to.</param>
void Bind(ConfigFile config);
/// <summary>
/// Loads the option from a preset configuration file, applying the values to the option's configuration.
/// </summary>
/// <param name="presetConfig">The ConfigFile representing the preset configuration.</param>
void LoadFromPreset(ConfigFile presetConfig);
}
+4 -11
View File
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using BepInEx.Configuration;
@@ -43,7 +42,7 @@ public static class ModdedOptionsManager
Groups.Add(group);
TypeToGroup.Add(type, group);
pluginInfo.InternalOptionGroups.Add(group);
pluginInfo.OptionGroups.Add(group);
typeof(OptionGroupSingleton<>).MakeGenericType(type)
#pragma warning disable S3011
@@ -122,7 +121,7 @@ public static class ModdedOptionsManager
option.ConfigDefinition = new ConfigDefinition(groupName, propertyName);
option.ParentMod = pluginInfo.MiraPlugin;
pluginInfo.InternalOptions.Add(option);
pluginInfo.Options.Add(option);
ModdedOptions.Add(option.Id, option);
group.Options.Add(option);
}
@@ -143,7 +142,7 @@ public static class ModdedOptionsManager
// we dont know how other plugins handle their configs
// this way, all the options are saved at once, instead of one by one
var oldConfigSetting = new Dictionary<MiraPluginInfo, bool>();
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
{
oldConfigSetting.Add(plugin, plugin.PluginConfig.SaveOnConfigSet);
plugin.PluginConfig.SaveOnConfigSet = false;
@@ -159,7 +158,7 @@ public static class ModdedOptionsManager
option.HandleNetData(netData.Data);
}
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
{
plugin.PluginConfig.Save();
plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin];
@@ -176,10 +175,7 @@ public static class ModdedOptionsManager
/// </summary>
/// <param name="__originalMethod">The original setter method.</param>
/// <param name="value">The new object value.</param>
#pragma warning disable CA1707
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony naming convention")]
public static void PropertySetterPatch(MethodBase __originalMethod, object value)
#pragma warning restore CA1707
{
var attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod() == __originalMethod).Value;
attribute.SetValue(value);
@@ -191,10 +187,7 @@ public static class ModdedOptionsManager
/// <param name="__originalMethod">The original getter method.</param>
/// <param name="__result">The result of the property getter.</param>
/// <returns>False so the original getter gets skipped.</returns>
#pragma warning disable CA1707
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony naming convention")]
public static bool PropertyGetterPatch(MethodBase __originalMethod, ref object __result)
#pragma warning restore CA1707
{
var attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod() == __originalMethod).Value;
__result = attribute.GetValue();
@@ -18,7 +18,7 @@ public class ModdedEnumOption : ModdedOption<int>
/// <summary>
/// Gets the string values of the enum.
/// </summary>
public string[] Values { get; }
public string[]? Values { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ModdedEnumOption"/> class.
@@ -27,8 +27,7 @@ public class ModdedEnumOption : ModdedOption<int>
/// <param name="defaultValue">The default value as an int.</param>
/// <param name="enumType">The Enum type.</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>
public ModdedEnumOption(string title, int defaultValue, Type enumType, string[]? values = null, bool includeInPreset=true) : base(title, defaultValue, includeInPreset)
public ModdedEnumOption(string title, int defaultValue, Type enumType, string[]? values = null) : base(title, defaultValue)
{
Values = values ?? Enum.GetNames(enumType);
Data = ScriptableObject.CreateInstance<StringGameSetting>();
@@ -38,7 +37,7 @@ public class ModdedEnumOption : ModdedOption<int>
data.Type = global::OptionTypes.String;
data.Values = values is null ?
Enum.GetNames(enumType).Select(CustomStringName.CreateAndRegister).ToArray()
: [.. values.Select(CustomStringName.CreateAndRegister)];
: values.Select(CustomStringName.CreateAndRegister).ToArray();
data.Index = Value;
}
@@ -88,7 +87,7 @@ public class ModdedEnumOption : ModdedOption<int>
/// <inheritdoc />
protected override void OnValueChanged(int newValue)
{
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false);
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data?.GetValueString(newValue), false);
if (!OptionBehaviour)
{
return;
@@ -110,7 +109,7 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
/// <summary>
/// Gets the string values of the enum.
/// </summary>
public string[] Values { get; }
public string[]? Values { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ModdedEnumOption{T}"/> class.
@@ -118,8 +117,7 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
/// <param name="title">The title of the option.</param>
/// <param name="defaultValue">The default value as an int.</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>
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) : base(title, defaultValue)
{
Values = values ?? Enum.GetNames(typeof(T));
Data = ScriptableObject.CreateInstance<StringGameSetting>();
@@ -129,7 +127,7 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
data.Type = global::OptionTypes.String;
data.Values = values is null ?
Enum.GetNames(typeof(T)).Select(CustomStringName.CreateAndRegister).ToArray()
: [.. values.Select(CustomStringName.CreateAndRegister)];
: values.Select(CustomStringName.CreateAndRegister).ToArray();
data.Index = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo);
}
@@ -48,7 +48,6 @@ public class ModdedNumberOption : ModdedOption<float>
/// <param name="suffixType">The suffix type.</param>
/// <param name="formatString">Optional format string for the option screen.</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>
public ModdedNumberOption(
string title,
float defaultValue,
@@ -57,8 +56,7 @@ public class ModdedNumberOption : ModdedOption<float>
float increment,
MiraNumberSuffixes suffixType,
string? formatString = null,
bool zeroInfinity = false,
bool includeInPreset = true) : base(title, defaultValue, includeInPreset)
bool zeroInfinity = false) : base(title, defaultValue)
{
Min = min;
Max = max;
+48 -54
View File
@@ -4,7 +4,6 @@ using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using Reactor.Localization.Utilities;
using Reactor.Networking.Rpc;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI.GameOptions.OptionTypes;
@@ -17,19 +16,29 @@ public abstract class ModdedOption<T> : IModdedOption
{
private IMiraPlugin? _parentMod;
/// <inheritdoc />
/// <summary>
/// Gets the unique identifier of the option.
/// </summary>
public uint Id { get; }
/// <inheritdoc />
/// <summary>
/// Gets the title of the option.
/// </summary>
public string Title { get; }
/// <inheritdoc />
/// <summary>
/// Gets the StringName object of the option.
/// </summary>
public StringNames StringName { get; }
/// <inheritdoc />
/// <summary>
/// Gets or sets the BaseGameSetting data of the option.
/// </summary>
public BaseGameSetting Data { get; protected set; } = null!;
/// <inheritdoc />
/// <summary>
/// Gets or sets the parent mod of the option.
/// </summary>
public IMiraPlugin? ParentMod
{
get => _parentMod;
@@ -57,16 +66,19 @@ public abstract class ModdedOption<T> : IModdedOption
/// </summary>
public Action<T>? ChangedEvent { get; set; }
/// <inheritdoc />
/// <summary>
/// Gets or sets the visibility of the option.
/// </summary>
public Func<bool> Visible { get; set; }
/// <inheritdoc />
public bool IncludeInPreset { get; set; }
/// <inheritdoc />
/// <summary>
/// Gets or sets the option behaviour of the option.
/// </summary>
public OptionBehaviour? OptionBehaviour { get; protected set; }
/// <inheritdoc />
/// <summary>
/// Gets or sets the config definition of the option.
/// </summary>
public ConfigDefinition? ConfigDefinition { get; set; }
/// <summary>
@@ -74,8 +86,7 @@ public abstract class ModdedOption<T> : IModdedOption
/// </summary>
/// <param name="title">The option title.</param>
/// <param name="defaultValue">The default value.</param>
/// <param name="includeInPreset">Whether to include the option in the preset.</param>
protected ModdedOption(string title, T defaultValue, bool includeInPreset = true)
protected ModdedOption(string title, T defaultValue)
{
Id = ModdedOptionsManager.NextId;
Title = title;
@@ -83,7 +94,6 @@ public abstract class ModdedOption<T> : IModdedOption
Value = defaultValue;
StringName = CustomStringName.CreateAndRegister(Title);
Visible = () => true;
IncludeInPreset = includeInPreset;
}
internal void ValueChanged(OptionBehaviour optionBehaviour)
@@ -95,8 +105,7 @@ public abstract class ModdedOption<T> : IModdedOption
/// Sets the value of the option.
/// </summary>
/// <param name="newValue">The new value.</param>
/// <param name="sendRpc">Whether to send the value to other players.</param>
public void SetValue(T newValue, bool sendRpc = true)
public void SetValue(T newValue)
{
var oldVal = Value;
Value = newValue;
@@ -106,57 +115,35 @@ public abstract class ModdedOption<T> : IModdedOption
ChangedEvent?.Invoke(Value);
}
if (sendRpc && AmongUsClient.Instance.AmHost)
if (AmongUsClient.Instance.AmHost)
{
if (ParentMod?.GetConfigFile().TryGetEntry<T>(ConfigDefinition, out var entry) == true)
{
entry.Value = Value;
}
Rpc<SyncOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [GetNetData()], true);
Rpc<SyncOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [GetNetData()]);
}
OnValueChanged(newValue);
}
/// <inheritdoc />
public void SaveToPreset(ConfigFile presetConfig, bool saveDefault=false)
{
if (ConfigDefinition is null)
{
Logger<MiraApiPlugin>.Error($"Attempted to save {Title} to preset, but ConfigDefinition is null.");
return;
}
Bind(presetConfig);
presetConfig[ConfigDefinition].BoxedValue = saveDefault ? DefaultValue : Value;
}
/// <inheritdoc />
public void Bind(ConfigFile config)
{
config.Bind(ConfigDefinition, DefaultValue);
}
/// <inheritdoc />
public void LoadFromPreset(ConfigFile presetConfig)
{
if (presetConfig.TryGetEntry(ConfigDefinition, out ConfigEntry<T> entry))
{
SetValue(entry.Value, false);
}
else
{
Logger<MiraApiPlugin>.Error($"Attempted to load {Title} from preset, but ConfigDefinition is not found in preset.");
}
}
/// <inheritdoc />
/// <summary>
/// Gets the float data of the option.
/// </summary>
/// <returns>A float object representing the option's value.</returns>
public abstract float GetFloatData();
/// <inheritdoc />
/// <summary>
/// Gets the net data of the option.
/// </summary>
/// <returns>A NetData object representing this option's data.</returns>
public abstract NetData GetNetData();
/// <inheritdoc />
/// <summary>
/// Handles incoming net data.
/// </summary>
/// <param name="data">The NetData's byte array.</param>
public abstract void HandleNetData(byte[] data);
/// <summary>
@@ -172,7 +159,14 @@ public abstract class ModdedOption<T> : IModdedOption
/// <returns>The value.</returns>
public abstract T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour);
/// <inheritdoc />
/// <summary>
/// Creates the option behaviour.
/// </summary>
/// <param name="toggleOpt">The ToggleOption prefab.</param>
/// <param name="numberOpt">The NumberOption prefab.</param>
/// <param name="stringOpt">The StringOption prefab.</param>
/// <param name="container">The options container.</param>
/// <returns>A new OptionBehaviour for this modded option.</returns>
public abstract OptionBehaviour CreateOption(
ToggleOption toggleOpt,
NumberOption numberOpt,
@@ -15,8 +15,7 @@ public class ModdedToggleOption : ModdedOption<bool>
/// </summary>
/// <param name="title">The option title.</param>
/// <param name="defaultValue">The default value.</param>
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
public ModdedToggleOption(string title, bool defaultValue, bool includeInPreset=true) : base(title, defaultValue, includeInPreset)
public ModdedToggleOption(string title, bool defaultValue) : base(title, defaultValue)
{
Data = ScriptableObject.CreateInstance<CheckboxGameSetting>();
+3 -12
View File
@@ -3,7 +3,6 @@ using System.Globalization;
using MiraAPI.Events;
using MiraAPI.Events.Mira;
using MiraAPI.Patches;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
using UnityEngine.Events;
@@ -37,11 +36,6 @@ public abstract class CustomActionButton
/// </summary>
public abstract LoadableAsset<Sprite> Sprite { get; }
/// <summary>
/// Gets the format string for the cooldown timer.
/// </summary>
public virtual string CooldownTimerFormatString => "0";
/// <summary>
/// Gets the button's effect duration in seconds. If the button has no effect, set to 0.
/// </summary>
@@ -184,10 +178,7 @@ public abstract class CustomActionButton
this.Location = location;
if (!moveButton)
{
return;
}
if (!moveButton) return;
if (HudManagerPatches.BottomLeft == null || HudManagerPatches.BottomRight == null)
{
@@ -455,12 +446,12 @@ public abstract class CustomActionButton
{
Button.SetFillUp(Timer, EffectDuration);
Button.cooldownTimerText.text = Timer.ToString(CooldownTimerFormatString, NumberFormatInfo.InvariantInfo);
Button.cooldownTimerText.text = Mathf.CeilToInt(Timer).ToString(NumberFormatInfo.InvariantInfo);
Button.cooldownTimerText.gameObject.SetActive(true);
}
else
{
Button.SetCooldownFormat(Timer, Cooldown, CooldownTimerFormatString);
Button.SetCoolDown(Timer, Cooldown);
}
}
+1 -1
View File
@@ -29,7 +29,7 @@ public static class CustomButtonManager
}
CustomButtons.Add(button);
pluginInfo.InternalButtons.Add(button);
pluginInfo.Buttons.Add(button);
typeof(CustomButtonSingleton<>).MakeGenericType(buttonType)
#pragma warning disable S3011
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
+2 -2
View File
@@ -40,8 +40,8 @@ public abstract class BaseModifier : IOptionable
/// Gets the parent mod of the modifier.
/// </summary>
public MiraPluginInfo ParentMod => Array.Find(
MiraPluginManager.Instance.RegisteredPlugins,
x => x.InternalModifiers.Exists(y => y.TypeId == TypeId)
MiraPluginManager.Instance.RegisteredPlugins(),
x => x.Modifiers.Exists(y => y.TypeId == TypeId)
) ?? throw new InvalidOperationException("Modifier is not registered.");
/// <summary>
+2 -2
View File
@@ -30,7 +30,7 @@ public static class ModifierExtensions
_ = ModifierManager.GetModifierType(typeId) ?? throw new InvalidOperationException(
$"Modifier with ID {typeId} is not registered.");
Rpc<AddModifierRpc>.Instance.Send(target, new ModifierData(typeId, Guid.NewGuid(), args), true);
Rpc<AddModifierRpc>.Instance.Send(target, new ModifierData(typeId, Guid.NewGuid(), args));
}
/// <summary>
@@ -63,7 +63,7 @@ public static class ModifierExtensions
/// </summary>
/// <param name="target">The player to remove the modifier from.</param>
/// <param name="uniqueId">The unique ID of the modifier.</param>
[MethodRpc((uint)MiraRpc.RemoveModifier, SendImmediately = true)]
[MethodRpc((uint)MiraRpc.RemoveModifier)]
public static void RpcRemoveModifier(this PlayerControl target, Guid uniqueId)
{
target.RemoveModifier(uniqueId);
+2 -11
View File
@@ -16,11 +16,6 @@ namespace MiraAPI.Modifiers;
/// </summary>
public static class ModifierManager
{
/// <summary>
/// Gets or sets a value indicating whether modifiers should be assigned to players.
/// </summary>
public static bool MiraAssignsModifiers { get; set; } = true;
private static readonly Dictionary<uint, Type> IdToTypeModifierMap = [];
private static readonly Dictionary<Type, uint> TypeToIdModifierMap = [];
private static readonly Dictionary<int, List<uint>> PrioritiesToIdsMap = [];
@@ -79,7 +74,7 @@ public static class ModifierManager
modifier = (BaseModifier)FormatterServices.GetUninitializedObject(modifierType);
}
info.InternalModifiers.Add(modifier);
info.Modifiers.Add(modifier);
if (modifier is not GameModifier gameModifier)
{
@@ -103,11 +98,7 @@ public static class ModifierManager
return true;
}
/// <summary>
/// Assigns modifiers to players. ONLY CALL THIS METHOD IF YOU KNOW WHAT YOU ARE DOING.
/// </summary>
/// <param name="plrs">The players to assign modifiers to.</param>
public static void AssignModifiers(List<PlayerControl> plrs)
internal static void AssignModifiers(List<PlayerControl> plrs)
{
var rand = new Random();
+1 -1
View File
@@ -29,7 +29,7 @@ public static class CustomMurderRpc
/// <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, SendImmediately = true)]
[MethodRpc((uint)MiraRpc.CustomMurder, LocalHandling = RpcLocalHandling.Before)]
public static void RpcCustomMurder(
this PlayerControl source,
PlayerControl target,
+13 -28
View File
@@ -5,44 +5,29 @@ using MiraAPI.GameEnd;
namespace MiraAPI.Patches.Events;
[HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))]
internal static class EndGamePatches
{
[HarmonyPatch(typeof(GameManager), nameof(GameManager.RpcEndGame))]
public static class GameManagerPatches
public static bool Prefix(EndGameManager __instance)
{
public static bool Prefix(GameOverReason endReason)
if (CustomGameOver.Instance is not { } gameOver)
{
var @event = new BeforeGameEndEvent(endReason);
MiraEventManager.InvokeEvent(@event);
return !@event.IsCancelled;
return true;
}
var result = gameOver.BeforeEndGameSetup(__instance);
return result;
}
[HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))]
public static class EndGameManagerPatches
public static void Postfix(EndGameManager __instance)
{
public static bool Prefix(EndGameManager __instance)
if (CustomGameOver.Instance is { } gameOver)
{
if (CustomGameOver.Instance is not { } gameOver)
{
return true;
}
var result = gameOver.BeforeEndGameSetup(__instance);
return result;
gameOver.AfterEndGameSetup(__instance);
CustomGameOver.Instance = null;
}
public static void Postfix(EndGameManager __instance)
{
if (CustomGameOver.Instance is { } gameOver)
{
gameOver.AfterEndGameSetup(__instance);
CustomGameOver.Instance = null;
}
var @event = new GameEndEvent(__instance);
MiraEventManager.InvokeEvent(@event);
}
var @event = new GameEndEvent(__instance);
MiraEventManager.InvokeEvent(@event);
}
}
@@ -14,17 +14,13 @@ public static class KillAnimationPatches
/// <summary>
/// Used to trigger the <see cref="AfterMurderEvent"/>.
/// </summary>
/// <param name="source">The killer.</param>
/// <param name="target">The target.</param>
[HarmonyPostfix]
[HarmonyPatch(typeof(KillAnimation._CoPerformKill_d__2), nameof(KillAnimation._CoPerformKill_d__2.MoveNext))]
public static void KillAnimPostfix(KillAnimation._CoPerformKill_d__2 __instance)
[HarmonyPatch(typeof(KillAnimation), nameof(KillAnimation.CoPerformKill))]
public static void KillAnimPostfix(PlayerControl source, PlayerControl target)
{
// Checking if state is -1 to wait for the end of coroutine.
if (__instance.__1__state != -1)
{
return;
}
var afterMurderEvent = new AfterMurderEvent(__instance.source, __instance.target, Helpers.GetBodyById(__instance.target.PlayerId));
var afterMurderEvent = new AfterMurderEvent(source, target, Helpers.GetBodyById(target.PlayerId));
MiraEventManager.InvokeEvent(afterMurderEvent);
}
}
+5 -36
View File
@@ -1,52 +1,21 @@
using AmongUs.InnerNet.GameDataMessages;
using HarmonyLib;
using InnerNet;
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Meeting;
using MiraAPI.Utilities;
using Reactor.Utilities;
namespace MiraAPI.Patches.Events;
[HarmonyPatch]
public static class ReportBodyEventPatch
{
private static bool ReportBodyEventHook(PlayerControl source, NetworkedPlayerInfo? target)
[HarmonyPrefix]
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdReportDeadBody))]
public static bool PlayerControlCmdReportDeadBodyPrefix(PlayerControl __instance, NetworkedPlayerInfo? target)
{
var body = target != null ? Helpers.GetBodyById(target.PlayerId) : null;
var @event = new ReportBodyEvent(source, target, body);
var @event = new ReportBodyEvent(__instance, target, body);
MiraEventManager.InvokeEvent(@event);
return !@event.IsCancelled;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ReportDeadBody))]
public static bool PlayerControlCmdReportDeadBodyPrefix(PlayerControl __instance, NetworkedPlayerInfo? target)
{
return ReportBodyEventHook(__instance, target);
}
[HarmonyPrefix]
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.LateBroadcastReliableMessage))]
public static bool LateBroadcastReliableMessagePrefix(IGameDataMessage rpcMessage)
{
var reportBodyMessage = rpcMessage.TryCast<RpcReportDeadBodyMessage>();
if (reportBodyMessage == null)
{
return true;
}
var source = AmongUsClient.Instance.FindObjectByNetId<PlayerControl>(reportBodyMessage.rpcObjectNetId);
if (source == null)
{
Logger<MiraApiPlugin>.Error("Failed to find source player for RpcReportDeadBodyMessage with NetId: " + reportBodyMessage.rpcObjectNetId);
return true;
}
var target = GameData.Instance.GetPlayerById(reportBodyMessage.targetPlayerId);
return ReportBodyEventHook(source, target);
}
}
+7 -7
View File
@@ -67,9 +67,9 @@ internal static class TaskAdderPatches
modifiersFolder.FolderName = "Modifiers";
modifiersFolder.name = "ModifiersFolder";
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
{
if (plugin.InternalRoles.Any(x => x.Value is ICustomRole { Configuration.ShowInFreeplay: true }))
if (plugin.CustomRoles.Any(x => x.Value is ICustomRole { Configuration.ShowInFreeplay: true }))
{
var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
newFolder.name = plugin.PluginId+":Roles";
@@ -78,7 +78,7 @@ internal static class TaskAdderPatches
rolesFolder.SubFolders.Add(newFolder);
}
if (plugin.InternalModifiers.Exists(x => x.ShowInFreeplay))
if (plugin.Modifiers.Exists(x => x.ShowInFreeplay))
{
var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
newFolder.name = plugin.PluginId+":Modifiers";
@@ -317,9 +317,9 @@ internal static class TaskAdderPatches
ref float num2,
ref float num3)
{
for (var m = 0; m < plugin.InternalModifiers.Count; m++)
for (var m = 0; m < plugin.Modifiers.Count; m++)
{
var modifier = plugin.InternalModifiers[m];
var modifier = plugin.Modifiers[m];
if (!modifier.ShowInFreeplay)
{
continue;
@@ -368,9 +368,9 @@ internal static class TaskAdderPatches
private static void PluginRoles(TaskAdderGame instance, MiraPluginInfo plugin, ref float num, ref float num2, ref float num3)
{
for (var m = 0; m < plugin.InternalRoles.Count; m++)
for (var m = 0; m < plugin.CustomRoles.Count; m++)
{
var roleBehaviour = plugin.InternalRoles.ElementAt(m).Value;
var roleBehaviour = plugin.CustomRoles.ElementAt(m).Value;
if (roleBehaviour.Role == RoleTypes.ImpostorGhost || roleBehaviour.Role == RoleTypes.CrewmateGhost ||
roleBehaviour is ICustomRole { Configuration.ShowInFreeplay: false })
{
+12
View File
@@ -63,6 +63,18 @@ public static class HudManagerPatches
gridArrange.Alignment = GridArrange.StartAlign.Right;
aspectPosition.Alignment = AspectPosition.EdgeAlignments.LeftBottom;
var fakeButton = Object.Instantiate(__instance.AbilityButton, BottomLeft.transform);
foreach (var renderer in fakeButton.GetComponentsInChildren<Renderer>())
{
renderer.enabled = false;
}
fakeButton.buttonLabelText.Destroy();
fakeButton.cooldownTimerText.Destroy();
fakeButton.usesRemainingText.Destroy();
fakeButton.GetComponent<PassiveButton>().Destroy();
fakeButton.ToggleVisible(true);
fakeButton.Destroy();
foreach (var button in CustomButtonManager.CustomButtons)
{
var location = button.Location switch
+8 -3
View File
@@ -57,10 +57,15 @@ public static class IntroCutscenePatches
}
[HarmonyPostfix]
[HarmonyPatch(nameof(IntroCutscene.OnDestroy))]
public static void GameBeginPatch(IntroCutscene __instance)
[HarmonyPatch(typeof(IntroCutscene._CoBegin_d__34), nameof(IntroCutscene._CoBegin_d__34.MoveNext))]
public static void GameBeginPatch(IntroCutscene._CoBegin_d__34 __instance)
{
MiraEventManager.InvokeEvent(new IntroEndEvent(__instance));
if (__instance.__1__state != -1)
{
return;
}
MiraEventManager.InvokeEvent(new IntroEndEvent(__instance.__4__this));
MiraEventManager.InvokeEvent(new RoundStartEvent(true));
}
}
@@ -14,12 +14,18 @@ using Object = UnityEngine.Object;
namespace MiraAPI.Patches.Options;
/// <summary>
/// Patches the GameOptionsMenu to add custom options.
/// </summary>
[HarmonyPatch(typeof(GameOptionsMenu))]
internal static class GameOptionsMenuPatch
public static class GameOptionsMenuPatch
{
/// <summary>
/// Update patch for the GameOptionsMenu.
/// </summary>
/// <param name="__instance">The GameOptionsMenu instance.</param>
[HarmonyPostfix]
[HarmonyPatch(nameof(GameOptionsMenu.Update))]
// ReSharper disable once InconsistentNaming
public static void UpdatePatch(GameOptionsMenu __instance)
{
if (GameSettingMenuPatches.SelectedModIdx == 0)
@@ -35,7 +41,7 @@ internal static class GameOptionsMenuPatch
else
{
var filteredGroups =
GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
GameSettingMenuPatches.SelectedMod?.OptionGroups
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu) ?? [];
foreach (var group in filteredGroups)
@@ -101,7 +107,7 @@ internal static class GameOptionsMenuPatch
private static void ModifiersUpdate(ref float num)
{
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
var groups = GameSettingMenuPatches.SelectedMod?.OptionGroups
.Where(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true) ?? [];
foreach (var modGroup in groups)
@@ -112,7 +118,7 @@ internal static class GameOptionsMenuPatch
private static void ModifiersCreate(GameOptionsMenu menu)
{
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
var groups = GameSettingMenuPatches.SelectedMod?.OptionGroups
.Where(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true) ?? [];
foreach (var group in groups)
{
@@ -122,7 +128,6 @@ internal static class GameOptionsMenuPatch
[HarmonyPrefix]
[HarmonyPatch(nameof(GameOptionsMenu.CreateSettings))]
// ReSharper disable once InconsistentNaming
public static bool SettingsPatch(GameOptionsMenu __instance)
{
if (GameSettingMenuPatches.SelectedModIdx == 0)
@@ -138,7 +143,7 @@ internal static class GameOptionsMenuPatch
return false;
}
var filteredGroups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
var filteredGroups = GameSettingMenuPatches.SelectedMod?.OptionGroups
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu) ?? [];
foreach (var group in filteredGroups)
@@ -151,7 +156,6 @@ internal static class GameOptionsMenuPatch
[HarmonyPrefix]
[HarmonyPatch(nameof(GameOptionsMenu.Initialize))]
// ReSharper disable once InconsistentNaming
public static bool InitPatch(GameOptionsMenu __instance)
{
if (__instance.Children != null && __instance.Children.Count != 0)
@@ -1,348 +0,0 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
using BepInEx.Configuration;
using HarmonyLib;
using MiraAPI.PluginLoading;
using MiraAPI.Presets;
using MiraAPI.Roles;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities;
using Reactor.Utilities.Extensions;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using Object = UnityEngine.Object;
namespace MiraAPI.Patches.Options;
internal static class GamePresetsTabPatches
{
private static GameObject _saveButton = null!;
private static GameObject _refreshButton = null!;
private static GameObject _folderButton = null!;
private static GameObject _newDivider = null!;
private static GameObject _presetHolder = null!;
private static GridArrange _arrange = null!;
private static readonly object Lock = new();
public static void DivideSize(GameObject obj, float amount)
{
foreach (var collider in obj.GetComponentsInChildren<Collider2D>(true))
{
if (collider.TryCast<BoxCollider2D>() is { } col)
{
col.size = new Vector2(col.size.x / amount, col.size.y);
}
}
foreach (var rend in obj.GetComponentsInChildren<SpriteRenderer>(true))
{
rend.size = new Vector2(rend.size.x / amount, rend.size.y);
}
}
[HarmonyPatch(typeof(GamePresetsTab), nameof(GamePresetsTab.OnEnable))]
public static class GamePresetsOnEnablePatch
{
// ReSharper disable once InconsistentNaming
public static void Postfix(GamePresetsTab __instance)
{
if (!GameSettingMenu.Instance)
{
return;
}
CreatePresetMenu(__instance);
__instance.StandardPresetButton.gameObject.SetActive(GameSettingMenuPatches.SelectedModIdx == 0);
__instance.SecondPresetButton.gameObject.SetActive(GameSettingMenuPatches.SelectedModIdx == 0);
if (GameSettingMenuPatches.SelectedModIdx != 0)
{
__instance.PresetDescriptionText.text = "Select or create an options preset for this mod";
}
else
{
__instance.SetSelectedText();
}
// Show the save and refresh buttons
if (_saveButton)
{
_saveButton.SetActive(GameSettingMenuPatches.SelectedModIdx != 0);
}
if (_refreshButton)
{
_refreshButton.SetActive(GameSettingMenuPatches.SelectedModIdx != 0);
}
if (_folderButton)
{
_folderButton.SetActive(GameSettingMenuPatches.SelectedModIdx != 0);
}
if (_newDivider)
{
_newDivider.SetActive(GameSettingMenuPatches.SelectedModIdx != 0);
}
var prefab = GameSettingMenu.Instance.GameSettingsButton;
foreach (var preset in MiraPluginManager.Instance.RegisteredPlugins.SelectMany(x => x.InternalPresets))
{
var button = Object.Instantiate(prefab, _presetHolder.transform);
button.buttonText.text = preset.Name;
if (button.buttonText.TryGetComponent<TextTranslatorTMP>(out var tmp))
{
tmp.DestroyImmediate();
}
button.buttonText.alignment = TextAlignmentOptions.Center;
button.OnClick = new Button.ButtonClickedEvent();
button.OnClick.AddListener(
(UnityAction)(() =>
{
preset.LoadPreset();
}));
button.gameObject.SetActive(false);
preset.PresetButton = button.gameObject;
}
foreach (var mod in MiraPluginManager.Instance.RegisteredPlugins)
{
foreach (var button in mod.InternalPresets.Select(x => x.PresetButton))
{
if (button != null)
{
button.SetActive(mod == GameSettingMenuPatches.SelectedMod);
}
}
}
_arrange.Start();
_arrange.ArrangeChilds();
}
}
[HarmonyPatch(typeof(GamePresetsTab), nameof(GamePresetsTab.OnDisable))]
public static class GamePresetsOnDisablePatch
{
// ReSharper disable once InconsistentNaming
public static void Postfix()
{
Logger<MiraApiPlugin>.Error("OnDisable called");
if (_saveButton)
{
_saveButton.SetActive(false);
}
if (_refreshButton)
{
_refreshButton.SetActive(false);
}
if (_folderButton)
{
_folderButton.SetActive(false);
}
if (_newDivider)
{
_newDivider.SetActive(false);
}
foreach (var mod in MiraPluginManager.Instance.RegisteredPlugins)
{
foreach (var button in mod.InternalPresets.Select(x => x.PresetButton))
{
if (button != null)
{
button.SetActive(false);
}
}
}
}
}
public static void CreatePresetMenu(GamePresetsTab presetTab)
{
var prefab = GameSettingMenu.Instance.GameSettingsButton;
if (prefab == null)
{
Logger<MiraApiPlugin>.Error("GameSettingsButton prefab is null");
return;
}
var tmpTranslator = presetTab.PresetDescriptionText.gameObject.GetComponent<TextTranslatorTMP>();
if (tmpTranslator)
{
tmpTranslator.Destroy();
}
if (!_newDivider)
{
var oldDiv = presetTab.transform.FindChild("DividerImage");
_newDivider = Object.Instantiate(oldDiv.gameObject, oldDiv.transform.parent);
_newDivider.transform.localPosition = new Vector3(1.85f, 0.13f, 0f);
_newDivider.transform.localScale = new Vector3(1.13f, 1, 1);
_newDivider.transform.localRotation = Quaternion.Euler(new Vector3(0, 0, 90));
}
PassiveButton saveButton;
if (!_saveButton)
{
saveButton = Object.Instantiate(prefab, presetTab.transform);
_saveButton = saveButton.gameObject;
_saveButton.gameObject.name = "SaveButton";
_saveButton.transform.localPosition = new Vector3(3.2f, 1.7f, -2);
// set the button text and alignment
var saveText = saveButton.buttonText;
saveText.text = "Save";
saveText.GetComponent<TextTranslatorTMP>().Destroy();
saveText.alignment = TextAlignmentOptions.Center;
saveText.transform.parent.localPosition = new Vector3(
-.525f,
saveText.transform.parent.localPosition.y,
saveText.transform.parent.localPosition.z);
DivideSize(saveButton.gameObject, 2f);
saveButton.OnClick = new Button.ButtonClickedEvent();
saveButton.OnClick.AddListener(
(UnityAction)(() =>
{
if (GameSettingMenuPatches.SelectedModIdx == 0)
{
return;
}
SavePresetPopup.CreatePopup(
name =>
{
if (GameSettingMenuPatches.SelectedMod == null)
{
return;
}
if (name == string.Empty)
{
return;
}
var presetFile = new ConfigFile(
Path.Combine(
PresetManager.PresetDirectory,
GameSettingMenuPatches.SelectedMod.PluginId,
$"{name}.cfg"),
false)
{
SaveOnConfigSet = false,
};
foreach (var option in GameSettingMenuPatches.SelectedMod.InternalOptions.Where(x => x.IncludeInPreset))
{
option.SaveToPreset(presetFile);
}
foreach (var role in GameSettingMenuPatches.SelectedMod.InternalRoles.Values.OfType<ICustomRole>().Where(x=>!x.Configuration.HideSettings))
{
role.SaveToPreset(presetFile);
}
presetFile.Save();
Refresh();
});
}));
}
saveButton = _saveButton.GetComponent<PassiveButton>();
PassiveButton refreshButton;
if (!_refreshButton)
{
refreshButton = Object.Instantiate(saveButton, presetTab.transform);
_refreshButton = refreshButton.gameObject;
_refreshButton.name = "RefreshButton";
_refreshButton.transform.localPosition = new Vector3(2.85f, 1.1f, -2);
var icon = new GameObject("Sprite");
icon.transform.SetParent(refreshButton.transform);
icon.transform.localPosition = new Vector3(-0.47f, -0.08f, -5f);
icon.transform.localScale = new Vector3(0.5f, 0.5f, 1);
icon.layer = refreshButton.gameObject.layer;
var iconSpriteRend = icon.AddComponent<SpriteRenderer>();
iconSpriteRend.sprite = MiraAssets.RefreshIcon.LoadAsset();
DivideSize(refreshButton.gameObject, 2f);
refreshButton.buttonText.gameObject.Destroy();
refreshButton.OnClick = new Button.ButtonClickedEvent();
refreshButton.OnClick.AddListener((UnityAction)Refresh);
}
refreshButton = _refreshButton.GetComponent<PassiveButton>();
if (!_folderButton)
{
var openFolderButton = Object.Instantiate(refreshButton, presetTab.transform);
_folderButton = openFolderButton.gameObject;
_folderButton.name = "OpenFolderButton";
_folderButton.transform.localPosition = new Vector3(3.55f, 1.1f, -2);
var folderRend = openFolderButton.transform.FindChild("Sprite").gameObject.GetComponent<SpriteRenderer>();
folderRend.sprite = MiraAssets.FolderIcon.LoadAsset();
folderRend.transform.localScale = new Vector3(0.4f, 0.4f, 1);
openFolderButton.OnClick = new Button.ButtonClickedEvent();
openFolderButton.OnClick.AddListener(
(UnityAction)(() =>
{
var directory = Path.Combine(
PresetManager.PresetDirectory,
GameSettingMenuPatches.SelectedMod?.PluginId ?? string.Empty);
Directory.CreateDirectory(directory);
Process.Start(
new ProcessStartInfo
{
FileName = directory,
UseShellExecute = true,
Verb = "open",
});
}));
}
_saveButton.SetActive(false);
_refreshButton.SetActive(false);
_folderButton.SetActive(false);
_newDivider.SetActive(false);
if (!_presetHolder)
{
_presetHolder = new GameObject("PresetHolder");
_presetHolder.transform.SetParent(presetTab.transform, false);
_presetHolder.transform.localScale = new Vector3(0.9f, 0.9f, 1);
_presetHolder.transform.localPosition = new Vector3(-1.9f, 1.7f, 0);
_arrange = _presetHolder.AddComponent<GridArrange>();
_arrange.Alignment = GridArrange.StartAlign.Right;
_arrange.CellSize = new Vector2(2.15f, -.55f);
_arrange.MaxColumns = 2;
}
}
private static void Refresh()
{
Logger<MiraApiPlugin>.Error("Refreshing presets");
if (GameSettingMenuPatches.SelectedMod == null)
{
Logger<MiraApiPlugin>.Error("Selected mod is null, cannot refresh presets");
return;
}
lock (Lock)
{
PresetManager.LoadPresets(GameSettingMenuPatches.SelectedMod);
}
GameSettingMenu.Instance.ChangeTab(0, false); // refresh the tab
}
}
@@ -4,7 +4,6 @@ using System.Linq;
using HarmonyLib;
using MiraAPI.Modifiers;
using MiraAPI.PluginLoading;
using MiraAPI.Presets;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities.Extensions;
using TMPro;
@@ -33,26 +32,6 @@ internal static class GameSettingMenuPatches
private static PassiveButton? _smallRolesButton;
private static Dictionary<int, Vector3> OptionsPositions { get; } = [];
private static Dictionary<int, Vector3> ModifiersPositions { get; } = [];
private static void SaveScrollPositions(GameSettingMenu gameSettingMenu)
{
if (_modifiersTab)
{
ModifiersPositions[SelectedModIdx] = _modifiersTab!.scrollBar.Inner.localPosition;
}
RoleSettingMenuPatches.RolePositions[SelectedModIdx] = gameSettingMenu.RoleSettingsTab.scrollBar.Inner.localPosition;
OptionsPositions[SelectedModIdx] = gameSettingMenu.GameSettingsTab.scrollBar.Inner.localPosition;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GameSettingMenu.ChangeTab))]
public static void ChangeTabPrefix(GameSettingMenu __instance)
{
SaveScrollPositions(__instance);
}
[HarmonyPostfix]
[HarmonyPatch(nameof(GameSettingMenu.ChangeTab))]
@@ -60,11 +39,7 @@ internal static class GameSettingMenuPatches
{
if ((previewOnly && Controller.currentTouchType == Controller.TouchType.Joystick) || !previewOnly)
{
if (_modifiersTab)
{
_modifiersTab!.gameObject.SetActive(tabNum == 3);
}
if (_modifiersTab) _modifiersTab!.gameObject.SetActive(tabNum == 3);
_modifiersButton?.SelectButton(tabNum == 3);
_smallRolesButton?.SelectButton(tabNum == 2);
if (tabNum == 3)
@@ -88,10 +63,7 @@ internal static class GameSettingMenuPatches
_modifiersButton?.SelectButton(tabNum == 3);
_smallRolesButton?.SelectButton(tabNum == 2);
if (tabNum != 3)
{
return;
}
if (tabNum != 3) return;
if (__instance.RoleSettingsButton.gameObject.active)
{
@@ -141,9 +113,13 @@ internal static class GameSettingMenuPatches
passiveButton.OnClick.AddListener(
(UnityAction)(() =>
{
SaveScrollPositions(__instance);
if (!__instance.RoleSettingsTab.AdvancedRolesSettings.active)
{
RoleSettingMenuPatches.RolePositions[SelectedModIdx] = __instance.RoleSettingsTab.scrollBar.Inner.localPosition;
}
OptionsPositions[SelectedModIdx] = __instance.GameSettingsTab.scrollBar.Inner.localPosition;
SelectedModIdx += 1;
if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins.Length)
if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins().Length)
{
SelectedModIdx = 0;
}
@@ -160,11 +136,15 @@ internal static class GameSettingMenuPatches
backButton.gameObject.GetComponent<PassiveButton>().OnClick.AddListener(
(UnityAction)(() =>
{
SaveScrollPositions(__instance);
if (!__instance.RoleSettingsTab.AdvancedRolesSettings.active)
{
RoleSettingMenuPatches.RolePositions[SelectedModIdx] = __instance.RoleSettingsTab.scrollBar.Inner.localPosition;
}
OptionsPositions[SelectedModIdx] = __instance.GameSettingsTab.scrollBar.Inner.localPosition;
SelectedModIdx -= 1;
if (SelectedModIdx < 0)
{
SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins.Length;
SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins().Length;
}
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
@@ -241,61 +221,54 @@ internal static class GameSettingMenuPatches
_modifierBtnOgPos = _modifiersButton.transform.localPosition;
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
{
PresetManager.LoadPresets(plugin);
}
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
}
private static void ChangeRoleSettingButton(bool replace, GameSettingMenu menu)
private static void ChangeRoleSettingButton(bool replace, GameSettingMenu __instance)
{
menu.RoleSettingsButton.OnClick = new ButtonClickedEvent();
menu.RoleSettingsButton.OnMouseOver = new UnityEvent();
__instance.RoleSettingsButton.OnClick = new ButtonClickedEvent();
__instance.RoleSettingsButton.OnMouseOver = new UnityEvent();
if (replace)
{
menu.RoleSettingsButton.buttonText.text = "Modifiers Settings";
menu.RoleSettingsButton.OnClick.AddListener(
__instance.RoleSettingsButton.buttonText.text = "Modifiers Settings";
__instance.RoleSettingsButton.OnClick.AddListener(
(UnityAction)(() =>
{
menu.ChangeTab(3, false);
__instance.ChangeTab(3, false);
}));
menu.RoleSettingsButton.OnMouseOver.AddListener(
__instance.RoleSettingsButton.OnMouseOver.AddListener(
(UnityAction)(() =>
{
menu.ChangeTab(3, true);
__instance.ChangeTab(3, true);
}));
}
else
{
menu.RoleSettingsButton.buttonText.text = "Roles Settings";
menu.RoleSettingsButton.OnClick.AddListener(
__instance.RoleSettingsButton.buttonText.text = "Roles Settings";
__instance.RoleSettingsButton.OnClick.AddListener(
(UnityAction)(() =>
{
menu.ChangeTab(2, false);
__instance.ChangeTab(2, false);
}));
menu.RoleSettingsButton.OnMouseOver.AddListener(
__instance.RoleSettingsButton.OnMouseOver.AddListener(
(UnityAction)(() =>
{
menu.ChangeTab(2, true);
__instance.ChangeTab(2, true);
}));
}
}
private static void UpdateText(GameSettingMenu menu, GameOptionsMenu settings, RolesSettingsMenu roles)
{
if (_text is not null && SelectedModIdx == 0)
{
_text.text = "Default";
_text.fontSizeMax = 3.2f;
SelectedMod = null;
}
else if (_text is not null)
{
_text.fontSizeMax = 2.3f;
SelectedMod = MiraPluginManager.Instance.RegisteredPlugins[SelectedModIdx - 1];
SelectedMod = MiraPluginManager.Instance.RegisteredPlugins()[SelectedModIdx - 1];
var name = SelectedMod.MiraPlugin.OptionsTitleText;
_text.text = name[..Math.Min(name.Length, 25)];
@@ -308,11 +281,9 @@ internal static class GameSettingMenuPatches
if (SelectedModIdx != 0)
{
var modHasRoles = SelectedMod!.InternalRoles.Count != 0;
var modHasModifiers = SelectedMod.InternalOptionGroups.Exists(
x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true);
var modHasOptions =
SelectedMod.InternalOptionGroups.Exists(x => x.OptionableType == null && !x.ShowInModifiersMenu);
var modHasRoles = SelectedMod!.CustomRoles.Count != 0;
var modHasModifiers = SelectedMod.OptionGroups.Exists(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true);
var modHasOptions = SelectedMod.OptionGroups.Exists(x => x.OptionableType == null && !x.ShowInModifiersMenu);
_modifiersButton.gameObject.SetActive(true);
_smallRolesButton.gameObject.SetActive(true);
@@ -352,7 +323,6 @@ internal static class GameSettingMenuPatches
{
menu.ChangeTab(0, false);
}
// If the mod has roles, we can enable the bigger role button and disable the small one.
if (modHasRoles)
{
@@ -409,11 +379,6 @@ internal static class GameSettingMenuPatches
ChangeRoleSettingButton(replaceWithModifiers, menu);
}
if (menu.PresetsTab.gameObject.active)
{
menu.ChangeTab(0, false);
}
CleanupTab(settings, roles);
}
@@ -425,10 +390,8 @@ internal static class GameSettingMenuPatches
{
continue;
}
child.gameObject.DestroyImmediate();
}
options.Clear();
}
@@ -446,7 +409,7 @@ internal static class GameSettingMenuPatches
if (_modifiersTab?.Children?.Count > 0)
{
CleanupSettings(_modifiersTab, true);
CleanupSettings(_modifiersTab);
}
void CleanupRoleSettings(RolesSettingsMenu rolesMenu)
@@ -467,7 +430,6 @@ internal static class GameSettingMenuPatches
{
role.gameObject.DestroyImmediate();
}
rolesMenu.roleChances?.Clear();
rolesMenu.AdvancedRolesSettings.gameObject.SetActive(false);
@@ -484,7 +446,7 @@ internal static class GameSettingMenuPatches
}
}
void CleanupSettings(GameOptionsMenu gameOptMenu, bool modifiers = false)
void CleanupSettings(GameOptionsMenu gameOptMenu)
{
ClearOptions(gameOptMenu.Children);
gameOptMenu.Children = null;
@@ -495,8 +457,7 @@ internal static class GameSettingMenuPatches
.ForEach(header => header.gameObject.DestroyImmediate());
gameOptMenu.Initialize();
var positions = modifiers ? ModifiersPositions : OptionsPositions;
if (positions.TryGetValue(SelectedModIdx, out var pos))
if (OptionsPositions.TryGetValue(SelectedModIdx, out var pos))
{
gameOptMenu.scrollBar.Inner.localPosition = pos;
gameOptMenu.scrollBar.UpdateScrollBars();
+19 -37
View File
@@ -24,7 +24,7 @@ public static class LobbyViewPanePatches
private static MiraPluginInfo? SelectedMod => SelectedModIdx == 0
? null
: MiraPluginManager.Instance.RegisteredPlugins[SelectedModIdx - 1];
: MiraPluginManager.Instance.RegisteredPlugins()[SelectedModIdx - 1];
private static PassiveButton? ModifiersTabButton { get; set; }
@@ -74,7 +74,7 @@ public static class LobbyViewPanePatches
(UnityAction)(() =>
{
SelectedModIdx += 1;
if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins.Length)
if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins().Length)
{
SelectedModIdx = 0;
}
@@ -98,7 +98,7 @@ public static class LobbyViewPanePatches
SelectedModIdx -= 1;
if (SelectedModIdx < 0)
{
SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins.Length;
SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins().Length;
}
Refresh(__instance);
@@ -113,27 +113,24 @@ public static class LobbyViewPanePatches
menu.scrollBar.ScrollToTop();
}
[HarmonyPostfix]
[HarmonyPatch(nameof(LobbyViewSettingsPane.ChangeTab))]
[HarmonyPatch(nameof(LobbyViewSettingsPane.RefreshTab))]
// CHANGED BECAUSE OF INLINING
public static void SetTabPatch(LobbyViewSettingsPane __instance)
[HarmonyPrefix]
[HarmonyPatch(nameof(LobbyViewSettingsPane.SetTab))]
public static bool SetTabPatch(LobbyViewSettingsPane __instance)
{
if (__instance.currentTab != ModifiersTabName || SelectedMod == null)
{
ModifiersTabButton?.SelectButton(false);
return;
return true;
}
__instance.taskTabButton.SelectButton(false);
__instance.rolesTabButton.SelectButton(false);
ModifiersTabButton?.SelectButton(true);
var filteredGroups = SelectedMod.InternalOptionGroups
.Where(x => x.GroupVisible() && (x.ShowInModifiersMenu || (x.OptionableType != null && x.OptionableType.IsAssignableTo(typeof(BaseModifier)))));
var filteredGroups = SelectedMod.OptionGroups
.Where(x => x.GroupVisible() && (x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true));
DrawOptions(__instance, filteredGroups);
return false;
}
[HarmonyPrefix]
@@ -145,14 +142,8 @@ public static class LobbyViewPanePatches
return true;
}
if (__instance.currentTab == ModifiersTabName)
{
return false;
}
var filteredGroups = SelectedMod.InternalOptionGroups
.Where(x => x is { ShowInModifiersMenu: false, OptionableType: null } && x.GroupVisible());
var filteredGroups = SelectedMod.OptionGroups
.Where(x => x is { ShowInModifiersMenu: false, OptionableType: null } && x.GroupVisible.Invoke());
DrawOptions(__instance, filteredGroups);
return false;
}
@@ -166,11 +157,6 @@ public static class LobbyViewPanePatches
return true;
}
if (__instance.currentTab == ModifiersTabName)
{
return false;
}
DrawRolesTab(__instance);
return false;
}
@@ -179,15 +165,12 @@ public static class LobbyViewPanePatches
{
var num = 1.44f;
var groupArray = groups.Where(x => x.GroupVisible() && x.Options.Any(y => y.Visible())).ToArray();
foreach (var group in groupArray)
foreach (var group in groups)
{
var categoryHeaderMasked = Object.Instantiate(
menu.categoryHeaderOrigin,
menu.settingsContainer,
true);
categoryHeaderMasked.SetHeader(StringNames.Name, 61);
categoryHeaderMasked.Title.text = group.GroupName;
categoryHeaderMasked.transform.localScale = Vector3.one;
@@ -199,7 +182,7 @@ public static class LobbyViewPanePatches
foreach (var option in group.Options)
{
if (!option.Visible())
if (!option.Visible.Invoke())
{
continue;
}
@@ -208,7 +191,6 @@ public static class LobbyViewPanePatches
menu.infoPanelOrigin,
menu.settingsContainer,
true);
viewSettingsInfoPanel.transform.localScale = Vector3.one;
float num2;
if (i % 2 == 0)
@@ -247,7 +229,7 @@ public static class LobbyViewPanePatches
num -= 0.85f;
}
menu.scrollBar.SetYBoundsMax(-num - 2);
menu.scrollBar.CalculateAndSetYBounds(menu.settingsInfo.Count + 10, 2f, 6f, 0.85f);
}
private static void DrawRolesTab(LobbyViewSettingsPane instance)
@@ -268,7 +250,7 @@ public static class LobbyViewPanePatches
var list = new List<Type>();
var roleGroups = SelectedMod.InternalRoles.Values.OfType<ICustomRole>()
var roleGroups = SelectedMod.CustomRoles.Values.OfType<ICustomRole>()
.ToLookup(x => x.RoleOptionsGroup);
// sort the groups by priority
@@ -333,7 +315,7 @@ public static class LobbyViewPanePatches
viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one;
viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f);
var advancedRoleOptions = SelectedMod.InternalOptionGroups
var advancedRoleOptions = SelectedMod.OptionGroups
.Where(x => x.OptionableType == customRole.GetType())
.SelectMany(x => x.Options)
.ToList();
@@ -420,7 +402,7 @@ public static class LobbyViewPanePatches
return 0;
}
var role = SelectedMod.InternalRoles.Values.FirstOrDefault(x => x.GetType() == roleType);
var role = SelectedMod.CustomRoles.Values.FirstOrDefault(x => x.GetType() == roleType);
if (role == null)
{
@@ -443,7 +425,7 @@ public static class LobbyViewPanePatches
var num = viewPanel.yPosStart;
var num2 = 1.08f;
var filteredOptions = SelectedMod.InternalOptionGroups
var filteredOptions = SelectedMod.OptionGroups
.Where(x => x.OptionableType == roleType)
.SelectMany(x=>x.Options)
.ToList();
@@ -2,6 +2,7 @@
using HarmonyLib;
using Il2CppSystem;
using MiraAPI.Roles;
using UnityEngine;
using Object = Il2CppSystem.Object;
namespace MiraAPI.Patches.Options;
+21 -226
View File
@@ -1,6 +1,5 @@
using HarmonyLib;
using Il2CppSystem;
using MiraAPI.Roles;
using MiraAPI.Utilities;
namespace MiraAPI.Patches.Options;
@@ -8,138 +7,6 @@ namespace MiraAPI.Patches.Options;
[HarmonyPatch]
public static class OptionsPatches
{
[HarmonyPrefix]
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.IncreaseChance))]
public static bool RoleIncreaseChancePrefix(RoleOptionSetting __instance)
{
if (!__instance.Role.IsCustomRole())
{
return true;
}
if (__instance.roleChance == 0)
{
__instance.roleMaxCount = 1;
}
__instance.roleChance += 10;
if (__instance.roleChance > 100)
{
__instance.roleChance = 0;
}
__instance.OnValueChanged.Invoke(__instance);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.DecreaseChance))]
public static bool RoleDecreaseChancePrefix(RoleOptionSetting __instance)
{
if (!__instance.Role.IsCustomRole())
{
return true;
}
if (__instance.roleChance == 0)
{
__instance.roleMaxCount = 1;
}
__instance.roleChance -= 10;
if (__instance.roleChance < 0)
{
__instance.roleChance = 100;
}
__instance.OnValueChanged.Invoke(__instance);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.IncreaseCount))]
public static bool RoleIncreaseCountPrefix(RoleOptionSetting __instance)
{
if (!__instance.Role.IsCustomRole())
{
return true;
}
if (__instance.roleMaxCount == 0)
{
__instance.roleChance = 50;
}
__instance.roleMaxCount += 1;
if (__instance.roleMaxCount > __instance.role.MaxCount)
{
__instance.roleMaxCount = 0;
}
__instance.OnValueChanged.Invoke(__instance);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.DecreaseCount))]
public static bool RoleDecreaseCountPrefix(RoleOptionSetting __instance)
{
if (!__instance.Role.IsCustomRole())
{
return true;
}
if (__instance.roleMaxCount == 0)
{
__instance.roleChance = 50;
}
__instance.roleMaxCount -= 1;
if (__instance.roleMaxCount < 0)
{
__instance.roleMaxCount = __instance.role.MaxCount;
}
__instance.OnValueChanged.Invoke(__instance);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.AdjustButtonsActiveState))]
[HarmonyPatch(typeof(StringOption), nameof(StringOption.AdjustButtonsActiveState))]
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.AdjustChanceButtonsActiveState))]
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.AdjustCountButtonsActiveState))]
public static bool AdjustButtonsPrefix(OptionBehaviour __instance)
{
if (__instance.IsCustom())
{
if (__instance.TryCast<NumberOption>() is { } numberOption)
{
numberOption.MinusBtn.SetInteractable(true);
numberOption.PlusBtn.SetInteractable(true);
}
if (__instance.TryCast<StringOption>() is { } stringOption)
{
stringOption.MinusBtn.SetInteractable(true);
stringOption.PlusBtn.SetInteractable(true);
}
return false;
}
if (__instance.TryCast<RoleOptionSetting>() is { } roleOptionSetting && roleOptionSetting.Role.IsCustomRole())
{
roleOptionSetting.CountMinusBtn.SetInteractable(true);
roleOptionSetting.CountPlusBtn.SetInteractable(true);
roleOptionSetting.ChanceMinusBtn.SetInteractable(true);
roleOptionSetting.ChancePlusBtn.SetInteractable(true);
return false;
}
return true;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(ToggleOption), nameof(ToggleOption.Initialize))]
public static bool ToggleInit(ToggleOption __instance)
@@ -149,30 +16,15 @@ public static class OptionsPatches
return true;
}
__instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title, Array.Empty<Object>());
__instance.TitleText.text =
TranslationController.Instance.GetString(__instance.Title, Array.Empty<Object>());
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(ToggleOption), nameof(ToggleOption.Toggle))]
// UpdateValue was inlined.
[HarmonyPatch(typeof(ToggleOption), nameof(ToggleOption.UpdateValue))]
public static bool ToggleUpdate(ToggleOption __instance)
{
if (!__instance.IsCustom())
{
return true;
}
__instance.CheckMark.enabled = !__instance.CheckMark.enabled;
__instance.OnValueChanged.Invoke(__instance);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(StringOption), nameof(StringOption.UpdateValue))]
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.UpdateValue))]
public static bool UpdateValuePrefix(OptionBehaviour __instance)
{
return !__instance.IsCustom();
}
@@ -186,84 +38,17 @@ public static class OptionsPatches
return true;
}
__instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title, Array.Empty<Object>());
__instance.TitleText.text =
TranslationController.Instance.GetString(__instance.Title, Array.Empty<Object>());
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.Increase))]
public static bool NumberIncrease(NumberOption __instance)
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.UpdateValue))]
public static bool NumberUpdate(NumberOption __instance)
{
if (!__instance.IsCustom())
{
return true;
}
__instance.Value += __instance.Increment;
if (__instance.Value > __instance.ValidRange.max)
{
__instance.Value = __instance.ValidRange.min;
}
__instance.OnValueChanged.Invoke(__instance);
__instance.AdjustButtonsActiveState();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.Decrease))]
public static bool NumberDecrease(NumberOption __instance)
{
if (!__instance.IsCustom())
{
return true;
}
__instance.Value -= __instance.Increment;
if (__instance.Value < __instance.ValidRange.min)
{
__instance.Value = __instance.ValidRange.max;
}
__instance.OnValueChanged.Invoke(__instance);
__instance.AdjustButtonsActiveState();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(StringOption), nameof(StringOption.Increase))]
public static bool StringOptionIncrease(StringOption __instance)
{
if (!__instance.IsCustom())
{
return true;
}
__instance.Value += 1;
if (__instance.Value >= __instance.Values.Length)
{
__instance.Value = 0;
}
__instance.OnValueChanged.Invoke(__instance);
__instance.AdjustButtonsActiveState();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(StringOption), nameof(StringOption.Decrease))]
public static bool StringOptionDecrease(StringOption __instance)
{
if (!__instance.IsCustom())
{
return true;
}
__instance.Value -= 1;
if (__instance.Value < 0)
{
__instance.Value = __instance.Values.Length - 1;
}
__instance.OnValueChanged.Invoke(__instance);
__instance.AdjustButtonsActiveState();
return false;
return !__instance.IsCustom();
}
[HarmonyPrefix]
@@ -275,9 +60,19 @@ public static class OptionsPatches
return true;
}
__instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title, Array.Empty<Object>());
__instance.ValueText.text = TranslationController.Instance.GetString(__instance.Values[__instance.Value], Array.Empty<Object>());
__instance.TitleText.text =
TranslationController.Instance.GetString(__instance.Title, Array.Empty<Object>());
__instance.ValueText.text = TranslationController.Instance.GetString(
__instance.Values[__instance.Value],
Array.Empty<Object>());
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(StringOption), nameof(StringOption.UpdateValue))]
public static bool StringUpdate(StringOption __instance)
{
return !__instance.IsCustom();
}
}
@@ -30,16 +30,9 @@ public static class RoleSettingMenuPatches
private static float ScrollerNum { get; set; } = 0.522f;
[HarmonyPostfix]
[HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))]
public static void SetQuotaTabPostfix(RolesSettingsMenu __instance)
{
__instance.scrollBar.SetScrollBounds(__instance);
}
[HarmonyPrefix]
[HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))]
public static bool SetQuotaTabPatch(RolesSettingsMenu __instance)
public static bool PatchStart(RolesSettingsMenu __instance)
{
Headers.ForEach(Object.Destroy);
RoleOptionSettings.ForEach(Object.Destroy);
@@ -76,7 +69,7 @@ public static class RoleSettingMenuPatches
var num3 = 0;
var roleGroups = GameSettingMenuPatches.SelectedMod?.InternalRoles.Values.OfType<ICustomRole>()
var roleGroups = GameSettingMenuPatches.SelectedMod?.CustomRoles.Values.OfType<ICustomRole>()
.ToLookup(x => x.RoleOptionsGroup);
if (roleGroups is null)
@@ -211,10 +204,9 @@ public static class RoleSettingMenuPatches
headerBtn.OnClick.AddListener(
(UnityAction)(() =>
{
RolePositions[GameSettingMenuPatches.SelectedModIdx] = __instance.scrollBar.Inner.localPosition;
if (RoleGroupHidden.TryGetValue(group, out var groupHidden))
if (RoleGroupHidden.TryGetValue(group, out var value))
{
RoleGroupHidden[group] = !groupHidden;
RoleGroupHidden[group] = !value;
}
foreach (var header in Headers)
{
@@ -235,20 +227,13 @@ public static class RoleSettingMenuPatches
ScrollerNum -= 0.4f;
}
}
__instance.scrollBar.SetScrollBounds(__instance);
__instance.scrollBar.SetScrollBounds();
return false;
}
private static void SetScrollBounds(this Scroller scroller, RolesSettingsMenu roleMenu)
private static void SetScrollBounds(this Scroller scroller)
{
if (GameSettingMenuPatches.SelectedModIdx == 0)
{
scroller.CalculateAndSetYBounds(roleMenu.roleChances.Count + 3, 1f, 6f, 0.43f);
return;
}
scroller.SetYBoundsMax(-ScrollerNum - 2);
scroller.CalculateAndSetYBounds(1 + 1.5f * Headers.Count + RoleOptionSettings.Count, 1f, 6f, 0.43f);
if (RolePositions.TryGetValue(GameSettingMenuPatches.SelectedModIdx, out var scroll))
{
scroller.Inner.localPosition = scroll;
@@ -271,12 +256,12 @@ public static class RoleSettingMenuPatches
[HarmonyPatch(nameof(RolesSettingsMenu.OpenChancesTab))]
public static void OpenChancesTabPostfix(RolesSettingsMenu __instance)
{
if (!__instance.scrollBar)
if (GameSettingMenuPatches.SelectedModIdx == 0 || !__instance.scrollBar)
{
return;
}
__instance.scrollBar.SetScrollBounds(__instance);
__instance.scrollBar.SetScrollBounds();
}
private static void ValueChanged(OptionBehaviour obj)
@@ -315,7 +300,7 @@ public static class RoleSettingMenuPatches
if (AmongUsClient.Instance.AmHost)
{
Rpc<SyncRoleOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [role.GetNetData()], true);
Rpc<SyncRoleOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [role.GetNetData()]);
}
GameOptionsManager.Instance.GameHostOptions = GameOptionsManager.Instance.CurrentGameOptions;
@@ -334,10 +319,9 @@ public static class RoleSettingMenuPatches
var num = hasImage ? -0.872f : -1;
// TODO: create sub groups under the role settings.
var filteredOptions = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
.Where(x => x.GroupVisible() && x.OptionableType == role.GetType())
.SelectMany(x => x.Options)
.Where(x => x.Visible())
var filteredOptions = GameSettingMenuPatches.SelectedMod?.OptionGroups
.Where(x=> x.OptionableType == role.GetType())
.SelectMany(x=>x.Options)
.ToList() ?? [];
foreach (var option in filteredOptions)
@@ -370,7 +354,7 @@ public static class RoleSettingMenuPatches
newOpt.Initialize();
}
__instance.scrollBar.SetYBoundsMax(-num - 3);
__instance.scrollBar.CalculateAndSetYBounds(__instance.advancedSettingChildren.Count + 3, 1f, 6f, 0.45f);
__instance.scrollBar.ScrollToTop();
}
@@ -494,10 +478,6 @@ public static class RoleSettingMenuPatches
roleOptionSetting.labelSprite.color = customRole.OptionsMenuColor;
roleOptionSetting.OnValueChanged = new Action<OptionBehaviour>(ValueChanged);
roleOptionSetting.SetClickMask(__instance.ButtonClickMask);
roleOptionSetting.ChanceMinusBtn.SetInteractable(true);
roleOptionSetting.ChancePlusBtn.SetInteractable(true);
roleOptionSetting.CountMinusBtn.SetInteractable(true);
roleOptionSetting.CountPlusBtn.SetInteractable(true);
__instance.roleChances.Add(roleOptionSetting);
roleOptionSetting.titleText.transform.localPosition = new Vector3(-0.5376f, -0.2923f, 0f);
@@ -505,7 +485,7 @@ public static class RoleSettingMenuPatches
roleOptionSetting.titleText.horizontalAlignment = HorizontalAlignmentOptions.Left;
if (GameSettingMenuPatches.SelectedMod is null ||
GameSettingMenuPatches.SelectedMod.InternalOptionGroups
GameSettingMenuPatches.SelectedMod.OptionGroups
.Exists(x => x.OptionableType == role.GetType()))
{
var newButton = Object.Instantiate(roleOptionSetting.buttons[0], roleOptionSetting.transform);
@@ -537,7 +517,7 @@ public static class RoleSettingMenuPatches
roleOptionSetting.ChancePlusBtn.gameObject.SetActive(false);
}
if (index < GameSettingMenuPatches.SelectedMod?.InternalRoles.Count - 1)
if (index < GameSettingMenuPatches.SelectedMod?.CustomRoles.Count - 1)
{
ScrollerNum -= 0.43f;
}
-1
View File
@@ -68,7 +68,6 @@ internal static class PlayerControlPatches
[HarmonyPrefix]
[HarmonyPatch(nameof(PlayerControl.RpcMurderPlayer))]
// ReSharper disable once InconsistentNaming
// Note: This method is partially inlined in 2025.5.20 but we already use custom murder RPCs.
public static void PlayerControlMurderPrefix(PlayerControl __instance, PlayerControl target, ref bool didSucceed)
{
var beforeMurderEvent = new BeforeMurderEvent(__instance, target);
+1 -2
View File
@@ -7,8 +7,7 @@ namespace MiraAPI.Patches.Roles;
/// <summary>
/// Patches the haunt menu to show the actual role name rather then team.
/// </summary>
[HarmonyPatch(typeof(HauntMenuMinigame), nameof(HauntMenuMinigame.SetHauntTarget))]
// SetFilterText was INLINED
[HarmonyPatch(typeof(HauntMenuMinigame), nameof(HauntMenuMinigame.SetFilterText))]
public static class HauntMenuMinigamePatch
{
public static void Postfix(HauntMenuMinigame __instance)
+1 -3
View File
@@ -14,8 +14,6 @@ public static class RoleManagerPatches
{
[HarmonyPrefix]
[HarmonyPatch(nameof(RoleManager.SetRole))]
// NOTE: As of 2025.5.20 update, this is INLINED in ONE place: GameManager::ReviveEveryoneFreeplay(void).
// Should not affect normal gameplay, but may cause issues in Freeplay.
public static bool SetRolePatch(RoleManager __instance, PlayerControl targetPlayer, RoleTypes roleType)
{
if (!targetPlayer)
@@ -65,7 +63,7 @@ public static class RoleManagerPatches
[HarmonyPatch(nameof(RoleManager.SelectRoles))]
public static void ModifierSelectionPatches(RoleManager __instance)
{
if (!AmongUsClient.Instance.AmHost || !ModifierManager.MiraAssignsModifiers)
if (!AmongUsClient.Instance.AmHost)
{
return;
}
@@ -1,6 +1,5 @@
using AmongUs.GameOptions;
using HarmonyLib;
using Il2CppSystem.Collections.Generic;
using MiraAPI.Roles;
using Reactor.Utilities;
@@ -12,23 +11,6 @@ namespace MiraAPI.Patches.Roles;
[HarmonyPatch(typeof(RoleOptionsCollectionV09))]
public static class RoleOptionsCollectionPatch
{
/// <summary>
/// This patch fixes GetNumPerGame being inlined (2025.5.20) in the original code.
/// </summary>
[HarmonyPrefix]
[HarmonyPatch(nameof(RoleOptionsCollectionV09.AnyRolesEnabled))]
public static bool AnyRolesEnabledPrefix(RoleOptionsCollectionV09 __instance, ref bool __result)
{
foreach (KeyValuePair<RoleTypes, RoleDataV09> keyValuePair in __instance.roles)
{
if (__instance.GetNumPerGame(keyValuePair.Key) > 0)
{
return true;
}
}
return false;
}
/// <summary>
/// Set the role chance for custom Launchpad roles based on config.
/// </summary>
-52
View File
@@ -1,52 +0,0 @@
using HarmonyLib;
using UnityEngine;
using UnityEngine.Events;
namespace MiraAPI.Patches;
[HarmonyPatch(typeof(ServerDropdown), nameof(ServerDropdown.FillServerOptions))]
public static class ServerDropdownPatch
{
public static bool Prefix(ServerDropdown __instance)
{
var num = 0;
__instance.background.size = new Vector2(8.4f, 4.8f);
foreach (var regionInfo in DestroyableSingleton<ServerManager>.Instance.AvailableRegions)
{
if (DestroyableSingleton<ServerManager>.Instance.CurrentRegion.Equals(regionInfo))
{
__instance.defaultButtonSelected = __instance.firstOption;
__instance.firstOption.ChangeButtonText(
DestroyableSingleton<TranslationController>.Instance.GetStringWithDefault(
regionInfo.TranslateName,
regionInfo.Name));
}
else
{
var region = regionInfo;
var serverListButton = __instance.ButtonPool.Get<ServerListButton>();
var x = num % 2 == 0 ? -2 : 2;
var y = -0.55f * (num / 2);
serverListButton.transform.localPosition = new Vector3(x, __instance.y_posButton + y, -1f);
serverListButton.transform.localScale = Vector3.one;
serverListButton.Text.text =
DestroyableSingleton<TranslationController>.Instance.GetStringWithDefault(
regionInfo.TranslateName,
regionInfo.Name);
serverListButton.Text.ForceMeshUpdate();
serverListButton.Button.OnClick.RemoveAllListeners();
serverListButton.Button.OnClick.AddListener((UnityAction)(() => { __instance.ChooseOption(region); }));
__instance.controllerSelectable.Add(serverListButton.Button);
__instance.background.transform.localPosition = new Vector3(
0f,
__instance.initialYPos + (-0.3f * (num / 2)),
0f);
__instance.background.size = new Vector2(__instance.background.size.x, 1.2f + (0.6f * (num / 2)));
num++;
}
}
return false;
}
}
@@ -205,7 +205,6 @@ internal static class MeetingHudPatches
// TODO: figure out a way to do host-authorization since right now any player can send RpcCastVote
[HarmonyPrefix]
[HarmonyPatch(nameof(MeetingHud.CmdCastVote))]
// Although this method is inlined in MeetingHud.Confirm, the next patch fixes that.
public static bool CmdCastVoteOverridePatch(MeetingHud __instance, byte playerId, byte suspectIdx)
{
VotingUtils.RpcCastVote(PlayerControl.LocalPlayer, playerId, suspectIdx);
+31 -30
View File
@@ -6,9 +6,7 @@ using MiraAPI.GameModes;
using MiraAPI.GameOptions;
using MiraAPI.Hud;
using MiraAPI.Modifiers;
using MiraAPI.Presets;
// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace MiraAPI.PluginLoading;
/// <summary>
@@ -25,58 +23,61 @@ public class MiraPluginInfo
}
/// <summary>
/// Gets a read only collection of this plugin's modifiers. This is not safe for modifiers with constructors.
/// Get a read only collection of this plugin's modifiers. This is probably not safe because they are uninitialized.
/// </summary>
public IReadOnlyCollection<BaseModifier> Modifiers { get; private set; } = null!;
/// <returns>Readonly collection of modifiers.</returns>
public IReadOnlyCollection<BaseModifier> GetModifiers()
{
return Modifiers.AsReadOnly();
}
/// <summary>
/// Gets a read only collection of this plugin's Option Groups.
/// Get a read only collection of this plugin's Option Groups.
/// </summary>
public IReadOnlyCollection<AbstractOptionGroup> OptionGroups { get; private set; } = null!;
/// <returns>Readonly collection of option groups.</returns>
public IReadOnlyCollection<AbstractOptionGroup> GetOptionGroups()
{
return OptionGroups.AsReadOnly();
}
/// <summary>
/// Gets a read only collection of this plugin's options.
/// </summary>
public IReadOnlyCollection<IModdedOption> Options { get; private set; } = null!;
/// <returns>Read only collection of options.</returns>
public IReadOnlyCollection<IModdedOption> GetOptions()
{
return Options.AsReadOnly();
}
/// <summary>
/// Gets a read only dictionary of Role IDs and the RoleBehaviour object they are associated with.
/// </summary>
public ReadOnlyDictionary<ushort, RoleBehaviour> Roles { get; private set; } = null!;
/// <returns>Read only dictionary of IDs and Roles.</returns>
public ReadOnlyDictionary<ushort, RoleBehaviour> GetRoles()
{
return new ReadOnlyDictionary<ushort, RoleBehaviour>(CustomRoles);
}
/// <summary>
/// Gets a read only collection of this plugin's custom buttons.
/// </summary>
public IReadOnlyCollection<CustomActionButton> Buttons { get; private set; } = null!;
/// <summary>
/// Gets a read only dictionary of this plugin's custom game modes.
/// </summary>
public IReadOnlyCollection<OptionPreset> Presets { get; internal set; } = null!;
internal void SavePublicCollections()
/// <returns>Read only collection of buttons.</returns>
public IReadOnlyCollection<CustomActionButton> GetButtons()
{
Presets = [..InternalPresets];
Modifiers = [..InternalModifiers];
OptionGroups = [..InternalOptionGroups];
Options = [..InternalOptions];
Roles = new ReadOnlyDictionary<ushort, RoleBehaviour>(InternalRoles);
Buttons = [..InternalButtons];
return Buttons.AsReadOnly();
}
internal List<OptionPreset> InternalPresets { get; } = [];
internal List<AbstractOptionGroup> OptionGroups { get; } = [];
internal List<AbstractOptionGroup> InternalOptionGroups { get; } = [];
internal List<IModdedOption> Options { get; } = [];
internal List<IModdedOption> InternalOptions { get; } = [];
internal List<BaseModifier> Modifiers { get; } = [];
internal List<BaseModifier> InternalModifiers { get; } = [];
internal Dictionary<ushort, RoleBehaviour> CustomRoles { get; } = [];
internal Dictionary<ushort, RoleBehaviour> InternalRoles { get; } = [];
internal Dictionary<int, CustomGameMode> GameModes { get; } = [];
internal Dictionary<int, CustomGameMode> InternalGameModes { get; } = [];
internal List<CustomActionButton> InternalButtons { get; } = [];
internal List<CustomActionButton> Buttons { get; } = [];
/// <summary>
/// Gets the plugin's ID, as defined in the plugin's BepInEx metadata.
+3 -13
View File
@@ -12,7 +12,6 @@ using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.Hud;
using MiraAPI.Modifiers;
using MiraAPI.Presets;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using Reactor.Networking;
@@ -27,9 +26,8 @@ public sealed class MiraPluginManager
{
private readonly Dictionary<Assembly, MiraPluginInfo> _registeredPlugins = [];
internal MiraPluginInfo[] RegisteredPlugins { get; private set; } = null!;
internal Dictionary<MiraPluginInfo, List<Type>> QueuedRoleRegistrations { get; } = [];
internal MiraPluginInfo[] RegisteredPlugins() => [.. _registeredPlugins.Values];
internal Dictionary<MiraPluginInfo, List<Type>> QueuedRoleRegistrations { get; } = new();
internal static MiraPluginManager Instance { get; private set; } = new();
internal void Initialize()
@@ -112,24 +110,16 @@ public sealed class MiraPluginManager
info.PluginConfig.Save();
info.PluginConfig.SaveOnConfigSet = oldConfigSetting;
info.InternalOptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority));
info.OptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority));
QueuedRoleRegistrations.Add(info, roles);
_registeredPlugins.Add(assembly, info);
info.SavePublicCollections();
PresetManager.CreateDefaultPreset(info);
PresetManager.LoadPresets(info);
Logger<MiraApiPlugin>.Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API.");
};
IL2CPPChainloader.Instance.Finished += PaletteManager.RegisterAllColors;
IL2CPPChainloader.Instance.Finished += () =>
{
// Save all buttons into a read-only collection for easy access
CustomButtonManager.Buttons = new ReadOnlyCollection<CustomActionButton>(CustomButtonManager.CustomButtons);
// Cache all the registered plugins into an array for easy access
RegisteredPlugins = [.._registeredPlugins.Values];
};
}
-74
View File
@@ -1,74 +0,0 @@
using System.Linq;
using BepInEx.Configuration;
using MiraAPI.GameOptions;
using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using UnityEngine;
namespace MiraAPI.Presets;
/// <summary>
/// Represents a preset of game options that can be applied to the game.
/// </summary>
public class OptionPreset
{
/// <summary>
/// Gets the name of the preset.
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the plugin associated with the preset.
/// </summary>
public MiraPluginInfo Plugin { get; }
/// <summary>
/// Gets the configuration file of the plugin associated with the preset.
/// </summary>
public ConfigFile PluginConfig { get; }
/// <summary>
/// Gets the configuration file for the preset.
/// </summary>
public ConfigFile PresetConfig { get; }
/// <summary>
/// Gets or sets the button associated with the preset in the UI.
/// </summary>
public GameObject? PresetButton { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="OptionPreset"/> class with the specified name and configuration file.
/// </summary>
/// <param name="name">>The name of the preset.</param>
/// <param name="plugin">The plugin associated with the preset.</param>
/// <param name="presetConfig">>The configuration file for the preset.</param>
public OptionPreset(string name, MiraPluginInfo plugin, ConfigFile presetConfig)
{
Name = name;
Plugin = plugin;
PluginConfig = plugin.PluginConfig;
PresetConfig = presetConfig;
}
/// <summary>
/// Loads the preset by applying the values from the preset configuration to the plugin configuration.
/// </summary>
public void LoadPreset()
{
PresetConfig.Reload();
foreach (var option in Plugin.InternalOptions)
{
option.LoadFromPreset(PresetConfig);
}
ModdedOptionsManager.SyncAllOptions();
foreach (var role in Plugin.InternalRoles.Values.OfType<ICustomRole>())
{
role.LoadFromPreset(PresetConfig);
}
CustomRoleManager.SyncAllRoleSettings();
}
}
-96
View File
@@ -1,96 +0,0 @@
using System.IO;
using System.Linq;
using BepInEx.Configuration;
using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI.Presets;
/// <summary>
/// Provides functionality to manage and load game option presets for plugins.
/// </summary>
public static class PresetManager
{
/// <summary>
/// Gets the directory where the presets are stored.
/// </summary>
public static string PresetDirectory { get; } = Path.GetFullPath("mira_presets", Application.persistentDataPath);
internal static void CreateDefaultPreset(MiraPluginInfo plugin)
{
var presetPath = Path.Combine(PresetDirectory, plugin.PluginId);
if (!Directory.Exists(presetPath))
{
Directory.CreateDirectory(presetPath);
}
var presetConfig = new ConfigFile(Path.Combine(presetPath, "Default.cfg"), false);
foreach (var option in plugin.InternalOptions.Where(x => x.IncludeInPreset))
{
option.SaveToPreset(presetConfig, true);
}
foreach (var role in plugin.InternalRoles.Values.OfType<ICustomRole>())
{
role.SaveToPreset(presetConfig);
}
presetConfig.Save();
}
/// <summary>
/// Loads the presets for the specified plugin by reading the configuration files from the preset directory.
/// </summary>
/// <param name="plugin">>The plugin for which the presets should be loaded.</param>
public static void LoadPresets(MiraPluginInfo plugin)
{
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, plugin.PluginId);
if (!Directory.Exists(pluginPresetPath))
{
Directory.CreateDirectory(pluginPresetPath);
}
foreach (var file in Directory.GetFiles(pluginPresetPath, "*.cfg"))
{
var fileName = Path.GetFileName(file);
Logger<MiraApiPlugin>.Info($"Loading preset file {fileName}");
var presetName = Path.GetFileNameWithoutExtension(file);
var presetConfig = new ConfigFile(file, false)
{
SaveOnConfigSet = false,
};
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();
plugin.InternalPresets.Add(new OptionPreset(presetName, plugin, presetConfig));
}
plugin.Presets = [..plugin.InternalPresets];
}
}
-101
View File
@@ -1,101 +0,0 @@
using System;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities.Attributes;
using Reactor.Utilities.Extensions;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
[RegisterInIl2Cpp]
#pragma warning disable S3903
#pragma warning disable CA1050
public class SavePresetPopup(nint cppPtr) : Minigame(cppPtr)
#pragma warning restore CA1050
#pragma warning restore S3903
{
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
private TextMeshPro textBoxText;
private TextBoxTMP textBox;
private Action<string> onSave;
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
// Cleanup holder object
public void OnDestroy()
{
transform.parent.gameObject.Destroy();
}
public override void Close()
{
// no-op
}
private void Awake()
{
var textboxHolder = transform.GetChild(1).GetChild(1);
var saveButton = transform.FindChild("SaveButton").GetComponent<PassiveButton>();
var closeButton = transform.FindChild("CloseButton").GetComponent<PassiveButton>();
textBox = textboxHolder.GetChild(0).GetComponent<TextBoxTMP>();
textBoxText = textBox.transform.GetChild(0).GetComponent<TextMeshPro>();
saveButton.OnClick = new Button.ButtonClickedEvent();
saveButton.OnClick.AddListener((UnityAction)(() =>
{
onSave.Invoke(textBoxText.text);
this.BaseClose();
}));
closeButton.OnClick = new Button.ButtonClickedEvent();
closeButton.OnClick.AddListener((UnityAction)(() =>
{
this.BaseClose();
}));
textBox.OnChange = new Button.ButtonClickedEvent();
textBox.OnChange.AddListener((UnityAction)(() =>
{
textBoxText.text = textBoxText.text.Replace(" ", string.Empty);
}));
textBox.GetComponent<PassiveButton>().OnClick.AddListener((UnityAction)(() =>
{
textBox.GiveFocus();
}));
foreach (var tmp in gameObject.GetComponentsInChildren<TextMeshPro>())
{
if (tmp.gameObject.transform.parent.name == "Textbox")
{
continue;
}
var text = tmp.text;
tmp.text = $"<font=\"LiberationSans SDF\" material=\"LiberationSans SDF RadialMenu Material\">{text}</font>";
}
}
public static void CreatePopup(Action<string> saveAction)
{
// Because innerscuff doesnt account for Z value in Close animation
var holder = new GameObject("PopupHolder")
{
transform =
{
parent = Camera.main!.transform,
localPosition = new Vector3(0, -1f, -500),
},
};
var gameObject = Instantiate(MiraAssets.PresetSavePopup.LoadAsset(), holder.transform);
var popup = gameObject.GetComponent<SavePresetPopup>();
popup.onSave = saveAction;
popup.Begin(null);
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.
Binary file not shown.
+4 -4
View File
@@ -72,7 +72,7 @@ public static class CustomRoleManager
continue;
}
pluginInfo.InternalRoles.Add((ushort)role.Role, role);
pluginInfo.CustomRoles.Add((ushort)role.Role, role);
}
pluginInfo.PluginConfig.Save();
@@ -161,7 +161,7 @@ public static class CustomRoleManager
/// <returns>A MiraPluginInfo object representing the parent mod of the role.</returns>
public static MiraPluginInfo FindParentMod(ICustomRole role)
{
return MiraPluginManager.Instance.RegisteredPlugins.First(plugin => plugin.InternalRoles.ContainsValue(role as RoleBehaviour ?? throw new InvalidOperationException()));
return MiraPluginManager.Instance.RegisteredPlugins().First(plugin => plugin.CustomRoles.ContainsValue(role as RoleBehaviour ?? throw new InvalidOperationException()));
}
/// <summary>
@@ -236,7 +236,7 @@ public static class CustomRoleManager
// we dont know how other plugins handle their configs
// this way, all the options are saved at once, instead of one by one
var oldConfigSetting = new Dictionary<MiraPluginInfo, bool>();
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
{
oldConfigSetting.Add(plugin, plugin.PluginConfig.SaveOnConfigSet);
plugin.PluginConfig.SaveOnConfigSet = false;
@@ -271,7 +271,7 @@ public static class CustomRoleManager
}
}
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
{
plugin.PluginConfig.Save();
plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin];
+2 -17
View File
@@ -4,7 +4,6 @@ using System.Linq;
using System.Text;
using AmongUs.GameOptions;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI.Roles;
@@ -43,7 +42,7 @@ public static class CustomRoleUtils
public static StringBuilder CreateForRole(ICustomRole role)
{
var taskStringBuilder = new StringBuilder();
taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleColor.ToTextColor()}Your role is <b>{role.RoleName}.</b></color>");
taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleColor.ToTextColor()}You are a <b>{role.RoleName}.</b></color>");
taskStringBuilder.Append("<size=70%>");
taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleLongDescription}");
return taskStringBuilder;
@@ -57,21 +56,7 @@ public static class CustomRoleUtils
public static LoadableAsset<AudioClip>? GetIntroSound(RoleTypes roleType)
{
var role = RoleManager.Instance.AllRoles.FirstOrDefault(role => role.Role == roleType);
if (role is ICustomRole customRole)
{
return customRole.Configuration.IntroSound;
}
if (role is ICustomRole customRole) return customRole.Configuration.IntroSound;
return new PreloadedAsset<AudioClip>(role!.IntroSound);
}
/// <summary>
/// Determines if a role is a custom role or not.
/// </summary>
/// <param name="role">The RoleBehaviour to check.</param>
/// <returns>True if the role is a custom role, false otherwise.</returns>
public static bool IsCustomRole(this RoleBehaviour role)
{
return CustomRoleManager.CustomRoles.ContainsKey((ushort)role.Role);
}
}
-39
View File
@@ -76,45 +76,6 @@ public interface ICustomRole : IOptionable
internal ConfigDefinition NumConfigDefinition => new("Roles", $"Num {GetType().FullName}");
internal ConfigDefinition ChanceConfigDefinition => new("Roles", $"Chance {GetType().FullName}");
/// <summary>
/// Binds the configuration options for this role to the provided ConfigFile.
/// </summary>
/// <param name="config">The ConfigFile to bind the options to.</param>
public virtual void BindConfig(ConfigFile config)
{
config.Bind(NumConfigDefinition, Configuration.DefaultRoleCount);
config.Bind(ChanceConfigDefinition, Configuration.DefaultChance);
}
/// <summary>
/// Saves the role's configuration to a preset ConfigFile.
/// </summary>
/// <param name="presetConfig">The ConfigFile to save the preset configuration to.</param>
/// <param name="useDefault">Whether to use the default values for the configuration.</param>
public virtual void SaveToPreset(ConfigFile presetConfig, bool useDefault=false)
{
BindConfig(presetConfig);
presetConfig[NumConfigDefinition].BoxedValue = useDefault ? Configuration.DefaultRoleCount : GetCount();
presetConfig[ChanceConfigDefinition].BoxedValue = useDefault ? Configuration.DefaultChance : GetChance();
}
/// <summary>
/// Loads the role's configuration from a preset ConfigFile.
/// </summary>
/// <param name="presetConfig">The ConfigFile containing the preset configuration.</param>
public virtual void LoadFromPreset(ConfigFile presetConfig)
{
if (presetConfig.TryGetEntry(NumConfigDefinition, out ConfigEntry<int> numEntry))
{
SetCount(numEntry.Value);
}
if (presetConfig.TryGetEntry(ChanceConfigDefinition, out ConfigEntry<int> chanceEntry))
{
SetChance(chanceEntry.Value);
}
}
/// <summary>
/// Gets the role chance option.
/// </summary>
@@ -1,25 +0,0 @@
using Reactor.Utilities.Extensions;
namespace MiraAPI.Utilities.Assets;
/// <summary>
/// Wrapper class to turn any UnityEngine.Object into a <see cref="LoadableAsset{T}"/>.
/// </summary>
/// <typeparam name="T">The type of the asset to be loaded. Must be a subclass of <see cref="UnityEngine.Object"/>.</typeparam>
public class LoadableAssetWrapper<T> : LoadableAsset<T> where T : UnityEngine.Object
{
/// <summary>
/// Initializes a new instance of the <see cref="LoadableAssetWrapper{T}"/> class.
/// </summary>
/// <param name="asset">The asset to wrap. This should be a reference to a UnityEngine.Object.</param>
public LoadableAssetWrapper(T asset)
{
LoadedAsset = asset.DontDestroy();
}
/// <inheritdoc />
public override T LoadAsset()
{
return LoadedAsset ?? throw new System.InvalidOperationException("The asset could not be loaded!");
}
}
-37
View File
@@ -8,23 +8,6 @@ namespace MiraAPI.Utilities.Assets;
/// </summary>
public static class MiraAssets
{
static MiraAssets()
{
var boxTex = SpriteTools.LoadTextureFromResourcePath(
"MiraAPI.Resources.RoundedBox.png",
System.Reflection.Assembly.GetCallingAssembly());
var boxSprite = Sprite.Create(
boxTex,
new Rect(0, 0, boxTex.width, boxTex.height),
new Vector2(0.5f, 0.5f),
100f,
0U,
SpriteMeshType.Tight,
new Vector4(20, 20, 20, 20));
RoundedBox = new LoadableAssetWrapper<Sprite>(boxSprite);
}
/// <summary>
/// Gets the color used for teal highlighting in UI.
/// </summary>
@@ -40,31 +23,11 @@ public static class MiraAssets
/// </summary>
public static LoadableAsset<GameObject> ModifierDisplay { get; } = new LoadableBundleAsset<GameObject>("Modifiers", MiraAssetBundle);
/// <summary>
/// Gets the Popup prefab for saving presets.
/// </summary>
public static LoadableAsset<GameObject> PresetSavePopup { get; } = new LoadableBundleAsset<GameObject>("PresetSavePopup", MiraAssetBundle);
/// <summary>
/// Gets the Refresh Icon sprite.
/// </summary>
public static LoadableAsset<Sprite> RefreshIcon { get; } = new LoadableBundleAsset<Sprite>("refresh", MiraAssetBundle);
/// <summary>
/// Gets the Folder Icon sprite.
/// </summary>
public static LoadableAsset<Sprite> FolderIcon { get; } = new LoadableBundleAsset<Sprite>("freePlay_folderTaskRoom", MiraAssetBundle);
/// <summary>
/// Gets the empty sprite asset.
/// </summary>
public static LoadableResourceAsset Empty { get; } = new("MiraAPI.Resources.Empty.png");
/// <summary>
/// Gets the RoundedBox sprite, which is a rounded rectangle used for UI elements.
/// </summary>
public static LoadableAsset<Sprite> RoundedBox { get; }
/// <summary>
/// Gets the Next Button sprite.
/// </summary>
+7 -19
View File
@@ -11,13 +11,14 @@ namespace MiraAPI.Utilities.Assets;
public static class SpriteTools
{
/// <summary>
/// Loads and returns a texture from a resource path using the specified assembly.
/// Load a sprite from a resource path.
/// </summary>
/// <param name="resourcePath">The path to the resource within the assembly.</param>
/// <param name="assembly">The assembly from which to load the resource.</param>
/// <returns>A <see cref="Texture2D"/> object loaded from the specified resource path.</returns>
/// <exception cref="ArgumentException">Thrown when the resource cannot be found in the specified assembly.</exception>
public static Texture2D LoadTextureFromResourcePath(string resourcePath, Assembly assembly)
/// <param name="resourcePath">The path to the resource.</param>
/// <param name="assembly">The assembly containing the resource.</param>
/// <param name="pixelsPerUnit">The pixels per unit for the loaded sprite.</param>
/// <returns>A sprite made from the resource.</returns>
/// <exception cref="Exception">The resource cannot be found.</exception>
public static Sprite LoadSpriteFromPath(string resourcePath, Assembly assembly, float pixelsPerUnit)
{
var tex = new Texture2D(1, 1, TextureFormat.ARGB32, false);
var myStream = assembly.GetManifestResourceStream(resourcePath);
@@ -32,19 +33,6 @@ public static class SpriteTools
}
tex.name = resourcePath;
return tex;
}
/// <summary>
/// Loads and returns a <see cref="Sprite"/> from a resource path using the specified assembly.
/// </summary>
/// <param name="resourcePath">The path to the resource within the assembly.</param>
/// <param name="assembly">The assembly from which to load the resource.</param>
/// <param name="pixelsPerUnit">The number of pixels per unit for the sprite.</param>
/// <returns>A <see cref="Sprite"/> object created from the texture loaded from the specified resource path.</returns>
public static Sprite LoadSpriteFromPath(string resourcePath, Assembly assembly, float pixelsPerUnit)
{
var tex = LoadTextureFromResourcePath(resourcePath, assembly);
var sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), pixelsPerUnit);
sprite.name = resourcePath;
return sprite;
+22 -101
View File
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
@@ -42,94 +41,6 @@ public static class Extensions
BitConverter.GetBytes(count.Value).AddRangeToArray(BitConverter.GetBytes(chance.Value)));
}
/// <summary>
/// Used if you override Minigame.Close.
/// </summary>
/// <param name="self">The minigame.</param>
public static void BaseClose(this Minigame self)
{
bool isComplete;
if (self.amClosing == Minigame.CloseState.Closing)
{
UnityEngine.Object.Destroy(self.gameObject);
return;
}
if (self.CloseSound && Constants.ShouldPlaySfx())
{
SoundManager.Instance.PlaySound(self.CloseSound, false, 1f, null);
}
if (PlayerControl.LocalPlayer.Data.Role.TeamType == RoleTeamTypes.Crewmate)
{
GameManager.Instance.LogicMinigame.OnMinigameClose();
}
if (PlayerControl.LocalPlayer)
{
PlayerControl.HideCursorTemporarily();
}
self.amClosing = Minigame.CloseState.Closing;
self.logger.Info(string.Concat("Closing minigame ", self.GetType().Name));
IAnalyticsReporter analytics = DestroyableSingleton<DebugAnalytics>.Instance.Analytics;
NetworkedPlayerInfo data = PlayerControl.LocalPlayer.Data;
TaskTypes taskType = self.TaskType;
float realtimeSinceStartup = Time.realtimeSinceStartup - self.timeOpened;
PlayerTask myTask = self.MyTask;
if (myTask != null)
{
isComplete = myTask.IsComplete;
}
else
{
isComplete = false;
}
analytics.MinigameClosed(data, taskType, realtimeSinceStartup, isComplete);
self.StartCoroutine(self.CoDestroySelf());
}
/// <summary>
/// Sets the cooldown of a button with a formatted string.
/// </summary>
/// <param name="button">The ActionButton to set the cooldown for.</param>
/// <param name="timer">The current timer value.</param>
/// <param name="maxTimer">The maximum timer value.</param>
/// <param name="format">The format string to use for the timer text.</param>
public static void SetCooldownFormat(this ActionButton button, float timer, float maxTimer, string format="0")
{
var num = Mathf.Clamp(timer / maxTimer, 0f, 1f);
button.isCoolingDown = num > 0f;
button.SetCooldownFill(num);
if (button.isCoolingDown)
{
button.cooldownTimerText.text = timer.ToString(format, NumberFormatInfo.InvariantInfo);
button.cooldownTimerText.gameObject.SetActive(true);
return;
}
button.cooldownTimerText.gameObject.SetActive(false);
}
/// <summary>
/// Sets the fill-up variant of a cooldown button with a formatted string.
/// </summary>
/// <param name="button">The ActionButton to set the cooldown for.</param>
/// <param name="timer">The current timer value.</param>
/// <param name="maxTimer">The maximum timer value.</param>
/// <param name="format">The format string to use for the timer text.</param>
public static void SetFillUpFormat(this ActionButton button, float timer, float maxTimer, string format="0")
{
var num = Mathf.Clamp(timer / maxTimer, 0f, 1f);
button.isCoolingDown = num > 0f;
if (button.isCoolingDown && timer < 3f)
{
button.graphic.transform.localPosition = button.position + (Vector3)UnityEngine.Random.insideUnitCircle * 0.05f;
button.cooldownTimerText.text = timer.ToString(format, NumberFormatInfo.InvariantInfo);
button.cooldownTimerText.gameObject.SetActive(true);
}
else
{
button.graphic.transform.localPosition = button.position;
}
button.SetCooldownFill(num);
}
/// <summary>
/// Gets a PlayerControl from their PlayerVoteArea in a meeting.
/// </summary>
@@ -191,12 +102,6 @@ public static class Extensions
return component;
}
/// <summary>
/// Gets the maximum value from a dictionary of integers, returning the key and value.
/// </summary>
/// <param name="self">The dictionary to search.</param>
/// <param name="tie">Whether there is a tie for the maximum value.</param>
/// <returns>The key-value pair with the maximum value.</returns>
public static KeyValuePair<byte, int> MaxPair(this Dictionary<byte, int> self, out bool tie)
{
tie = true;
@@ -216,12 +121,6 @@ public static class Extensions
return result;
}
/// <summary>
/// Gets the maximum value from a dictionary of floats, returning the key and value.
/// </summary>
/// <param name="self">The dictionary to search.</param>
/// <param name="tie">Whether there is a tie for the maximum value.</param>
/// <returns>The key-value pair with the maximum value.</returns>
public static KeyValuePair<byte, float> MaxPair(this Dictionary<byte, float> self, out bool tie)
{
tie = true;
@@ -443,6 +342,18 @@ public static class Extensions
return new Color(color.r - amount, color.g - amount, color.b - amount);
}
/// <summary>
/// Gets an alternate color based on the original color.
/// </summary>
/// <param name="color">The original color.</param>
/// <param name="amount">The amount to darken or lighten the original color by between 0.0 and 1.0.</param>
/// <returns>An alternate color that has been darkened or lightened.</returns>
[Obsolete("Use FindAlternateColor for WACG compliance.")]
public static Color GetAlternateColor(this Color color, float amount = 0.45f)
{
return color.IsColorDark() ? LightenColor(color, amount) : DarkenColor(color, amount);
}
/// <summary>
/// Lightens a color by a specified amount.
/// </summary>
@@ -454,6 +365,16 @@ public static class Extensions
return new Color(color.r + amount, color.g + amount, color.b + amount);
}
/// <summary>
/// Checks if a color is dark.
/// </summary>
/// <param name="color">The color to check.</param>
/// <returns>True if the color is dark, false otherwise.</returns>
public static bool IsColorDark(this Color color)
{
return color.r < 0.5f && color is { g: < 0.5f, b: < 0.5f };
}
/// <summary>
/// Gets the nearest dead body to a player.
/// </summary>
+2 -2
View File
@@ -71,7 +71,7 @@ public static class VotingUtils
/// <param name="source">The player who is sending the RPC. Should be the host.</param>
/// <param name="voterId">The player who voted.</param>
/// <param name="votedFor">The player who the voter voted for.</param>
[MethodRpc((uint)MiraRpc.RemoveVote, SendImmediately = true)]
[MethodRpc((uint)MiraRpc.RemoveVote)]
public static void RpcRemoveVote(PlayerControl source, byte voterId, byte votedFor)
{
if (!source.IsHost()) return;
@@ -107,7 +107,7 @@ public static class VotingUtils
/// <param name="source">The player who sent this RPC.</param>
/// <param name="srcPlayerId">The id of the player who casted the vote.</param>
/// <param name="suspectPlayerId">The voted player's id.</param>
[MethodRpc((uint)MiraRpc.CastVote, SendImmediately = true)]
[MethodRpc((uint)MiraRpc.CastVote)]
public static void RpcCastVote(PlayerControl source, byte srcPlayerId, byte suspectPlayerId)
{
CustomCastVote(srcPlayerId, suspectPlayerId);
+49 -70
View File
@@ -1,6 +1,4 @@
[![](https://dcbadge.limes.pink/api/server/AEfHJGwggC)](https://discord.gg/AEfHJGwggC)
> This mod is not affiliated with Among Us or Innersloth LLC, and the content contained therein is not endorsed or otherwise sponsored by Innersloth LLC. Portions of the materials contained herein are property of Innersloth LLC. © Innersloth LLC.
[![](https://dcbadge.limes.pink/api/server/all-of-us-launchpad-794950428756410429)](https://discord.gg/all-of-us-launchpad-794950428756410429)
# Mira API
@@ -10,32 +8,27 @@ A thorough, but simple, Among Us modding API and utility library that covers:
- Modifiers
- Buttons
- Custom Colors
- Events
- Voting
- Assets
- Compatibility
- ~~Game Modes~~ (coming soon)
Mira API strives to be comprehensive, yet straightforward, while also using as many base game elements as possible.
The result is a less intrusive, better modding API that covers general use cases.
Mira API strives to be simple and easy to use, while also using as many base game elements as possible. The result is a less intrusive, better modding API that covers general use cases.
**Join the [Discord](https://discord.gg/FYYqJU2bvp) for support and to stay updated on the latest releases**
**Join the [Discord](https://discord.gg/all-of-us-launchpad-794950428756410429) for support and to stay updated on the latest releases**
# Usage
To start using Mira API, you need to:
1. Add a reference to Mira API either through a [DLL](https://github.com/All-Of-Us-Mods/MiraAPI/releases), project reference, or [NuGet package](https://www.nuget.org/packages/AllOfUs.MiraAPI).
2. Add a BepInDependency on your plugin class like this: `[BepInDependency(MiraApiPlugin.Id)]`
3. Implement the IMiraPlugin interface in your plugin class.
3. Implement the IMiraPlugin interface on your plugin class.
Mira API also depends on [Reactor](https://github.com/NuclearPowered/Reactor) to function properly!
Remember to include it as a reference and `BepInDependency`!
Mira API also depends on [Reactor](https://github.com/NuclearPowered/Reactor) in order to function properly! Do not forget to include it as a reference and `BepInDependency`!
For a full example, see [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/ExamplePlugin.cs).
## Recommended Project Structure
It is highly recommended to follow this project structure when using Mira API to keep your code clean and organized.
You can also view the Example Mod in this repository for some guidance.
It is highly recommended to follow this project structure when using Mira API in order to keep your code clean and organized. You can also view the Example Mod in this repository for some guidance.
```
MyMiraMod/
├── Buttons/
@@ -61,36 +54,28 @@ MyMiraMod/
```
## Roles
Roles are straightforward in Mira API. There are two things you need to do to create a custom role:
1. Create a class that inherits from a base game role (like `CrewmateRole`, `ImpostorRole`, etc.)
Roles are very simple in Mira API. There are 3 things you need to do to create a custom role:
1. Create a class that inherits from a base game role (like `CrewmateRole`, `ImpostorRole`, etc)
2. Implement the `ICustomRole` interface from Mira API.
3. Add the `[RegisterCustomRole]` attribute to the class.
**Disclaimer: Make sure your plugin class has the following attribute `[ReactorModFlags(ModFlags.RequireOnAllClients)]` or else your roles will not register correctly.**
Note: For step 1, if you are making neutral roles,
you can choose either `CrewmateRole` or `ImpostorRole`,
or even `RoleBehaviour` if you are okay with starting from scratch.
Note: For step 1, if you are making neutral roles, choose either `CrewmateRole` or `ImpostorRole` as the base depending on if it can kill or not!
Mira API handles everything else, from adding the proper options to the settings menu,
to managing the role assignment at the start of the game.
There are no extra steps on the developer's part.
Mira API handles everything else, from adding the proper options to the settings menu, to managing the role assignment at the start of the game. There are no extra steps on the developer's part.
See [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Roles/CustomRole.cs) for a code example.
## Modifiers
Mira API uses a different definition of 'modifiers' than other Among Us mods.
For example, in Town Of Us, a modifier is an extra "ability" that is applied on top of the base role.
However, in Mira API, modifiers are very flexible.
A modifier is anything that "modifiers" a player's abilities or interactions.
Mira API uses a different definition of 'modifiers' than other Among Us mods. For example, in Town Of Us, a modifier is an extra "ability" that is applied on top of the base role. However, in Mira API, modifiers are very flexible. A modifier is anything that "modifiers" a player's abilities or interactions.
Mira provides three classes for working with modifiers:
Mira provides 3 classes for working with modifiers:
- `BaseModifier`: The base for every modifier. You MUST add and remove this modifier from a player manually!
- `TimedModifier`: A modifier that has a time limit. This modifier has to be added manually, but will automatically remove itself after the time limit.
- `TimedModifier`: A modifier that has a time limit. This modifier has to be added manually, but will automatically remove itself after the time limit
- `GameModifier`: This works like the typical TOU modifier, where it is automatically applied at the beginning of the game, then removed at the end.
Modifiers provide various overridable functions and properties for custom behavior.
They can also be used for "tagging" a player.
You can check if a player has a modifier through the extension method `HasModifier` on a `PlayerControl` object.
Modifiers provide various overridable functions and properties for custom behaviour. They can also be used for "tagging" a player. You can check if a player has a modifier through the extension method `HasModifier` on a `PlayerControl` object.
To start using a modifier, pick one of the base classes above and create a class that inherits from it. Implement the properties and methods you would like, then add the `[RegisterModifier]` attribute to the class.
@@ -98,17 +83,9 @@ An example Game modifier can be found [here](https://github.com/All-Of-Us-Mods/M
An example Timer modifier can be found [here](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Modifiers/ModifierTimerExample.cs).
## Options
Options are also simple in Mira API.
Mira API handles all the hard work behind the scenes,
so developers only have to follow a few steps to create their custom options.
The Options API is split up into Groups and Options.
Every Option needs to be in a Group.
Options are also very simple in Mira API. Mira API handles all the hard work behind the scenes, so developers only have to follow a few steps to create their custom options. The Options API is split up into Groups and Options. Every Option needs to be in a Group.
To create a group, you need to create a class that inherits from the `AbstractOptionGroup` abstract class.
Groups contain some properties for developers to control,
including `GroupName`, `GroupColor`, `GroupVisible`, and `OptionableType`.
The full list of properties can be found in the `AbstractOptionGroup` class.
Only the `GroupName` is required.
To create a group, you need to create a class that inherits from the `AbstractOptionGroup` abstract class. Groups contain 4 properties, `GroupName`, `GroupColor`, `GroupVisible`, and `AdvancedRole`. Only the `GroupName` is required.
Here is an example of a group class:
```csharp
@@ -164,39 +141,46 @@ And this is an example of a ModdedOption property:
public ModdedToggleOption YeezusAbility { get; } = new ModdedToggleOption("Yeezus Ability", false);
```
There are currently three types of ModdedOptions you can create:
Here is a full list of ModdedOption classes you can use:
- `ModdedEnumOption`
- `ModdedNumberOption`
- `ModdedToggleOption`
To see a full example of an option class,
see [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Options/ExampleOptions.cs).
To see a full example of an options class, see [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Options/ExampleOptions.cs).
### Role Options
You can also specify a role type for an option or option group.
To set the role type for an entire group, set the `OptionableType` property on that group like this:
To set the role type for an entire group, set the `AdvancedRole` property on that group like this:
```csharp
public class MyOptionsGroup : AbstractOptionGroup
{
public override string GroupName => "My Options";
public override Type? OptionableType => typeof(MyRole); // this is the role that will have these options
public override Type AdvancedRole => typeof(MyRole); // this is the role that will have these options
[ModdedNumberOption("Ability Uses", min: 0, max: 10)]
public float AbilityUses { get; set; } = 5f;
}
```
You can also use a modifier type for the `OptionableType` property.
This will associate that option group with the specified modifier type
and display it in the modifier options menu.
To set the role type for individual options, specify the `roleType` parameter in the option like this:
```csharp
// this group doesnt specify a role, so it will show up in the global settings
public class MyOptionsGroup : AbstractOptionGroup
{
public override string GroupName => "My Options";
// this option will only show up in the settings for MyRole
[ModdedNumberOption("Ability Uses", min: 0, max: 10, roleType: typeof(MyRole))]
public float AbilityUses { get; set; } = 5f;
}
```
An example can be found [here](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Options/Roles/CustomRoleSettings.cs).
## Custom Murders
Mira API provides its own implementation for murders.
Our implementation allows for more customization on kills, and helps bypass server checks.
Mira API provides it's own implementation for murders. Our implementation allows for more customization on kills, and helps bypass server checks.
You can use `PlayerControl.RpcCustomMurder` to perform a networked custom murder, or `PlayerControl.CustomMurder` to normally perform a custom murder.
For example:
```cs
@@ -206,23 +190,18 @@ This will kill a player without creating a dead body and without teleporting the
## Buttons
Mira API provides a simple interface for adding ability buttons to the game.
Mira API provides a simple interface for adding ability buttons to the game. There is only 2 steps:
1. Create a class that inherits from the `CustomActionButton` class and implement the properties and methods.
2. Add the `[RegisterCustomButton]` attribute to the class.
The only thing you need to do is create a class
that inherits from the `CustomActionButton` class and implement the properties and methods.
All other tasks and logic required to add the button to the game are handled by Mira API.
Mira API handles all other tasks and logic required to add the button to the game.
In case you need to access your `CustomActionButton` instance from another class,
you can use the `CustomButtonSingleton` class like this:
In case you need to access your `CustomActionButton` instance from another class, you can use the `CustomButtonSingleton` class like this:
```csharp
var myButton = CustomButtonSingleton<MyCoolButton>.Instance;
```
The button API is straightforward, but provides a lot of flexibility.
There are various methods you can override to customize the behavior of your button.
See [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI/Hud/CustomActionButton.cs)
for a full list of methods you can override.
The button API is simple, but provides a lot of flexibility. There are various methods you can override to customize the behaviour of your button. See [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI/Hud/CustomActionButton.cs) for a full list of methods you can override.
An example button can be found [here](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Buttons/ExampleButton.cs).
@@ -230,7 +209,7 @@ An example button can be found [here](https://github.com/All-Of-Us-Mods/MiraAPI/
Mira provides a simple Custom Color API that allows you to add custom player colors to the game.
Creating custom colors isn't difficult, but there are some requirements for Mira to register your colors.
Creating custom colors isn't difficult, but there are some requirements for your colors to be registered by Mira.
1. Create a ***STATIC*** class to house all your `CustomColor` objects.
2. Inside this class, create a `CustomColor` property for each color you intend to add.
@@ -251,17 +230,13 @@ public static class MyCustomColors
## Assets
Mira API provides a simple, but expandable asset system.
The core of the system is the `LoadableAsset<T>` class.
This is a generic abstract class that provides a pattern for loading assets.
Mira API provides a simple, but expandable asset system. The core of the system is the `LoadableAsset<T>` class. This is a generic abstract class that provides a pattern for loading assets.
Mira API comes with a few asset loaders:
Mira API comes with two asset loaders:
1. `LoadableBundleAsset<T>`: This is used for loading assets from AssetBundles.
2. `LoadableAddressableAsset<T>`: This is used for loading assets from Addressables.
3. `LoadableResourceAsset`: This is used for loading **only sprites** from the Embedded Resources within a mod.
4. `LoadableAudioResourceAsset`: This is used for loading **only audio clips** from the Embedded Resources within a mod.
2. `LoadableResourceAsset`: This is used for loading **only sprites** from the Embedded Resources within a mod.
The code below shows how to use an asset loaders:
The code below shows how to use these asset loaders:
```csharp
// Load a sprite from an AssetBundle
AssetBundle bundle = AssetBundleManager.Load("MyBundle"); // AssetBundleManager is a utility provided by Reactor
@@ -277,3 +252,7 @@ Sprite button = buttonSpriteAsset.LoadAsset();
You can view an example file [here](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/ExampleAssets.cs).
You can create your own asset loaders by inheriting from `LoadableAsset<T>` and implementing the `LoadAsset` method.
# Disclaimer
> This mod is not affiliated with Among Us or Innersloth LLC, and the content contained therein is not endorsed or otherwise sponsored by Innersloth LLC. Portions of the materials contained herein are property of Innersloth LLC. © Innersloth LLC.
+1
View File
@@ -6,5 +6,6 @@
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="Reactor" value="https://nuget.reactor.gg/v3/index.json" />
<add key="Samboy063" value="https://nuget.samboy.dev/v3/index.json" />
<add key="Local" value="packages/" />
</packageSources>
</configuration>
Binary file not shown.