Merge pull request #52 from All-Of-Us-Mods/dev

mira api 0.2.1
This commit is contained in:
XtraCube authored and GitHub committed 2025-08-17 16:39:35 -04:00
commit 08ca995eb1
60 files changed
+1169 -132

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<VersionPrefix>0.2.0</VersionPrefix>
<VersionPrefix>0.2.1</VersionPrefix>
<VersionSuffix>dev</VersionSuffix>
<Authors>All Of Us, XtraCube, Angxl</Authors>
@@ -6,6 +6,7 @@ using MiraAPI.Hud;
using MiraAPI.Modifiers;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using Rewired;
using UnityEngine;
namespace MiraAPI.Example.Buttons.Freezer;
@@ -19,6 +20,8 @@ public class FreezeButton : CustomActionButton<PlayerControl>
public override int MaxUses => (int)OptionGroupSingleton<FreezerRoleSettings>.Instance.FreezeUses;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
public override KeyboardKeyCode Defaultkeybind => KeyboardKeyCode.T;
public override ModifierKey Modifier1 => ModifierKey.Control;
protected override void OnClick()
{
+4
View File
@@ -2,6 +2,7 @@
using MiraAPI.Hud;
using MiraAPI.Modifiers;
using MiraAPI.Utilities.Assets;
using Rewired;
using UnityEngine;
namespace MiraAPI.Example.Buttons;
@@ -15,6 +16,9 @@ public class MeetingButton : CustomActionButton
public override int MaxUses => 3;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
public override KeyboardKeyCode Defaultkeybind => KeyboardKeyCode.P;
public override ModifierKey Modifier1 => ModifierKey.Control;
public override ModifierKey Modifier2 => ModifierKey.Shift;
public override bool Enabled(RoleBehaviour? role)
{
@@ -3,6 +3,7 @@ using MiraAPI.Example.Roles;
using MiraAPI.GameEnd;
using MiraAPI.Hud;
using MiraAPI.Utilities.Assets;
using Rewired;
using UnityEngine;
namespace MiraAPI.Example.Buttons;
@@ -12,6 +13,7 @@ public class NeutralKillerButton : CustomActionButton
public override string Name => "Win Game";
public override float Cooldown => 0f;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
public override KeyboardKeyCode Defaultkeybind => KeyboardKeyCode.K;
protected override void OnClick()
{
CustomGameOver.Trigger<NeutralKillerGameOver>([PlayerControl.LocalPlayer.Data]);
@@ -5,6 +5,7 @@ using MiraAPI.GameOptions;
using MiraAPI.Hud;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities;
using Rewired;
using UnityEngine;
namespace MiraAPI.Example.Buttons.Teleporter;
@@ -21,7 +22,7 @@ public class TeleportButton : CustomActionButton
public override LoadableAsset<Sprite> Sprite => ExampleAssets.TeleportButton;
public override Color TextOutlineColor => new Color32(221, 176, 152, 255);
public static bool IsZoom { get; private set; }
public override KeyboardKeyCode Defaultkeybind => KeyboardKeyCode.M;
public override bool Enabled(RoleBehaviour? role)
{
return role is TeleporterRole;
+4 -1
View File
@@ -6,7 +6,10 @@ namespace MiraAPI.Example;
[RegisterCustomColors]
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));
@@ -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 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 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)
{
/// <summary>
/// Gets the main color.
/// Gets or sets the main color.
/// </summary>
public Color32 MainColor { get; } = mainColor;
public Color32 MainColor { get; set; } = mainColor;
/// <summary>
/// Gets the shadow color.
/// Gets or sets the shadow color.
/// </summary>
public Color32 ShadowColor { get; } = shadowColor;
public Color32 ShadowColor { get; set; } = shadowColor;
/// <summary>
/// Gets the name of the color.
/// Gets or sets the name of the color.
/// </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 />
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,
}
@@ -0,0 +1,21 @@
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// Event invoked on IntroCutscene.OnDestroy and ExileController.WrapUp to determine if it should run the round event or not.
/// </summary>
public class BeforeRoundStartEvent : MiraCancelableEvent
{
/// <summary>
/// Gets a value indicating whether the event was triggered by the IntroCutscene or ExileController.
/// </summary>
public bool TriggeredByIntro { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BeforeRoundStartEvent"/> class.
/// </summary>
/// <param name="triggeredByIntro">Whether the event was triggered by the intro or not.</param>
public BeforeRoundStartEvent(bool triggeredByIntro)
{
TriggeredByIntro = triggeredByIntro;
}
}
@@ -1,7 +1,7 @@
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// Start Round event, invoked on IntroCutscene.OnDestroy and ExileController.WrapUp.
/// Start Round event, invoked on IntroCutscene.OnDestroy and ExileController.WrapUp if the event isn't cancelled.
/// </summary>
public class RoundStartEvent : MiraEvent
{
@@ -0,0 +1,44 @@
using System;
using MiraAPI.Utilities;
using MiraAPI.Voting;
namespace MiraAPI.Events.Vanilla.Meeting.Voting;
/// <summary>
/// Invoked before the dummy selects their vote. If canceled, the dummy won't vote.
/// </summary>
public class DummyVoteEvent : MiraCancelableEvent
{
/// <summary>
/// Gets the dummy that needs to vote.
/// </summary>
public DummyBehaviour Dummy { get; }
/// <summary>
/// Gets the instance of the dummy's vote data.
/// </summary>
public PlayerVoteData VoteData { get; }
/// <summary>
/// Gets or sets a value indicating whether the dummy can vote to skip the meeting.
/// </summary>
public bool CanSkip { get; set; }
/// <summary>
/// Gets or sets a predicate for validating whether the dummy can vote on a player.
/// By default, excludes dead and disconnected players.
/// </summary>
public Predicate<PlayerControl> PlayerIsValid { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="DummyVoteEvent"/> class.
/// </summary>
/// <param name="dummy">The voting dummy.</param>
public DummyVoteEvent(DummyBehaviour dummy)
{
Dummy = dummy;
VoteData = dummy.myPlayer.GetVoteData();
CanSkip = true;
PlayerIsValid = x => !x.Data.IsDead && !x.Data.Disconnected;
}
}
+2 -1
View File
@@ -62,9 +62,10 @@ public interface IModdedOption
/// <param name="toggleOpt">The ToggleOption template.</param>
/// <param name="numberOpt">The NumberOption 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>
/// <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>
/// Gets the value as a float.
@@ -6,6 +6,7 @@ using System.Reflection;
using BepInEx.Configuration;
using HarmonyLib;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.GameOptions.OptionTypes;
using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities;
@@ -22,6 +23,7 @@ public static class ModdedOptionsManager
private static readonly Dictionary<PropertyInfo, ModdedOptionAttribute> OptionAttributes = [];
private static readonly Dictionary<Type, AbstractOptionGroup> TypeToGroup = [];
internal static readonly Dictionary<OptionBehaviour, ModdedPlayerOption> CreatedPlayerOptions = [];
internal static readonly Dictionary<uint, IModdedOption> ModdedOptions = [];
internal static readonly List<AbstractOptionGroup> Groups = [];
@@ -44,7 +44,7 @@ public class ModdedEnumOption : ModdedOption<int>
}
/// <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);
@@ -135,7 +135,7 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
}
/// <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);
@@ -93,6 +93,7 @@ public class ModdedNumberOption : ModdedOption<float>
ToggleOption toggleOpt,
NumberOption numberOpt,
StringOption stringOpt,
PlayerOption playerOpt,
Transform 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;
_parentMod = value;
var entry = _parentMod.GetConfigFile().Bind(ConfigDefinition, DefaultValue);
Value = entry.Value;
}
@@ -177,6 +178,7 @@ public abstract class ModdedOption<T> : IModdedOption
ToggleOption toggleOpt,
NumberOption numberOpt,
StringOption stringOpt,
PlayerOption playerOpt,
Transform container);
/// <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 />
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);
+23
View File
@@ -5,6 +5,7 @@ using MiraAPI.Events.Mira;
using MiraAPI.Patches;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using Rewired;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
@@ -52,6 +53,28 @@ public abstract class CustomActionButton
/// </summary>
public virtual int MaxUses => 0;
/// <summary>
/// Gets or sets the current key binding for this button.
/// </summary>
public abstract KeyboardKeyCode Defaultkeybind { get; }
/// <summary>
/// The first optional modifier key (e.g., Control, Shift, Alt) that must be held with the main key to activate the button.
/// </summary>
public virtual ModifierKey Modifier1 => ModifierKey.None;
/// <summary>
/// The second optional modifier key.
/// Set to <see cref="ModifierKey.None"/> if unused.
/// </summary>
public virtual ModifierKey Modifier2 => ModifierKey.None;
/// <summary>
/// The third optional modifier key.
/// Set to <see cref="ModifierKey.None"/> if unused.
/// </summary>
public virtual ModifierKey Modifier3 => ModifierKey.None;
/// <summary>
/// Gets the button's text outline color.
/// </summary>
+116
View File
@@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using Rewired;
namespace MiraAPI.Keybinds
{
/// <summary>
/// Lets you register and manage mod keybinds with conflict checks and rebinding support.
/// </summary>
public static class KeybindManager
{
/// <summary>
/// Stores all info for a registered keybind.
/// </summary>
public class KeybindEntry
{
/// <summary>Unique identifier for this keybind.</summary>
public string Id;
/// <summary>Text shown in the Among Us rebinding menu to describe this keybind.</summary>
public string Description;
/// <summary>
/// The method that runs when this keybind is pressed.
/// </summary>
public Action Handler;
/// <summary>
/// The key currently assigned to this keybind.
/// </summary>
public KeyboardKeyCode Key;
/// <summary>
/// If true, this keybind will be checked for conflicts with other exclusive keybinds using the same key.
/// </summary>
public bool Exclusive;
/// <summary>
/// The first optional modifier key (e.g., Control, Shift, Alt) that must be held with the main key.
/// </summary>
public ModifierKey Modifier1 = ModifierKey.None;
/// <summary>
/// The second optional modifier key, if needed. Set to <c>ModifierKey.None</c> if unused.
/// </summary>
public ModifierKey Modifier2 = ModifierKey.None;
/// <summary>
/// The third optional modifier key, if needed. Set to <c>ModifierKey.None</c> if unused.
/// </summary>
public ModifierKey Modifier3 = ModifierKey.None;
}
private static readonly List<KeybindEntry> Registered = new();
/// <summary>
/// Registers a new mod keybind.
/// </summary>
/// <param name="id">Unique identifier for the keybind.</param>
/// <param name="description">Description to display in UI.</param>
/// <param name="key">Default key to use if no rebind exists.</param>
/// <param name="handler">Method to invoke when the keybind is pressed.</param>
/// <param name="exclusive">If true, conflicts with other exclusive keybinds on the same key.</param>
/// <param name="modifier1">Optional first modifier key (e.g., <c>Control</c>, <c>Shift</c>, <c>Alt</c>) that must be held with the main key.</param>
/// <param name="modifier2">Optional second modifier key. Use <c>ModifierKey.None</c> if unused.</param>
/// <param name="modifier3">Optional third modifier key. Use <c>ModifierKey.None</c> if unused.</param>
/// <returns> Returns the key that was registered for this keybind. </returns>
public static KeyboardKeyCode Register(string id, string description, KeyboardKeyCode key, Action handler, bool exclusive = true, ModifierKey modifier1 = ModifierKey.None, ModifierKey modifier2 = ModifierKey.None, ModifierKey modifier3 = ModifierKey.None)
{
Registered.Add(new KeybindEntry
{
Id = id,
Description = description,
Key = key,
Handler = handler,
Exclusive = exclusive,
Modifier1 = modifier1,
Modifier2 = modifier2,
Modifier3 = modifier3,
});
return key;
}
/// <summary>
/// Returns all conflicts where exclusive keybinds use the same key.
/// </summary>
/// <returns>A list of pairs of keybinds that conflict.</returns>
public static List<(KeybindEntry, KeybindEntry)> GetConflicts()
{
var conflicts = new List<(KeybindEntry, KeybindEntry)>();
for (int i = 0; i < Registered.Count; i++)
{
for (int j = i + 1; j < Registered.Count; j++)
{
var a = Registered[i];
var b = Registered[j];
if (a.Key == b.Key && a.Exclusive && b.Exclusive)
{
conflicts.Add((a, b));
}
}
}
return conflicts;
}
/// <summary>
/// Returns all currently registered keybind entries.
/// </summary>
/// <returns>A list of all registered keybind entries.</returns>
public static List<KeybindEntry> GetEntries()
{
return Registered;
}
}
}
+23
View File
@@ -0,0 +1,23 @@
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using MiraAPI.PluginLoading;
using Reactor;
using Reactor.Networking;
using Reactor.Networking.Attributes;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI;
/// <summary>
/// Mira API Config File Handler
/// </summary>
public class MiraApiConfig(ConfigFile config)
{
/// <summary>
/// Gets whether the modifiers hud should be on the left side of the screen (under roles/task tab). Recommended for streamers.
/// </summary>
public ConfigEntry<bool> ModifiersHudLeftSide { get; private set; } = config.Bind("Displays", "Show Modifiers HUD on Left Side", false);
}
+5
View File
@@ -30,6 +30,10 @@ public partial class MiraApiPlugin : BasePlugin
/// </summary>
public static Color DefaultHeaderColor { get; } = new Color32(77, 77, 77, 255);
/// <summary>
/// Gets the Mira Api Config Handler.
/// </summary>
public MiraApiConfig? MiraConfig { get; private set; }
private static MiraPluginManager? PluginManager { get; set; }
internal Harmony Harmony { get; } = new(Id);
@@ -40,6 +44,7 @@ public partial class MiraApiPlugin : BasePlugin
ReactorCredits.Register("Mira API", Version, true, ReactorCredits.AlwaysShow);
MiraConfig = new MiraApiConfig(Config);
PluginManager = new MiraPluginManager();
PluginManager.Initialize();
}
+5
View File
@@ -64,6 +64,11 @@ public abstract class BaseModifier : IOptionable
/// </summary>
public virtual bool ShowInFreeplay => false;
/// <summary>
/// Gets a value indicating the color that should be used for the modifier within freeplay.
/// </summary>
public virtual Color FreeplayFileColor => Color.gray;
/// <summary>
/// Gets a value indicating whether the modifier is unique. If true, the player can only have one instance of this modifier.
/// </summary>
+13 -1
View File
@@ -6,6 +6,7 @@ using System.Linq;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.Modifiers.ModifierDisplay;
using MiraAPI.Modifiers.Types;
using MiraAPI.Patches.Roles;
using Reactor.Utilities;
using Reactor.Utilities.Attributes;
using UnityEngine;
@@ -126,7 +127,18 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
if (_player.AmOwner && ModifierDisplay && HudManager.InstanceExists)
{
ModifierDisplay!.gameObject.SetActive(MeetingHud.Instance || HudManager.Instance.TaskPanel.isActiveAndEnabled);
var taskPanelOpen = HudManager.Instance.TaskPanel.open;
var roleTabOpen = HudManagerPatches.RoleTab != null && HudManagerPatches.RoleTab.open;
var leftSideHud = PluginSingleton<MiraApiPlugin>.Instance.MiraConfig!.ModifiersHudLeftSide.Value;
if (leftSideHud && (taskPanelOpen || roleTabOpen))
{
ModifierDisplay!.gameObject.SetActive(false);
}
else
{
ModifierDisplay!.gameObject.SetActive(MeetingHud.Instance || HudManager.Instance.TaskPanel.isActiveAndEnabled);
}
}
}
@@ -1,9 +1,12 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using HarmonyLib;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.Patches.Roles;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities;
using Reactor.Utilities.Attributes;
using Reactor.Utilities.Extensions;
using TMPro;
@@ -94,6 +97,16 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
IsOpen = false;
_children.gameObject.SetActive(false);
if (PluginSingleton<MiraApiPlugin>.Instance.MiraConfig!.ModifiersHudLeftSide.Value)
{
var aspect = GetComponent<AspectPosition>();
_toggleButton.transform.localPosition = new Vector3(-1f, 2.7f, 0f);
_children.transform.localPosition = new Vector3(-0.2f, -0.05f, 0f);
aspect.Alignment = AspectPosition.EdgeAlignments.LeftTop;
aspect.DistanceFromEdge = new Vector3(1.8f, 2.55f, -20f);
aspect.AdjustPosition();
}
}
/// <summary>
@@ -109,7 +122,16 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
/// </summary>
public void ToggleTab()
{
IsOpen = !IsOpen;
SetOpened(!IsOpen);
}
/// <summary>
/// Use this to set the visibility of the modifiers tab.
/// </summary>
/// <param name="val">Whether it should be visible or not.</param>
public void SetOpened(bool val)
{
IsOpen = val;
_children.gameObject.SetActive(IsOpen);
RefreshModifiers();
}
@@ -118,7 +140,6 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
{
_modifiers.Remove(component.Modifier!);
component.gameObject.DestroyImmediate();
RefreshModifiers();
}
@@ -194,13 +215,7 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
_toggleBtnText.text = $"Modifiers ({filteredModifiers.Count})";
if (filteredModifiers.Count == 0)
{
_toggleButton.gameObject.SetActive(false);
return;
}
_toggleButton.gameObject.SetActive(true);
_toggleButton.gameObject.SetActive(filteredModifiers.Count != 0);
foreach (var mod in modifiersToAdd)
{
+10
View File
@@ -5,6 +5,7 @@ using System.Runtime.Serialization;
using MiraAPI.Modifiers.Types;
using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using Reactor.Utilities;
using Reactor.Utilities.Extensions;
using Random = System.Random;
@@ -21,6 +22,13 @@ public static class ModifierManager
/// </summary>
public static bool MiraAssignsModifiers { get; set; } = true;
/// <summary>
/// Gets the list of all registered modifiers.
/// </summary>
public static IReadOnlyList<BaseModifier> Modifiers { get; internal set; } = [];
internal static List<BaseModifier> InternalModifiers { get; } = [];
private static readonly Dictionary<uint, Type> IdToTypeModifierMap = [];
private static readonly Dictionary<Type, uint> TypeToIdModifierMap = [];
private static readonly Dictionary<int, List<uint>> PrioritiesToIdsMap = [];
@@ -80,6 +88,7 @@ public static class ModifierManager
}
info.InternalModifiers.Add(modifier);
InternalModifiers.Add(modifier);
if (modifier is not GameModifier gameModifier)
{
@@ -117,6 +126,7 @@ public static class ModifierManager
.Select(x => Activator.CreateInstance(x.Value) as GameModifier)
.OfType<GameModifier>()
.Where(x => x.GetAmountPerGame() > 0 && x.GetAssignmentChance() > 0)
.Shuffle()
.OrderByDescending(x => x.Priority())
.ToArray();
+35
View File
@@ -0,0 +1,35 @@
using HarmonyLib;
using UnityEngine;
namespace MiraAPI.Patches;
#pragma warning disable SA1629
/// <summary>
/// Allows players to paste text into chat.
/// Source: https://github.com/CallOfCreator/NewMod/blob/main/NewMod/Patches/ClipboardPatch.cs
/// </summary>
#pragma warning restore SA1629
[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);
}
}
+7 -5
View File
@@ -12,14 +12,16 @@ public static class CanUsePatches
public static IEnumerable<MethodBase> TargetMethods()
{
yield return AccessTools.Method(typeof(Console), nameof(Console.CanUse));
yield return AccessTools.Method(typeof(MapConsole), nameof(MapConsole.CanUse));
yield return AccessTools.Method(typeof(SystemConsole), nameof(SystemConsole.CanUse));
yield return AccessTools.Method(typeof(Ladder), nameof(Ladder.CanUse));
yield return AccessTools.Method(typeof(PlatformConsole), nameof(PlatformConsole.CanUse));
yield return AccessTools.Method(typeof(OpenDoorConsole), nameof(OpenDoorConsole.CanUse));
yield return AccessTools.Method(typeof(DeconControl), nameof(DeconControl.CanUse));
yield return AccessTools.Method(typeof(DoorConsole), nameof(DoorConsole.CanUse));
yield return AccessTools.Method(typeof(Ladder), nameof(Ladder.CanUse));
yield return AccessTools.Method(typeof(MapConsole), nameof(MapConsole.CanUse));
yield return AccessTools.Method(typeof(OpenDoorConsole), nameof(OpenDoorConsole.CanUse));
yield return AccessTools.Method(typeof(OptionsConsole), nameof(OptionsConsole.CanUse));
yield return AccessTools.Method(typeof(PlatformConsole), nameof(PlatformConsole.CanUse));
yield return AccessTools.Method(typeof(SystemConsole), nameof(SystemConsole.CanUse));
yield return AccessTools.Method(typeof(ZiplineConsole), nameof(ZiplineConsole.CanUse));
// vent is not necessary as it has its own patch
}
[HarmonyPriority(Priority.Last)]
@@ -0,0 +1,17 @@
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
namespace MiraAPI.Patches.Events;
[HarmonyPatch(typeof(TutorialManager._RunTutorial_d__3), nameof(TutorialManager._RunTutorial_d__3.MoveNext))]
public static class FreeplayRoundStartPatch
{
public static void Postfix(TutorialManager._RunTutorial_d__3 __instance, ref bool __result)
{
if (!__result)
{
MiraEventManager.InvokeEvent(new RoundStartEvent(true));
}
}
}
@@ -9,7 +9,12 @@ internal static class SpawnInMinigamePatch
{
public static void Postfix()
{
var @event = new RoundStartEvent(false);
var @event = new BeforeRoundStartEvent(false);
MiraEventManager.InvokeEvent(@event);
if (@event.IsCancelled) return;
var @event2 = new RoundStartEvent(false);
MiraEventManager.InvokeEvent(@event2);
}
}
@@ -25,7 +25,12 @@ internal static class SubmergedSpawnGamePatch
public static void Postfix()
{
var @event = new RoundStartEvent(false);
var @event = new BeforeRoundStartEvent(false);
MiraEventManager.InvokeEvent(@event);
if (@event.IsCancelled) return;
var @event2 = new RoundStartEvent(false);
MiraEventManager.InvokeEvent(@event2);
}
}
@@ -22,15 +22,4 @@ internal static class TaskAddButtonPatches
return true;
}
[HarmonyPostfix]
[HarmonyPatch(nameof(TaskAddButton.Role), MethodType.Setter)]
public static void RoleGetterPatch(TaskAddButton __instance)
{
if (__instance.role is ICustomRole { Team: ModdedRoleTeams.Custom } customRole)
{
__instance.FileImage.color = customRole.IntroConfiguration?.IntroTeamColor ?? Color.gray;
}
__instance.RolloverHandler.OutColor = __instance.FileImage.color;
}
}
+23 -3
View File
@@ -10,6 +10,7 @@ using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using Object = UnityEngine.Object;
@@ -19,7 +20,6 @@ namespace MiraAPI.Patches.Freeplay;
internal static class TaskAdderPatches
{
private static Scroller _scroller = null!;
[HarmonyPostfix]
[HarmonyPatch(nameof(TaskAdderGame.Begin))]
public static void AddRolesFolder(TaskAdderGame __instance)
@@ -32,6 +32,11 @@ internal static class TaskAdderPatches
_scroller.allowY = true;
_scroller.Inner = inner.transform;
var scrollerHelper = __instance.TaskParent.gameObject.AddComponent<ManualScrollHelper>();
scrollerHelper.scroller = _scroller;
scrollerHelper.verticalAxis = RewiredConstsEnum.Action.TaskRVertical;
scrollerHelper.scrollSpeed = 10f;
GameObject hitbox = new("Hitbox")
{
layer = 5,
@@ -295,7 +300,7 @@ internal static class TaskAdderPatches
if (_scroller)
{
_scroller.CalculateAndSetYBounds(__instance.ActiveItems.Count, 6, 5, 1.4f);
_scroller.CalculateAndSetYBounds(__instance.ActiveItems.Count, 6, 4.5f, 1.65f);
_scroller.SetYBoundsMin(0.0f);
_scroller.ScrollToTop();
}
@@ -332,7 +337,8 @@ internal static class TaskAdderPatches
taskAddButton.SafePositionWorld = instance.SafePositionWorld;
taskAddButton.Text.text = modifier.ModifierName;
taskAddButton.Text.EnableMasking();
taskAddButton.FileImage.color = modifier.FreeplayFileColor;
taskAddButton.RolloverHandler.OutColor = modifier.FreeplayFileColor;
taskAddButton.Button.OnClick = new Button.ButtonClickedEvent();
taskAddButton.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() =>
{
@@ -384,6 +390,20 @@ internal static class TaskAdderPatches
instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3);
taskAddButton2.Role = roleBehaviour;
if (roleBehaviour is ICustomRole custom)
{
var customColor = custom.IntroConfiguration?.IntroTeamColor ?? Color.gray;
if (custom.Team is ModdedRoleTeams.Crewmate)
{
customColor = Palette.CrewmateBlue;
}
else if (custom.Team is ModdedRoleTeams.Impostor)
{
customColor = Palette.ImpostorRed;
}
taskAddButton2.FileImage.color = customColor;
taskAddButton2.RolloverHandler.OutColor = customColor;
}
if (taskAddButton2.Button == null)
{
continue;
+36
View File
@@ -0,0 +1,36 @@
using HarmonyLib;
using MiraAPI.Keybinds;
using UnityEngine;
namespace MiraAPI.Patches;
/// <summary>
/// Checks keybind conflicts in the lobby and disables secondary ones.
/// </summary>
[HarmonyPatch(typeof(GameStartManager))]
public static class GameStartManagerPatch
{
public static bool conflictShown = false;
[HarmonyPatch(nameof(GameStartManager.Start))]
[HarmonyPostfix]
public static void StartPostfix(GameStartManager __instance)
{
if (conflictShown) return;
var conflicts = KeybindManager.GetConflicts();
if (conflicts.Count == 0) return;
conflictShown = true;
var message = "The following keybinds are disabled due to conflicts:\n\n";
foreach (var (a, b) in conflicts)
{
message += $"• <color=#FF5555>{b.Description}</color> (conflicts with {a.Description})\n";
b.Handler = null;
}
HudManager.Instance.ShowPopUp(message.TrimEnd());
}
}
+12 -24
View File
@@ -1,37 +1,25 @@
using HarmonyLib;
using MiraAPI.Hud;
using Reactor.Utilities;
namespace MiraAPI.Patches.Hud;
/// <summary>
/// Reset button patches.
/// </summary>
[HarmonyPatch]
[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))]
[HarmonyPatch(typeof(ExileController), nameof(ExileController.ReEnableGameplay))]
public static class ButtonResetPatches
{
/// <summary>
/// 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()
public static void Postfix()
{
foreach (var customActionButton in CustomButtonManager.CustomButtons)
{
customActionButton.ResetCooldownAndOrEffect();
}
}
/// <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();
try
{
customActionButton.ResetCooldownAndOrEffect();
}
catch (System.Exception ex)
{
Logger<MiraApiPlugin>.Error($"Error resetting cooldown and effect for button {customActionButton.GetType().Name}: {ex}");
}
}
}
}
+44 -3
View File
@@ -1,6 +1,11 @@
using HarmonyLib;
using System;
using System.Collections;
using HarmonyLib;
using MiraAPI.Hud;
using MiraAPI.Keybinds;
using Reactor.Utilities;
using Reactor.Utilities.Extensions;
using Rewired;
using UnityEngine;
using Object = UnityEngine.Object;
@@ -77,7 +82,14 @@ public static class HudManagerPatches
continue;
}
button.CreateButton(location);
try
{
button.CreateButton(location);
}
catch (System.Exception e)
{
Logger<MiraApiPlugin>.Error($"Failed to create custom button {button.GetType().Name}: {e}");
}
}
gridArrange.Start();
@@ -105,7 +117,36 @@ public static class HudManagerPatches
foreach (var button in CustomButtonManager.CustomButtons)
{
button.SetActive(isActive, role);
try
{
button.SetActive(isActive, role);
}
catch (System.Exception e)
{
Logger<MiraApiPlugin>.Error($"Failed to set custom button {button.GetType().Name} active: {e}");
}
}
}
[HarmonyPatch(nameof(HudManager.Update))]
[HarmonyPostfix]
public static void StartPostfix()
{
if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return;
if (PlayerControl.LocalPlayer.Data.IsDead) return;
foreach (var entry in KeybindManager.GetEntries())
{
var player = ReInput.players.GetPlayer(0);
var keyboard = player.controllers.Keyboard;
if (player.GetButtonDown(entry.Id) &&
(entry.Modifier1 == ModifierKey.None || keyboard.GetModifierKey(entry.Modifier1)) &&
(entry.Modifier2 == ModifierKey.None || keyboard.GetModifierKey(entry.Modifier2)) &&
(entry.Modifier3 == ModifierKey.None || keyboard.GetModifierKey(entry.Modifier3)))
{
entry.Handler?.Invoke();
}
}
}
}
+5
View File
@@ -61,6 +61,11 @@ public static class IntroCutscenePatches
public static void GameBeginPatch(IntroCutscene __instance)
{
MiraEventManager.InvokeEvent(new IntroEndEvent(__instance));
var @event = new BeforeRoundStartEvent(true);
MiraEventManager.InvokeEvent(@event);
if (@event.IsCancelled) return;
MiraEventManager.InvokeEvent(new RoundStartEvent(true));
}
}
+20
View File
@@ -0,0 +1,20 @@
using HarmonyLib;
using MiraAPI.Keybinds;
using MiraAPI.Utilities;
using Rewired;
namespace MiraAPI.Patches.Keybinds
{
[HarmonyPatch(typeof(InputManager_Base), nameof(InputManager_Base.Awake))]
public static class KeybindMenuPatch
{
[HarmonyPrefix]
private static void StartPrefix(InputManager_Base __instance)
{
foreach (var entry in KeybindManager.GetEntries())
{
__instance.userData.RegisterModBind(entry.Id, entry.Description, entry.Key, modifier1: entry.Modifier1, modifier2: entry.Modifier2, modifier3: entry.Modifier3);
}
}
}
}
+2 -2
View File
@@ -84,7 +84,7 @@ public static class HatsTabPatches
text.gameObject.transform.localScale = Vector3.one;
text.GetComponent<TextTranslatorTMP>().Destroy();
text.EnableStencilMasking();
text.text = $"{groupName}\nPress Ctrl or Tab to cycle pages";
text.text = $"{groupName} ({currentPage + 1}/{sortedHats.Count})\nPress Ctrl or Tab to cycle pages";
text.alignment = TextAlignmentOptions.Center;
text.fontSize = 3f;
text.fontSizeMax = 3f;
@@ -114,7 +114,7 @@ public static class HatsTabPatches
yield return null;
}
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f;
__instance.SetScrollerBounds();
yield return new WaitForSeconds(0.01f);
}
__instance.currentHatIsEquipped = true;
+2 -2
View File
@@ -73,7 +73,7 @@ public static class NameplatesTabPatches
text.gameObject.transform.localScale = Vector3.one;
text.GetComponent<TextTranslatorTMP>().Destroy();
text.EnableStencilMasking();
text.text = $"{groupName}\nPress Ctrl or Tab to cycle pages";
text.text = $"{groupName} ({currentPage + 1}/{SortedNameplates.Count})\nPress Ctrl or Tab to cycle pages";
text.alignment = TextAlignmentOptions.Center;
text.fontSize = 3f;
text.fontSizeMax = 3f;
@@ -91,7 +91,7 @@ public static class NameplatesTabPatches
hatIndex += 1;
}
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f;
__instance.SetScrollerBounds();
__instance.currentNameplateIsEquipped = true;
}
+2 -2
View File
@@ -76,7 +76,7 @@ public static class SkinsTabPatches
text.gameObject.transform.localScale = Vector3.one;
text.GetComponent<TextTranslatorTMP>().Destroy();
text.EnableStencilMasking();
text.text = $"{groupName}\nPress Ctrl or Tab to cycle pages";
text.text = $"{groupName} ({currentPage + 1}/{sortedSkins.Count})\nPress Ctrl or Tab to cycle pages";
text.alignment = TextAlignmentOptions.Center;
text.fontSize = 3f;
text.fontSizeMax = 3f;
@@ -94,7 +94,7 @@ public static class SkinsTabPatches
hatIndex += 1;
}
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f;
__instance.SetScrollerBounds();
__instance.currentSkinIsEquipped = true;
}
+2 -2
View File
@@ -72,7 +72,7 @@ public static class VisorsTabPatches
text.gameObject.transform.localScale = Vector3.one;
text.GetComponent<TextTranslatorTMP>().Destroy();
text.EnableStencilMasking();
text.text = $"{groupName}\nPress Ctrl or Tab to cycle pages";
text.text = $"{groupName} ({currentPage + 1}/{SortedVisors.Count})\nPress Ctrl or Tab to cycle pages";
text.alignment = TextAlignmentOptions.Center;
text.fontSize = 3f;
text.fontSizeMax = 3f;
@@ -90,7 +90,7 @@ public static class VisorsTabPatches
hatIndex += 1;
}
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f;
__instance.SetScrollerBounds();
__instance.currentVisorIsEquipped = true;
}
@@ -220,6 +220,7 @@ internal static class GameOptionsMenuPatch
menu.checkboxOrigin,
menu.numberOptionOrigin,
menu.stringOptionOrigin,
menu.playerOptionOrigin,
menu.settingsContainer));
foreach (var newOpt in options)
@@ -288,7 +288,7 @@ internal static class GameSettingMenuPatches
{
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;
SelectedMod = null;
}
@@ -298,7 +298,7 @@ internal static class GameSettingMenuPatches
SelectedMod = MiraPluginManager.Instance.RegisteredPlugins[SelectedModIdx - 1];
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;
+20 -3
View File
@@ -1,7 +1,13 @@
using HarmonyLib;
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
using Il2CppSystem;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.OptionTypes;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using UnityEngine;
using Object = Il2CppSystem.Object;
namespace MiraAPI.Patches.Options;
@@ -22,7 +28,9 @@ public static class OptionsPatches
__instance.roleMaxCount = 1;
}
__instance.roleChance += 10;
var increment = Input.GetKey(KeyCode.LeftShift) ? 5 : 10;
__instance.roleChance += increment;
if (__instance.roleChance > 100)
{
__instance.roleChance = 0;
@@ -46,7 +54,9 @@ public static class OptionsPatches
__instance.roleMaxCount = 1;
}
__instance.roleChance -= 10;
var increment = Input.GetKey(KeyCode.LeftShift) ? 5 : 10;
__instance.roleChance -= increment;
if (__instance.roleChance < 0)
{
__instance.roleChance = 100;
@@ -107,6 +117,7 @@ public static class OptionsPatches
[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)
@@ -123,6 +134,11 @@ public static class OptionsPatches
stringOption.MinusBtn.SetInteractable(true);
stringOption.PlusBtn.SetInteractable(true);
}
if (__instance.TryCast<PlayerOption>() is { } playerOption)
{
playerOption.MinusBtn.SetInteractable(true);
playerOption.PlusBtn.SetInteractable(true);
}
return false;
}
@@ -172,6 +188,7 @@ public static class OptionsPatches
[HarmonyPrefix]
[HarmonyPatch(typeof(StringOption), nameof(StringOption.UpdateValue))]
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.UpdateValue))]
[HarmonyPatch(typeof(PlayerOption), nameof(PlayerOption.UpdateValue))]
public static bool UpdateValuePrefix(OptionBehaviour __instance)
{
return !__instance.IsCustom();
@@ -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 HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using MiraAPI.GameOptions;
using MiraAPI.Networking;
using MiraAPI.Roles;
using MiraAPI.Utilities;
@@ -30,6 +31,9 @@ public static class RoleSettingMenuPatches
private static float ScrollerNum { get; set; } = 0.522f;
private static RoleBehaviour? CurrentRole { get; set; }
private static List<IModdedOption>? CurrentRoleOptions { get; set; }
[HarmonyPostfix]
[HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))]
public static void SetQuotaTabPostfix(RolesSettingsMenu __instance)
@@ -45,6 +49,8 @@ public static class RoleSettingMenuPatches
RoleOptionSettings.ForEach(Object.Destroy);
Headers.Clear();
RoleOptionSettings.Clear();
CurrentRole = null;
CurrentRoleOptions = null;
__instance.roleChances = new Il2CppSystem.Collections.Generic.List<RoleOptionSetting>();
__instance.advancedSettingChildren = new Il2CppSystem.Collections.Generic.List<OptionBehaviour>();
@@ -262,9 +268,31 @@ public static class RoleSettingMenuPatches
[HarmonyPrefix]
[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]
@@ -276,6 +304,8 @@ public static class RoleSettingMenuPatches
return;
}
CurrentRole = null;
CurrentRoleOptions = null;
__instance.scrollBar.SetScrollBounds(__instance);
}
@@ -328,27 +358,26 @@ public static class RoleSettingMenuPatches
optBehaviour.gameObject.DestroyImmediate();
}
CurrentRole = role;
__instance.advancedSettingChildren.Clear();
var hasImage = role.RoleScreenshot != null;
var num = hasImage ? -0.872f : -1;
// TODO: create sub groups under the role settings.
var filteredOptions = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
.Where(x => x.GroupVisible() && x.OptionableType == role.GetType())
.SelectMany(x => x.Options)
.Where(x => x.Visible())
.ToList() ?? [];
CurrentRoleOptions = filteredOptions;
foreach (var option in filteredOptions)
{
var newOpt = option.CreateOption(
__instance.checkboxOrigin,
__instance.numberOptionOrigin,
__instance.stringOptionOrigin,
GameSettingMenu.Instance.GameSettingsTab.playerOptionOrigin,
__instance.AdvancedRolesSettings.transform);
newOpt.transform.localPosition = new Vector3(hasImage ? 2.17f : 1.1f, num, -2f);
newOpt.SetClickMask(__instance.ButtonClickMask);
SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren<SpriteRenderer>(true);
@@ -366,11 +395,10 @@ public static class RoleSettingMenuPatches
newOpt.LabelBackground.enabled = false;
__instance.advancedSettingChildren.Add(newOpt);
num += -0.45f;
newOpt.gameObject.SetActive(false);
newOpt.Initialize();
}
__instance.scrollBar.SetYBoundsMax(-num - 3);
__instance.scrollBar.ScrollToTop();
}
@@ -506,7 +534,7 @@ public static class RoleSettingMenuPatches
if (GameSettingMenuPatches.SelectedMod is null ||
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);
newButton.name = "ConfigButton";
+12 -4
View File
@@ -7,6 +7,7 @@ using MiraAPI.Hud;
using MiraAPI.Modifiers;
using MiraAPI.Utilities;
using MiraAPI.Voting;
using Reactor.Utilities;
using Reactor.Utilities.Extensions;
namespace MiraAPI.Patches;
@@ -94,12 +95,19 @@ internal static class PlayerControlPatches
continue;
}
if (!button.Enabled(__instance.Data?.Role))
try
{
continue;
}
if (!button.Enabled(__instance.Data?.Role))
{
continue;
}
button.FixedUpdateHandler(__instance);
button.FixedUpdateHandler(__instance);
}
catch (System.Exception e)
{
Logger<MiraApiPlugin>.Error($"Failed to update custom button {button.GetType().Name}: {e}");
}
}
}
}
+6 -1
View File
@@ -38,8 +38,13 @@ internal static class EjectionPatches
{
public static void Postfix()
{
var @event = new RoundStartEvent(false);
var @event = new BeforeRoundStartEvent(false);
MiraEventManager.InvokeEvent(@event);
if (@event.IsCancelled) return;
var @event2 = new RoundStartEvent(false);
MiraEventManager.InvokeEvent(@event2);
}
}
}
+5 -1
View File
@@ -23,7 +23,11 @@ public static class GameStartupPatch
_runOnce = true;
if (MiraPluginManager.Instance.QueuedRoleRegistrations.Count <= 0) return;
if (MiraPluginManager.Instance.QueuedRoleRegistrations.Count <= 0)
{
return;
}
foreach (var queue in MiraPluginManager.Instance.QueuedRoleRegistrations)
{
CustomRoleManager.RegisterRoleTypes(queue.Value, queue.Key);
+8 -8
View File
@@ -12,7 +12,7 @@ namespace MiraAPI.Patches.Roles;
public static class HudManagerPatches
{
// Custom role tab.
private static TaskPanelBehaviour? _roleTab;
public static TaskPanelBehaviour? RoleTab;
/// <summary>
/// Fixes Kill Button not showing for Neutral killing role.
@@ -38,9 +38,9 @@ public static class HudManagerPatches
__instance.SabotageButton.gameObject.SetActive(isActive && customRole.Configuration.CanUseSabotage);
}
if (_roleTab)
if (RoleTab)
{
_roleTab?.gameObject.SetActive(isActive);
RoleTab?.gameObject.SetActive(isActive);
}
}
@@ -62,16 +62,16 @@ public static class HudManagerPatches
if (role is ICustomRole { Configuration.RoleHintType: RoleHintType.RoleTab } customRole)
{
if (_roleTab == null)
if (RoleTab == null)
{
_roleTab = CustomRoleManager.CreateRoleTab(customRole);
RoleTab = CustomRoleManager.CreateRoleTab(customRole);
}
CustomRoleManager.UpdateRoleTab(_roleTab, customRole);
CustomRoleManager.UpdateRoleTab(RoleTab, customRole);
}
else if (_roleTab)
else if (RoleTab)
{
_roleTab?.gameObject.Destroy();
RoleTab?.gameObject.Destroy();
}
}
}
+8 -1
View File
@@ -1,6 +1,7 @@
using HarmonyLib;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
namespace MiraAPI.Patches;
@@ -14,6 +15,8 @@ public static class ServerDropdownPatch
foreach (var regionInfo in DestroyableSingleton<ServerManager>.Instance.AvailableRegions)
{
var findingGame = SceneManager.GetActiveScene().name is "FindAGame";
if (DestroyableSingleton<ServerManager>.Instance.CurrentRegion.Equals(regionInfo))
{
__instance.defaultButtonSelected = __instance.firstOption;
@@ -27,6 +30,10 @@ public static class ServerDropdownPatch
var region = regionInfo;
var serverListButton = __instance.ButtonPool.Get<ServerListButton>();
var x = num % 2 == 0 ? -2 : 2;
if (findingGame)
{
x += 2;
}
var y = -0.55f * (num / 2);
serverListButton.transform.localPosition = new Vector3(x, __instance.y_posButton + y, -1f);
serverListButton.transform.localScale = Vector3.one;
@@ -39,7 +46,7 @@ public static class ServerDropdownPatch
serverListButton.Button.OnClick.AddListener((UnityAction)(() => { __instance.ChooseOption(region); }));
__instance.controllerSelectable.Add(serverListButton.Button);
__instance.background.transform.localPosition = new Vector3(
0f,
findingGame ? 2f : 0f,
__instance.initialYPos + (-0.3f * (num / 2)),
0f);
__instance.background.size = new Vector2(__instance.background.size.x, 1.2f + (0.6f * (num / 2)));
+69 -6
View File
@@ -1,16 +1,79 @@
using HarmonyLib;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using BepInEx.Unity.IL2CPP.Utils.Collections;
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Meeting.Voting;
using MiraAPI.Utilities;
using MiraAPI.Voting;
using Reactor.Utilities;
using Reactor.Utilities.Extensions;
using UnityEngine;
namespace MiraAPI.Patches.Voting;
[HarmonyPatch(typeof(DummyBehaviour), nameof(DummyBehaviour.Update))]
internal static class DummyBehaviourPatch
[HarmonyPatch(typeof(DummyBehaviour))]
internal static class DummyBehaviourPatches
{
public static void Postfix(DummyBehaviour __instance)
[HarmonyPrefix]
[HarmonyPatch(nameof(DummyBehaviour.Update))]
public static bool DummyUpdatePatch(DummyBehaviour __instance)
{
if (MeetingHud.Instance && __instance.myPlayer.GetVoteData())
NetworkedPlayerInfo data = __instance.myPlayer.Data;
if (data == null || data.IsDead) return false;
if (MeetingHud.Instance)
{
__instance.voted = __instance.myPlayer.GetVoteData().VotesRemaining == 0;
if (!__instance.voted)
{
__instance.StartCoroutine(CoDoVote(__instance).WrapToIl2Cpp());
}
}
else
{
__instance.voted = false;
}
return false;
}
private static IEnumerator CoDoVote(DummyBehaviour dummy)
{
Logger<MiraApiPlugin>.Warning(dummy.voted);
dummy.voted = true;
yield return new WaitForSeconds(dummy.voteTime.Next());
var dummyVoteEvent = new DummyVoteEvent(dummy);
MiraEventManager.InvokeEvent(dummyVoteEvent);
if (dummyVoteEvent.IsCancelled)
{
dummy.voted = false;
yield break;
}
List<byte> potentialSuspects = new();
potentialSuspects.AddRange(PlayerControl.AllPlayerControls
.ToArray()
.Where(p => p != dummy.myPlayer)
.Where(p => dummyVoteEvent.PlayerIsValid(p))
.Select(p => p.PlayerId));
if (dummyVoteEvent.CanSkip)
{
potentialSuspects.Add(253);
}
if (CanVote(dummy))
{
VotingUtils.RpcCastVote(PlayerControl.LocalPlayer, dummy.myPlayer.PlayerId, potentialSuspects.Random());
}
}
private static bool CanVote(this DummyBehaviour dummy)
{
var dummyVoteData = dummy.myPlayer.GetVoteData();
return dummyVoteData.VotesRemaining > 0;
}
}
+12 -1
View File
@@ -11,12 +11,14 @@ using MiraAPI.GameEnd;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.Hud;
using MiraAPI.Keybinds;
using MiraAPI.Modifiers;
using MiraAPI.Presets;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using Reactor.Networking;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI.PluginLoading;
@@ -129,7 +131,16 @@ public sealed class MiraPluginManager
CustomButtonManager.Buttons = new ReadOnlyCollection<CustomActionButton>(CustomButtonManager.CustomButtons);
// Cache all the registered plugins into an array for easy access
RegisteredPlugins = [.._registeredPlugins.Values];
RegisteredPlugins = [.. _registeredPlugins.Values];
ModifierManager.Modifiers = new ReadOnlyCollection<BaseModifier>(ModifierManager.InternalModifiers);
foreach (var button in CustomButtonManager.Buttons)
{
KeybindManager.Register($"{button.Name}_Keybind", $"Keybind for {button.Name}", button.Defaultkeybind, button.ClickHandler, modifier1: button.Modifier1, modifier2: button.Modifier2, modifier3: button.Modifier3);
Logger<MiraApiPlugin>.Info($"Registered keybind for button '{button.GetType().Name}' with default key {button.Defaultkeybind}.");
}
};
}
-3
View File
@@ -96,6 +96,3 @@ public class SavePresetPopup(nint cppPtr) : Minigame(cppPtr)
popup.Begin(null);
}
}
Binary file not shown.
+13
View File
@@ -35,6 +35,16 @@ public static class CustomRoleManager
public static readonly LoadableAsset<AudioClip> ImpostorIntroSound =
CustomRoleUtils.GetIntroSound(RoleTypes.Impostor)!;
/// <summary>
/// Gets the list of custom roles as RoleBehaviour objects.
/// </summary>
public static IReadOnlyList<RoleBehaviour> CustomRoleBehaviours { get; private set; } = [];
/// <summary>
/// Gets the list of custom roles as ICustomRole objects.
/// </summary>
public static IReadOnlyList<ICustomRole> CustomMiraRoles { get; private set; } = [];
internal static readonly Dictionary<ushort, RoleBehaviour> CustomRoles = [];
internal static readonly Dictionary<Type, ushort> RoleIds = [];
@@ -53,6 +63,9 @@ public static class CustomRoleManager
{
RoleManager.GhostRoles.Add(role.Role);
}
CustomRoleBehaviours = CustomRoles.Values.ToList();
CustomMiraRoles = CustomRoles.Values.OfType<ICustomRole>().ToList();
}
internal static void RegisterRoleTypes(List<Type> roles, MiraPluginInfo pluginInfo)
+49
View File
@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace MiraAPI.Utilities;
/// <summary>
/// Extensions for IEnumerable.
/// </summary>
public static class EnumerableExtensions
{
/// <summary>
/// Shuffle the elements of the source enumerable using a new instance of Random.
/// </summary>
/// <param name="source">The source enumerable.</param>
/// <typeparam name="T">The type of the elements.</typeparam>
/// <returns>>The shuffled enumerable.</returns>
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source)
{
return source.Shuffle(new Random());
}
/// <summary>
/// Shuffle the elements of the source enumerable using the provided Random instance.
/// </summary>
/// <param name="source">The source enumerable.</param>
/// <param name="rng">The Random instance to use for shuffling.</param>
/// <typeparam name="T">The type of the elements.</typeparam>
/// <returns>>The shuffled enumerable.</returns>
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(rng);
return source.ShuffleIterator(rng);
}
private static IEnumerable<T> ShuffleIterator<T>(this IEnumerable<T> source, Random rng)
{
var buffer = source.ToList();
for (var i = 0; i < buffer.Count; i++)
{
var j = rng.Next(i, buffer.Count);
yield return buffer[j];
buffer[j] = buffer[i];
}
}
}
+70 -4
View File
@@ -10,6 +10,8 @@ using MiraAPI.Networking;
using MiraAPI.Roles;
using MiraAPI.Voting;
using Reactor.Utilities;
using Rewired;
using Rewired.Data;
using TMPro;
using UnityEngine;
@@ -92,7 +94,7 @@ public static class Extensions
/// <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")
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;
@@ -113,7 +115,7 @@ public static class Extensions
/// <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")
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;
@@ -154,6 +156,24 @@ public static class Extensions
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>
/// Determines if a float is an integer.
/// </summary>
@@ -495,6 +515,7 @@ public static class Extensions
/// <param name="includeImpostors">Whether impostors should be included in the search.</param>
/// <param name="distance">The radius to search within.</param>
/// <param name="ignoreColliders">Whether colliders should be ignored when searching.</param>
/// <param name="includeGhosts">Determines if Ghosts are included.</param>
/// <param name="predicate">Optional predicate to test if the object is valid.</param>
/// <returns>The closest player if there is one, false otherwise.</returns>
public static PlayerControl? GetClosestPlayer(
@@ -502,16 +523,16 @@ public static class Extensions
bool includeImpostors,
float distance,
bool ignoreColliders = false,
bool includeGhosts = false,
Predicate<PlayerControl>? predicate = null)
{
var filteredPlayers = Helpers.GetClosestPlayers(playerControl, distance, ignoreColliders)
.Where(
playerInfo => !playerInfo.Data.Disconnected &&
playerInfo.PlayerId != playerControl.PlayerId &&
!playerInfo.Data.IsDead &&
(includeGhosts || !playerInfo.Data.IsDead) &&
(includeImpostors || !playerInfo.Data.Role.IsImpostor))
.ToList();
return predicate != null ? filteredPlayers.Find(predicate) : filteredPlayers.FirstOrDefault();
}
@@ -526,4 +547,49 @@ public static class Extensions
renderer.material.SetColor(ShaderID.OutlineColor, color ?? Color.clear);
renderer.material.SetColor(ShaderID.AddColor, color ?? Color.clear);
}
// Inspired by: https://github.com/eDonnes124/Town-Of-Us-R/blob/master/source/Patches/Keybinds.cs#L29
/// <summary>
/// Registers a new mod keybind as a user-assignable button action in Rewired.
/// </summary>
/// <param name="userData">The Rewired user data to add the action to.</param>
/// <param name="actionName">The internal name of the action.</param>
/// <param name="description">Text shown in the rebinding UI.</param>
/// <param name="key">The default key to assign to this action.</param>
/// <param name="category">Category ID to group actions in Rewired (default is 0).</param>
/// <param name="elementIdentifierId">The element identifier ID (default is -1, meaning none specified).</param>
/// <param name="type">The <see cref="InputActionType"/> for this action (default is Button).</param>
/// <param name="modifier1">The first optional modifier key (e.g., <c>Control</c>, <c>Shift</c>, <c>Alt</c>) that must be held together with the main key.</param>
/// <param name="modifier2">The second optional modifier key. Set to <see cref="ModifierKey.None"/> if not used.</param>
/// <param name="modifier3">The third optional modifier key. Set to <see cref="ModifierKey.None"/> if not used.</param>
/// <returns>The action ID of the newly registered action.</returns>
public static int RegisterModBind(this UserData userData, string actionName, string description, KeyboardKeyCode key, int category = 0, int elementIdentifierId = -1, InputActionType type = InputActionType.Button, ModifierKey modifier1 = ModifierKey.None, ModifierKey modifier2 = ModifierKey.None, ModifierKey modifier3 = ModifierKey.None)
{
userData.AddAction(category);
var action = userData.GetAction(userData.actions.Count - 1)!;
action.name = actionName;
action.descriptiveName = description;
action.categoryId = category;
action.type = type;
action.userAssignable = true;
var map = new ActionElementMap
{
_elementIdentifierId = elementIdentifierId,
_actionId = action.id,
_elementType = ControllerElementType.Button,
_axisContribution = Pole.Positive,
_keyboardKeyCode = key,
_modifierKey1 = modifier1,
_modifierKey2 = modifier2,
_modifierKey3 = modifier3,
};
userData.keyboardMaps[0].actionElementMaps.Add(map);
userData.joystickMaps[0].actionElementMaps.Add(map);
return action.id;
}
}
+30 -7
View File
@@ -1,7 +1,11 @@
using System.Collections;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Il2CppSystem.IO;
using MiraAPI.Keybinds;
using Rewired;
using TMPro;
using UnityEngine;
using Object = UnityEngine.Object;
@@ -19,7 +23,7 @@ public static class Helpers
/// <returns>A list of alive players.</returns>
public static List<PlayerControl> GetAlivePlayers()
{
return [.. GameData.Instance.AllPlayers.ToArray().Where(x => !x.IsDead && !x.Disconnected && x.Object).Select(x=>x.Object)];
return [.. GameData.Instance.AllPlayers.ToArray().Where(x => !x.IsDead && !x.Disconnected && x.Object).Select(x => x.Object)];
}
/// <summary>
@@ -36,10 +40,10 @@ public static class Helpers
case 100:
return true;
default:
{
var num = Random.RandomRangeInt(1, 101);
return num <= probability;
}
{
var num = UnityEngine.Random.RandomRangeInt(1, 101);
return num <= probability;
}
}
}
@@ -398,6 +402,25 @@ public static class Helpers
public static string RandomString(int length, string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
{
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[Random.RandomRangeInt(0, s.Length)]).ToArray());
.Select(s => s[UnityEngine.Random.RandomRangeInt(0, s.Length)]).ToArray());
}
/// <summary>
/// Finds and returns an unused KeyCode that is not equal to the excluded key.
/// </summary>
/// <param name="exclude">The KeyCode to skip during the search.</param>
/// <returns>
/// The first available KeyCode not currently used by any registered keybind,
/// or KeyCode.None if none are available.
/// </returns>
public static KeyboardKeyCode FindAvailableKey(KeyboardKeyCode exclude)
{
foreach (KeyboardKeyCode key in Enum.GetValues(typeof(KeyboardKeyCode)))
{
if (key == exclude) continue;
bool used = KeybindManager.GetEntries().Exists(e => e.Key == key);
if (!used) return key;
}
return KeyboardKeyCode.None;
}
}