Merge pull request #74 from All-Of-Us-Mods/new-features

- Adds custom game tabs, with their own names and description blurbs.
- All keybinds have been moved to the right for consistency and to prevent hide and seek vent uses being obscured.
- Holding shift will split options in half
- Vanilla roles can be blacklisted from spawning or appearing in menus via other mira mods.
- Roles can check if they can spawn on the current gamemode or under specific conditions, such as the current map.
- Add options that can have customizable text for -1 and 0 values respectively.
- Fix buttons used via keybinds bypassing ButtonClickEvents
This commit is contained in:
Atony authored and GitHub committed 2025-12-14 07:47:27 -06:00
commit 8e09b7fee0
42 files changed
+1218 -98

No files matched your search

@@ -0,0 +1,35 @@
namespace MiraAPI.Events.Vanilla.Player;
/// <summary>
/// The event that is invoked when a player completes a task within Hide and Seek. Non-cancelable.
/// </summary>
public class CompleteHnsTaskEvent : MiraEvent
{
/// <summary>
/// Gets the instance of the <see cref="PlayerControl"/>.
/// </summary>
public PlayerControl Player { get; }
/// <summary>
/// Gets the instance of the <see cref="PlayerTask"/> that the player completed.
/// </summary>
public PlayerTask Task { get; }
/// <summary>
/// Gets the instance of the <see cref="HnsManager"/> attached to the game.
/// </summary>
public HideAndSeekManager HnsManager { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CompleteHnsTaskEvent"/> class.
/// </summary>
/// <param name="hnsManager">The Hide and Seek Manager currently active.</param>
/// <param name="player">The player who completed the task.</param>
/// <param name="task">The task that the player completed.</param>
public CompleteHnsTaskEvent(HideAndSeekManager hnsManager, PlayerControl player, PlayerTask task)
{
HnsManager = hnsManager;
Player = player;
Task = task;
}
}
+36 -1
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MiraAPI.Modifiers;
using UnityEngine;
namespace MiraAPI.GameOptions;
@@ -28,11 +29,16 @@ public abstract class AbstractOptionGroup
public virtual Type? OptionableType => null;
/// <summary>
/// Gets a value indicating whether the group should be shown in the modifiers menu.
/// Gets a value indicating whether the group should be shown in the modifiers menu. This is deprecated, please use ParentMenu!
/// </summary>
// TODO: make this not a boolean
public virtual bool ShowInModifiersMenu => false;
/// <summary>
/// Gets a value indicating which menu the group is in.
/// </summary>
public virtual MenuCategory ParentMenu => MenuCategory.Roles;
/// <summary>
/// Gets the function that determines whether the group should be visible or not.
/// </summary>
@@ -62,4 +68,33 @@ public abstract class AbstractOptionGroup<T> : AbstractOptionGroup where T : IOp
{
/// <inheritdoc />
public override Type OptionableType => typeof(T);
/// <summary>
/// Gets a value indicating which menu the group is in.
/// </summary>
public override MenuCategory ParentMenu
{
get
{
var type = typeof(T);
if (type == typeof(BaseModifier))
{
return MenuCategory.Modifiers;
}
else if (type == typeof(RoleBehaviour))
{
return MenuCategory.Roles;
}
return MenuCategory.Game;
}
}
}
public enum MenuCategory
{
Game,
Roles,
Modifiers,
CustomOne,
CustomTwo
}
@@ -21,7 +21,15 @@ public class ModdedNumberOptionAttribute(
{
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
{
return new ModdedNumberOption(Title, (float)(value ?? min+increment), min, max, increment, suffixType, formatString, zeroInfinity);
return new ModdedNumberOption(
Title,
(float)(value ?? min + increment),
min,
max,
increment,
suffixType,
formatString,
zeroInfinity);
}
/// <inheritdoc />
+2 -2
View File
@@ -17,9 +17,9 @@ public interface IModdedOption
uint Id { get; }
/// <summary>
/// Gets the title of the option.
/// Gets or sets the title of the option.
/// </summary>
string Title { get; }
string Title { get; set; }
/// <summary>
/// Gets the StringName for the option, used for localization.
@@ -0,0 +1,40 @@
using System;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.GameOptions.OptionTypes;
using Reactor.Utilities.Attributes;
using UnityEngine;
namespace MiraAPI.GameOptions;
/// <summary>
/// Component added onto options generated via MiraAPI. This allows developers to hook patches onto options in general, without using a list.
/// </summary>
[RegisterInIl2Cpp]
public class MiraNumberOptionComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
{
/// <summary>
/// Gets or sets the modded option associated with the object.
/// </summary>
[HideFromIl2Cpp]
public ModdedNumberOption NumberOption { get; set; }
/// <summary>
/// Gets or sets the default increment.
/// </summary>
public float DefaultIncrement { get; set; }
/// <summary>
/// Gets or sets a value indicating whether holding shift will split the increment in half.
/// </summary>
public bool ShiftIncrementToggle { get; set; }
/// <summary>
/// Gets or sets a value indicating what Zero displays.
/// </summary>
public string ZeroValue { get; set; } = "#";
/// <summary>
/// Gets or sets a value indicating what Negative One displays.
/// </summary>
public string NegativeValue { get; set; } = "#";
}
@@ -47,6 +47,8 @@ public class ModdedEnumOption : ModdedOption<int>
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
{
var stringOption = Object.Instantiate(stringOpt, container);
stringOption.name =
$"{ParentMod!.OptionsTitleText}.EnumOption.{TranslationController.Instance.GetString(StringName)}";
stringOption.SetUpFromData(Data, 20);
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
@@ -138,6 +140,8 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
{
var stringOption = Object.Instantiate(stringOpt, container);
stringOption.name =
$"{ParentMod!.OptionsTitleText}.EnumOption.{TranslationController.Instance.GetString(StringName)}";
stringOption.SetUpFromData(Data, 20);
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
@@ -32,10 +32,82 @@ public class ModdedNumberOption : ModdedOption<float>
/// </summary>
public MiraNumberSuffixes SuffixType { get; }
/// <summary>
/// Gets a value indicating what zero appears as.
/// </summary>
public string ZeroWordValue { get; }
/// <summary>
/// Gets a value indicating what a negative one appears as.
/// </summary>
public string NegativeWordValue { get; }
/// <summary>
/// Gets a value indicating whether zero is infinity.
/// </summary>
public bool ZeroInfinity { get; }
public bool ZeroInfinity => ZeroWordValue == "∞";
/// <summary>
/// Gets a value indicating whether holding shift will divide the increment.
/// </summary>
public bool ShiftIncrement { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ModdedNumberOption"/> class.
/// </summary>
/// <param name="title">The title of the option.</param>
/// <param name="defaultValue">The default value as a float.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <param name="increment">The increment.</param>
/// <param name="suffixType">The suffix type.</param>
/// <param name="formatString">Optional format string for the option screen.</param>
/// <param name="zeroBehavior">Determines what is shown for zero. Options: ∞, #, or a word value (such as Off).</param>
/// <param name="negativeBehavior">Determines what is shown for negative 1. Options: ∞, #, or a word value (such as Off).</param>
/// <param name="halfIncrements">Whether increments can be split in half.</param>
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
public ModdedNumberOption(
string title,
float defaultValue,
float min,
float max,
float increment,
string zeroBehavior,
string negativeBehavior,
MiraNumberSuffixes suffixType,
string? formatString = null,
bool halfIncrements = false,
bool includeInPreset = true) : base(title, defaultValue, includeInPreset)
{
Min = min;
Max = max;
Increment = increment;
SuffixType = suffixType;
ZeroWordValue = zeroBehavior;
NegativeWordValue = negativeBehavior;
ShiftIncrement = halfIncrements;
Value = Mathf.Clamp(defaultValue, min, max);
Data = ScriptableObject.CreateInstance<FloatGameSetting>();
var data = (FloatGameSetting)Data;
data.Type = global::OptionTypes.Float;
data.Title = StringName;
data.Value = Value;
data.Increment = Increment;
data.ValidRange = new FloatRange(Min, Max);
data.FormatString = formatString ??
(defaultValue.IsInteger() &&
Increment.IsInteger() &&
Value.IsInteger() &&
Min.IsInteger() &&
Max.IsInteger() ? "0" : "0.0");
data.ZeroIsInfinity = ZeroInfinity;
data.SuffixType = (NumberSuffixes)SuffixType;
data.OptionName = FloatOptionNames.Invalid;
}
/// <summary>
/// Initializes a new instance of the <see cref="ModdedNumberOption"/> class.
@@ -64,7 +136,9 @@ public class ModdedNumberOption : ModdedOption<float>
Max = max;
Increment = increment;
SuffixType = suffixType;
ZeroInfinity = zeroInfinity;
ZeroWordValue = zeroInfinity ? "∞" : "#";
NegativeWordValue = "#";
ShiftIncrement = increment != 1f;
Value = Mathf.Clamp(defaultValue, min, max);
@@ -97,6 +171,14 @@ public class ModdedNumberOption : ModdedOption<float>
Transform container)
{
var numberOption = Object.Instantiate(numberOpt, Vector3.zero, Quaternion.identity, container);
numberOption.name =
$"{ParentMod!.OptionsTitleText}.NumberOption.{TranslationController.Instance.GetString(StringName)}";
var optionComponent = numberOption.gameObject.AddComponent<MiraNumberOptionComponent>();
optionComponent.NumberOption = this;
optionComponent.DefaultIncrement = Increment;
optionComponent.ShiftIncrementToggle = ShiftIncrement;
optionComponent.NegativeValue = NegativeWordValue;
optionComponent.ZeroValue = ZeroWordValue;
numberOption.SetUpFromData(Data, 20);
numberOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
@@ -21,7 +21,7 @@ public abstract class ModdedOption<T> : IModdedOption
public uint Id { get; }
/// <inheritdoc />
public string Title { get; }
public string Title { get; set; }
/// <inheritdoc />
public StringNames StringName { get; }
@@ -55,6 +55,8 @@ public class ModdedPlayerOption : ModdedOption<int>
Transform container)
{
var playerOption = Object.Instantiate(playerOpt, container);
playerOption.name =
$"{ParentMod!.OptionsTitleText}.PlayerOption.{TranslationController.Instance.GetString(StringName)}";
var filteredList = GetFilteredPlayers();
Values = filteredList.ToIl2CppList();
@@ -29,6 +29,8 @@ public class ModdedToggleOption : ModdedOption<bool>
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
{
var toggleOption = Object.Instantiate(toggleOpt, Vector3.zero, Quaternion.identity, container);
toggleOption.name =
$"{ParentMod!.OptionsTitleText}.ToggleOption.{TranslationController.Instance.GetString(StringName)}";
toggleOption.SetUpFromData(Data, 20);
+45 -7
View File
@@ -64,9 +64,14 @@ public abstract class CustomActionButton
public virtual float EffectDuration => 0;
/// <summary>
/// Gets the maximum amount of uses the button has. If the button has infinite uses, set to 0.
/// Gets a value indicating whether the button has limited uses.
/// </summary>
public virtual int MaxUses => 0;
public bool LimitedUses => ZeroIsInfinite ? MaxUses > 0 : MaxUses >= 0;
/// <summary>
/// Gets the maximum number of uses the button has. If the button has infinite uses, set to 0 or -1 based on what value that ZeroIsInfinite is set to.
/// </summary>
public virtual int MaxUses => ZeroIsInfinite ? 0 : -1;
/// <summary>
/// Gets the value indicating uses mode.
@@ -89,9 +94,9 @@ public abstract class CustomActionButton
public virtual ButtonLocation Location { get; set; } = ButtonLocation.BottomLeft;
/// <summary>
/// Gets a value indicating whether the button has limited uses.
/// Gets or sets a value indicating whether limited uses are determined via zero or a negative number of uses.
/// </summary>
public bool LimitedUses => MaxUses > 0;
public virtual bool ZeroIsInfinite { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether the effect is currently active, if there is one.
@@ -206,7 +211,39 @@ public abstract class CustomActionButton
{
if (Enabled(PlayerControl.LocalPlayer.Data.Role))
{
ClickHandler();
// Invoke the generic button click event.
var genericEvent = new MiraButtonClickEvent(this);
MiraEventManager.InvokeEvent(genericEvent);
if (genericEvent.IsCancelled)
{
MiraEventManager.InvokeEvent(new MiraButtonCancelledEvent(this));
}
// Invoke the button click event for specific button.
var eventType = CustomButtonManager.ButtonEventTypes[GetType()];
var @event = (MiraCancelableEvent)Activator.CreateInstance(eventType, this, genericEvent)!;
var specificInvoked = MiraEventManager.InvokeEvent(@event, eventType);
if (@event.IsCancelled)
{
var cancelEventType = CustomButtonManager.ButtonCancelledEventTypes[GetType()];
var cancelEvent = (MiraEvent)Activator.CreateInstance(cancelEventType, this)!;
MiraEventManager.InvokeEvent(cancelEvent, cancelEventType);
}
if (specificInvoked)
{
if (!@event.IsCancelled)
{
ClickHandler();
}
}
else
{
if (!genericEvent.IsCancelled)
{
ClickHandler();
}
}
}
});
@@ -214,9 +251,10 @@ public abstract class CustomActionButton
Helpers.CreateKeybindIcon(
Button.gameObject,
Keybind.CurrentKey,
new Vector3(MaxUses <= 0 ? -0.4f : 0.4f, 0.45f, -9f)
new Vector3(0.4f, 0.45f, -9f)
);
KeybindText = KeybindIcon.transform.GetChild(0).GetComponent<TextMeshPro>();
HudManagerPatches.ModdedKeybindIcons.Add(KeybindText);
Button.usesRemainingSprite.transform.localPosition = new(-0.341f, 0.45f, -0.1f);
}
}
@@ -494,7 +532,7 @@ public abstract class CustomActionButton
if (Keybind != null && KeybindText != null)
{
KeybindText.text = Keybind.CurrentKey.ToString();
KeybindIcon?.SetActive(ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard &&
KeybindIcon?.SetActive(ActiveInputManager.currentControlType is ActiveInputManager.InputType.Keyboard &&
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value &&
Keybind.CurrentKey != KeyboardKeyCode.None);
}
+1 -1
View File
@@ -19,6 +19,6 @@ public class VanillaKeybind : BaseKeybind
public VanillaKeybind(int id)
{
RewiredInputAction = KeybindUtils.GetInputActionById(id);
Id = RewiredInputAction?.name;
Id = RewiredInputAction != null ? RewiredInputAction.name : id.ToString();
}
}
+4 -2
View File
@@ -28,9 +28,11 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
public ConfigEntry<bool> ModifiersHudLeftSide { get; private set; } = config.Bind("Displays", "Show Modifiers HUD on Left Side", false);
/// <summary>
/// Gets whether to show keybinds in the control mapper.
/// Gets whether to show keybinds on buttons.
/// </summary>
public ConfigEntry<bool> ShowKeybinds { get; private set; } = config.Bind("Keybinds", "Show Keybinds in Control Mapper", true);
[LocalToggleSetting]
public ConfigEntry<bool> ShowKeybinds { get; private set; } = config.Bind("Keybinds", "Show Keybinds on Buttons", true);
// This would be placed in the keybinds menu, but it crashes for Epic Games users. - Atony
/// <summary>
/// Gets whether to apply cosmetic changes to the TaskAdder.
+2 -1
View File
@@ -179,6 +179,7 @@ public static class ModifierManager
private static bool IsGameModifierValid(PlayerControl player, GameModifier modifier, uint modifierId)
{
return (player.Data.Role is not ICustomRole role || role.IsModifierApplicable(modifier)) &&
modifier.IsModifierValidOn(player.Data.Role) && !player.HasModifier(modifierId);
modifier.IsModifierValidOn(player.Data.Role) && !player.HasModifier(modifierId) &&
modifier.CanSpawnOnCurrentMode();
}
}
+6
View File
@@ -39,4 +39,10 @@ public abstract class GameModifier : BaseModifier
/// <param name="reason">The reason why the game ended.</param>
/// <returns>True if the player won, false if they lost. Return null to use the player's default win condition.</returns>
public virtual bool? DidWin(GameOverReason reason) => null;
/// <summary>
/// Determines whether the modifier can spawn in general, accounting for gamemodes and everything else.
/// </summary>
/// <returns>True if the modifier is able to spawn, otherwise false.</returns>
public virtual bool CanSpawnOnCurrentMode() => !GameManager.Instance.IsHideAndSeek();
}
+1 -1
View File
@@ -87,11 +87,11 @@ public static class CustomMurderRpc
bool playKillSound = true)
{
source.isKilling = false;
var data = target.Data;
if (resultFlags.HasFlag(MurderResultFlags.FailedError))
{
return;
}
var data = target.Data;
if (resultFlags.HasFlag(MurderResultFlags.FailedProtected) ||
(resultFlags.HasFlag(MurderResultFlags.DecisionByHost) && target.protectedByGuardianId > -1))
@@ -0,0 +1,17 @@
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Player;
namespace MiraAPI.Patches.Events;
[HarmonyPatch(typeof(HideAndSeekManager))]
public static class HnsFinishTaskPatch
{
[HarmonyPostfix]
[HarmonyPatch(nameof(HideAndSeekManager.FinishTask))]
public static void FinishTaskPostfix(HideAndSeekManager __instance, PlayerTask task)
{
var completeTaskEvent = new CompleteHnsTaskEvent(__instance, task.Owner, task);
MiraEventManager.InvokeEvent(completeTaskEvent);
}
}
@@ -1,4 +1,5 @@
using HarmonyLib;
/*
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
@@ -18,3 +19,4 @@ internal static class SpawnInMinigamePatch
MiraEventManager.InvokeEvent(@event2);
}
}
*/
@@ -1,4 +1,5 @@
using System.Collections.Generic;
/*
using System.Collections.Generic;
using System.Reflection;
using HarmonyLib;
using MiraAPI.Events;
@@ -34,3 +35,4 @@ internal static class SubmergedSpawnGamePatch
MiraEventManager.InvokeEvent(@event2);
}
}
*/
+12 -11
View File
@@ -21,10 +21,14 @@ using Object = UnityEngine.Object;
namespace MiraAPI.Patches.Freeplay;
[HarmonyPatch(typeof(TaskAdderGame))]
internal static class TaskAdderPatches
public static class TaskAdderPatches
{
private static Scroller _scroller = null!;
private static Dictionary<string, TaskFolder> folders = new();
public static string CrewmateName => TranslationController.Instance.GetString(StringNames.Crewmate);
public static string ImpostorName => TranslationController.Instance.GetString(StringNames.Impostor);
public static string NeutralName => "Neutral";
public static string ModifiersName => "Modifiers";
[HarmonyPostfix]
[HarmonyPatch(nameof(TaskAdderGame.Begin))]
@@ -66,19 +70,16 @@ internal static class TaskAdderPatches
__instance.RootFolderPrefab.GetComponent<PassiveButton>().ClickMask = collider;
__instance.RootFolderPrefab.gameObject.SetActive(false);
__instance.TaskParent = inner.transform;
var crewLocale = TranslationController.Instance.GetString(StringNames.Crewmate);
var impLocale = TranslationController.Instance.GetString(StringNames.Impostor);
var crewmateFolder = __instance.Root.SubFolders.ToArray().FirstOrDefault(x => x.FolderName == crewLocale)!;
var impostorFolder = __instance.Root.SubFolders.ToArray().FirstOrDefault(x => x.FolderName == impLocale)!;
var crewmateFolder = __instance.Root.SubFolders.ToArray().FirstOrDefault(x => x.FolderName == CrewmateName)!;
var impostorFolder = __instance.Root.SubFolders.ToArray().FirstOrDefault(x => x.FolderName == ImpostorName)!;
//var neutralFolder = __instance.CreateFolder("Neutral", __instance.Root, 2, Color.gray);
var modifiersFolder = __instance.CreateFolder("Modifiers", __instance.Root, 0, Color.blue);
var modifiersFolder = __instance.CreateFolder(ModifiersName, __instance.Root, 0, Color.blue);
folders.Clear();
folders.Add(crewmateFolder.FolderName, crewmateFolder);
folders.Add(impostorFolder.FolderName, impostorFolder);
//folders.Add("Neutrals", neutralFolder);
folders.Add("Modifiers", modifiersFolder);
folders.Add(ModifiersName, modifiersFolder);
int folderIdx = 2;
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
@@ -137,8 +138,8 @@ internal static class TaskAdderPatches
Object.Instantiate(__instance.RootFolderPrefab, __instance.transform);
impFolder.gameObject.SetActive(false);
impFolder.SetFolderColor(TaskFolder.FolderColor.Red);
crewFolder.FolderName = TranslationController.Instance.GetString(StringNames.Crewmate);
impFolder.FolderName = TranslationController.Instance.GetString(StringNames.Impostor);
crewFolder.FolderName = CrewmateName;
impFolder.FolderName = ImpostorName;
rootFolder.SubFolders.Insert(0, crewFolder);
rootFolder.SubFolders.Insert(0, impFolder);
Il2CppSystem.Collections.Generic.List<RoleBehaviour> impRoles = new();
@@ -440,7 +441,7 @@ internal static class TaskAdderPatches
}
}
if (folders.TryGetValue("Modifiers", out var modifiersFolder) && taskFolder.IsChildOf(modifiersFolder))
if (folders.TryGetValue(ModifiersName, out var modifiersFolder) && taskFolder.IsChildOf(modifiersFolder))
{
var plugin = MiraPluginManager.GetPluginByGuid(taskFolder.name.Replace("(Clone)", string.Empty));
if (plugin != null)
@@ -9,6 +9,11 @@ namespace MiraAPI.Patches.Hud;
public static class ButtonResetPatches
{
public static void Postfix()
{
ResetCooldowns();
}
public static void ResetCooldowns()
{
foreach (var customActionButton in CustomButtonManager.CustomButtons)
{
+13 -3
View File
@@ -27,6 +27,8 @@ public static class HudManagerPatches
private static Dictionary<TextMeshPro, int> vanillaKeybindIcons = new();
internal static List<TextMeshPro> ModdedKeybindIcons = new();
/*
/// <summary>
/// Trigger hudstart on current custom gamemode
@@ -88,6 +90,7 @@ public static class HudManagerPatches
fakeButton.ToggleVisible(true);
fakeButton.Destroy();
}
ModdedKeybindIcons = [];
foreach (var button in CustomButtonManager.CustomButtons)
{
@@ -112,13 +115,15 @@ public static class HudManagerPatches
Error($"Failed to create custom button {button.GetType().Name}: {e}");
}
}
__instance.ImpostorVentButton.transform.SetParent(null);
__instance.ImpostorVentButton.transform.SetParent(BottomRight.transform);
gridArrange.Start();
gridArrange.ArrangeChilds();
aspectPosition.AdjustPosition();
vanillaKeybindIcons = [];
var keybindIconPos = new Vector3(-0.4f, 0.4f, -9.5f);
var keybindIconPos = new Vector3(0.4f, 0.4f, -9.5f);
var vanillaButtons = new Dictionary<GameObject, int>
{
{ __instance.KillButton.gameObject, 8 },
@@ -180,11 +185,16 @@ public static class HudManagerPatches
[HarmonyPostfix]
public static void UpdatePostfix()
{
var canSeeBinds = ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard &&
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value;
foreach (var btnIcon in vanillaKeybindIcons)
{
btnIcon.Key.text = KeybindUtils.GetKeycodeByActionId(btnIcon.Value).ToString();
btnIcon.Key.transform.parent.gameObject.SetActive(ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard &&
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value);
btnIcon.Key.transform.parent.gameObject.SetActive(canSeeBinds);
}
foreach (var btnIcon in ModdedKeybindIcons)
{
btnIcon.transform.parent.gameObject.SetActive(canSeeBinds);
}
var player = ReInput.players.GetPlayer(0);
@@ -15,7 +15,8 @@ namespace MiraAPI.Patches.Keybinds;
[HarmonyPatch(typeof(ControlMapper))]
public static class ControlMapperPatches
{
[HarmonyPrefix]
// Patching this crashes for Epic Games users
/*[HarmonyPrefix]
[HarmonyPatch(nameof(ControlMapper.Start))]
private static void StartPrefix(ControlMapper __instance)
{
@@ -37,7 +38,7 @@ public static class ControlMapperPatches
entry.Value = !entry.Value;
text.text = $"Show Keybinds: {(entry.Value ? "On" : "Off")}";
}));
}
}*/
[HarmonyPrefix]
[HarmonyPatch(nameof(ControlMapper.Update))]
@@ -32,11 +32,20 @@ internal static class GameOptionsMenuPatch
{
ModifiersUpdate(ref num);
}
else if (__instance.name == "CUSTOM TAB 1")
{
CustomMenuOneUpdate(ref num);
}
else if (__instance.name == "CUSTOM TAB 2")
{
CustomMenuTwoUpdate(ref num);
}
else
{
var filteredGroups =
GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu) ?? [];
.Where(x => x.OptionableType == null &&
!x.ShowInModifiersMenu && x.ParentMenu == MenuCategory.Roles) ?? [];
foreach (var group in filteredGroups)
{
@@ -99,6 +108,48 @@ internal static class GameOptionsMenuPatch
}
}
private static void CustomMenuOneUpdate(ref float num)
{
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
.Where(x => x.ParentMenu == MenuCategory.CustomOne) ?? [];
foreach (var modGroup in groups)
{
UpdateGroup(modGroup, ref num);
}
}
private static void CustomMenuOneCreate(GameOptionsMenu menu)
{
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
.Where(x => x.ParentMenu == MenuCategory.CustomOne) ?? [];
foreach (var group in groups)
{
CreateGroup(menu, group);
}
}
private static void CustomMenuTwoUpdate(ref float num)
{
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
.Where(x => x.ParentMenu == MenuCategory.CustomTwo) ?? [];
foreach (var modGroup in groups)
{
UpdateGroup(modGroup, ref num);
}
}
private static void CustomMenuTwoCreate(GameOptionsMenu menu)
{
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
.Where(x => x.ParentMenu == MenuCategory.CustomTwo) ?? [];
foreach (var group in groups)
{
CreateGroup(menu, group);
}
}
private static void ModifiersUpdate(ref float num)
{
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
@@ -137,9 +188,19 @@ internal static class GameOptionsMenuPatch
ModifiersCreate(__instance);
return false;
}
else if (__instance.name == "CUSTOM TAB 1")
{
CustomMenuOneCreate(__instance);
return false;
}
else if (__instance.name == "CUSTOM TAB 2")
{
CustomMenuTwoCreate(__instance);
return false;
}
var filteredGroups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu) ?? [];
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu && x.ParentMenu == MenuCategory.Roles) ?? [];
foreach (var group in filteredGroups)
{
+202 -33
View File
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
using MiraAPI.GameOptions;
using MiraAPI.Modifiers;
using MiraAPI.PluginLoading;
using MiraAPI.Presets;
@@ -27,13 +28,23 @@ internal static class GameSettingMenuPatches
private static Vector3 _roleBtnOgPos;
private static Vector3 _smallRoleBtnOgPos;
private static Vector3 _modifierBtnOgPos;
private static Vector3 _customOneBtnOgPos;
private static Vector3 _customTwoBtnOgPos;
private static GameOptionsMenu? _modifiersTab;
private static PassiveButton? _modifiersButton;
private static PassiveButton? _smallRolesButton;
private static GameOptionsMenu? _customOneTab;
private static PassiveButton? _customOneButton;
private static GameOptionsMenu? _customTwoTab;
private static PassiveButton? _customTwoButton;
private static GameObject? _nextModButton;
private static GameObject? _previousModButton;
private static Dictionary<int, Vector3> OptionsPositions { get; } = [];
private static Dictionary<int, Vector3> ModifiersPositions { get; } = [];
private static Dictionary<int, Vector3> CustomOnePositions { get; } = [];
private static Dictionary<int, Vector3> CustomTwoPositions { get; } = [];
private static void SaveScrollPositions(GameSettingMenu gameSettingMenu)
{
@@ -41,6 +52,14 @@ internal static class GameSettingMenuPatches
{
ModifiersPositions[SelectedModIdx] = _modifiersTab!.scrollBar.Inner.localPosition;
}
else if (_customOneTab)
{
CustomOnePositions[SelectedModIdx] = _customOneTab!.scrollBar.Inner.localPosition;
}
else if (_customTwoTab)
{
CustomTwoPositions[SelectedModIdx] = _customTwoTab!.scrollBar.Inner.localPosition;
}
RoleSettingMenuPatches.RolePositions[SelectedModIdx] = gameSettingMenu.RoleSettingsTab.scrollBar.Inner.localPosition;
OptionsPositions[SelectedModIdx] = gameSettingMenu.GameSettingsTab.scrollBar.Inner.localPosition;
@@ -65,9 +84,21 @@ internal static class GameSettingMenuPatches
_modifiersTab!.gameObject.SetActive(tabNum == 3);
}
if (_customOneTab)
{
_customOneTab!.gameObject.SetActive(tabNum == 4);
}
if (_customTwoTab)
{
_customTwoTab!.gameObject.SetActive(tabNum == 5);
}
_modifiersButton?.SelectButton(tabNum == 3);
_smallRolesButton?.SelectButton(tabNum == 2);
if (tabNum == 3)
_customOneButton?.SelectButton(tabNum == 4);
_customTwoButton?.SelectButton(tabNum == 5);
if (tabNum >= 3 && tabNum <= 5)
{
if (__instance.RoleSettingsButton.gameObject.active)
{
@@ -75,7 +106,21 @@ internal static class GameSettingMenuPatches
}
else
{
_modifiersButton?.SelectButton(true);
switch (tabNum)
{
case 3:
_modifiersButton?.SelectButton(true);
__instance.MenuDescriptionText.text = SelectedMod != null ? SelectedMod.MiraPlugin.ModifierMenuDescription : "Configure modifiers and their settings here!";
break;
case 4:
_customOneButton?.SelectButton(true);
__instance.MenuDescriptionText.text = SelectedMod != null ? SelectedMod.MiraPlugin.CustomOptionMenuOneDescription : "Apply game settings for this mod!";
break;
case 5:
_customTwoButton?.SelectButton(true);
__instance.MenuDescriptionText.text = SelectedMod != null ? SelectedMod.MiraPlugin.CustomOptionMenuTwoDescription : "Apply game settings for this mod!";
break;
}
}
}
}
@@ -87,19 +132,30 @@ internal static class GameSettingMenuPatches
_modifiersButton?.SelectButton(tabNum == 3);
_smallRolesButton?.SelectButton(tabNum == 2);
_customOneButton?.SelectButton(tabNum == 4);
_customTwoButton?.SelectButton(tabNum == 5);
if (tabNum != 3)
if (tabNum >= 3 && tabNum <= 5)
{
return;
}
if (__instance.RoleSettingsButton.gameObject.active)
{
__instance.RoleSettingsButton.SelectButton(true);
}
else
{
_modifiersButton?.SelectButton(true);
if (__instance.RoleSettingsButton.gameObject.active)
{
__instance.RoleSettingsButton.SelectButton(true);
}
else
{
switch (tabNum)
{
case 3:
_modifiersButton?.SelectButton(true);
break;
case 4:
_customOneButton?.SelectButton(true);
break;
case 5:
_customTwoButton?.SelectButton(true);
break;
}
}
}
}
@@ -127,16 +183,16 @@ internal static class GameSettingMenuPatches
_text.alignment = TextAlignmentOptions.Center;
var nextButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject;
nextButton.transform.localPosition = new Vector3(-2.2663f, 1.5272f, -25f);
nextButton.name = "RightArrowButton";
nextButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().sprite =
_nextModButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject;
_nextModButton.transform.localPosition = new Vector3(-2.2663f, 1.5272f, -25f);
_nextModButton.name = "RightArrowButton";
_nextModButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().sprite =
MiraAssets.NextButton.LoadAsset();
nextButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().sprite =
_nextModButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().sprite =
MiraAssets.NextButtonActive.LoadAsset();
nextButton.gameObject.GetComponent<CloseButtonConsoleBehaviour>().DestroyImmediate();
_nextModButton.gameObject.GetComponent<CloseButtonConsoleBehaviour>().DestroyImmediate();
var passiveButton = nextButton.gameObject.GetComponent<PassiveButton>();
var passiveButton = _nextModButton.gameObject.GetComponent<PassiveButton>();
passiveButton.OnClick = new ButtonClickedEvent();
passiveButton.OnClick.AddListener(
(UnityAction)(() =>
@@ -151,13 +207,13 @@ internal static class GameSettingMenuPatches
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
}));
var backButton = Object.Instantiate(nextButton, __instance.BackButton.transform.parent).gameObject;
backButton.transform.localPosition = new Vector3(-4.4209f, 1.5272f, -25f);
backButton.name = "LeftArrowButton";
backButton.gameObject.GetComponent<CloseButtonConsoleBehaviour>().Destroy();
backButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().flipX =
backButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().flipX = true;
backButton.gameObject.GetComponent<PassiveButton>().OnClick.AddListener(
_previousModButton = Object.Instantiate(_nextModButton, __instance.BackButton.transform.parent).gameObject;
_previousModButton.transform.localPosition = new Vector3(-4.4209f, 1.5272f, -25f);
_previousModButton.name = "LeftArrowButton";
_previousModButton.gameObject.GetComponent<CloseButtonConsoleBehaviour>().Destroy();
_previousModButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().flipX =
_previousModButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().flipX = true;
_previousModButton.gameObject.GetComponent<PassiveButton>().OnClick.AddListener(
(UnityAction)(() =>
{
SaveScrollPositions(__instance);
@@ -174,6 +230,61 @@ internal static class GameSettingMenuPatches
_modifiersTab = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent);
_modifiersTab.name = "MODIFIERS TAB";
// clone game settings tab for both custom tabs
_customOneTab = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent);
_customOneTab.name = "CUSTOM TAB 1";
_customTwoTab = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent);
_customTwoTab.name = "CUSTOM TAB 2";
// create button for the first custom category
var customPos = new Vector3(
__instance.RoleSettingsButton.transform.localPosition.x,
__instance.RoleSettingsButton.transform.localPosition.y,
__instance.RoleSettingsButton.transform.localPosition.z);
_customOneButton = Object.Instantiate(__instance.RoleSettingsButton, __instance.RoleSettingsButton.transform.parent);
_customOneButton.buttonText.gameObject.GetComponent<TextTranslatorTMP>().Destroy();
_customOneButton.OnClick = new ButtonClickedEvent();
_customOneButton.OnClick.AddListener(
(UnityAction)(() =>
{
__instance.ChangeTab(4, false);
}));
_customOneButton.OnMouseOver = new UnityEvent();
_customOneButton.OnMouseOver.AddListener(
(UnityAction)(() =>
{
__instance.ChangeTab(4, true);
}));
_customOneButton.buttonText.text = SelectedMod != null ? SelectedMod.MiraPlugin.CustomOptionMenuNameOne : "Custom Category 1";
customPos.y -= 0.637f;
_customOneButton.transform.localPosition = customPos;
_customOneButton.name = "CustomOneButton";
_customOneBtnOgPos = _customOneButton.transform.localPosition;
// create button for the first custom category
_customTwoButton = Object.Instantiate(_customOneButton, _customOneButton.transform.parent);
_customTwoButton.OnClick = new ButtonClickedEvent();
_customTwoButton.OnClick.AddListener(
(UnityAction)(() =>
{
__instance.ChangeTab(5, false);
}));
_customTwoButton.OnMouseOver = new UnityEvent();
_customTwoButton.OnMouseOver.AddListener(
(UnityAction)(() =>
{
__instance.ChangeTab(5, true);
}));
_customTwoButton.buttonText.text = SelectedMod != null ? SelectedMod.MiraPlugin.CustomOptionMenuNameTwo : "Custom Category 2";
customPos.y -= 0.637f;
_customTwoButton.transform.localPosition = customPos;
_customTwoButton.name = "CustomTwoButton";
_customTwoBtnOgPos = _customTwoButton.transform.localPosition;
// create button for modifiers
__instance.RoleSettingsButton.buttonText.gameObject.GetComponent<TextTranslatorTMP>().Destroy();
__instance.RoleSettingsButton.gameObject.SetActive(false);
@@ -304,20 +415,32 @@ internal static class GameSettingMenuPatches
bool replaceWithModifiers = true;
_smallRolesButton!.transform.localPosition = _smallRoleBtnOgPos;
_modifiersButton!.transform.localPosition = _modifierBtnOgPos;
_customOneButton!.transform.localPosition = _customOneBtnOgPos;
_customTwoButton!.transform.localPosition = _customTwoBtnOgPos;
menu.RoleSettingsButton.transform.localPosition = _roleBtnOgPos;
if (SelectedModIdx != 0)
{
_customOneButton.buttonText.text = SelectedMod!.MiraPlugin.CustomOptionMenuNameOne;
_customTwoButton.buttonText.text = SelectedMod!.MiraPlugin.CustomOptionMenuNameTwo;
var modHasRoles = SelectedMod!.InternalRoles.Count != 0;
var modHasCustomOne = SelectedMod.InternalOptionGroups.Exists(
x => x.ParentMenu == MenuCategory.CustomOne);
var modHasCustomTwo = SelectedMod.InternalOptionGroups.Exists(
x => x.ParentMenu == MenuCategory.CustomTwo);
var modHasModifiers = SelectedMod.InternalOptionGroups.Exists(
x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true);
x => x.ShowInModifiersMenu || x.ParentMenu == MenuCategory.Modifiers || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true);
var modHasOptions =
SelectedMod.InternalOptionGroups.Exists(x => x.OptionableType == null && !x.ShowInModifiersMenu);
SelectedMod.InternalOptionGroups.Exists(x => x.OptionableType == null && (!x.ShowInModifiersMenu ||
(x.ParentMenu != MenuCategory.Modifiers && x.ParentMenu != MenuCategory.Roles)));
_modifiersButton.gameObject.SetActive(true);
_smallRolesButton.gameObject.SetActive(true);
_customOneButton.gameObject.SetActive(true);
_customTwoButton.gameObject.SetActive(true);
menu.GameSettingsButton.gameObject.SetActive(true);
menu.RoleSettingsButton.gameObject.SetActive(false);
var defaultButton = menu.RoleSettingsButton;
// If there are no registered custom roles in the selected mod, hide the button.
if (!modHasRoles)
@@ -334,6 +457,7 @@ internal static class GameSettingMenuPatches
{
_modifiersButton.gameObject.SetActive(false);
menu.RoleSettingsButton.gameObject.SetActive(true);
defaultButton = _modifiersButton;
if (_modifiersTab!.gameObject.active)
{
@@ -356,6 +480,7 @@ internal static class GameSettingMenuPatches
// If the mod has roles, we can enable the bigger role button and disable the small one.
if (modHasRoles)
{
defaultButton = menu.RoleSettingsButton;
menu.RoleSettingsButton.gameObject.SetActive(true);
_smallRolesButton.gameObject.SetActive(false);
}
@@ -370,6 +495,7 @@ internal static class GameSettingMenuPatches
{
menu.ChangeTab(0, false);
}
defaultButton = menu.RoleSettingsButton;
// If the mod has roles and modifiers, we can move their buttons to the game settings button position, since nothing is there.
if (menu.RoleSettingsButton.gameObject.active)
@@ -389,16 +515,37 @@ internal static class GameSettingMenuPatches
_smallRolesButton.transform.localPosition.z);
}
}
// If the mod has no primary custom tab, we can go ahead and hide the button.
if (!modHasCustomOne)
{
_customOneButton.gameObject.SetActive(false);
if (_customOneTab!.gameObject.active)
{
defaultButton.SelectButton(true);
}
}
// If the mod has no secondary custom tab, we can go ahead and hide the button.
if (!modHasCustomTwo)
{
_customTwoButton.gameObject.SetActive(false);
if (_customTwoTab!.gameObject.active)
{
defaultButton.SelectButton(true);
}
}
}
else
{
_modifiersButton.gameObject.SetActive(false);
_smallRolesButton.gameObject.SetActive(false);
_customOneButton.gameObject.SetActive(false);
_customTwoButton.gameObject.SetActive(false);
menu.RoleSettingsButton.gameObject.SetActive(true);
menu.GameSettingsButton.gameObject.SetActive(true);
replaceWithModifiers = false;
if (_modifiersTab!.gameObject.active)
if (_modifiersTab!.gameObject.active || _customOneTab!.gameObject.active || _customTwoTab!.gameObject.active)
{
menu.ChangeTab(0, false);
}
@@ -446,7 +593,17 @@ internal static class GameSettingMenuPatches
if (_modifiersTab?.Children?.Count > 0)
{
CleanupSettings(_modifiersTab, true);
CleanupSettings(_modifiersTab, MenuCategory.Modifiers);
}
if (_customOneTab?.Children?.Count > 0)
{
CleanupSettings(_customOneTab, MenuCategory.CustomOne);
}
if (_customTwoTab?.Children?.Count > 0)
{
CleanupSettings(_customTwoTab, MenuCategory.CustomTwo);
}
void CleanupRoleSettings(RolesSettingsMenu rolesMenu)
@@ -484,7 +641,7 @@ internal static class GameSettingMenuPatches
}
}
void CleanupSettings(GameOptionsMenu gameOptMenu, bool modifiers = false)
void CleanupSettings(GameOptionsMenu gameOptMenu, MenuCategory menuCategory = MenuCategory.Roles)
{
ClearOptions(gameOptMenu.Children);
gameOptMenu.Children = null;
@@ -495,7 +652,19 @@ internal static class GameSettingMenuPatches
.ForEach(header => header.gameObject.DestroyImmediate());
gameOptMenu.Initialize();
var positions = modifiers ? ModifiersPositions : OptionsPositions;
var positions = OptionsPositions;
switch (menuCategory)
{
case MenuCategory.Modifiers:
positions = ModifiersPositions;
break;
case MenuCategory.CustomOne:
positions = CustomOnePositions;
break;
case MenuCategory.CustomTwo:
positions = CustomTwoPositions;
break;
}
if (positions.TryGetValue(SelectedModIdx, out var pos))
{
gameOptMenu.scrollBar.Inner.localPosition = pos;
+107 -3
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AmongUs.GameOptions;
using HarmonyLib;
using MiraAPI.GameOptions;
using MiraAPI.Modifiers;
@@ -163,7 +164,110 @@ public static class LobbyViewPanePatches
{
if (SelectedModIdx == 0)
{
return true;
float num = 0.95f;
float num2 = -6.53f;
CategoryHeaderMasked categoryHeaderMasked =
Object.Instantiate(__instance.categoryHeaderOrigin, __instance.settingsContainer);
categoryHeaderMasked.SetHeader(StringNames.RoleQuotaLabel, 61);
categoryHeaderMasked.transform.localScale = Vector3.one;
categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, 1.26f, -2f);
__instance.settingsInfo.Add(categoryHeaderMasked.gameObject);
var list = new List<RoleBehaviour>();
for (int i = 0; i < 2; i++)
{
var categoryHeaderRoleVariant =
Object.Instantiate(__instance.categoryHeaderRoleOrigin, __instance.settingsContainer);
categoryHeaderRoleVariant.SetHeader(
(i == 0) ? StringNames.CrewmateRolesHeader : StringNames.ImpostorRolesHeader,
61);
categoryHeaderRoleVariant.transform.localScale = Vector3.one;
categoryHeaderRoleVariant.transform.localPosition = new Vector3(0.09f, num, -2f);
__instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject);
num -= 0.696f;
var roles = RoleManager.Instance.AllRoles.ToArray().Where(x =>
x is not ICustomRole && !x.IsRoleBlacklisted() && x.Role != RoleTypes.Crewmate &&
x.Role != RoleTypes.Impostor && ((i == 0 && x.TeamType is RoleTeamTypes.Crewmate) ||
(i == 1 && x.TeamType is RoleTeamTypes.Impostor)) &&
x.Role != RoleTypes.CrewmateGhost && x.Role != RoleTypes.ImpostorGhost).ToList();
for (int j = 0; j < roles.Count; j++)
{
var roleBehaviour = roles[j];
int chancePerGame =
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(
roleBehaviour.Role);
int numPerGame =
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions
.GetNumPerGame(roleBehaviour.Role);
bool flag = numPerGame == 0;
var viewSettingsInfoPanelRoleVariant =
Object.Instantiate(
__instance.infoPanelRoleOrigin,
__instance.settingsContainer);
viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one;
viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f);
if (!flag)
{
list.Add(roleBehaviour);
}
viewSettingsInfoPanelRoleVariant.SetInfo(
roleBehaviour.NiceName,
numPerGame,
chancePerGame,
61,
(i == 0) ? Palette.CrewmateRoleBlue : Palette.ImpostorRoleRed,
roleBehaviour.RoleIconSolid,
i == 0,
flag);
__instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject);
num -= 0.664f;
}
}
if (list.Count > 0)
{
var categoryHeaderMasked2 =
Object.Instantiate(__instance.categoryHeaderOrigin, __instance.settingsContainer);
categoryHeaderMasked2.SetHeader(StringNames.RoleSettingsLabel, 61);
categoryHeaderMasked2.transform.localScale = Vector3.one;
categoryHeaderMasked2.transform.localPosition = new Vector3(-9.77f, num, -2f);
__instance.settingsInfo.Add(categoryHeaderMasked2.gameObject);
num -= 2.1f;
float num3 = 0f;
for (int k = 0; k < list.Count; k++)
{
float num4;
if (k % 2 == 0)
{
num4 = -5.8f;
if (k > 0)
{
num -= num3 + 0.85f;
num3 = 0f;
}
}
else
{
num4 = 0.14999962f;
}
var advancedRoleViewPanel =
Object.Instantiate(__instance.advancedRolePanelOrigin, __instance.settingsContainer);
advancedRoleViewPanel.transform.localScale = Vector3.one;
advancedRoleViewPanel.transform.localPosition = new Vector3(num4, num, -2f);
float num5 = advancedRoleViewPanel.SetUp(list[k], 0.85f, 61);
if (num5 > num3)
{
num3 = num5;
}
__instance.settingsInfo.Add(advancedRoleViewPanel.gameObject);
}
}
__instance.scrollBar.SetYBoundsMax(-num);
return false;
}
if (__instance.currentTab == ModifiersTabName)
@@ -278,7 +382,7 @@ public static class LobbyViewPanePatches
foreach (var grouping in sortedRoleGroups)
{
if (!grouping.Any() || grouping.All(x => x.Configuration.HideSettings))
if (!grouping.Any() || grouping.All(x => x.Configuration.HideSettings || !x.VisibleInSettings()))
{
continue;
}
@@ -309,7 +413,7 @@ public static class LobbyViewPanePatches
foreach (var customRole in grouping)
{
if (customRole.Configuration.HideSettings)
if (customRole.Configuration.HideSettings || !customRole.VisibleInSettings())
{
continue;
}
+20 -2
View File
@@ -216,7 +216,16 @@ public static class OptionsPatches
return true;
}
__instance.Value += __instance.Increment;
var increment = __instance.Increment;
if (Input.GetKey(KeyCode.LeftShift) &&
__instance.TryGetComponent<MiraNumberOptionComponent>(out var miraNumber) &&
miraNumber.ShiftIncrementToggle)
{
increment /= 2;
}
__instance.Value += increment;
if (__instance.Value > __instance.ValidRange.max)
{
__instance.Value = __instance.ValidRange.min;
@@ -235,7 +244,16 @@ public static class OptionsPatches
return true;
}
__instance.Value -= __instance.Increment;
var increment = __instance.Increment;
if (Input.GetKey(KeyCode.LeftShift) &&
__instance.TryGetComponent<MiraNumberOptionComponent>(out var miraNumber) &&
miraNumber.ShiftIncrementToggle)
{
increment /= 2;
}
__instance.Value -= increment;
if (__instance.Value < __instance.ValidRange.min)
{
__instance.Value = __instance.ValidRange.max;
@@ -74,10 +74,10 @@ public static class RoleSettingMenuPatches
var num = 0.662f;
var list = CustomRoleManager.AllRoles.Where(
r => !r.IsCustomRole() && r.TeamType == RoleTeamTypes.Crewmate && r.Role != RoleTypes.Crewmate &&
r => !r.IsCustomRole() && !r.IsRoleBlacklisted() && r.TeamType == RoleTeamTypes.Crewmate && r.Role != RoleTypes.Crewmate &&
r.Role != RoleTypes.CrewmateGhost).ToList();
var list2 = CustomRoleManager.AllRoles.Where(
r => !r.IsCustomRole() && r.TeamType == RoleTeamTypes.Impostor && r.Role != RoleTypes.Impostor &&
r => !r.IsCustomRole() && !r.IsRoleBlacklisted() && r.TeamType == RoleTeamTypes.Impostor && r.Role != RoleTypes.Impostor &&
r.Role != RoleTypes.ImpostorGhost).ToList();
if (__instance.roleTabs == null || __instance.roleTabs.Count == 0)
@@ -160,7 +160,7 @@ public static class RoleSettingMenuPatches
foreach (var grouping in sortedRoleGroups)
{
if (!grouping.Any() || grouping.All(x=>x.Configuration.HideSettings))
if (!grouping.Any() || grouping.All(x=> x.Configuration.HideSettings || !x.VisibleInSettings()))
{
continue;
}
+30 -1
View File
@@ -1,5 +1,8 @@
using System.Globalization;
using System.Linq;
using HarmonyLib;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.OptionTypes;
using MiraAPI.Utilities;
using UnityEngine;
@@ -28,7 +31,33 @@ public static class SettingPatches
string result;
var suffix = (MiraNumberSuffixes)__instance.SuffixType;
if (__instance.ZeroIsInfinity && Mathf.Abs(value) < 0.0001f)
var custom =
ModdedOptionsManager.ModdedOptions.Values.FirstOrDefault(opt =>
opt.OptionBehaviour != null && opt.OptionBehaviour.Data == __instance);
if (custom is ModdedNumberOption moddedNumberOption)
{
if (moddedNumberOption.NegativeWordValue != "#" && (int)value == -1)
{
result = $"<b>{moddedNumberOption.NegativeWordValue}</b>";
}
else if (moddedNumberOption.ZeroWordValue != "#" && Mathf.Abs(value) < 0.0001f)
{
result = $"<b>{moddedNumberOption.ZeroWordValue}</b>";
}
else
{
result = suffix switch
{
MiraNumberSuffixes.None => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo),
MiraNumberSuffixes.Multiplier => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + "x",
MiraNumberSuffixes.Percent => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + "%",
_ => TranslationController.Instance.GetString(
StringNames.GameSecondsAbbrev,
(Il2CppSystem.Object[])[value.ToString(__instance.FormatString, CultureInfo.InvariantCulture)]),
};
}
}
else if (__instance.ZeroIsInfinity && Mathf.Abs(value) < 0.0001f)
{
result = "<b>∞</b>";
}
+4 -2
View File
@@ -127,17 +127,19 @@ internal static class PlayerControlPatches
return;
}
var role = __instance.Data?.Role;
foreach (var button in CustomButtonManager.CustomButtons)
{
if (__instance.Data?.Role == null)
if (role == null)
{
continue;
}
try
{
if (!button.Enabled(__instance.Data?.Role))
if (!button.Enabled(role))
{
button.SetActive(false, role);
continue;
}
+1 -6
View File
@@ -46,7 +46,7 @@ public static class KillButtonPatches
}
/// <summary>
/// Use Custom Murder if player is custom role.
/// Use Custom Murder instead of Murder Player from vanilla.
/// </summary>
[HarmonyPrefix]
[HarmonyPatch(nameof(KillButton.DoClick))]
@@ -58,11 +58,6 @@ public static class KillButtonPatches
return false;
}
if (PlayerControl.LocalPlayer.Data.Role is not ICustomRole)
{
return true;
}
PlayerControl.LocalPlayer.RpcCustomMurder(__instance.currentTarget);
__instance.SetTarget(null);
@@ -0,0 +1,183 @@
using AmongUs.GameOptions;
using HarmonyLib;
using Il2CppSystem.Collections.Generic;
using System.Linq;
using MiraAPI.Roles;
namespace MiraAPI.Patches.Roles;
[HarmonyPatch(typeof(LogicRoleSelectionHnS))]
public static class LogicRoleSelectionHnsPatch
{
[HarmonyPrefix]
[HarmonyPatch(nameof(LogicRoleSelectionHnS.AssignRolesForTeam))]
public static bool AssignRolesForTeam(
LogicRoleSelectionHnS __instance,
List<NetworkedPlayerInfo> players,
IGameOptions opts,
RoleTeamTypes team,
int teamMax,
Il2CppSystem.Nullable<RoleTypes> defaultRole)
{
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Team: {team}, Max: {teamMax}, Players: {players.Count}, DefaultRole: {defaultRole}");
int num = 0;
IRoleOptionsCollection roleOptions = opts.RoleOptions;
var source = RoleManager.Instance.AllRoles.ToArray()
.Where(role => role.TeamType == team && !RoleManager.IsGhostRole(role.Role) &&
CustomRoleUtils.CanSpawnOnCurrentMode(role));
var assignmentData = source.Where(x => !x.IsDead).Select(role =>
new RoleManager.RoleAssignmentData(
role,
roleOptions.GetNumPerGame(role.Role),
roleOptions.GetChancePerGame(role.Role))).ToList();
var source2 = CustomRoleUtils.GetPossibleRoles(assignmentData, x => x.Chance == 100);
var guaranteedRoles = source.Where(x => source2.Contains(((ushort)x.Role, 100)));
List<RoleTypes> list = new List<RoleTypes>();
if (team == RoleTeamTypes.Crewmate)
{
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Before Guaranteed Assignment");
foreach (RoleManager.RoleAssignmentData roleAssignmentData in guaranteedRoles.Select((x) =>
new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100)))
{
for (;;)
{
RoleManager.RoleAssignmentData roleAssignmentData2 = roleAssignmentData;
int count = roleAssignmentData2.Count;
roleAssignmentData2.Count = count - 1;
if (count <= 0)
{
break;
}
list.Add(roleAssignmentData.Role.Role);
}
}
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Guaranteed Assignment");
AssignRolesFromList(players, teamMax, list, ref num);
var list2 = source.Where(x => !x.IsDead).Select(role =>
new RoleManager.RoleAssignmentData(
role,
roleOptions.GetNumPerGame(role.Role),
roleOptions.GetChancePerGame(role.Role))).ToList();
list.Clear();
foreach (RoleManager.RoleAssignmentData roleAssignmentData3 in list2)
{
for (int i = 0; i < roleAssignmentData3.Count; i++)
{
if (HashRandom.Next(101) < roleAssignmentData3.Chance)
{
list.Add(roleAssignmentData3.Role.Role);
}
}
}
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Potential Assignment");
AssignRolesFromList(players, teamMax, list, ref num);
var basicRole = RoleTypes.Engineer;
while (list.Count < players.Count && list.Count + num < teamMax)
{
list.Add(basicRole);
}
AssignRolesFromList(players, teamMax, list, ref num);
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Fallback Assignment");
}
else if (team == RoleTeamTypes.Impostor)
{
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Before Guaranteed Assignment");
var newImpostors = new List<NetworkedPlayerInfo>();
// Specified Seeker
if (__instance.hnsManager.LogicOptionsHnS.HasImpostorPlayerID() &&
__instance.hnsManager.LogicOptionsHnS.ValidateImpostorPlayerID(players) &&
!AmongUsClient.Instance.IsGamePublic)
{
NetworkedPlayerInfo networkedPlayerInfo = players.ToArray()
.First(p => p.PlayerId == __instance.hnsManager.LogicOptionsHnS.ImpostorPlayerID());
players.Remove(networkedPlayerInfo);
newImpostors.Add(networkedPlayerInfo);
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Seeker is {networkedPlayerInfo.PlayerName}, ID: {networkedPlayerInfo.PlayerId}");
}
// Random Seeker
else
{
int num2 = 0;
while (num2 < teamMax && players.Count > 0)
{
PseudoRandomList<NetworkedPlayerInfo> pseudoRandomList = new PseudoRandomList<NetworkedPlayerInfo>(AmongUsClient.Instance.GameId);
players._items.Do(x => pseudoRandomList.Add(x));
for (int i = 0; i < GameData.RoundsPlayedInSession; i++)
{
pseudoRandomList.PickRandom();
}
NetworkedPlayerInfo networkedPlayerInfo = pseudoRandomList.PickRandom();
players.Remove(networkedPlayerInfo);
newImpostors.Add(networkedPlayerInfo);
num2++;
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Seeker is {networkedPlayerInfo.PlayerName}, ID: {networkedPlayerInfo.PlayerId}");
}
}
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Guaranteed Assignment");
foreach (RoleManager.RoleAssignmentData roleAssignmentData in guaranteedRoles.Select((x) =>
new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100)))
{
for (;;)
{
RoleManager.RoleAssignmentData roleAssignmentData2 = roleAssignmentData;
int count = roleAssignmentData2.Count;
roleAssignmentData2.Count = count - 1;
if (count <= 0)
{
break;
}
list.Add(roleAssignmentData.Role.Role);
}
}
AssignRolesFromList(newImpostors, teamMax, list, ref num);
var list2 = source.Where(x => !x.IsDead).Select(role =>
new RoleManager.RoleAssignmentData(
role,
roleOptions.GetNumPerGame(role.Role),
roleOptions.GetChancePerGame(role.Role))).ToList();
list.Clear();
foreach (RoleManager.RoleAssignmentData roleAssignmentData3 in list2)
{
for (int i = 0; i < roleAssignmentData3.Count; i++)
{
if (HashRandom.Next(101) < roleAssignmentData3.Chance)
{
list.Add(roleAssignmentData3.Role.Role);
}
}
}
AssignRolesFromList(newImpostors, teamMax, list, ref num);
var basicRole = RoleTypes.Impostor;
while (list.Count < newImpostors.Count && list.Count + num < teamMax)
{
list.Add(basicRole);
}
AssignRolesFromList(newImpostors, teamMax, list, ref num);
}
return false;
}
public static void AssignRolesFromList(List<NetworkedPlayerInfo> players, int teamMax, List<RoleTypes> roleList, ref int rolesAssigned)
{
while (roleList.Count > 0 && players.Count > 0 && rolesAssigned < teamMax)
{
int index = HashRandom.FastNext(roleList.Count);
RoleTypes roleType = roleList[index];
roleList.RemoveAt(index);
int index2 = HashRandom.FastNext(players.Count);
players[index2].Object.RpcSetRole(roleType, false);
players.RemoveAt(index2);
rolesAssigned++;
}
}
}
@@ -0,0 +1,93 @@
using AmongUs.GameOptions;
using HarmonyLib;
using Il2CppSystem.Collections.Generic;
using System.Linq;
using MiraAPI.Roles;
namespace MiraAPI.Patches.Roles;
[HarmonyPatch(typeof(LogicRoleSelectionNormal))]
public static class LogicRoleSelectionNormalPatch
{
[HarmonyPrefix]
[HarmonyPatch(nameof(LogicRoleSelectionNormal.AssignRolesForTeam))]
public static bool AssignRolesForTeam(
LogicRoleSelectionNormal __instance,
List<NetworkedPlayerInfo> players,
IGameOptions opts,
RoleTeamTypes team,
int teamMax,
Il2CppSystem.Nullable<RoleTypes> defaultRole)
{
int num = 0;
var source = RoleManager.Instance.AllRoles.ToArray()
.Where(role => role.TeamType == team && !RoleManager.IsGhostRole(role.Role) &&
CustomRoleUtils.CanSpawnOnCurrentMode(role));
List<RoleTypes> list = new List<RoleTypes>();
IRoleOptionsCollection roleOptions = opts.RoleOptions;
var assignmentData = source.Where(x => !x.IsDead).Select(role =>
new RoleManager.RoleAssignmentData(
role,
roleOptions.GetNumPerGame(role.Role),
roleOptions.GetChancePerGame(role.Role))).ToList();
var source2 = CustomRoleUtils.GetPossibleRoles(assignmentData, x => x.Chance == 100);
var guaranteedRoles = source.Where(x => source2.Contains(((ushort)x.Role, 100)));
foreach (RoleManager.RoleAssignmentData roleAssignmentData in guaranteedRoles.Select((x) =>
new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100)))
{
for (;;)
{
RoleManager.RoleAssignmentData roleAssignmentData2 = roleAssignmentData;
int count = roleAssignmentData2.Count;
roleAssignmentData2.Count = count - 1;
if (count <= 0)
{
break;
}
list.Add(roleAssignmentData.Role.Role);
}
}
__instance.AssignRolesFromList(players, teamMax, list, ref num);
var list2 = source.Where(x => !x.IsDead).Select(role =>
new RoleManager.RoleAssignmentData(
role,
roleOptions.GetNumPerGame(role.Role),
roleOptions.GetChancePerGame(role.Role))).ToList();
list.Clear();
foreach (RoleManager.RoleAssignmentData roleAssignmentData3 in list2)
{
for (int i = 0; i < roleAssignmentData3.Count; i++)
{
if (HashRandom.Next(101) < roleAssignmentData3.Chance)
{
list.Add(roleAssignmentData3.Role.Role);
}
}
}
__instance.AssignRolesFromList(players, teamMax, list, ref num);
var defaultRole2 = team is RoleTeamTypes.Crewmate ? RoleTypes.Crewmate : RoleTypes.Impostor;
try
{
defaultRole2 = defaultRole.Value;
}
catch
{
// Ignored
}
while (list.Count < players.Count && list.Count + num < teamMax)
{
list.Add(defaultRole2);
}
__instance.AssignRolesFromList(players, teamMax, list, ref num);
return false;
}
}
+2 -2
View File
@@ -20,7 +20,7 @@ public static class NameTagPatch
return false;
}
if (PlayerControl.LocalPlayer.Data.Role.IsImpostor && otherPlayerRole.IsImpostor)
if (PlayerControl.LocalPlayer.Data?.Role?.IsImpostor == true && otherPlayerRole.IsImpostor)
{
return true;
}
@@ -30,7 +30,7 @@ public static class NameTagPatch
return true;
}
if (!PlayerControl.LocalPlayer || !PlayerControl.LocalPlayer.Data || !PlayerControl.LocalPlayer.Data.Role || !otherPlayerRole)
if (!PlayerControl.LocalPlayer || !PlayerControl.LocalPlayer.Data || !PlayerControl.LocalPlayer.Data?.Role || !otherPlayerRole)
{
__result = Color.white;
}
@@ -1,6 +1,7 @@
using System.Linq;
using AmongUs.GameOptions;
using HarmonyLib;
using InnerNet;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
using MiraAPI.Modifiers;
+25
View File
@@ -12,6 +12,31 @@ public interface IMiraPlugin
/// </summary>
string OptionsTitleText { get; }
/// <summary>
/// Gets the name for the first custom category in the game options menu, if any.
/// </summary>
public virtual string CustomOptionMenuNameOne => "Custom Category 1";
/// <summary>
/// Gets the name for the second custom category in the game options menu, if any.
/// </summary>
public virtual string CustomOptionMenuNameTwo => "Custom Category 2";
/// <summary>
/// Gets the description for the second custom category in the game options menu, if any.
/// </summary>
public virtual string ModifierMenuDescription => "Configure modifiers and their settings here!";
/// <summary>
/// Gets the description for the first custom category in the game options menu, if any.
/// </summary>
public virtual string CustomOptionMenuOneDescription => "Apply game settings for this mod!";
/// <summary>
/// Gets the description for the second custom category in the game options menu, if any.
/// </summary>
public virtual string CustomOptionMenuTwoDescription => "Apply game settings for this mod!";
/// <summary>
/// Gets the BepInEx configuration file for the plugin.
/// </summary>
+1 -1
View File
@@ -51,7 +51,7 @@ public class MiraPluginInfo
public IReadOnlyCollection<CustomActionButton> Buttons { get; private set; } = null!;
/// <summary>
/// Gets a read only dictionary of this plugin's custom game modes.
/// Gets a read only dictionary of this plugin's custom presets.
/// </summary>
public IReadOnlyCollection<OptionPreset> Presets { get; internal set; } = null!;
+63 -1
View File
@@ -1,4 +1,5 @@
using System.IO;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using BepInEx.Configuration;
using MiraAPI.PluginLoading;
@@ -18,6 +19,13 @@ public static class PresetManager
/// </summary>
public static string PresetDirectory { get; } = Path.GetFullPath("mira_presets", Application.persistentDataPath);
internal static List<OptionPreset> InternalMasterPresets { get; } = [];
/// <summary>
/// Gets a read only dictionary of this plugin's custom presets.
/// </summary>
public static IReadOnlyCollection<OptionPreset> MasterPresets { get; internal set; } = null!;
internal static void CreateDefaultPreset(MiraPluginInfo plugin)
{
var presetPath = Path.Combine(PresetDirectory, plugin.PluginId);
@@ -93,4 +101,58 @@ public static class PresetManager
}
plugin.Presets = [..plugin.InternalPresets];
}
/// <summary>
/// Loads the master presets by reading the configuration files from the preset directory.
/// </summary>
public static void LoadMasterPreset()
{
/*foreach (var btn in plugin.InternalPresets.Select(x=>x.PresetButton))
{
if (btn != null)
{
Object.DestroyImmediate(btn);
}
}
plugin.InternalPresets.Clear();*/
if (!Directory.Exists(PresetDirectory))
{
Directory.CreateDirectory(PresetDirectory);
}
var pluginPresetPath = Path.Combine(PresetDirectory, "MasterPresets");
if (!Directory.Exists(pluginPresetPath))
{
Directory.CreateDirectory(pluginPresetPath);
}
foreach (var file in Directory.GetFiles(pluginPresetPath, "*.cfg"))
{
var fileName = Path.GetFileName(file);
Info($"Loading preset file {fileName}");
var presetName = Path.GetFileNameWithoutExtension(file);
var presetConfig = new ConfigFile(file, false)
{
SaveOnConfigSet = false,
};
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
{
foreach (var option in plugin.InternalOptions.Where(x=>x.IncludeInPreset))
{
option.Bind(presetConfig);
}
foreach (var role in plugin.InternalRoles.Values.OfType<ICustomRole>().Where(x=>!x.Configuration.HideSettings))
{
role.BindConfig(presetConfig);
}
}
presetConfig.Save();
// InternalMasterPresets.Add(new OptionPreset(presetName, plugin, presetConfig));
}
MasterPresets = [..InternalMasterPresets];
}
}
+3 -3
View File
@@ -1,8 +1,8 @@
using System;
using AmongUs.GameOptions;
using System;
using MiraAPI.Patches.Freeplay;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Roles;
/// <summary>
@@ -47,7 +47,7 @@ public record struct CustomRoleConfiguration
{
ModdedRoleTeams.Crewmate => TranslationController.Instance.GetString(StringNames.Crewmate),
ModdedRoleTeams.Impostor => TranslationController.Instance.GetString(StringNames.Impostor),
_ => "Neutral",
_ => TaskAdderPatches.NeutralName,
};
IntroSound = role.Team is ModdedRoleTeams.Crewmate
? CustomRoleManager.CrewmateIntroSound
+58 -1
View File
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
@@ -13,6 +14,62 @@ namespace MiraAPI.Roles;
/// </summary>
public static class CustomRoleUtils
{
/// <summary>
/// Determines whether the specified role can spawn in general, accounting for gamemodes and everything else.
/// </summary>
/// <param name="role">The role you would like to check for.</param>
/// <returns>True if the role is able to spawn, otherwise false.</returns>
public static bool CanSpawnOnCurrentMode(RoleBehaviour role)
{
if (role is ICustomRole custom)
{
return custom.CanSpawnOnCurrentMode();
}
if (GameManager.Instance.IsHideAndSeek())
{
return role.Role is RoleTypes.Engineer || role.Role is RoleTypes.Impostor;
}
return true;
}
public static List<(ushort RoleType, int Chance)> GetPossibleRoles(
List<RoleManager.RoleAssignmentData> assignmentData,
Func<RoleManager.RoleAssignmentData, bool>? predicate = null)
{
var roles = new List<(ushort, int)>();
assignmentData.Where(x => predicate == null || predicate(x)).ToList().ForEach(x =>
{
for (var i = 0; i < x.Count; i++)
{
roles.Add(((ushort)x.Role.Role, x.Chance));
}
});
return roles;
}
public static RoleManager.RoleAssignmentData GetAssignData(RoleTypes roleType)
{
var currentGameOptions = GameOptionsManager.Instance.CurrentGameOptions;
var roleOptions = currentGameOptions.RoleOptions;
var role = GetRegisteredRole(roleType);
var assignmentData = new RoleManager.RoleAssignmentData(role, roleOptions.GetNumPerGame(role!.Role),
roleOptions.GetChancePerGame(role.Role));
return assignmentData;
}
public static RoleBehaviour? GetRegisteredRole(RoleTypes roleType)
{
// we want to prioritize the custom roles because the role has the right RoleColour/TeamColor
var role = CustomRoleManager.AllRoles.FirstOrDefault(x => x.Role == roleType);
return role;
}
/// <summary>
/// Gets all active in-game roles.
/// </summary>
+13 -1
View File
@@ -1,4 +1,5 @@
using System.Text;
using System;
using System.Text;
using BepInEx.Configuration;
using MiraAPI.GameOptions;
using MiraAPI.Modifiers;
@@ -239,4 +240,15 @@ public interface ICustomRole : IOptionable
{
return true;
}
/// <summary>
/// Determines whether the role can spawn in general, accounting for gamemodes and everything else.
/// </summary>
/// <returns>True if the role is able to spawn, otherwise false.</returns>
public virtual bool CanSpawnOnCurrentMode() => !GameManager.Instance.IsHideAndSeek();
/// <summary>
/// Gets the function that determines whether the role should be toggled on or off in the game settings.
/// </summary>
public virtual Func<bool> VisibleInSettings => () => true;
}
+16
View File
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using AmongUs.GameOptions;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using MiraAPI.Roles;
@@ -536,4 +537,19 @@ public static class Helpers
}
return role.NiceName;
}
/// <summary>
/// Returns whether the role is blacklisted from appearing and spawning. (Only applicable to vanilla roles)
/// </summary>
/// <param name="role">The role to check.</param>
/// <returns>The role's blacklist status.</returns>
public static bool IsRoleBlacklisted(this RoleBehaviour role)
{
// This should be patchable by mods when a vanilla role is meant to be replaced by a custom role.
if (role.Role is RoleTypes.Engineer)
{
return true;
}
return false;
}
}