Compare commits

...
Author SHA1 Message Date
XtraCube 5628b79814 Merge remote-tracking branch 'origin/presets' into dev
# Conflicts:
#	MiraAPI/GameOptions/IModdedOption.cs
#	MiraAPI/GameOptions/ModdedOptionsManager.cs
#	MiraAPI/GameOptions/OptionTypes/ModdedOption.cs
#	MiraAPI/Patches/Options/GameSettingMenuPatches.cs
#	MiraAPI/Patches/Options/LobbyViewPanePatches.cs
#	MiraAPI/Patches/Options/RoleSettingMenuPatches.cs
#	MiraAPI/Utilities/Assets/MiraAssets.cs
#	MiraAPI/Utilities/Assets/SpriteTools.cs
#	MiraAPI/Utilities/Extensions.cs
2025-06-23 11:44:30 -04:00
XtraCube 72a44f65f1 fix role menu scrolling bug 2025-06-23 11:31:49 -04:00
XtraCube db6a336a63 Add modifier tab scroll positions and fix scroll positions not saving in some cases. 2025-06-23 11:07:28 -04:00
XtraCube 90b23ec2ca fix role scroll position issue 2025-06-23 10:31:47 -04:00
XtraCube 20e927208c make options wrap around in values 2025-06-21 20:17:57 -04:00
XtraCube 13ef3bdd49 add format string for cooldown timers 2025-06-21 19:32:28 -04:00
XtraCube b3ff6755bb suppress naming warnings 2025-06-21 18:52:18 -04:00
XtraCube 67604600cd remove obsolete code 2025-06-21 18:52:09 -04:00
XtraCube 6e35de6185 add BeforeGameEndEvent.cs 2025-06-21 18:43:56 -04:00
XtraCube a26ec51c7d send murder RPC immediately 2025-06-21 18:24:32 -04:00
XtraCube 3f6ae4cc41 send gameover immediately 2025-06-21 18:21:37 -04:00
XtraCube 15a2cdcd5b Merge pull request #36 from All-Of-Us-Mods/2025.5.20
Update to 2025.5.20
2025-06-21 17:27:23 -04:00
XtraCube cf0bd6ec38 fixed presets 2025-06-20 08:43:11 -04:00
XtraCube 130236e899 oops fix bad code 2025-06-20 08:38:31 -04:00
XtraCube 3b7b247ac0 refresh presets when opening preset menu 2025-06-20 08:32:24 -04:00
XtraCube 52de9a22de fix preset loading networking 2025-06-19 23:24:02 -04:00
XtraCube 372a9e3aa9 suppress RPC warnings 2025-06-16 21:31:58 -04:00
XtraCube 10a9dcfa34 suppress RPC warnings 2025-06-11 21:26:08 -04:00
XtraCube d46fdb0185 comments about rpcmurder inlining 2025-05-30 17:15:52 -04:00
XtraCube a361afbcbf add comment about meetinghud inline issue 2025-05-30 17:12:03 -04:00
XtraCube 24cdac0243 Fix role options collection inlining 2025-05-30 17:07:11 -04:00
XtraCube 5fe75843ad fix haunt menu inlining 2025-05-30 17:01:43 -04:00
XtraCube a0436dcbba Fix ToggleOption inlining 2025-05-30 16:58:32 -04:00
XtraCube 35be381a77 Fix LobbyViewPane inlining 2025-05-30 16:53:15 -04:00
XtraCube 1298beb517 fix kill anim inlining 2025-05-30 16:06:21 -04:00
XtraCube d41c19a176 Adjust kill anim patch for inlining 2025-05-30 15:45:59 -04:00
XtraCube 476f4400ad Fix ReportBodyEventPatch.cs 2025-05-29 22:04:34 -04:00
XtraCube 2b46d90570 Merge branch 'dev' into 2025.5.20 2025-05-27 16:09:29 -04:00
XtraCube ae4aaf7af9 adjust mirabundle name to work on epic 2025-05-27 16:09:14 -04:00
XtraCube 757b9436ca adjust role message 2025-05-27 16:09:05 -04:00
XtraCube 0ab80c42f9 Update discord badge 2025-05-20 22:25:21 -04:00
XtraCube b94472ee07 save options scroll states 2025-05-16 19:42:59 -04:00
XtraCube f8056c29da Merge branch 'dev' into presets 2025-05-16 19:27:26 -04:00
XtraCube 8d4f2baf1e Merge remote-tracking branch 'origin/presets' into presets 2025-05-16 19:24:52 -04:00
XtraCube 11ba3dae64 save scroll position in roles menu 2025-05-16 19:24:47 -04:00
AlchlcDvl 86145f94f4 Merge branch 'dev' into presets 2025-05-04 22:29:23 -04:00
XtraCube e0da386567 fix lag and broken roles saving/loading 2025-05-04 18:10:34 -04:00
XtraCube 6f1da2de9f add roles to presets 2025-05-02 19:25:03 -04:00
XtraCube b85358e304 Merge branch '2025.3.25' into presets 2025-05-02 19:19:33 -04:00
XtraCube 2c901bb9b9 remove unused thingy 2025-05-02 14:56:28 -04:00
XtraCube 68f40a96b7 fix preset system 2025-05-02 14:54:42 -04:00
XtraCube a96ffc42f2 fix saving default preset only 2025-04-29 17:23:05 -04:00
XtraCube 0907b14893 Merge branch 'feat-addressables-2' into presets 2025-04-29 17:22:57 -04:00
ang-xd 32a44d646d Futher progress on presets UI 2025-04-12 23:29:09 -04:00
ang-xd 8f590b94a8 Merge branch 'feat-addressables-2' into presets 2025-04-12 20:54:35 -04:00
XtraCube 2e769d79f2 try fix 2025-03-30 14:56:18 -04:00
XtraCube 41305ad0e2 Merge branch 'dev' into presets 2025-03-30 12:09:32 -04:00
XtraCube 2418e17af6 this doesnt work 2025-03-29 21:29:46 -04:00
XtraCube 6fb0afe14e add folder button 2025-03-29 21:29:40 -04:00
XtraCube ab0063dd99 update preset system 2025-03-29 14:56:07 -04:00
XtraCube a35bf60870 add rounded box sprite 2025-03-29 14:26:15 -04:00
XtraCube 692b9d7180 improve spritetools 2025-03-29 14:26:06 -04:00
XtraCube b99efaa420 create loadable asset wrapper 2025-03-29 14:25:57 -04:00
XtraCube 2ecfa267d6 Merge branch 'dev' into presets
# Conflicts:
#	MiraAPI/Patches/Options/LobbyViewPanePatches.cs
2025-03-29 13:55:18 -04:00
XtraCube 9afe821b0f fix stupid innerscuff code 2025-03-27 18:09:27 -04:00
XtraCube a8d076656d more WIP presets 2025-03-26 18:45:48 -04:00
XtraCube 429b64fc03 preset v2 2025-03-23 17:28:22 -04:00
XtraCube bf5dadc6a0 that def doesnt work 2025-03-23 15:34:20 -04:00
XtraCube b2137230da Merge branch 'dev' into presets 2025-03-23 15:19:51 -04:00
XtraCube 9e8c05f21d text input WIP 2025-03-23 15:01:31 -04:00
XtraCube 2f56c95b39 Merge branch 'dev' into presets
# Conflicts:
#	MiraAPI/GameOptions/OptionTypes/ModdedOption.cs
2025-03-23 13:45:49 -04:00
XtraCube 38f72c42a3 presets v1 2025-03-23 13:44:40 -04:00
XtraCube 5b608f5b3a preset WIP 2025-03-21 15:21:18 -04:00
XtraCube 2248726450 IncludeInPreset property 2025-03-17 09:12:26 -04:00
XtraCube c60537cbd1 add comments to imoddedoption 2025-03-17 09:02:24 -04:00
47 changed files with 1471 additions and 265 deletions

No files matched your search

@@ -0,0 +1,21 @@
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);
Rpc<CustomGameOverRpc>.Instance.Send(PlayerControl.LocalPlayer, data, true);
}
/// <summary>
+24
View File
@@ -46,6 +46,11 @@ 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>
@@ -78,4 +83,23 @@ 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);
}
+11 -4
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using BepInEx.Configuration;
@@ -42,7 +43,7 @@ public static class ModdedOptionsManager
Groups.Add(group);
TypeToGroup.Add(type, group);
pluginInfo.OptionGroups.Add(group);
pluginInfo.InternalOptionGroups.Add(group);
typeof(OptionGroupSingleton<>).MakeGenericType(type)
#pragma warning disable S3011
@@ -121,7 +122,7 @@ public static class ModdedOptionsManager
option.ConfigDefinition = new ConfigDefinition(groupName, propertyName);
option.ParentMod = pluginInfo.MiraPlugin;
pluginInfo.Options.Add(option);
pluginInfo.InternalOptions.Add(option);
ModdedOptions.Add(option.Id, option);
group.Options.Add(option);
}
@@ -142,7 +143,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;
@@ -158,7 +159,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];
@@ -175,7 +176,10 @@ 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);
@@ -187,7 +191,10 @@ 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,7 +27,8 @@ 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>
public ModdedEnumOption(string title, int defaultValue, Type enumType, string[]? values = null) : base(title, defaultValue)
/// <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)
{
Values = values ?? Enum.GetNames(enumType);
Data = ScriptableObject.CreateInstance<StringGameSetting>();
@@ -37,7 +38,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).ToArray();
: [.. values.Select(CustomStringName.CreateAndRegister)];
data.Index = Value;
}
@@ -87,7 +88,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;
@@ -109,7 +110,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.
@@ -117,7 +118,8 @@ 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>
public ModdedEnumOption(string title, T defaultValue, string[]? values = null) : base(title, defaultValue)
/// <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)
{
Values = values ?? Enum.GetNames(typeof(T));
Data = ScriptableObject.CreateInstance<StringGameSetting>();
@@ -127,7 +129,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).ToArray();
: [.. values.Select(CustomStringName.CreateAndRegister)];
data.Index = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo);
}
@@ -48,6 +48,7 @@ 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,
@@ -56,7 +57,8 @@ public class ModdedNumberOption : ModdedOption<float>
float increment,
MiraNumberSuffixes suffixType,
string? formatString = null,
bool zeroInfinity = false) : base(title, defaultValue)
bool zeroInfinity = false,
bool includeInPreset = true) : base(title, defaultValue, includeInPreset)
{
Min = min;
Max = max;
+54 -48
View File
@@ -4,6 +4,7 @@ using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using Reactor.Localization.Utilities;
using Reactor.Networking.Rpc;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI.GameOptions.OptionTypes;
@@ -16,29 +17,19 @@ public abstract class ModdedOption<T> : IModdedOption
{
private IMiraPlugin? _parentMod;
/// <summary>
/// Gets the unique identifier of the option.
/// </summary>
/// <inheritdoc />
public uint Id { get; }
/// <summary>
/// Gets the title of the option.
/// </summary>
/// <inheritdoc />
public string Title { get; }
/// <summary>
/// Gets the StringName object of the option.
/// </summary>
/// <inheritdoc />
public StringNames StringName { get; }
/// <summary>
/// Gets or sets the BaseGameSetting data of the option.
/// </summary>
/// <inheritdoc />
public BaseGameSetting Data { get; protected set; } = null!;
/// <summary>
/// Gets or sets the parent mod of the option.
/// </summary>
/// <inheritdoc />
public IMiraPlugin? ParentMod
{
get => _parentMod;
@@ -66,19 +57,16 @@ public abstract class ModdedOption<T> : IModdedOption
/// </summary>
public Action<T>? ChangedEvent { get; set; }
/// <summary>
/// Gets or sets the visibility of the option.
/// </summary>
/// <inheritdoc />
public Func<bool> Visible { get; set; }
/// <summary>
/// Gets or sets the option behaviour of the option.
/// </summary>
/// <inheritdoc />
public bool IncludeInPreset { get; set; }
/// <inheritdoc />
public OptionBehaviour? OptionBehaviour { get; protected set; }
/// <summary>
/// Gets or sets the config definition of the option.
/// </summary>
/// <inheritdoc />
public ConfigDefinition? ConfigDefinition { get; set; }
/// <summary>
@@ -86,7 +74,8 @@ public abstract class ModdedOption<T> : IModdedOption
/// </summary>
/// <param name="title">The option title.</param>
/// <param name="defaultValue">The default value.</param>
protected ModdedOption(string title, T defaultValue)
/// <param name="includeInPreset">Whether to include the option in the preset.</param>
protected ModdedOption(string title, T defaultValue, bool includeInPreset = true)
{
Id = ModdedOptionsManager.NextId;
Title = title;
@@ -94,6 +83,7 @@ public abstract class ModdedOption<T> : IModdedOption
Value = defaultValue;
StringName = CustomStringName.CreateAndRegister(Title);
Visible = () => true;
IncludeInPreset = includeInPreset;
}
internal void ValueChanged(OptionBehaviour optionBehaviour)
@@ -105,7 +95,8 @@ public abstract class ModdedOption<T> : IModdedOption
/// Sets the value of the option.
/// </summary>
/// <param name="newValue">The new value.</param>
public void SetValue(T newValue)
/// <param name="sendRpc">Whether to send the value to other players.</param>
public void SetValue(T newValue, bool sendRpc = true)
{
var oldVal = Value;
Value = newValue;
@@ -115,35 +106,57 @@ public abstract class ModdedOption<T> : IModdedOption
ChangedEvent?.Invoke(Value);
}
if (AmongUsClient.Instance.AmHost)
if (sendRpc && AmongUsClient.Instance.AmHost)
{
if (ParentMod?.GetConfigFile().TryGetEntry<T>(ConfigDefinition, out var entry) == true)
{
entry.Value = Value;
}
Rpc<SyncOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [GetNetData()]);
Rpc<SyncOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [GetNetData()], true);
}
OnValueChanged(newValue);
}
/// <summary>
/// Gets the float data of the option.
/// </summary>
/// <returns>A float object representing the option's value.</returns>
/// <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 />
public abstract float GetFloatData();
/// <summary>
/// Gets the net data of the option.
/// </summary>
/// <returns>A NetData object representing this option's data.</returns>
/// <inheritdoc />
public abstract NetData GetNetData();
/// <summary>
/// Handles incoming net data.
/// </summary>
/// <param name="data">The NetData's byte array.</param>
/// <inheritdoc />
public abstract void HandleNetData(byte[] data);
/// <summary>
@@ -159,14 +172,7 @@ public abstract class ModdedOption<T> : IModdedOption
/// <returns>The value.</returns>
public abstract T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour);
/// <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>
/// <inheritdoc />
public abstract OptionBehaviour CreateOption(
ToggleOption toggleOpt,
NumberOption numberOpt,
@@ -15,7 +15,8 @@ public class ModdedToggleOption : ModdedOption<bool>
/// </summary>
/// <param name="title">The option title.</param>
/// <param name="defaultValue">The default value.</param>
public ModdedToggleOption(string title, bool defaultValue) : base(title, defaultValue)
/// <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)
{
Data = ScriptableObject.CreateInstance<CheckboxGameSetting>();
+8 -2
View File
@@ -3,6 +3,7 @@ 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;
@@ -36,6 +37,11 @@ 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>
@@ -446,12 +452,12 @@ public abstract class CustomActionButton
{
Button.SetFillUp(Timer, EffectDuration);
Button.cooldownTimerText.text = Mathf.CeilToInt(Timer).ToString(NumberFormatInfo.InvariantInfo);
Button.cooldownTimerText.text = Timer.ToString(CooldownTimerFormatString, NumberFormatInfo.InvariantInfo);
Button.cooldownTimerText.gameObject.SetActive(true);
}
else
{
Button.SetCoolDown(Timer, Cooldown);
Button.SetCooldownFormat(Timer, Cooldown, CooldownTimerFormatString);
}
}
+1 -1
View File
@@ -29,7 +29,7 @@ public static class CustomButtonManager
}
CustomButtons.Add(button);
pluginInfo.Buttons.Add(button);
pluginInfo.InternalButtons.Add(button);
typeof(CustomButtonSingleton<>).MakeGenericType(buttonType)
#pragma warning disable S3011
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
+114
View File
@@ -0,0 +1,114 @@
using System;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities.Attributes;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
namespace MiraAPI.Hud;
/// <summary>
/// Simple input box for user input.
/// </summary>
[RegisterInIl2Cpp]
public class InputBox : MonoBehaviour
{
public InputField inputField = null!;
public void CreateDialog(string title, string placeholder, string submitText, Action<string> onSubmit)
{
var canvasObj = new GameObject("DialogCanvas");
canvasObj.transform.SetParent(transform);
canvasObj.layer = LayerMask.NameToLayer("UI");
var canvas = canvasObj.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.overrideSorting = true;
canvas.sortingOrder = 1000; // Ensure the canvas is on top of other UI elements
canvasObj.AddComponent<CanvasScaler>().uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
canvasObj.AddComponent<GraphicRaycaster>();
var panelObj = new GameObject("DialogPanel");
panelObj.transform.SetParent(canvasObj.transform);
var panelRect = panelObj.AddComponent<RectTransform>();
var img = panelObj.AddComponent<Image>();
img.color = new Color(1, 1, 1, 0.9f);
img.raycastTarget = true;
img.sprite = MiraAssets.RoundedBox.LoadAsset();
img.type = Image.Type.Sliced;
panelRect.sizeDelta = new Vector2(300, 200);
panelRect.anchoredPosition = Vector2.zero;
var titleText = CreateText(panelObj, title);
titleText.alignment = TextAnchor.MiddleCenter;
titleText.fontSize = 26;
titleText.fontStyle = FontStyle.Bold;
titleText.color = Color.white;
var titleRect = titleText.GetComponent<RectTransform>();
titleRect.sizeDelta = new Vector2(280, 50);
titleRect.anchoredPosition = new Vector2(0, 70);
var inputObj = new GameObject("InputField");
inputObj.transform.SetParent(panelObj.transform);
var inputRect = inputObj.AddComponent<RectTransform>();
inputRect.sizeDelta = new Vector2(220, 40);
inputRect.anchoredPosition = new Vector2(0, 20);
var inputBg = inputObj.AddComponent<Image>();
inputBg.color = new Color(1, 1, 1, 0.8f);
inputField = inputObj.AddComponent<InputField>();
inputField.textComponent = CreateText(inputObj, string.Empty, 18, Color.black);
inputField.placeholder = CreateText(inputObj, placeholder, 18, new Color(0.5f, 0.5f, 0.5f));
var buttonObj = new GameObject("SubmitButton");
buttonObj.transform.SetParent(panelObj.transform);
var button = buttonObj.AddComponent<Button>();
var buttonRect = buttonObj.AddComponent<RectTransform>();
buttonRect.sizeDelta = new Vector2(150, 50);
buttonRect.anchoredPosition = new Vector2(0, -50);
var buttonImg = buttonObj.AddComponent<Image>();
buttonImg.color = new Color(1f, 1f, 1f, 1f);
buttonImg.sprite = MiraAssets.RoundedBox.LoadAsset();
buttonImg.type = Image.Type.Sliced;
var buttonText = CreateText(buttonObj, submitText, 20, Color.white);
buttonText.alignment = TextAnchor.MiddleCenter;
var colors = button.colors;
colors.highlightedColor = Color.green;
colors.pressedColor = MiraAssets.AcceptedTeal;
button.colors = colors;
button.onClick.AddListener((UnityAction)(() =>
{
onSubmit(inputField.text);
DestroyImmediate(canvasObj);
}));
inputField.ActivateInputField();
}
private static Text CreateText(GameObject parent, string textContent = "", int fontSize = 20, Color? textColor = null, Font? font = null)
{
var textObj = new GameObject("Text");
textObj.transform.SetParent(parent.transform);
var text = textObj.AddComponent<Text>();
text.font = font ?? Resources.GetBuiltinResource<Font>("Arial.ttf");
text.text = textContent;
text.color = textColor ?? Color.white;
text.fontSize = fontSize;
text.alignment = TextAnchor.MiddleCenter;
var rect = textObj.GetComponent<RectTransform>();
rect.sizeDelta = new Vector2(200, 40);
rect.anchoredPosition = Vector2.zero;
return text;
}
}
+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.Modifiers.Exists(y => y.TypeId == TypeId)
MiraPluginManager.Instance.RegisteredPlugins,
x => x.InternalModifiers.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));
Rpc<AddModifierRpc>.Instance.Send(target, new ModifierData(typeId, Guid.NewGuid(), args), true);
}
/// <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)]
[MethodRpc((uint)MiraRpc.RemoveModifier, SendImmediately = true)]
public static void RpcRemoveModifier(this PlayerControl target, Guid uniqueId)
{
target.RemoveModifier(uniqueId);
+1 -1
View File
@@ -74,7 +74,7 @@ public static class ModifierManager
modifier = (BaseModifier)FormatterServices.GetUninitializedObject(modifierType);
}
info.Modifiers.Add(modifier);
info.InternalModifiers.Add(modifier);
if (modifier is not GameModifier gameModifier)
{
+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)]
[MethodRpc((uint)MiraRpc.CustomMurder, LocalHandling = RpcLocalHandling.Before, SendImmediately = true)]
public static void RpcCustomMurder(
this PlayerControl source,
PlayerControl target,
+28 -13
View File
@@ -5,29 +5,44 @@ using MiraAPI.GameEnd;
namespace MiraAPI.Patches.Events;
[HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))]
internal static class EndGamePatches
{
public static bool Prefix(EndGameManager __instance)
[HarmonyPatch(typeof(GameManager), nameof(GameManager.RpcEndGame))]
public static class GameManagerPatches
{
if (CustomGameOver.Instance is not { } gameOver)
public static bool Prefix(GameOverReason endReason)
{
return true;
}
var @event = new BeforeGameEndEvent(endReason);
MiraEventManager.InvokeEvent(@event);
var result = gameOver.BeforeEndGameSetup(__instance);
return result;
return !@event.IsCancelled;
}
}
public static void Postfix(EndGameManager __instance)
[HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))]
public static class EndGameManagerPatches
{
if (CustomGameOver.Instance is { } gameOver)
public static bool Prefix(EndGameManager __instance)
{
gameOver.AfterEndGameSetup(__instance);
CustomGameOver.Instance = null;
if (CustomGameOver.Instance is not { } gameOver)
{
return true;
}
var result = gameOver.BeforeEndGameSetup(__instance);
return result;
}
var @event = new GameEndEvent(__instance);
MiraEventManager.InvokeEvent(@event);
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);
}
}
}
@@ -14,13 +14,17 @@ 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), nameof(KillAnimation.CoPerformKill))]
public static void KillAnimPostfix(PlayerControl source, PlayerControl target)
[HarmonyPatch(typeof(KillAnimation._CoPerformKill_d__2), nameof(KillAnimation._CoPerformKill_d__2.MoveNext))]
public static void KillAnimPostfix(KillAnimation._CoPerformKill_d__2 __instance)
{
var afterMurderEvent = new AfterMurderEvent(source, target, Helpers.GetBodyById(target.PlayerId));
// 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));
MiraEventManager.InvokeEvent(afterMurderEvent);
}
}
+36 -5
View File
@@ -1,21 +1,52 @@
using HarmonyLib;
using AmongUs.InnerNet.GameDataMessages;
using HarmonyLib;
using InnerNet;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Meeting;
using MiraAPI.Utilities;
using Reactor.Utilities;
namespace MiraAPI.Patches.Events;
[HarmonyPatch]
public static class ReportBodyEventPatch
{
[HarmonyPrefix]
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdReportDeadBody))]
public static bool PlayerControlCmdReportDeadBodyPrefix(PlayerControl __instance, NetworkedPlayerInfo? target)
private static bool ReportBodyEventHook(PlayerControl source, NetworkedPlayerInfo? target)
{
var body = target != null ? Helpers.GetBodyById(target.PlayerId) : null;
var @event = new ReportBodyEvent(__instance, target, body);
var @event = new ReportBodyEvent(source, 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.CustomRoles.Any(x => x.Value is ICustomRole { Configuration.ShowInFreeplay: true }))
if (plugin.InternalRoles.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.Modifiers.Exists(x => x.ShowInFreeplay))
if (plugin.InternalModifiers.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.Modifiers.Count; m++)
for (var m = 0; m < plugin.InternalModifiers.Count; m++)
{
var modifier = plugin.Modifiers[m];
var modifier = plugin.InternalModifiers[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.CustomRoles.Count; m++)
for (var m = 0; m < plugin.InternalRoles.Count; m++)
{
var roleBehaviour = plugin.CustomRoles.ElementAt(m).Value;
var roleBehaviour = plugin.InternalRoles.ElementAt(m).Value;
if (roleBehaviour.Role == RoleTypes.ImpostorGhost || roleBehaviour.Role == RoleTypes.CrewmateGhost ||
roleBehaviour is ICustomRole { Configuration.ShowInFreeplay: false })
{
@@ -14,18 +14,12 @@ using Object = UnityEngine.Object;
namespace MiraAPI.Patches.Options;
/// <summary>
/// Patches the GameOptionsMenu to add custom options.
/// </summary>
[HarmonyPatch(typeof(GameOptionsMenu))]
public static class GameOptionsMenuPatch
internal 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)
@@ -41,7 +35,7 @@ public static class GameOptionsMenuPatch
else
{
var filteredGroups =
GameSettingMenuPatches.SelectedMod?.OptionGroups
GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu) ?? [];
foreach (var group in filteredGroups)
@@ -107,7 +101,7 @@ public static class GameOptionsMenuPatch
private static void ModifiersUpdate(ref float num)
{
var groups = GameSettingMenuPatches.SelectedMod?.OptionGroups
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
.Where(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true) ?? [];
foreach (var modGroup in groups)
@@ -118,7 +112,7 @@ public static class GameOptionsMenuPatch
private static void ModifiersCreate(GameOptionsMenu menu)
{
var groups = GameSettingMenuPatches.SelectedMod?.OptionGroups
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
.Where(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true) ?? [];
foreach (var group in groups)
{
@@ -128,6 +122,7 @@ public static class GameOptionsMenuPatch
[HarmonyPrefix]
[HarmonyPatch(nameof(GameOptionsMenu.CreateSettings))]
// ReSharper disable once InconsistentNaming
public static bool SettingsPatch(GameOptionsMenu __instance)
{
if (GameSettingMenuPatches.SelectedModIdx == 0)
@@ -143,7 +138,7 @@ public static class GameOptionsMenuPatch
return false;
}
var filteredGroups = GameSettingMenuPatches.SelectedMod?.OptionGroups
var filteredGroups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu) ?? [];
foreach (var group in filteredGroups)
@@ -156,6 +151,7 @@ public 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)
@@ -0,0 +1,348 @@
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.Presets))
{
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.Presets.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.Presets.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,6 +4,7 @@ using System.Linq;
using HarmonyLib;
using MiraAPI.Modifiers;
using MiraAPI.PluginLoading;
using MiraAPI.Presets;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities.Extensions;
using TMPro;
@@ -32,6 +33,26 @@ 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))]
@@ -39,7 +60,11 @@ 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)
@@ -63,7 +88,10 @@ 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)
{
@@ -113,13 +141,9 @@ internal static class GameSettingMenuPatches
passiveButton.OnClick.AddListener(
(UnityAction)(() =>
{
if (!__instance.RoleSettingsTab.AdvancedRolesSettings.active)
{
RoleSettingMenuPatches.RolePositions[SelectedModIdx] = __instance.RoleSettingsTab.scrollBar.Inner.localPosition;
}
OptionsPositions[SelectedModIdx] = __instance.GameSettingsTab.scrollBar.Inner.localPosition;
SaveScrollPositions(__instance);
SelectedModIdx += 1;
if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins().Length)
if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins.Length)
{
SelectedModIdx = 0;
}
@@ -136,15 +160,11 @@ internal static class GameSettingMenuPatches
backButton.gameObject.GetComponent<PassiveButton>().OnClick.AddListener(
(UnityAction)(() =>
{
if (!__instance.RoleSettingsTab.AdvancedRolesSettings.active)
{
RoleSettingMenuPatches.RolePositions[SelectedModIdx] = __instance.RoleSettingsTab.scrollBar.Inner.localPosition;
}
OptionsPositions[SelectedModIdx] = __instance.GameSettingsTab.scrollBar.Inner.localPosition;
SaveScrollPositions(__instance);
SelectedModIdx -= 1;
if (SelectedModIdx < 0)
{
SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins().Length;
SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins.Length;
}
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
@@ -221,54 +241,61 @@ 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 __instance)
private static void ChangeRoleSettingButton(bool replace, GameSettingMenu menu)
{
__instance.RoleSettingsButton.OnClick = new ButtonClickedEvent();
__instance.RoleSettingsButton.OnMouseOver = new UnityEvent();
menu.RoleSettingsButton.OnClick = new ButtonClickedEvent();
menu.RoleSettingsButton.OnMouseOver = new UnityEvent();
if (replace)
{
__instance.RoleSettingsButton.buttonText.text = "Modifiers Settings";
__instance.RoleSettingsButton.OnClick.AddListener(
menu.RoleSettingsButton.buttonText.text = "Modifiers Settings";
menu.RoleSettingsButton.OnClick.AddListener(
(UnityAction)(() =>
{
__instance.ChangeTab(3, false);
menu.ChangeTab(3, false);
}));
__instance.RoleSettingsButton.OnMouseOver.AddListener(
menu.RoleSettingsButton.OnMouseOver.AddListener(
(UnityAction)(() =>
{
__instance.ChangeTab(3, true);
menu.ChangeTab(3, true);
}));
}
else
{
__instance.RoleSettingsButton.buttonText.text = "Roles Settings";
__instance.RoleSettingsButton.OnClick.AddListener(
menu.RoleSettingsButton.buttonText.text = "Roles Settings";
menu.RoleSettingsButton.OnClick.AddListener(
(UnityAction)(() =>
{
__instance.ChangeTab(2, false);
menu.ChangeTab(2, false);
}));
__instance.RoleSettingsButton.OnMouseOver.AddListener(
menu.RoleSettingsButton.OnMouseOver.AddListener(
(UnityAction)(() =>
{
__instance.ChangeTab(2, true);
menu.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)];
@@ -281,9 +308,11 @@ internal static class GameSettingMenuPatches
if (SelectedModIdx != 0)
{
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);
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);
_modifiersButton.gameObject.SetActive(true);
_smallRolesButton.gameObject.SetActive(true);
@@ -323,6 +352,7 @@ 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)
{
@@ -379,6 +409,11 @@ internal static class GameSettingMenuPatches
ChangeRoleSettingButton(replaceWithModifiers, menu);
}
if (menu.PresetsTab.gameObject.active)
{
menu.ChangeTab(0, false);
}
CleanupTab(settings, roles);
}
@@ -390,8 +425,10 @@ internal static class GameSettingMenuPatches
{
continue;
}
child.gameObject.DestroyImmediate();
}
options.Clear();
}
@@ -409,7 +446,7 @@ internal static class GameSettingMenuPatches
if (_modifiersTab?.Children?.Count > 0)
{
CleanupSettings(_modifiersTab);
CleanupSettings(_modifiersTab, true);
}
void CleanupRoleSettings(RolesSettingsMenu rolesMenu)
@@ -430,6 +467,7 @@ internal static class GameSettingMenuPatches
{
role.gameObject.DestroyImmediate();
}
rolesMenu.roleChances?.Clear();
rolesMenu.AdvancedRolesSettings.gameObject.SetActive(false);
@@ -446,7 +484,7 @@ internal static class GameSettingMenuPatches
}
}
void CleanupSettings(GameOptionsMenu gameOptMenu)
void CleanupSettings(GameOptionsMenu gameOptMenu, bool modifiers = false)
{
ClearOptions(gameOptMenu.Children);
gameOptMenu.Children = null;
@@ -457,7 +495,9 @@ internal static class GameSettingMenuPatches
.ForEach(header => header.gameObject.DestroyImmediate());
gameOptMenu.Initialize();
if (OptionsPositions.TryGetValue(SelectedModIdx, out var pos))
var positions = modifiers ? ModifiersPositions : OptionsPositions;
if (positions.TryGetValue(SelectedModIdx, out var pos))
{
gameOptMenu.scrollBar.Inner.localPosition = pos;
gameOptMenu.scrollBar.UpdateScrollBars();
+27 -16
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,24 +113,25 @@ public static class LobbyViewPanePatches
menu.scrollBar.ScrollToTop();
}
[HarmonyPrefix]
[HarmonyPatch(nameof(LobbyViewSettingsPane.SetTab))]
public static bool SetTabPatch(LobbyViewSettingsPane __instance)
[HarmonyPostfix]
[HarmonyPatch(nameof(LobbyViewSettingsPane.ChangeTab))]
[HarmonyPatch(nameof(LobbyViewSettingsPane.RefreshTab))]
// CHANGED BECAUSE OF INLINING
public static void SetTabPatch(LobbyViewSettingsPane __instance)
{
if (__instance.currentTab != ModifiersTabName || SelectedMod == null)
{
ModifiersTabButton?.SelectButton(false);
return true;
return;
}
__instance.taskTabButton.SelectButton(false);
__instance.rolesTabButton.SelectButton(false);
ModifiersTabButton?.SelectButton(true);
var filteredGroups = SelectedMod.OptionGroups
.Where(x => x.GroupVisible() && (x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true));
var filteredGroups = SelectedMod.InternalOptionGroups
.Where(x => x.GroupVisible() && (x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true));
DrawOptions(__instance, filteredGroups);
return false;
}
[HarmonyPrefix]
@@ -142,8 +143,13 @@ public static class LobbyViewPanePatches
return true;
}
var filteredGroups = SelectedMod.OptionGroups
.Where(x => x is { ShowInModifiersMenu: false, OptionableType: null } && x.GroupVisible.Invoke());
if (__instance.currentTab == ModifiersTabName)
{
return false;
}
var filteredGroups = SelectedMod.InternalOptionGroups
.Where(x => x.OptionableType == null && x.GroupVisible.Invoke());
DrawOptions(__instance, filteredGroups);
return false;
}
@@ -157,6 +163,11 @@ public static class LobbyViewPanePatches
return true;
}
if (__instance.currentTab == ModifiersTabName)
{
return false;
}
DrawRolesTab(__instance);
return false;
}
@@ -250,7 +261,7 @@ public static class LobbyViewPanePatches
var list = new List<Type>();
var roleGroups = SelectedMod.CustomRoles.Values.OfType<ICustomRole>()
var roleGroups = SelectedMod.InternalRoles.Values.OfType<ICustomRole>()
.ToLookup(x => x.RoleOptionsGroup);
// sort the groups by priority
@@ -315,7 +326,7 @@ public static class LobbyViewPanePatches
viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one;
viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f);
var advancedRoleOptions = SelectedMod.OptionGroups
var advancedRoleOptions = SelectedMod.InternalOptionGroups
.Where(x => x.OptionableType == customRole.GetType())
.SelectMany(x => x.Options)
.ToList();
@@ -402,7 +413,7 @@ public static class LobbyViewPanePatches
return 0;
}
var role = SelectedMod.CustomRoles.Values.FirstOrDefault(x => x.GetType() == roleType);
var role = SelectedMod.InternalRoles.Values.FirstOrDefault(x => x.GetType() == roleType);
if (role == null)
{
@@ -425,7 +436,7 @@ public static class LobbyViewPanePatches
var num = viewPanel.yPosStart;
var num2 = 1.08f;
var filteredOptions = SelectedMod.OptionGroups
var filteredOptions = SelectedMod.InternalOptionGroups
.Where(x => x.OptionableType == roleType)
.SelectMany(x=>x.Options)
.ToList();
+100 -19
View File
@@ -23,8 +23,32 @@ public static class OptionsPatches
}
[HarmonyPrefix]
[HarmonyPatch(typeof(ToggleOption), nameof(ToggleOption.UpdateValue))]
[HarmonyPatch(typeof(ToggleOption), nameof(ToggleOption.Toggle))]
// UpdateValue was inlined.
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();
}
[HarmonyPrefix]
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.AdjustButtonsActiveState))]
[HarmonyPatch(typeof(StringOption), nameof(StringOption.AdjustButtonsActiveState))]
public static bool AdjustButtonsPrefix(OptionBehaviour __instance)
{
return !__instance.IsCustom();
}
@@ -38,17 +62,84 @@ 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.UpdateValue))]
public static bool NumberUpdate(NumberOption __instance)
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.Increase))]
public static bool NumberIncrease(NumberOption __instance)
{
return !__instance.IsCustom();
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;
}
[HarmonyPrefix]
@@ -60,19 +151,9 @@ 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,9 +30,16 @@ 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 PatchStart(RolesSettingsMenu __instance)
public static bool SetQuotaTabPatch(RolesSettingsMenu __instance)
{
Headers.ForEach(Object.Destroy);
RoleOptionSettings.ForEach(Object.Destroy);
@@ -69,7 +76,7 @@ public static class RoleSettingMenuPatches
var num3 = 0;
var roleGroups = GameSettingMenuPatches.SelectedMod?.CustomRoles.Values.OfType<ICustomRole>()
var roleGroups = GameSettingMenuPatches.SelectedMod?.InternalRoles.Values.OfType<ICustomRole>()
.ToLookup(x => x.RoleOptionsGroup);
if (roleGroups is null)
@@ -227,13 +234,19 @@ public static class RoleSettingMenuPatches
ScrollerNum -= 0.4f;
}
}
__instance.scrollBar.SetScrollBounds();
__instance.scrollBar.SetScrollBounds(__instance);
return false;
}
private static void SetScrollBounds(this Scroller scroller)
private static void SetScrollBounds(this Scroller scroller, RolesSettingsMenu roleMenu)
{
scroller.CalculateAndSetYBounds(1 + 1.5f * Headers.Count + RoleOptionSettings.Count, 1f, 6f, 0.43f);
if (GameSettingMenuPatches.SelectedModIdx == 0)
{
scroller.CalculateAndSetYBounds(roleMenu.roleChances.Count + 3, 1f, 6f, 0.43f);
return;
}
scroller.CalculateAndSetYBounds(1.5f * Headers.Count + RoleOptionSettings.Count, 1f, 6f, 0.43f);
if (RolePositions.TryGetValue(GameSettingMenuPatches.SelectedModIdx, out var scroll))
{
scroller.Inner.localPosition = scroll;
@@ -256,12 +269,12 @@ public static class RoleSettingMenuPatches
[HarmonyPatch(nameof(RolesSettingsMenu.OpenChancesTab))]
public static void OpenChancesTabPostfix(RolesSettingsMenu __instance)
{
if (GameSettingMenuPatches.SelectedModIdx == 0 || !__instance.scrollBar)
if (!__instance.scrollBar)
{
return;
}
__instance.scrollBar.SetScrollBounds();
__instance.scrollBar.SetScrollBounds(__instance);
}
private static void ValueChanged(OptionBehaviour obj)
@@ -300,7 +313,7 @@ public static class RoleSettingMenuPatches
if (AmongUsClient.Instance.AmHost)
{
Rpc<SyncRoleOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [role.GetNetData()]);
Rpc<SyncRoleOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [role.GetNetData()], true);
}
GameOptionsManager.Instance.GameHostOptions = GameOptionsManager.Instance.CurrentGameOptions;
@@ -319,7 +332,7 @@ public static class RoleSettingMenuPatches
var num = hasImage ? -0.872f : -1;
// TODO: create sub groups under the role settings.
var filteredOptions = GameSettingMenuPatches.SelectedMod?.OptionGroups
var filteredOptions = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
.Where(x=> x.OptionableType == role.GetType())
.SelectMany(x=>x.Options)
.ToList() ?? [];
@@ -485,7 +498,7 @@ public static class RoleSettingMenuPatches
roleOptionSetting.titleText.horizontalAlignment = HorizontalAlignmentOptions.Left;
if (GameSettingMenuPatches.SelectedMod is null ||
GameSettingMenuPatches.SelectedMod.OptionGroups
GameSettingMenuPatches.SelectedMod.InternalOptionGroups
.Exists(x => x.OptionableType == role.GetType()))
{
var newButton = Object.Instantiate(roleOptionSetting.buttons[0], roleOptionSetting.transform);
@@ -517,7 +530,7 @@ public static class RoleSettingMenuPatches
roleOptionSetting.ChancePlusBtn.gameObject.SetActive(false);
}
if (index < GameSettingMenuPatches.SelectedMod?.CustomRoles.Count - 1)
if (index < GameSettingMenuPatches.SelectedMod?.InternalRoles.Count - 1)
{
ScrollerNum -= 0.43f;
}
+1
View File
@@ -68,6 +68,7 @@ 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);
+2 -1
View File
@@ -7,7 +7,8 @@ namespace MiraAPI.Patches.Roles;
/// <summary>
/// Patches the haunt menu to show the actual role name rather then team.
/// </summary>
[HarmonyPatch(typeof(HauntMenuMinigame), nameof(HauntMenuMinigame.SetFilterText))]
[HarmonyPatch(typeof(HauntMenuMinigame), nameof(HauntMenuMinigame.SetHauntTarget))]
// SetFilterText was INLINED
public static class HauntMenuMinigamePatch
{
public static void Postfix(HauntMenuMinigame __instance)
@@ -14,6 +14,8 @@ 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)
@@ -1,5 +1,6 @@
using AmongUs.GameOptions;
using HarmonyLib;
using Il2CppSystem.Collections.Generic;
using MiraAPI.Roles;
using Reactor.Utilities;
@@ -11,6 +12,23 @@ 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>
@@ -205,6 +205,7 @@ 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);
+30 -31
View File
@@ -6,7 +6,9 @@ using MiraAPI.GameModes;
using MiraAPI.GameOptions;
using MiraAPI.Hud;
using MiraAPI.Modifiers;
using MiraAPI.Presets;
// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace MiraAPI.PluginLoading;
/// <summary>
@@ -23,61 +25,58 @@ public class MiraPluginInfo
}
/// <summary>
/// Get a read only collection of this plugin's modifiers. This is probably not safe because they are uninitialized.
/// Gets a read only collection of this plugin's modifiers. This is not safe for modifiers with constructors.
/// </summary>
/// <returns>Readonly collection of modifiers.</returns>
public IReadOnlyCollection<BaseModifier> GetModifiers()
{
return Modifiers.AsReadOnly();
}
public IReadOnlyCollection<BaseModifier> Modifiers { get; private set; } = null!;
/// <summary>
/// Get a read only collection of this plugin's Option Groups.
/// Gets a read only collection of this plugin's Option Groups.
/// </summary>
/// <returns>Readonly collection of option groups.</returns>
public IReadOnlyCollection<AbstractOptionGroup> GetOptionGroups()
{
return OptionGroups.AsReadOnly();
}
public IReadOnlyCollection<AbstractOptionGroup> OptionGroups { get; private set; } = null!;
/// <summary>
/// Gets a read only collection of this plugin's options.
/// </summary>
/// <returns>Read only collection of options.</returns>
public IReadOnlyCollection<IModdedOption> GetOptions()
{
return Options.AsReadOnly();
}
public IReadOnlyCollection<IModdedOption> Options { get; private set; } = null!;
/// <summary>
/// Gets a read only dictionary of Role IDs and the RoleBehaviour object they are associated with.
/// </summary>
/// <returns>Read only dictionary of IDs and Roles.</returns>
public ReadOnlyDictionary<ushort, RoleBehaviour> GetRoles()
{
return new ReadOnlyDictionary<ushort, RoleBehaviour>(CustomRoles);
}
public ReadOnlyDictionary<ushort, RoleBehaviour> Roles { get; private set; } = null!;
/// <summary>
/// Gets a read only collection of this plugin's custom buttons.
/// </summary>
/// <returns>Read only collection of buttons.</returns>
public IReadOnlyCollection<CustomActionButton> GetButtons()
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()
{
return Buttons.AsReadOnly();
Presets = [..InternalPresets];
Modifiers = [..InternalModifiers];
OptionGroups = [..InternalOptionGroups];
Options = [..InternalOptions];
Roles = new ReadOnlyDictionary<ushort, RoleBehaviour>(InternalRoles);
Buttons = [..InternalButtons];
}
internal List<AbstractOptionGroup> OptionGroups { get; } = [];
internal List<OptionPreset> InternalPresets { get; } = [];
internal List<IModdedOption> Options { get; } = [];
internal List<AbstractOptionGroup> InternalOptionGroups { get; } = [];
internal List<BaseModifier> Modifiers { get; } = [];
internal List<IModdedOption> InternalOptions { get; } = [];
internal Dictionary<ushort, RoleBehaviour> CustomRoles { get; } = [];
internal List<BaseModifier> InternalModifiers { get; } = [];
internal Dictionary<int, CustomGameMode> GameModes { get; } = [];
internal Dictionary<ushort, RoleBehaviour> InternalRoles { get; } = [];
internal List<CustomActionButton> Buttons { get; } = [];
internal Dictionary<int, CustomGameMode> InternalGameModes { get; } = [];
internal List<CustomActionButton> InternalButtons { get; } = [];
/// <summary>
/// Gets the plugin's ID, as defined in the plugin's BepInEx metadata.
+13 -3
View File
@@ -12,6 +12,7 @@ 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;
@@ -26,8 +27,9 @@ public sealed class MiraPluginManager
{
private readonly Dictionary<Assembly, MiraPluginInfo> _registeredPlugins = [];
internal MiraPluginInfo[] RegisteredPlugins() => [.. _registeredPlugins.Values];
internal Dictionary<MiraPluginInfo, List<Type>> QueuedRoleRegistrations { get; } = new();
internal MiraPluginInfo[] RegisteredPlugins { get; private set; } = null!;
internal Dictionary<MiraPluginInfo, List<Type>> QueuedRoleRegistrations { get; } = [];
internal static MiraPluginManager Instance { get; private set; } = new();
internal void Initialize()
@@ -110,16 +112,24 @@ public sealed class MiraPluginManager
info.PluginConfig.Save();
info.PluginConfig.SaveOnConfigSet = oldConfigSetting;
info.OptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority));
info.InternalOptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority));
QueuedRoleRegistrations.Add(info, roles);
_registeredPlugins.Add(assembly, info);
info.SavePublicCollections();
PresetManager.CreateDefaultPreset(info);
PresetManager.LoadPresets(info);
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
@@ -0,0 +1,74 @@
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();
}
}
+95
View File
@@ -0,0 +1,95 @@
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"))
{
Logger<MiraApiPlugin>.Info($"Loading preset file {file}");
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];
}
}
+91
View File
@@ -0,0 +1,91 @@
using System;
using Il2CppInterop.Runtime.InteropTypes.Fields;
using MiraAPI.Modifiers.ModifierDisplay;
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;
[RegisterInIl2Cpp]
public class SavePresetPopup(nint ptr) : Minigame(ptr)
{
private PassiveButton saveButton;
private PassiveButton closeButton;
private TextMeshPro textBoxText;
private TextBoxTMP textBox;
private Action<string> onSave;
// Cleanup holder object
public void OnDestroy()
{
transform.parent.gameObject.Destroy();
}
public override void Close()
{
}
private void Awake()
{
var textboxHolder = transform.GetChild(1).GetChild(1);
saveButton = transform.FindChild("SaveButton").GetComponent<PassiveButton>();
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,
},
};
holder.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.

After

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.CustomRoles.Add((ushort)role.Role, role);
pluginInfo.InternalRoles.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.CustomRoles.ContainsValue(role as RoleBehaviour ?? throw new InvalidOperationException()));
return MiraPluginManager.Instance.RegisteredPlugins.First(plugin => plugin.InternalRoles.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];
+1 -1
View File
@@ -42,7 +42,7 @@ public static class CustomRoleUtils
public static StringBuilder CreateForRole(ICustomRole role)
{
var taskStringBuilder = new StringBuilder();
taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleColor.ToTextColor()}You are a <b>{role.RoleName}.</b></color>");
taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleColor.ToTextColor()}Your role is <b>{role.RoleName}.</b></color>");
taskStringBuilder.Append("<size=70%>");
taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleLongDescription}");
return taskStringBuilder;
+39
View File
@@ -76,6 +76,45 @@ 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>
@@ -0,0 +1,25 @@
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,6 +8,23 @@ 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>
@@ -23,11 +40,31 @@ 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>
+19 -7
View File
@@ -11,14 +11,13 @@ namespace MiraAPI.Utilities.Assets;
public static class SpriteTools
{
/// <summary>
/// Load a sprite from a resource path.
/// Loads and returns a texture from a resource path using the specified assembly.
/// </summary>
/// <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)
/// <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)
{
var tex = new Texture2D(1, 1, TextureFormat.ARGB32, false);
var myStream = assembly.GetManifestResourceStream(resourcePath);
@@ -33,6 +32,19 @@ 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;
+101 -22
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
@@ -41,6 +42,94 @@ 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>
@@ -102,6 +191,12 @@ 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;
@@ -121,6 +216,12 @@ 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;
@@ -342,18 +443,6 @@ 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>
@@ -365,16 +454,6 @@ 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)]
[MethodRpc((uint)MiraRpc.RemoveVote, SendImmediately = true)]
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)]
[MethodRpc((uint)MiraRpc.CastVote, SendImmediately = true)]
public static void RpcCastVote(PlayerControl source, byte srcPlayerId, byte suspectPlayerId)
{
CustomCastVote(srcPlayerId, suspectPlayerId);
+1 -1
View File
@@ -1,4 +1,4 @@
[![](https://dcbadge.limes.pink/api/server/all-of-us-launchpad-794950428756410429)](https://discord.gg/all-of-us-launchpad-794950428756410429)
[![](https://dcbadge.limes.pink/api/server/AEfHJGwggC)](https://discord.gg/AEfHJGwggC)
# Mira API