Merge branch 'dev' into commission

This commit is contained in:
XtraCube committed 2025-06-29 11:23:32 -04:00
commit 24b52ededc
28 files changed
+668 -68

No files matched your search

+4 -1
View File
@@ -6,7 +6,10 @@ namespace MiraAPI.Example;
[RegisterCustomColors] [RegisterCustomColors]
public static class ExampleColors public static class ExampleColors
{ {
public static CustomColor Cerulean { get; } = new("Cerulean", new Color(0.0f, 0.48f, 0.65f)); public static CustomColor Cerulean { get; } = new("Cerulean", new Color(0.0f, 0.48f, 0.65f))
{
ColorBrightness = CustomColorBrightness.Lighter,
};
public static CustomColor Rose { get; } = new("Rose", new Color(0.98f, 0.26f, 0.62f)); public static CustomColor Rose { get; } = new("Rose", new Color(0.98f, 0.26f, 0.62f));
@@ -10,6 +10,8 @@ public class ExampleOptions2 : AbstractOptionGroup
public override uint GroupPriority => 0; // This group will be displayed first. The default value is uint.MaxValue. public override uint GroupPriority => 0; // This group will be displayed first. The default value is uint.MaxValue.
public ModdedPlayerOption PlayerOption2 { get; } = new("YOU MUST CHOSE A PLAYER", false);
public ModdedPlayerOption PlayerOption { get; } = new("Eh whatever");
public ModdedToggleOption ToggleOpt1 { get; } = new("Toggle Option 1", false); public ModdedToggleOption ToggleOpt1 { get; } = new("Toggle Option 1", false);
public ModdedToggleOption ToggleOpt2 { get; } = new("Toggle Option 2", false) public ModdedToggleOption ToggleOpt2 { get; } = new("Toggle Option 2", false)
+27 -6
View File
@@ -13,19 +13,24 @@ namespace MiraAPI.Colors;
public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColor) public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColor)
{ {
/// <summary> /// <summary>
/// Gets the main color. /// Gets or sets the main color.
/// </summary> /// </summary>
public Color32 MainColor { get; } = mainColor; public Color32 MainColor { get; set; } = mainColor;
/// <summary> /// <summary>
/// Gets the shadow color. /// Gets or sets the shadow color.
/// </summary> /// </summary>
public Color32 ShadowColor { get; } = shadowColor; public Color32 ShadowColor { get; set; } = shadowColor;
/// <summary> /// <summary>
/// Gets the name of the color. /// Gets or sets the name of the color.
/// </summary> /// </summary>
public StringNames Name { get; } = name; public StringNames Name { get; set; } = name;
/// <summary>
/// Gets or sets whether the color is lighter or darker. Can be used to help with colorblind stuff.
/// </summary>
public CustomColorBrightness ColorBrightness { get; set; } = CustomColorBrightness.Darker;
/// <inheritdoc /> /// <inheritdoc />
public CustomColor(StringNames name, Color32 mainColor) : this(name, mainColor, mainColor.GetShadowColor(60)) public CustomColor(StringNames name, Color32 mainColor) : this(name, mainColor, mainColor.GetShadowColor(60))
@@ -42,3 +47,19 @@ public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 sha
{ {
} }
} }
/// <summary>
/// Can be used to help with colorblind stuff.
/// </summary>
public enum CustomColorBrightness
{
/// <summary>
/// A lighter color.
/// </summary>
Lighter,
/// <summary>
/// A darker color.
/// </summary>
Darker,
}
+2 -1
View File
@@ -62,9 +62,10 @@ public interface IModdedOption
/// <param name="toggleOpt">The ToggleOption template.</param> /// <param name="toggleOpt">The ToggleOption template.</param>
/// <param name="numberOpt">The NumberOption template.</param> /// <param name="numberOpt">The NumberOption template.</param>
/// <param name="stringOpt">The StringOption template.</param> /// <param name="stringOpt">The StringOption template.</param>
/// <param name="playerOpt">The PlayerOption template.</param>
/// <param name="container">>The Transform container for the option.</param> /// <param name="container">>The Transform container for the option.</param>
/// <returns>>The created OptionBehaviour object.</returns> /// <returns>>The created OptionBehaviour object.</returns>
OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container); OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container);
/// <summary> /// <summary>
/// Gets the value as a float. /// Gets the value as a float.
@@ -6,6 +6,7 @@ using System.Reflection;
using BepInEx.Configuration; using BepInEx.Configuration;
using HarmonyLib; using HarmonyLib;
using MiraAPI.GameOptions.Attributes; using MiraAPI.GameOptions.Attributes;
using MiraAPI.GameOptions.OptionTypes;
using MiraAPI.Networking; using MiraAPI.Networking;
using MiraAPI.PluginLoading; using MiraAPI.PluginLoading;
using MiraAPI.Utilities; using MiraAPI.Utilities;
@@ -22,6 +23,7 @@ public static class ModdedOptionsManager
private static readonly Dictionary<PropertyInfo, ModdedOptionAttribute> OptionAttributes = []; private static readonly Dictionary<PropertyInfo, ModdedOptionAttribute> OptionAttributes = [];
private static readonly Dictionary<Type, AbstractOptionGroup> TypeToGroup = []; private static readonly Dictionary<Type, AbstractOptionGroup> TypeToGroup = [];
internal static readonly Dictionary<OptionBehaviour, ModdedPlayerOption> CreatedPlayerOptions = [];
internal static readonly Dictionary<uint, IModdedOption> ModdedOptions = []; internal static readonly Dictionary<uint, IModdedOption> ModdedOptions = [];
internal static readonly List<AbstractOptionGroup> Groups = []; internal static readonly List<AbstractOptionGroup> Groups = [];
@@ -44,7 +44,7 @@ public class ModdedEnumOption : ModdedOption<int>
} }
/// <inheritdoc /> /// <inheritdoc />
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container) public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
{ {
var stringOption = Object.Instantiate(stringOpt, container); var stringOption = Object.Instantiate(stringOpt, container);
@@ -135,7 +135,7 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
} }
/// <inheritdoc /> /// <inheritdoc />
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container) public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
{ {
var stringOption = Object.Instantiate(stringOpt, container); var stringOption = Object.Instantiate(stringOpt, container);
@@ -93,6 +93,7 @@ public class ModdedNumberOption : ModdedOption<float>
ToggleOption toggleOpt, ToggleOption toggleOpt,
NumberOption numberOpt, NumberOption numberOpt,
StringOption stringOpt, StringOption stringOpt,
PlayerOption playerOpt,
Transform container) Transform container)
{ {
var numberOption = Object.Instantiate(numberOpt, Vector3.zero, Quaternion.identity, container); var numberOption = Object.Instantiate(numberOpt, Vector3.zero, Quaternion.identity, container);
@@ -37,6 +37,7 @@ public abstract class ModdedOption<T> : IModdedOption
{ {
if (_parentMod != null || value == null) return; if (_parentMod != null || value == null) return;
_parentMod = value; _parentMod = value;
var entry = _parentMod.GetConfigFile().Bind(ConfigDefinition, DefaultValue); var entry = _parentMod.GetConfigFile().Bind(ConfigDefinition, DefaultValue);
Value = entry.Value; Value = entry.Value;
} }
@@ -177,6 +178,7 @@ public abstract class ModdedOption<T> : IModdedOption
ToggleOption toggleOpt, ToggleOption toggleOpt,
NumberOption numberOpt, NumberOption numberOpt,
StringOption stringOpt, StringOption stringOpt,
PlayerOption playerOpt,
Transform container); Transform container);
/// <summary> /// <summary>
@@ -0,0 +1,138 @@
using System;
using System.Linq;
using Il2CppSystem.Collections.Generic;
using MiraAPI.Networking;
using MiraAPI.Utilities;
using UnityEngine;
using Object = UnityEngine.Object;
namespace MiraAPI.GameOptions.OptionTypes;
/// <summary>
/// An option for selecting an ingame player. It returns an index of the values list, NOT A PLAYER ID. To get a player out of this, index the Values list.
/// </summary>
public class ModdedPlayerOption : ModdedOption<int>
{
/// <summary>
/// Gets or sets the included players.
/// </summary>
public List<NetworkedPlayerInfo> Values { get; set; }
/// <summary>
/// Gets or sets a filter for the players included in the option.
/// </summary>
public Func<NetworkedPlayerInfo, bool>? PlayerFilter { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the option includes a "None" option. If None is selected, value will return -1.
/// </summary>
public bool AllowNone { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ModdedPlayerOption"/> class.
/// </summary>
/// <param name="title">The title of the option.</param>
/// <param name="allowNone">Whether the option includes a none option. If None is selected, value will return -1.</param>
public ModdedPlayerOption(string title, bool allowNone = true) : base(title, allowNone ? -1 : 0, false)
{
Data = ScriptableObject.CreateInstance<PlayerSelectionGameSetting>();
var data = (PlayerSelectionGameSetting)Data;
data.Title = StringName;
data.Type = global::OptionTypes.Player;
Values = new List<NetworkedPlayerInfo>();
AllowNone = allowNone;
Value = DefaultValue;
}
/// <inheritdoc />
public override OptionBehaviour CreateOption(
ToggleOption toggleOpt,
NumberOption numberOpt,
StringOption stringOpt,
PlayerOption playerOpt,
Transform container)
{
var playerOption = Object.Instantiate(playerOpt, container);
var filteredList = GetFilteredPlayers();
Values = filteredList.ToIl2CppList();
playerOption.SetUpFromData(Data, 20);
playerOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
playerOption.Title = StringName;
playerOption.TitleText.text = TranslationController.Instance.GetString(StringName);
playerOption.Values = Values;
playerOption.Value = Value;
playerOption.playerIndex = filteredList.FindIndex(p => p.PlayerId == Value);
OptionBehaviour = playerOption;
ModdedOptionsManager.CreatedPlayerOptions.TryAdd(playerOption, this);
playerOption.SetValueText();
playerOption.AdjustButtonsActiveState();
return playerOption;
}
/// <summary>
/// Returns a player ran through the filter.
/// </summary>
/// <returns>A list of filtered players.</returns>
public System.Collections.Generic.List<NetworkedPlayerInfo> GetFilteredPlayers()
{
return GameData.Instance.AllPlayers.ToArray()
.Where(x => PlayerFilter?.Invoke(x) ?? true)
.ToList();
}
/// <summary>
/// Returns a player from the value. If AllowNone is enabled and none is enabled this will return null.
/// </summary>
/// <returns>A player or null if chosen none.</returns>
public NetworkedPlayerInfo? GetPlayerValue()
{
return Value < 0 ? null : Values[Value];
}
/// <inheritdoc />
public override float GetFloatData()
{
return Value;
}
/// <inheritdoc />
public override NetData GetNetData()
{
return new NetData(Id, BitConverter.GetBytes(Value));
}
/// <inheritdoc />
public override void HandleNetData(byte[] data)
{
SetValue(BitConverter.ToInt32(data));
}
/// <inheritdoc />
public override int GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
{
return optionBehaviour.GetInt();
}
/// <inheritdoc />
protected override void OnValueChanged(int newValue)
{
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false);
if (!OptionBehaviour)
{
return;
}
if (OptionBehaviour is PlayerOption opt)
{
opt.Value = newValue;
}
}
}
@@ -26,7 +26,7 @@ public class ModdedToggleOption : ModdedOption<bool>
} }
/// <inheritdoc /> /// <inheritdoc />
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container) public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
{ {
var toggleOption = Object.Instantiate(toggleOpt, Vector3.zero, Quaternion.identity, container); var toggleOption = Object.Instantiate(toggleOpt, Vector3.zero, Quaternion.identity, container);
+33
View File
@@ -0,0 +1,33 @@
using HarmonyLib;
using UnityEngine;
namespace MiraAPI.Patches;
/// <summary>
/// Allows players to paste text into chat.
/// Source: https://github.com/CallOfCreator/NewMod/blob/main/NewMod/Patches/ClipboardPatch.cs
/// </summary>
[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))]
public static class ChatControllerPatch
{
public static void Prefix(ChatController __instance)
{
if (!HudManager.Instance.Chat.IsOpenOrOpening) return;
var ctrlPressed = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
if (!ctrlPressed || !Input.GetKeyDown(KeyCode.V)) return;
var clipboard = GUIUtility.systemCopyBuffer;
if (string.IsNullOrWhiteSpace(clipboard)) return;
clipboard = clipboard.Replace("<", string.Empty)
.Replace(">", string.Empty)
.Replace("\r", string.Empty);
if (!Input.GetKey(KeyCode.LeftShift) && !Input.GetKey(KeyCode.RightShift))
clipboard = clipboard.Replace("\n", string.Empty);
__instance.freeChatField.textArea.SetText(__instance.freeChatField.textArea.text + clipboard);
}
}
+10 -22
View File
@@ -1,37 +1,25 @@
using HarmonyLib; using HarmonyLib;
using MiraAPI.Hud; using MiraAPI.Hud;
using Reactor.Utilities;
namespace MiraAPI.Patches.Hud; namespace MiraAPI.Patches.Hud;
/// <summary> [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))]
/// Reset button patches. [HarmonyPatch(typeof(ExileController), nameof(ExileController.ReEnableGameplay))]
/// </summary>
[HarmonyPatch]
public static class ButtonResetPatches public static class ButtonResetPatches
{ {
/// <summary> public static void Postfix()
/// Resets the cooldown and effect of all custom buttons when the meeting starts.
/// </summary>
[HarmonyPostfix]
[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))]
public static void MeetingHudStartPostfix()
{ {
foreach (var customActionButton in CustomButtonManager.CustomButtons) foreach (var customActionButton in CustomButtonManager.CustomButtons)
{ {
customActionButton.ResetCooldownAndOrEffect(); try
}
}
/// <summary>
/// Resets the cooldown and effect of all custom buttons after the exile screen is closed.
/// </summary>
[HarmonyPostfix]
[HarmonyPatch(typeof(ExileController), nameof(ExileController.ReEnableGameplay))]
public static void ExileControllerReEnableGameplayPostfix()
{
foreach (var customActionButton in CustomButtonManager.CustomButtons)
{ {
customActionButton.ResetCooldownAndOrEffect(); customActionButton.ResetCooldownAndOrEffect();
} }
catch (System.Exception ex)
{
Logger<MiraApiPlugin>.Error($"Error resetting cooldown and effect for button {customActionButton.GetType().Name}: {ex}");
}
}
} }
} }
+15
View File
@@ -1,5 +1,6 @@
using HarmonyLib; using HarmonyLib;
using MiraAPI.Hud; using MiraAPI.Hud;
using Reactor.Utilities;
using Reactor.Utilities.Extensions; using Reactor.Utilities.Extensions;
using UnityEngine; using UnityEngine;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
@@ -77,8 +78,15 @@ public static class HudManagerPatches
continue; continue;
} }
try
{
button.CreateButton(location); button.CreateButton(location);
} }
catch (System.Exception e)
{
Logger<MiraApiPlugin>.Error($"Failed to create custom button {button.GetType().Name}: {e}");
}
}
gridArrange.Start(); gridArrange.Start();
gridArrange.ArrangeChilds(); gridArrange.ArrangeChilds();
@@ -104,8 +112,15 @@ public static class HudManagerPatches
} }
foreach (var button in CustomButtonManager.CustomButtons) foreach (var button in CustomButtonManager.CustomButtons)
{
try
{ {
button.SetActive(isActive, role); button.SetActive(isActive, role);
} }
catch (System.Exception e)
{
Logger<MiraApiPlugin>.Error($"Failed to set custom button {button.GetType().Name} active: {e}");
}
}
} }
} }
+1 -1
View File
@@ -114,7 +114,7 @@ public static class HatsTabPatches
yield return null; yield return null;
} }
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f; __instance.SetScrollerBounds();
yield return new WaitForSeconds(0.01f); yield return new WaitForSeconds(0.01f);
} }
__instance.currentHatIsEquipped = true; __instance.currentHatIsEquipped = true;
+1 -1
View File
@@ -91,7 +91,7 @@ public static class NameplatesTabPatches
hatIndex += 1; hatIndex += 1;
} }
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f; __instance.SetScrollerBounds();
__instance.currentNameplateIsEquipped = true; __instance.currentNameplateIsEquipped = true;
} }
+1 -1
View File
@@ -94,7 +94,7 @@ public static class SkinsTabPatches
hatIndex += 1; hatIndex += 1;
} }
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f; __instance.SetScrollerBounds();
__instance.currentSkinIsEquipped = true; __instance.currentSkinIsEquipped = true;
} }
+1 -1
View File
@@ -90,7 +90,7 @@ public static class VisorsTabPatches
hatIndex += 1; hatIndex += 1;
} }
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f; __instance.SetScrollerBounds();
__instance.currentVisorIsEquipped = true; __instance.currentVisorIsEquipped = true;
} }
@@ -220,6 +220,7 @@ internal static class GameOptionsMenuPatch
menu.checkboxOrigin, menu.checkboxOrigin,
menu.numberOptionOrigin, menu.numberOptionOrigin,
menu.stringOptionOrigin, menu.stringOptionOrigin,
menu.playerOptionOrigin,
menu.settingsContainer)); menu.settingsContainer));
foreach (var newOpt in options) foreach (var newOpt in options)
@@ -288,7 +288,7 @@ internal static class GameSettingMenuPatches
{ {
if (_text is not null && SelectedModIdx == 0) if (_text is not null && SelectedModIdx == 0)
{ {
_text.text = "Default"; _text.text = $"<size=40%>(Page 0/{MiraPluginManager.Instance.RegisteredPlugins.Length})</size>\nDefault";
_text.fontSizeMax = 3.2f; _text.fontSizeMax = 3.2f;
SelectedMod = null; SelectedMod = null;
} }
@@ -298,7 +298,7 @@ internal static class GameSettingMenuPatches
SelectedMod = MiraPluginManager.Instance.RegisteredPlugins[SelectedModIdx - 1]; SelectedMod = MiraPluginManager.Instance.RegisteredPlugins[SelectedModIdx - 1];
var name = SelectedMod.MiraPlugin.OptionsTitleText; var name = SelectedMod.MiraPlugin.OptionsTitleText;
_text.text = name[..Math.Min(name.Length, 25)]; _text.text = $"<size=50%>(Page {SelectedModIdx}/{MiraPluginManager.Instance.RegisteredPlugins.Length})</size>\n" + name[..Math.Min(name.Length, 25)];
} }
bool replaceWithModifiers = true; bool replaceWithModifiers = true;
+152 -11
View File
@@ -1,12 +1,161 @@
using HarmonyLib; using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
using Il2CppSystem; using Il2CppSystem;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.OptionTypes;
using MiraAPI.Roles;
using MiraAPI.Utilities; using MiraAPI.Utilities;
using UnityEngine;
using Object = Il2CppSystem.Object;
namespace MiraAPI.Patches.Options; namespace MiraAPI.Patches.Options;
[HarmonyPatch] [HarmonyPatch]
public static class OptionsPatches public static class OptionsPatches
{ {
[HarmonyPrefix]
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.IncreaseChance))]
public static bool RoleIncreaseChancePrefix(RoleOptionSetting __instance)
{
if (!__instance.Role.IsCustomRole())
{
return true;
}
if (__instance.roleChance == 0)
{
__instance.roleMaxCount = 1;
}
var increment = Input.GetKey(KeyCode.LeftShift) ? 5 : 10;
__instance.roleChance += increment;
if (__instance.roleChance > 100)
{
__instance.roleChance = 0;
}
__instance.OnValueChanged.Invoke(__instance);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.DecreaseChance))]
public static bool RoleDecreaseChancePrefix(RoleOptionSetting __instance)
{
if (!__instance.Role.IsCustomRole())
{
return true;
}
if (__instance.roleChance == 0)
{
__instance.roleMaxCount = 1;
}
var increment = Input.GetKey(KeyCode.LeftShift) ? 5 : 10;
__instance.roleChance -= increment;
if (__instance.roleChance < 0)
{
__instance.roleChance = 100;
}
__instance.OnValueChanged.Invoke(__instance);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.IncreaseCount))]
public static bool RoleIncreaseCountPrefix(RoleOptionSetting __instance)
{
if (!__instance.Role.IsCustomRole())
{
return true;
}
if (__instance.roleMaxCount == 0)
{
__instance.roleChance = 50;
}
__instance.roleMaxCount += 1;
if (__instance.roleMaxCount > __instance.role.MaxCount)
{
__instance.roleMaxCount = 0;
}
__instance.OnValueChanged.Invoke(__instance);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.DecreaseCount))]
public static bool RoleDecreaseCountPrefix(RoleOptionSetting __instance)
{
if (!__instance.Role.IsCustomRole())
{
return true;
}
if (__instance.roleMaxCount == 0)
{
__instance.roleChance = 50;
}
__instance.roleMaxCount -= 1;
if (__instance.roleMaxCount < 0)
{
__instance.roleMaxCount = __instance.role.MaxCount;
}
__instance.OnValueChanged.Invoke(__instance);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.AdjustButtonsActiveState))]
[HarmonyPatch(typeof(StringOption), nameof(StringOption.AdjustButtonsActiveState))]
[HarmonyPatch(typeof(PlayerOption), nameof(PlayerOption.AdjustButtonsActiveState))]
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.AdjustChanceButtonsActiveState))]
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.AdjustCountButtonsActiveState))]
public static bool AdjustButtonsPrefix(OptionBehaviour __instance)
{
if (__instance.IsCustom())
{
if (__instance.TryCast<NumberOption>() is { } numberOption)
{
numberOption.MinusBtn.SetInteractable(true);
numberOption.PlusBtn.SetInteractable(true);
}
if (__instance.TryCast<StringOption>() is { } stringOption)
{
stringOption.MinusBtn.SetInteractable(true);
stringOption.PlusBtn.SetInteractable(true);
}
if (__instance.TryCast<PlayerOption>() is { } playerOption)
{
playerOption.MinusBtn.SetInteractable(true);
playerOption.PlusBtn.SetInteractable(true);
}
return false;
}
if (__instance.TryCast<RoleOptionSetting>() is { } roleOptionSetting && roleOptionSetting.Role.IsCustomRole())
{
roleOptionSetting.CountMinusBtn.SetInteractable(true);
roleOptionSetting.CountPlusBtn.SetInteractable(true);
roleOptionSetting.ChanceMinusBtn.SetInteractable(true);
roleOptionSetting.ChancePlusBtn.SetInteractable(true);
return false;
}
return true;
}
[HarmonyPrefix] [HarmonyPrefix]
[HarmonyPatch(typeof(ToggleOption), nameof(ToggleOption.Initialize))] [HarmonyPatch(typeof(ToggleOption), nameof(ToggleOption.Initialize))]
public static bool ToggleInit(ToggleOption __instance) public static bool ToggleInit(ToggleOption __instance)
@@ -16,8 +165,7 @@ public static class OptionsPatches
return true; return true;
} }
__instance.TitleText.text = __instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title, Array.Empty<Object>());
TranslationController.Instance.GetString(__instance.Title, Array.Empty<Object>());
return false; return false;
} }
@@ -40,19 +188,12 @@ public static class OptionsPatches
[HarmonyPrefix] [HarmonyPrefix]
[HarmonyPatch(typeof(StringOption), nameof(StringOption.UpdateValue))] [HarmonyPatch(typeof(StringOption), nameof(StringOption.UpdateValue))]
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.UpdateValue))] [HarmonyPatch(typeof(NumberOption), nameof(NumberOption.UpdateValue))]
[HarmonyPatch(typeof(PlayerOption), nameof(PlayerOption.UpdateValue))]
public static bool UpdateValuePrefix(OptionBehaviour __instance) public static bool UpdateValuePrefix(OptionBehaviour __instance)
{ {
return !__instance.IsCustom(); 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();
}
[HarmonyPrefix] [HarmonyPrefix]
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.Initialize))] [HarmonyPatch(typeof(NumberOption), nameof(NumberOption.Initialize))]
public static bool NumberInit(NumberOption __instance) public static bool NumberInit(NumberOption __instance)
@@ -0,0 +1,128 @@
using System;
using System.Linq;
using HarmonyLib;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.OptionTypes;
using MiraAPI.Utilities;
using Reactor.Utilities;
using UnityEngine;
using Object = Il2CppSystem.Object;
namespace MiraAPI.Patches.Options;
[HarmonyPatch(typeof(PlayerOption))]
public static class PlayerOptionPatches
{
// Updates the list of players according to the filter.
[HarmonyPrefix]
[HarmonyPatch(nameof(PlayerOption.FixedUpdate))]
public static bool PlayerOptionUpdatePatch(PlayerOption __instance)
{
if (!__instance.IsCustom())
{
return true;
}
if (!ModdedOptionsManager.CreatedPlayerOptions.TryGetValue(__instance, out var moddedPlrOpt)) return false;
var filteredPlayers = moddedPlrOpt.GetFilteredPlayers();
if (filteredPlayers.Count != __instance.Values.Count)
{
filteredPlayers.Sort((a, b) => a.PlayerId.CompareTo(b.PlayerId));
__instance.Values = filteredPlayers.ToIl2CppList();
moddedPlrOpt.Values = __instance.Values;
__instance.playerIndex = filteredPlayers.FindIndex(p => p.PlayerId == __instance.Value);
__instance.SetValueText();
}
__instance.CheckValueChanged();
return false;
}
// Disable going under 0 if you aren't allowed to choose None.
[HarmonyPrefix]
[HarmonyPatch(nameof(PlayerOption.UpdatePlayerIndex))]
public static bool UpdatePlayerIndexPatch(PlayerOption __instance, int index)
{
if (!__instance.IsCustom() || ModdedOptionsManager.CreatedPlayerOptions[__instance].AllowNone)
{
return true;
}
var newIndex = Mathf.Clamp(index, 0, __instance.Values.Count - 1);
__instance.playerIndex = newIndex;
__instance.Value = __instance.Values[newIndex].PlayerId;
return false;
}
// Replace "Round Robin" with "None" in the Host Options Menu.
[HarmonyPostfix]
[HarmonyPatch(nameof(PlayerOption.SetValueText))]
public static void SetValueTextPatch(PlayerOption __instance)
{
if (__instance.playerIndex < 0)
{
__instance.ValueText.text = TranslationController.Instance.GetString(StringNames.FilterNone);
}
}
// Innersloth hardcode literally replaces the value with the HNS impostor option OnEnable, so I have to patch that out. Smh.
[HarmonyPrefix]
[HarmonyPatch(nameof(PlayerOption.OnEnable))]
public static bool OnEnablePatch(PlayerOption __instance)
{
return !__instance.IsCustom();
}
[HarmonyPrefix]
[HarmonyPatch(nameof(PlayerOption.GetInt))]
public static bool GetIntPatch(PlayerOption __instance, ref int __result)
{
if (!__instance.IsCustom())
{
return true;
}
__result = __instance.Value;
return false;
}
// Used to replace "Round Robin" with "None" for option notification & view pane.
[HarmonyPrefix]
[HarmonyPatch(typeof(PlayerSelectionGameSetting), nameof(PlayerSelectionGameSetting.GetValueString))]
public static bool GetStringPatch(PlayerSelectionGameSetting __instance, float value, ref string __result)
{
var option = ModdedOptionsManager.ModdedOptions.FirstOrDefault(x => x.Value.Data == __instance).Value;
if (option == null) return true;
var networkedPlayerInfo = GameData.Instance.AllPlayers.ToArray().FirstOrDefault(p => p.PlayerId == (int)value);
if (networkedPlayerInfo != null)
{
__result = networkedPlayerInfo.PlayerName;
return false;
}
__result = TranslationController.Instance.GetString(StringNames.FilterNone);
return false;
}
// "UpdateValue" was inlined and I needed to replace it, so I just rewrote the method where it's called from.
[HarmonyPrefix]
[HarmonyPatch(nameof(PlayerOption.CheckValueChanged))]
public static bool UpdateValuePatch(PlayerOption __instance)
{
if (!__instance.IsCustom())
{
return true;
}
if (__instance.oldValue == __instance.Value) return false;
__instance.oldValue = __instance.Value;
__instance.SetValueText();
return false;
}
}
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using HarmonyLib; using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppInterop.Runtime.InteropTypes.Arrays;
using MiraAPI.GameOptions;
using MiraAPI.Networking; using MiraAPI.Networking;
using MiraAPI.Roles; using MiraAPI.Roles;
using MiraAPI.Utilities; using MiraAPI.Utilities;
@@ -30,6 +31,9 @@ public static class RoleSettingMenuPatches
private static float ScrollerNum { get; set; } = 0.522f; private static float ScrollerNum { get; set; } = 0.522f;
private static RoleBehaviour? CurrentRole { get; set; }
private static List<IModdedOption>? CurrentRoleOptions { get; set; }
[HarmonyPostfix] [HarmonyPostfix]
[HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))] [HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))]
public static void SetQuotaTabPostfix(RolesSettingsMenu __instance) public static void SetQuotaTabPostfix(RolesSettingsMenu __instance)
@@ -45,6 +49,8 @@ public static class RoleSettingMenuPatches
RoleOptionSettings.ForEach(Object.Destroy); RoleOptionSettings.ForEach(Object.Destroy);
Headers.Clear(); Headers.Clear();
RoleOptionSettings.Clear(); RoleOptionSettings.Clear();
CurrentRole = null;
CurrentRoleOptions = null;
__instance.roleChances = new Il2CppSystem.Collections.Generic.List<RoleOptionSetting>(); __instance.roleChances = new Il2CppSystem.Collections.Generic.List<RoleOptionSetting>();
__instance.advancedSettingChildren = new Il2CppSystem.Collections.Generic.List<OptionBehaviour>(); __instance.advancedSettingChildren = new Il2CppSystem.Collections.Generic.List<OptionBehaviour>();
@@ -211,9 +217,10 @@ public static class RoleSettingMenuPatches
headerBtn.OnClick.AddListener( headerBtn.OnClick.AddListener(
(UnityAction)(() => (UnityAction)(() =>
{ {
if (RoleGroupHidden.TryGetValue(group, out var value)) RolePositions[GameSettingMenuPatches.SelectedModIdx] = __instance.scrollBar.Inner.localPosition;
if (RoleGroupHidden.TryGetValue(group, out var groupHidden))
{ {
RoleGroupHidden[group] = !value; RoleGroupHidden[group] = !groupHidden;
} }
foreach (var header in Headers) foreach (var header in Headers)
{ {
@@ -261,9 +268,31 @@ public static class RoleSettingMenuPatches
[HarmonyPrefix] [HarmonyPrefix]
[HarmonyPatch(nameof(RolesSettingsMenu.Update))] [HarmonyPatch(nameof(RolesSettingsMenu.Update))]
public static bool UpdatePatch() public static bool UpdatePatch(RolesSettingsMenu __instance)
{ {
return GameSettingMenuPatches.SelectedModIdx == 0; if (GameSettingMenuPatches.SelectedModIdx == 0) return true;
if (CurrentRole == null || CurrentRoleOptions == null) return false;
var hasImage = CurrentRole.RoleScreenshot != null;
var num = hasImage ? -0.872f : -1;
foreach (var opt in CurrentRoleOptions)
{
if (opt.OptionBehaviour == null) continue;
if (!opt.Visible.Invoke())
{
opt.OptionBehaviour.gameObject.SetActive(false);
continue;
}
opt.OptionBehaviour.transform.localPosition = new Vector3(hasImage ? 2.17f : 1.1f, num, -2f);
opt.OptionBehaviour.gameObject.SetActive(true);
num += -0.45f;
}
__instance.scrollBar.SetYBoundsMax(-num - 3);
return false;
} }
[HarmonyPostfix] [HarmonyPostfix]
@@ -275,6 +304,8 @@ public static class RoleSettingMenuPatches
return; return;
} }
CurrentRole = null;
CurrentRoleOptions = null;
__instance.scrollBar.SetScrollBounds(__instance); __instance.scrollBar.SetScrollBounds(__instance);
} }
@@ -327,26 +358,26 @@ public static class RoleSettingMenuPatches
optBehaviour.gameObject.DestroyImmediate(); optBehaviour.gameObject.DestroyImmediate();
} }
CurrentRole = role;
__instance.advancedSettingChildren.Clear(); __instance.advancedSettingChildren.Clear();
var hasImage = role.RoleScreenshot != null;
var num = hasImage ? -0.872f : -1;
// TODO: create sub groups under the role settings. // TODO: create sub groups under the role settings.
var filteredOptions = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups var filteredOptions = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
.Where(x=> x.OptionableType == role.GetType()) .Where(x => x.GroupVisible() && x.OptionableType == role.GetType())
.SelectMany(x=>x.Options) .SelectMany(x => x.Options)
.ToList() ?? []; .ToList() ?? [];
CurrentRoleOptions = filteredOptions;
foreach (var option in filteredOptions) foreach (var option in filteredOptions)
{ {
var newOpt = option.CreateOption( var newOpt = option.CreateOption(
__instance.checkboxOrigin, __instance.checkboxOrigin,
__instance.numberOptionOrigin, __instance.numberOptionOrigin,
__instance.stringOptionOrigin, __instance.stringOptionOrigin,
GameSettingMenu.Instance.GameSettingsTab.playerOptionOrigin,
__instance.AdvancedRolesSettings.transform); __instance.AdvancedRolesSettings.transform);
newOpt.transform.localPosition = new Vector3(hasImage ? 2.17f : 1.1f, num, -2f);
newOpt.SetClickMask(__instance.ButtonClickMask); newOpt.SetClickMask(__instance.ButtonClickMask);
SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren<SpriteRenderer>(true); SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren<SpriteRenderer>(true);
@@ -364,11 +395,10 @@ public static class RoleSettingMenuPatches
newOpt.LabelBackground.enabled = false; newOpt.LabelBackground.enabled = false;
__instance.advancedSettingChildren.Add(newOpt); __instance.advancedSettingChildren.Add(newOpt);
num += -0.45f; newOpt.gameObject.SetActive(false);
newOpt.Initialize(); newOpt.Initialize();
} }
__instance.scrollBar.SetYBoundsMax(-num - 3);
__instance.scrollBar.ScrollToTop(); __instance.scrollBar.ScrollToTop();
} }
@@ -492,6 +522,10 @@ public static class RoleSettingMenuPatches
roleOptionSetting.labelSprite.color = customRole.OptionsMenuColor; roleOptionSetting.labelSprite.color = customRole.OptionsMenuColor;
roleOptionSetting.OnValueChanged = new Action<OptionBehaviour>(ValueChanged); roleOptionSetting.OnValueChanged = new Action<OptionBehaviour>(ValueChanged);
roleOptionSetting.SetClickMask(__instance.ButtonClickMask); roleOptionSetting.SetClickMask(__instance.ButtonClickMask);
roleOptionSetting.ChanceMinusBtn.SetInteractable(true);
roleOptionSetting.ChancePlusBtn.SetInteractable(true);
roleOptionSetting.CountMinusBtn.SetInteractable(true);
roleOptionSetting.CountPlusBtn.SetInteractable(true);
__instance.roleChances.Add(roleOptionSetting); __instance.roleChances.Add(roleOptionSetting);
roleOptionSetting.titleText.transform.localPosition = new Vector3(-0.5376f, -0.2923f, 0f); roleOptionSetting.titleText.transform.localPosition = new Vector3(-0.5376f, -0.2923f, 0f);
@@ -500,7 +534,7 @@ public static class RoleSettingMenuPatches
if (GameSettingMenuPatches.SelectedMod is null || if (GameSettingMenuPatches.SelectedMod is null ||
GameSettingMenuPatches.SelectedMod.InternalOptionGroups GameSettingMenuPatches.SelectedMod.InternalOptionGroups
.Exists(x => x.OptionableType == role.GetType())) .Exists(x => x.GroupVisible() && x.OptionableType == role.GetType() && x.Options.Any(y => y.Visible())))
{ {
var newButton = Object.Instantiate(roleOptionSetting.buttons[0], roleOptionSetting.transform); var newButton = Object.Instantiate(roleOptionSetting.buttons[0], roleOptionSetting.transform);
newButton.name = "ConfigButton"; newButton.name = "ConfigButton";
+8
View File
@@ -7,6 +7,7 @@ using MiraAPI.Hud;
using MiraAPI.Modifiers; using MiraAPI.Modifiers;
using MiraAPI.Utilities; using MiraAPI.Utilities;
using MiraAPI.Voting; using MiraAPI.Voting;
using Reactor.Utilities;
using Reactor.Utilities.Extensions; using Reactor.Utilities.Extensions;
namespace MiraAPI.Patches; namespace MiraAPI.Patches;
@@ -94,6 +95,8 @@ internal static class PlayerControlPatches
continue; continue;
} }
try
{
if (!button.Enabled(__instance.Data?.Role)) if (!button.Enabled(__instance.Data?.Role))
{ {
continue; continue;
@@ -101,5 +104,10 @@ internal static class PlayerControlPatches
button.FixedUpdateHandler(__instance); button.FixedUpdateHandler(__instance);
} }
catch (System.Exception e)
{
Logger<MiraApiPlugin>.Error($"Failed to update custom button {button.GetType().Name}: {e}");
}
}
} }
} }
+52
View File
@@ -0,0 +1,52 @@
using HarmonyLib;
using UnityEngine;
using UnityEngine.Events;
namespace MiraAPI.Patches;
[HarmonyPatch(typeof(ServerDropdown), nameof(ServerDropdown.FillServerOptions))]
public static class ServerDropdownPatch
{
public static bool Prefix(ServerDropdown __instance)
{
var num = 0;
__instance.background.size = new Vector2(8.4f, 4.8f);
foreach (var regionInfo in DestroyableSingleton<ServerManager>.Instance.AvailableRegions)
{
if (DestroyableSingleton<ServerManager>.Instance.CurrentRegion.Equals(regionInfo))
{
__instance.defaultButtonSelected = __instance.firstOption;
__instance.firstOption.ChangeButtonText(
DestroyableSingleton<TranslationController>.Instance.GetStringWithDefault(
regionInfo.TranslateName,
regionInfo.Name));
}
else
{
var region = regionInfo;
var serverListButton = __instance.ButtonPool.Get<ServerListButton>();
var x = num % 2 == 0 ? -2 : 2;
var y = -0.55f * (num / 2);
serverListButton.transform.localPosition = new Vector3(x, __instance.y_posButton + y, -1f);
serverListButton.transform.localScale = Vector3.one;
serverListButton.Text.text =
DestroyableSingleton<TranslationController>.Instance.GetStringWithDefault(
regionInfo.TranslateName,
regionInfo.Name);
serverListButton.Text.ForceMeshUpdate();
serverListButton.Button.OnClick.RemoveAllListeners();
serverListButton.Button.OnClick.AddListener((UnityAction)(() => { __instance.ChooseOption(region); }));
__instance.controllerSelectable.Add(serverListButton.Button);
__instance.background.transform.localPosition = new Vector3(
0f,
__instance.initialYPos + (-0.3f * (num / 2)),
0f);
__instance.background.size = new Vector2(__instance.background.size.x, 1.2f + (0.6f * (num / 2)));
num++;
}
}
return false;
}
}
-3
View File
@@ -96,6 +96,3 @@ public class SavePresetPopup(nint cppPtr) : Minigame(cppPtr)
popup.Begin(null); popup.Begin(null);
} }
} }
+16 -1
View File
@@ -4,6 +4,7 @@ using System.Linq;
using System.Text; using System.Text;
using AmongUs.GameOptions; using AmongUs.GameOptions;
using MiraAPI.Utilities.Assets; using MiraAPI.Utilities.Assets;
using Reactor.Utilities;
using UnityEngine; using UnityEngine;
namespace MiraAPI.Roles; namespace MiraAPI.Roles;
@@ -56,7 +57,21 @@ public static class CustomRoleUtils
public static LoadableAsset<AudioClip>? GetIntroSound(RoleTypes roleType) public static LoadableAsset<AudioClip>? GetIntroSound(RoleTypes roleType)
{ {
var role = RoleManager.Instance.AllRoles.FirstOrDefault(role => role.Role == roleType); var role = RoleManager.Instance.AllRoles.FirstOrDefault(role => role.Role == roleType);
if (role is ICustomRole customRole) return customRole.Configuration.IntroSound; if (role is ICustomRole customRole)
{
return customRole.Configuration.IntroSound;
}
return new PreloadedAsset<AudioClip>(role!.IntroSound); return new PreloadedAsset<AudioClip>(role!.IntroSound);
} }
/// <summary>
/// Determines if a role is a custom role or not.
/// </summary>
/// <param name="role">The RoleBehaviour to check.</param>
/// <returns>True if the role is a custom role, false otherwise.</returns>
public static bool IsCustomRole(this RoleBehaviour role)
{
return CustomRoleManager.CustomRoles.ContainsKey((ushort)role.Role);
}
} }
+18
View File
@@ -154,6 +154,24 @@ public static class Extensions
return TutorialManager.InstanceExists || AmongUsClient.Instance.HostId == playerControl.OwnerId; return TutorialManager.InstanceExists || AmongUsClient.Instance.HostId == playerControl.OwnerId;
} }
/// <summary>
/// Used to convert a System.Collections.Generic.List to Il2cppSystem.
/// </summary>
/// <param name="systemList">The list.</param>
/// <typeparam name="T">The type in the list.</typeparam>
/// <returns>The converted list.</returns>
public static Il2CppSystem.Collections.Generic.List<T> ToIl2CppList<T>(this List<T> systemList)
{
var il2cppList = new Il2CppSystem.Collections.Generic.List<T>();
foreach (var item in systemList)
{
il2cppList.Add(item);
}
return il2cppList;
}
/// <summary> /// <summary>
/// Determines if a float is an integer. /// Determines if a float is an integer.
/// </summary> /// </summary>
+1 -1
View File
@@ -19,7 +19,7 @@ A thorough, but simple, Among Us modding API and utility library that covers:
Mira API strives to be comprehensive, yet straightforward, while also using as many base game elements as possible. Mira API strives to be comprehensive, yet straightforward, while also using as many base game elements as possible.
The result is a less intrusive, better modding API that covers general use cases. The result is a less intrusive, better modding API that covers general use cases.
**Join the [Discord](https://discord.gg/all-of-us-launchpad-794950428756410429) for support and to stay updated on the latest releases** **Join the [Discord](https://discord.gg/FYYqJU2bvp) for support and to stay updated on the latest releases**
# Usage # Usage