Compare commits

..
26 Commits
Author SHA1 Message Date
Atony e8536e5d4b Merge pull request #80 from All-Of-Us-Mods/dev
## What's New
- Individual game options can be reset if they are a part of a preset.
- A new meeting-specific murder rpc is now available for use, checking for kills happening within and outside of a meeting.
- ## What's Changed
- Framerate Slider has been renamed to be more accurate
- Memory usage per frame has been improved
2026-02-08 16:30:05 -06:00
Atony 090db766b2 Bump version from 0.3.6 to 0.3.7 2026-02-08 16:25:37 -06:00
Atony 1f0027108e Automatically change reactor dev build indicator 2026-02-01 13:22:57 -06:00
Atony 665724010d Fix how to play errors without custom roles 2026-01-26 13:02:39 -06:00
Atony 2d58c91289 Replace alt murder rpc 2026-01-21 04:47:27 -06:00
XtraCube b3ed6d67f9 Reduce memory allocations in HudManagerPatches.cs 2026-01-20 16:35:44 -05:00
Atony 64ef78e3a2 Fix wordwrap on local setting 2026-01-20 10:21:37 -06:00
Atony 5e9e4012ef Update CustomMurderRpc.cs 2026-01-19 21:38:45 -06:00
Atony fe04a7033a Change kill button to use alt murder rpc 2026-01-19 21:15:52 -06:00
Atony ba0ee49255 Add reset button for game options 2026-01-19 20:10:21 -06:00
Atony 97528b4380 Add alternative Murder RPC for meeting checks 2026-01-19 19:31:31 -06:00
Atony 50030c113b Merge pull request #78 from All-Of-Us-Mods/dev
## What's New
- Screen shake no longer interferes with scrolling in the host options menu
## What's Changed
- Sabotage flashes no longer break on Starlight
- Using the button scale setting no longer breaks the UI.
- If a role fails to register to a marshalling error (nullables) the game will no longer crash.
2026-01-04 12:41:46 -06:00
Atony 20e51cc993 Bump version 2026-01-04 12:38:43 -06:00
Atony 64b5e01ba1 Improve scrolling in roles menu itself 2026-01-03 19:06:51 -06:00
Atony d4469153ab Improve scrolling in roles menu 2026-01-03 19:00:46 -06:00
Atony ab67c233d8 Add FPS slider (default to 120 fps) 2026-01-03 18:55:12 -06:00
Atony c72eacc7a2 Fix options scroll with screen shake 2026-01-03 18:54:57 -06:00
Atony 13072d6834 Fix dualshock light errors on Starlight 2026-01-02 22:57:58 -06:00
Atony c6342750ef Add checks to prevent errors on Starlight 2026-01-02 15:17:31 -06:00
XtraCube 5c2e95376d add exception handling around role registration 2025-12-31 22:38:01 -05:00
Atony 067859b9fd Fix scale factor not setting properly when changed 2025-12-31 18:36:19 -06:00
Atony fe3bfcdbc4 Merge pull request #77 from All-Of-Us-Mods/dev
- Added Vanilla Button Click Event (and respective Cancel Event)
- Added accessibility settings for sabotage flashes and blares
- Added an in-game slider for resizing ability buttons
2025-12-28 18:00:57 -06:00
Atony 5d01462a32 Adjust local setting groups 2025-12-28 17:55:33 -06:00
Atony a9b6f40ed0 Add Button Scale Slider 2025-12-28 17:51:39 -06:00
Atony eae0024ec3 Add Accessibility settings for sabotages 2025-12-22 21:41:36 -06:00
Atony 2907564d6a Add Vanilla Button Click Event (& Cancel Event) 2025-12-16 22:14:50 -06:00
27 changed files with 726 additions and 54 deletions

No files matched your search

+2 -2
View File
@@ -8,7 +8,7 @@
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<VersionPrefix>0.3.4</VersionPrefix>
<VersionPrefix>0.3.7</VersionPrefix>
<VersionSuffix>dev</VersionSuffix>
<Authors>All Of Us, XtraCube, Angxl</Authors>
@@ -42,4 +42,4 @@
<PropertyGroup>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)\stylecop.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>
</Project>
+18 -2
View File
@@ -1,4 +1,5 @@
using System.Linq;
using System.Globalization;
using System.Linq;
using MiraAPI.Events;
using MiraAPI.Events.Mira;
using MiraAPI.Events.Vanilla.Gameplay;
@@ -17,7 +18,7 @@ public static class ExampleEventHandlers
{
public static void Initialize()
{
// You can register event handlers with the MiraEventManager class.
// You can register event handlers with the MiraEventManager class
var handle = MiraEventManager.RegisterEventHandler<BeforeMurderEvent>(@event =>
{
Logger<ExamplePlugin>.Info($"{@event.Source.Data.PlayerName} is about to kill {@event.Target.Data.PlayerName}");
@@ -37,6 +38,21 @@ public static class ExampleEventHandlers
MiraEventManager.UnregisterEventHandler(handle);
}
// If you want to check vanilla buttons, use something like this!
[RegisterEvent]
public static void VanillaButtonClickHandler(VanillaButtonClickEvent @event)
{
Logger<ExamplePlugin>.Info($"{@event.Button.buttonLabelText.text} is being used!");
if (@event.PlayerTarget != null)
{
Logger<ExamplePlugin>.Info($"{@event.PlayerTarget.Data.PlayerName} is being targeted!");
}
else if (@event.VentTarget != null)
{
Logger<ExamplePlugin>.Info($"Vent with id {@event.VentTarget.Id.ToString(CultureInfo.InvariantCulture)} is being targeted!");
}
}
// If you want to add extra votes to a player, do something like this.
[RegisterEvent]
public static void StartMeetingEvent(StartMeetingEvent _)
@@ -1,4 +1,6 @@
namespace MiraAPI.Events.Vanilla.Gameplay;
using MiraAPI.Networking;
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// Event that is invoked before a player is murdered. This event is cancelable.
@@ -15,6 +17,24 @@ public sealed class BeforeMurderEvent : MiraCancelableEvent
/// </summary>
public PlayerControl Target { get; }
/// <summary>
/// Gets whether the murder was meant to be done in a meeting via an enum.
/// </summary>
public MeetingCheck InMeeting { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BeforeMurderEvent"/> class.
/// </summary>
/// <param name="source">The player that is killing the target.</param>
/// <param name="target">The player that is being killed.</param>
/// <param name="inMeeting">Whether the murder is intended to be triggered in a meeting.</param>
public BeforeMurderEvent(PlayerControl source, PlayerControl target, MeetingCheck inMeeting)
{
Source = source;
Target = target;
InMeeting = inMeeting;
}
/// <summary>
/// Initializes a new instance of the <see cref="BeforeMurderEvent"/> class.
/// </summary>
@@ -24,5 +44,6 @@ public sealed class BeforeMurderEvent : MiraCancelableEvent
{
Source = source;
Target = target;
InMeeting = MeetingCheck.Ignore;
}
}
@@ -0,0 +1,44 @@
using MiraAPI.Hud;
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// Invoked when a Vanilla Button click is cancelled. Do not use for custom buttons.
/// </summary>
/// <typeparam name="T">The Vanilla Button type.</typeparam>
public sealed class VanillaButtonCancelledEvent<T> : MiraEvent where T : AbilityButton
{
/// <summary>
/// Gets Vanilla Button whose click was cancelled.
/// </summary>
public T Button { get; }
/// <summary>
/// Initializes a new instance of the <see cref="VanillaButtonCancelledEvent{T}"/> class.
/// </summary>
/// <param name="button">The Mira Button whose click was cancelled.</param>
public VanillaButtonCancelledEvent(T button)
{
Button = button;
}
}
/// <summary>
/// Invoked when a Vanilla Button click is cancelled. Do not use for custom buttons.
/// </summary>
public sealed class VanillaButtonCancelledEvent : MiraEvent
{
/// <summary>
/// Gets Vanilla Button whose click was cancelled.
/// </summary>
public AbilityButton Button { get; }
/// <summary>
/// Initializes a new instance of the <see cref="VanillaButtonCancelledEvent"/> class.
/// </summary>
/// <param name="button">The Vanilla Button whose click was cancelled.</param>
public VanillaButtonCancelledEvent(AbilityButton button)
{
Button = button;
}
}
@@ -0,0 +1,65 @@
using MiraAPI.Hud;
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// Button click event for Vanilla Buttons only. Do not use for custom buttons.
/// </summary>
/// <typeparam name="T">The Vanilla Button type.</typeparam>
public sealed class VanillaButtonClickEvent<T> : MiraCancelableEvent where T : AbilityButton
{
/// <summary>
/// Gets Vanilla Button that was clicked.
/// </summary>
public T Button { get; }
/// <summary>
/// Gets the generic click event that is invoked before this button-specific event.
/// </summary>
public VanillaButtonClickEvent GenericClickEvent { get; }
/// <summary>
/// Initializes a new instance of the <see cref="VanillaButtonClickEvent{T}"/> class.
/// </summary>
/// <param name="button">The Mira Button that was clicked.</param>
/// <param name="genericClickEvent">The generic click event invoked before button-specific events.</param>
public VanillaButtonClickEvent(T button, VanillaButtonClickEvent genericClickEvent)
{
GenericClickEvent = genericClickEvent;
Button = button;
}
}
/// <summary>
/// Button click event for Vanilla Buttons only. Do not use for custom buttons.
/// </summary>
public sealed class VanillaButtonClickEvent : MiraCancelableEvent
{
/// <summary>
/// Gets Vanilla Button that was clicked.
/// </summary>
public AbilityButton Button { get; }
/// <summary>
/// Gets the target player, if any, of the Vanilla Button that was clicked.
/// </summary>
public PlayerControl? PlayerTarget { get; }
/// <summary>
/// Gets the target vent, if any, of the Vanilla Button that was clicked.
/// </summary>
public Vent? VentTarget { get; }
/// <summary>
/// Initializes a new instance of the <see cref="VanillaButtonClickEvent"/> class.
/// </summary>
/// <param name="button">The Vanilla Button that was clicked.</param>
/// <param name="playerTarget">The Player target, if any.</param>
/// <param name="ventTarget">The Vent target, if any.</param>
public VanillaButtonClickEvent(AbilityButton button, PlayerControl? playerTarget = null, Vent? ventTarget = null)
{
Button = button;
PlayerTarget = playerTarget;
VentTarget = ventTarget;
}
}
+1 -1
View File
@@ -69,4 +69,4 @@ public class BaseKeybind
{
Handler -= action;
}
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ namespace MiraAPI.Keybinds;
public class MiraKeybind : BaseKeybind
{
/// <summary>
/// Gets name of the keybind
/// Gets name of the keybind.
/// </summary>
public string Name { get; }
+12 -1
View File
@@ -1,4 +1,5 @@
global using static Reactor.Utilities.Logger<MiraAPI.MiraApiPlugin>;
using System;
using BepInEx;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
@@ -21,6 +22,11 @@ namespace MiraAPI;
[ReactorModFlags(ModFlags.RequireOnAllClients)]
public partial class MiraApiPlugin : BasePlugin
{
/// <summary>
/// Gets a value indicating whether the current device is running Starlight (on mobile).
/// </summary>
public static bool IsMobile => Constants.GetPlatformType() is Platforms.Android or Platforms.IPhone;
/// <summary>
/// Gets the branding Mira API color.
/// </summary>
@@ -31,6 +37,11 @@ public partial class MiraApiPlugin : BasePlugin
/// </summary>
public static Color DefaultHeaderColor { get; } = new Color32(77, 77, 77, 255);
/// <summary>
/// Gets a value indicating whether the current build is a development build or not. This mostly avoids confusion for users asking why the mod appears red.
/// </summary>
public static bool IsDevBuild => Version.Contains("ci", StringComparison.OrdinalIgnoreCase) || Version.Contains("dev", StringComparison.OrdinalIgnoreCase);
private static MiraPluginManager? PluginManager { get; set; }
internal Harmony Harmony { get; } = new(Id);
@@ -39,7 +50,7 @@ public partial class MiraApiPlugin : BasePlugin
{
Harmony.PatchAll();
ReactorCredits.Register("Mira API", Version, true, ReactorCredits.AlwaysShow);
ReactorCredits.Register("Mira API", Version, IsDevBuild, ReactorCredits.AlwaysShow);
PluginManager = new MiraPluginManager();
PluginManager.Initialize();
+63 -4
View File
@@ -1,12 +1,15 @@
using BepInEx.Configuration;
using MiraAPI.LocalSettings;
using MiraAPI.LocalSettings.Attributes;
using MiraAPI.Patches;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI;
/// <summary>
/// Mira API Config File Handler
/// Mira API Config File Handler.
/// </summary>
public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
{
@@ -21,22 +24,78 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
HideIconOnHover = false,
};
/// <summary>
/// Gets or sets the value stored for scaling buttons properly.
/// </summary>
public static float OldButtonScaleFactor { get; set; }
/// <inheritdoc />
public override void Open()
{
base.Open();
OldButtonScaleFactor = ButtonUIFactorSlider.Value;
}
/// <inheritdoc />
public override void OnOptionChanged(ConfigEntryBase configEntry)
{
base.OnOptionChanged(configEntry);
if (configEntry == ButtonUIFactorSlider)
{
if (HudManager.InstanceExists)
{
HudManagerPatches.ResizeUI(1f / OldButtonScaleFactor);
HudManagerPatches.ResizeUI(ButtonUIFactorSlider.Value);
}
OldButtonScaleFactor = ButtonUIFactorSlider.Value;
}
else if (configEntry == SetFpsSlider)
{
Application.targetFrameRate = (int)SetFpsSlider.Value;
}
}
/// <summary>
/// Gets whether the modifiers hud should be on the left side of the screen (under roles/task tab). Recommended for streamers.
/// </summary>
[LocalToggleSetting]
public ConfigEntry<bool> ModifiersHudLeftSide { get; private set; } = config.Bind("Displays", "Show Modifiers HUD on Left Side", false);
public ConfigEntry<bool> ModifiersHudLeftSide { get; private set; } = config.Bind("Visuals/UI", "Show Modifiers HUD on Left Side", false);
/// <summary>
/// Gets whether to show keybinds on buttons.
/// </summary>
[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
public ConfigEntry<bool> ShowKeybinds { get; private set; } = config.Bind("Visuals/UI", "Show Keybinds on Buttons", true);
/// <summary>
/// Gets the scale of the buttons.
/// </summary>
[LocalSliderSetting(min: 0.5f, max: 1.5f, suffixType: MiraNumberSuffixes.Multiplier, formatString: "0.00", displayValue: true)]
public ConfigEntry<float> ButtonUIFactorSlider { get; private set; } =
config.Bind("Visuals/UI", "Button Scale Factor", 0.75f);
/// <summary>
/// Gets the fps specified by the player.
/// </summary>
[LocalSliderSetting(min: 60f, max: 240f, suffixType: MiraNumberSuffixes.None, formatString: "0", displayValue: true, roundValue: true)]
public ConfigEntry<float> SetFpsSlider { get; private set; } =
config.Bind("Visuals/UI", "Max FPS", 120f);
/// <summary>
/// Gets whether to apply cosmetic changes to the TaskAdder.
/// </summary>
[LocalToggleSetting]
public ConfigEntry<bool> PrettyTaskAdder { get; private set; } = config.Bind("Freeplay", "Pretty Task Laptop", true);
/// <summary>
/// Gets whether to show the red flash from sabotages.
/// </summary>
[LocalToggleSetting]
public ConfigEntry<bool> EnableSabotageFlashes { get; private set; } = config.Bind("Accessibility", "Enable Sabotage Flashes", true);
/// <summary>
/// Gets whether to enable the sabotage sound effects or not.
/// </summary>
[LocalToggleSetting]
public ConfigEntry<bool> EnableSabotageBlares { get; private set; } = config.Bind("Accessibility", "Enable Sabotage Blare", true);
}
+63 -4
View File
@@ -32,7 +32,6 @@ public static class CustomMurderRpc
/// <param name="teleportMurderer">Should the killer be snapped to the dead player.</param>
/// <param name="showKillAnim">Should the kill animation be shown.</param>
/// <param name="playKillSound">Should the kill sound be played.</param>
[MethodRpc((uint)MiraRpc.CustomMurder, LocalHandling = RpcLocalHandling.Before)]
public static void RpcCustomMurder(
this PlayerControl source,
PlayerControl target,
@@ -42,11 +41,51 @@ public static class CustomMurderRpc
bool teleportMurderer = true,
bool showKillAnim = true,
bool playKillSound = true)
{
source.RpcCustomMurder(
target,
MeetingCheck.Ignore,
didSucceed,
resetKillTimer,
createDeadBody,
teleportMurderer,
showKillAnim,
playKillSound);
}
/// <summary>
/// Networked Custom Murder method, which checks for meetings as well.
/// </summary>
/// <param name="source">The killer.</param>
/// <param name="target">The player to murder.</param>
/// <param name="inMeeting">Whether the murder is intended to be triggered in a meeting.</param>
/// <param name="didSucceed">Whether the murder was successful or not.</param>
/// <param name="resetKillTimer">Should the kill timer be reset.</param>
/// <param name="createDeadBody">Should a dead body be created.</param>
/// <param name="teleportMurderer">Should the killer be snapped to the dead player.</param>
/// <param name="showKillAnim">Should the kill animation be shown.</param>
/// <param name="playKillSound">Should the kill sound be played.</param>
[MethodRpc((uint)MiraRpc.CustomMurder, LocalHandling = RpcLocalHandling.Before)]
public static void RpcCustomMurder(
this PlayerControl source,
PlayerControl target,
MeetingCheck inMeeting,
bool didSucceed = true,
bool resetKillTimer = true,
bool createDeadBody = true,
bool teleportMurderer = true,
bool showKillAnim = true,
bool playKillSound = true)
{
var murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError;
var beforeMurderEvent = new BeforeMurderEvent(source, target);
var beforeMurderEvent = new BeforeMurderEvent(source, target, inMeeting);
MiraEventManager.InvokeEvent(beforeMurderEvent);
var isMeetingActive = MeetingHud.Instance != null || ExileController.Instance != null;
if ((inMeeting is MeetingCheck.ForMeeting && !isMeetingActive) || (inMeeting is MeetingCheck.OutsideMeeting && isMeetingActive))
{
beforeMurderEvent.Cancel();
}
if (beforeMurderEvent.IsCancelled)
{
@@ -91,7 +130,6 @@ public static class CustomMurderRpc
{
return;
}
var data = target.Data;
if (resultFlags.HasFlag(MurderResultFlags.FailedProtected) ||
(resultFlags.HasFlag(MurderResultFlags.DecisionByHost) && target.protectedByGuardianId > -1))
@@ -174,7 +212,7 @@ public static class CustomMurderRpc
{
try
{
HudManager.Instance.KillOverlay.ShowKillAnimation(source.Data, data);
HudManager.Instance.KillOverlay.ShowKillAnimation(source.Data, target.Data);
}
catch (Exception e)
{
@@ -298,3 +336,24 @@ public static class CustomMurderRpc
source.isKilling = false;
}
}
/// <summary>
/// Checks for custom murders.
/// </summary>
public enum MeetingCheck
{
/// <summary>
/// Checks for meetings.
/// </summary>
ForMeeting,
/// <summary>
/// Checks for a meeting to not be active.
/// </summary>
OutsideMeeting,
/// <summary>
/// Doesn't check for a meeting at all.
/// </summary>
Ignore,
}
@@ -0,0 +1,67 @@
using System.Collections;
using BepInEx.Unity.IL2CPP.Utils.Collections;
using HarmonyLib;
using MiraAPI.LocalSettings;
using UnityEngine;
namespace MiraAPI.Patches.Accessibility;
[HarmonyPatch]
public static class HudManagerFlashPatches
{
[HarmonyPatch(typeof(HudManager), nameof(HudManager.StartReactorFlash))]
[HarmonyPrefix]
public static bool ReactorFlashPrefix(HudManager __instance)
{
if (__instance.ReactorFlash == null)
{
__instance.ReactorFlash = __instance.StartCoroutine(CoReactorFlash().WrapToIl2Cpp());
}
return false;
}
[HarmonyPatch(typeof(HudManager), nameof(HudManager.StartOxyFlash))]
[HarmonyPrefix]
public static bool OxygenFlashPrefix(HudManager __instance)
{
if (__instance.OxyFlash == null)
{
__instance.OxyFlash = __instance.StartCoroutine(CoReactorFlash().WrapToIl2Cpp());
}
return false;
}
public static IEnumerator CoReactorFlash()
{
if (!HudManager.InstanceExists)
{
yield break;
}
var hudManager = HudManager.Instance;
WaitForSeconds wait = new WaitForSeconds(1f);
bool light = false;
hudManager.FullScreen.color = new Color(1f, 0f, 0f, 0.37254903f);
while (true)
{
var settins = LocalSettingsTabSingleton<MiraApiSettings>.Instance;
hudManager.FullScreen.gameObject.SetActive(settins.EnableSabotageFlashes.Value && !hudManager.FullScreen.gameObject.activeSelf);
if (settins.EnableSabotageBlares.Value)
{
SoundManager.Instance.PlaySound(ShipStatus.Instance.SabotageSound, false, 1f, null);
}
light = !light;
/*if (!MiraApiPlugin.IsMobile)
{
if (hudManager.lightFlashHandle == null)
{
hudManager.lightFlashHandle = DualshockLightManager.Instance.AllocateLight();
hudManager.lightFlashHandle.color = new Color(1f, 0f, 0f, 1f);
hudManager.lightFlashHandle.intensity = 1f;
}
hudManager.lightFlashHandle.color.SetAlpha(light ? 1f : 0f);
}*/
yield return wait;
}
}
}
@@ -0,0 +1,75 @@
using System.Linq;
using AmongUs.GameOptions;
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
using MiraAPI.Events.Vanilla.Usables;
namespace MiraAPI.Patches.Events;
/// <summary>
/// Patches to invoke vent related MiraEvents.
/// </summary>
[HarmonyPatch]
public static class VanillaButtonPatches
{
[HarmonyPrefix]
[HarmonyPatch(typeof(AbilityButton), nameof(AbilityButton.DoClick))]
public static bool DoClickPrefix(AbilityButton __instance)
{
// Invoke the generic button click event.
PlayerControl? playerTarget = null;
Vent? ventTarget = null;
var role = PlayerControl.LocalPlayer.Data.Role;
if (role.Role is RoleTypes.Tracker)
{
playerTarget = role.Cast<TrackerRole>().currentTarget;
}
else if (role.Role is RoleTypes.GuardianAngel)
{
playerTarget = role.Cast<GuardianAngelRole>().currentTarget;
}
else if (role.Role is RoleTypes.Engineer)
{
ventTarget = role.Cast<EngineerRole>().currentTarget;
}
var genericEvent = new VanillaButtonClickEvent(__instance, playerTarget, ventTarget);
MiraEventManager.InvokeEvent(genericEvent);
if (genericEvent.IsCancelled)
{
MiraEventManager.InvokeEvent(new VanillaButtonCancelledEvent(__instance));
}
if (!genericEvent.IsCancelled)
{
return true;
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(SecondaryAbilityButton), nameof(SecondaryAbilityButton.DoClick))]
public static bool SecondaryDoClickPrefix(AbilityButton __instance)
{
// Invoke the generic button click event.
PlayerControl? playerTarget = null;
var role = PlayerControl.LocalPlayer.Data.Role;
if (role.Role is RoleTypes.Detective)
{
playerTarget = role.Cast<DetectiveRole>().currentTarget;
}
var genericEvent = new VanillaButtonClickEvent(__instance, playerTarget);
MiraEventManager.InvokeEvent(genericEvent);
if (genericEvent.IsCancelled)
{
MiraEventManager.InvokeEvent(new VanillaButtonCancelledEvent(__instance));
}
if (!genericEvent.IsCancelled)
{
return true;
}
return false;
}
}
+118 -11
View File
@@ -1,5 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using HarmonyLib;
using MiraAPI.Hud;
using MiraAPI.Keybinds;
@@ -25,9 +25,87 @@ public static class HudManagerPatches
public static Transform? BottomRight { get; private set; }
public static Transform? Buttons { get; private set; }
private static Dictionary<TextMeshPro, int> vanillaKeybindIcons = new();
private static Dictionary<TextMeshPro, int> vanillaKeybindIcons = [];
internal static List<TextMeshPro> ModdedKeybindIcons = new();
internal static List<TextMeshPro> ModdedKeybindIcons = [];
public static IEnumerator CoResizeUI()
{
while (!HudManager.Instance)
{
yield return null;
}
yield return new WaitForSeconds(0.01f);
ResizeUI(LocalSettingsTabSingleton<MiraApiSettings>.Instance.ButtonUIFactorSlider.Value);
}
public static void ResizeUI(float scaleFactor)
{
var baseButtons = HudManager.Instance.transform.FindChild("Buttons");
if (baseButtons != null)
{
foreach (var aspect in baseButtons.GetComponentsInChildren<AspectPosition>(true))
{
if (aspect.gameObject == null)
{
continue;
}
if (aspect.gameObject.transform.parent.name == "TopRight")
{
continue;
}
if (aspect.gameObject.transform.parent.transform.parent.name == "TopRight")
{
continue;
}
aspect.gameObject.SetActive(!aspect.isActiveAndEnabled);
aspect.DistanceFromEdge *= new Vector2(scaleFactor, scaleFactor);
aspect.gameObject.SetActive(!aspect.isActiveAndEnabled);
}
}
foreach (var button in HudManager.Instance.GetComponentsInChildren<ActionButton>(true))
{
if (button.gameObject == null)
{
continue;
}
button.gameObject.SetActive(!button.isActiveAndEnabled);
button.gameObject.transform.localScale *= scaleFactor;
button.gameObject.SetActive(!button.isActiveAndEnabled);
}
if (baseButtons != null)
{
foreach (var arrange in baseButtons.GetComponentsInChildren<GridArrange>(true))
{
if (!arrange.gameObject || !arrange.transform)
{
continue;
}
arrange.gameObject.SetActive(!arrange.isActiveAndEnabled);
arrange.CellSize = new Vector2(scaleFactor, scaleFactor);
arrange.gameObject.SetActive(!arrange.isActiveAndEnabled);
if (arrange.isActiveAndEnabled && arrange.gameObject.transform.childCount != 0)
{
try
{
arrange.ArrangeChilds();
}
catch
{
// Error($"Error arranging child objects in GridArrange: {e}");
}
}
}
}
}
/*
/// <summary>
@@ -146,9 +224,15 @@ public static class HudManagerPatches
}
var icon = Helpers.CreateKeybindIcon(buttonObj, key, keybindIconPos);
vanillaKeybindIcons.Add(icon.transform.GetChild(0).GetComponent<TextMeshPro>(), actionId);
var comp = buttonObj.GetComponent<ActionButton>();
KeybindManager.VanillaKeybinds[comp.GetType()].Button = comp;
if (!MiraApiPlugin.IsMobile)
{
var comp = buttonObj.GetComponent<ActionButton>();
KeybindManager.VanillaKeybinds[comp.GetType()].Button = comp;
}
}
MiraApiSettings.OldButtonScaleFactor =
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ButtonUIFactorSlider.Value;
Coroutines.Start(CoResizeUI());
}
/// <summary>
@@ -187,31 +271,54 @@ public static class HudManagerPatches
{
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(canSeeBinds);
}
foreach (var btnIcon in ModdedKeybindIcons)
{
btnIcon.transform.parent.gameObject.SetActive(canSeeBinds);
}
var player = ReInput.players.GetPlayer(0);
var keyboard = player.controllers.Keyboard;
foreach (var entry in KeybindManager.Keybinds)
{
var keyboard = player.controllers.Keyboard;
bool modifierKeysPressed = entry.ModifierKeys.All(k => keyboard.GetModifierKey(k)) ||
entry.ModifierKeys.Length <= 0;
var modKeys = entry.ModifierKeys;
bool modifierKeysPressed;
if (modKeys.Length == 0)
{
modifierKeysPressed = true;
}
else
{
modifierKeysPressed = true;
foreach (var key in modKeys)
{
if (keyboard.GetModifierKey(key)) continue;
modifierKeysPressed = false;
break;
}
}
if (player.GetButtonDown(entry.Id) && modifierKeysPressed)
{
entry.Invoke();
}
}
foreach (var entry in KeybindManager.VanillaKeybinds.Values.Where(x => player.GetButtonDown(x.Id)))
// suppressed warning because we want minimum allocations here
#pragma warning disable S3267
foreach (var entry in KeybindManager.VanillaKeybinds.Values)
#pragma warning restore S3267
{
entry.Invoke();
if (player.GetButtonDown(entry.Id))
{
entry.Invoke();
}
}
}
}
@@ -1,5 +1,7 @@
using HarmonyLib;
using MiraAPI.LocalSettings;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Patches.Menu;
@@ -17,5 +19,6 @@ public static class MainMenuManagerPatches
public static void AwakePostfix()
{
AddressablesLoader.LoadAll();
Application.targetFrameRate = (int)LocalSettingsTabSingleton<MiraApiSettings>.Instance.SetFpsSlider.Value;
}
}
+104 -12
View File
@@ -1,8 +1,11 @@
using System;
using System.Linq;
using AmongUs.GameOptions;
using HarmonyLib;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.GameOptions;
using MiraAPI.Modifiers;
using MiraAPI.Presets;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using Reactor.Localization.Utilities;
@@ -10,6 +13,7 @@ using Reactor.Utilities.Extensions;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using Object = UnityEngine.Object;
namespace MiraAPI.Patches.Options;
@@ -17,6 +21,20 @@ namespace MiraAPI.Patches.Options;
[HarmonyPatch(typeof(GameOptionsMenu))]
internal static class GameOptionsMenuPatch
{
[HarmonyPostfix]
[HarmonyPatch(nameof(GameOptionsMenu.OnEnable))]
public static void OpenPatch(GameOptionsMenu __instance)
{
HudManager.Instance.PlayerCam.OverrideScreenShakeEnabled = false;
}
[HarmonyPostfix]
[HarmonyPatch(nameof(GameOptionsMenu.OnDisable))]
public static void ClosePatch(GameOptionsMenu __instance)
{
HudManager.Instance.PlayerCam.OverrideScreenShakeEnabled = true;
}
[HarmonyPostfix]
[HarmonyPatch(nameof(GameOptionsMenu.Update))]
// ReSharper disable once InconsistentNaming
@@ -153,7 +171,7 @@ internal static class GameOptionsMenuPatch
private static void ModifiersUpdate(ref float num)
{
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
.Where(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true) ?? [];
.Where(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? [];
foreach (var modGroup in groups)
{
@@ -164,7 +182,7 @@ internal static class GameOptionsMenuPatch
private static void ModifiersCreate(GameOptionsMenu menu)
{
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
.Where(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true) ?? [];
.Where(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? [];
foreach (var group in groups)
{
CreateGroup(menu, group);
@@ -257,7 +275,9 @@ internal static class GameOptionsMenuPatch
categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister(group.GroupName), 20);
categoryHeaderMasked.Background.color = group.GroupColor;
categoryHeaderMasked.Divider.color = group.GroupColor;
categoryHeaderMasked.Title.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) ? Color.white : group.GroupColor.FindAlternateColor();
categoryHeaderMasked.Title.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor)
? Color.white
: group.GroupColor.FindAlternateColor();
categoryHeaderMasked.Background.sprite = MiraAssets.CategoryHeader.LoadAsset();
categoryHeaderMasked.Background.sprite.texture.filterMode = FilterMode.Bilinear;
@@ -276,13 +296,20 @@ internal static class GameOptionsMenuPatch
newText.transform.localPosition = new Vector3(2.6249f, -0.165f, 0f);
newText.gameObject.GetComponent<TextTranslatorTMP>().Destroy();
var options = group.Options.Select(
opt => opt.CreateOption(
menu.checkboxOrigin,
menu.numberOptionOrigin,
menu.stringOptionOrigin,
menu.playerOptionOrigin,
menu.settingsContainer));
var options = group.Options.Select(opt => opt.CreateOption(
menu.checkboxOrigin,
menu.numberOptionOrigin,
menu.stringOptionOrigin,
menu.playerOptionOrigin,
menu.settingsContainer));
OptionPreset? defaultPreset = null;
if (GameSettingMenuPatches.SelectedMod != null && PresetManager.DefaultPresets.TryGetValue(
GameSettingMenuPatches.SelectedMod,
out var preset))
{
defaultPreset = preset;
}
foreach (var newOpt in options)
{
@@ -318,14 +345,79 @@ internal static class GameOptionsMenuPatch
if (newOpt is ToggleOption toggle)
{
toggle.CheckMark.sprite = MiraAssets.Checkmark.LoadAsset();
toggle.CheckMark.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor ? group.GroupColor : MiraAssets.AcceptedTeal;
toggle.CheckMark.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor
? group.GroupColor
: MiraAssets.AcceptedTeal;
var rend = toggle.CheckMark.transform.parent.FindChild("ActiveSprite")
.GetComponent<SpriteRenderer>();
rend.sprite = MiraAssets.CheckmarkBox.LoadAsset();
rend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor ? group.GroupColor : MiraAssets.AcceptedTeal;
rend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor
? group.GroupColor
: MiraAssets.AcceptedTeal;
}
menu.Children.Add(newOpt);
var resetBtn = new GameObject("ResetOption");
resetBtn.transform.parent = newOpt.transform;
resetBtn.transform.localScale = new(.5f, .5f, 1);
resetBtn.layer = LayerMask.NameToLayer("UI");
resetBtn.transform.localPosition = new Vector3(-3.1f, 0f, -2f);
var resetRend = resetBtn.AddComponent<SpriteRenderer>();
resetRend.sprite = MiraAssets.ResetButton.LoadAsset();
resetRend.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor)
? Color.white
: group.GroupColor.FindAlternateColor();
var resetBoxCol = resetBtn.gameObject.AddComponent<BoxCollider2D>();
resetBoxCol.size = new Vector2(1f, 1f);
resetBoxCol.offset = new Vector2(0, 0);
var passiveButton = resetBtn.AddComponent<PassiveButton>();
passiveButton.OnClick = new Button.ButtonClickedEvent();
passiveButton.ClickSound = menu.BackButton.GetComponent<PassiveButton>().ClickSound;
passiveButton.OnMouseOver = new UnityEvent();
passiveButton.OnMouseOver.AddListener(
(UnityAction)(() =>
{
resetRend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor
? group.GroupColor
: MiraAssets.AcceptedTeal;
}));
passiveButton.OnMouseOut = new UnityEvent();
passiveButton.OnMouseOut.AddListener(
(UnityAction)(() =>
{
resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor)
? Color.white
: group.GroupColor.FindAlternateColor();
}));
if (newOpt is ToggleOption toggleOpt)
{
passiveButton.OnClick.AddListener(
(UnityAction)(() =>
{
defaultPreset!.ResetOption(toggleOpt);
}));
}
else if (newOpt is NumberOption numOpt)
{
passiveButton.OnClick.AddListener(
(UnityAction)(() =>
{
defaultPreset!.ResetOption(numOpt);
}));
}
else if (newOpt is StringOption strOpt)
{
passiveButton.OnClick.AddListener(
(UnityAction)(() =>
{
defaultPreset!.ResetOption(strOpt);
}));
}
if (!defaultPreset!.IsOptionInPreset(newOpt))
{
resetBtn.Destroy();
}
newOpt.Initialize();
newOpt.gameObject.SetActive(false);
@@ -35,6 +35,19 @@ public static class RoleSettingMenuPatches
private static RoleBehaviour? CurrentRole { get; set; }
private static List<IModdedOption>? CurrentRoleOptions { get; set; }
[HarmonyPostfix]
[HarmonyPatch(nameof(RolesSettingsMenu.OnEnable))]
public static void OpenPatch(RolesSettingsMenu __instance)
{
HudManager.Instance.PlayerCam.OverrideScreenShakeEnabled = false;
}
[HarmonyPostfix]
[HarmonyPatch(nameof(RolesSettingsMenu.OnDisable))]
public static void ClosePatch(RolesSettingsMenu __instance)
{
HudManager.Instance.PlayerCam.OverrideScreenShakeEnabled = true;
}
[HarmonyPostfix]
[HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))]
public static void SetQuotaTabPostfix(RolesSettingsMenu __instance)
+4
View File
@@ -13,6 +13,10 @@ internal static class HowToPlayPatch
// yes i patched the entire method
private static void Prefix(HowToPlayScene __instance)
{
if (RoleManager.Instance.AllRoles.ToArray().All(x => !x.IsCustomRole()))
{
return;
}
__instance.sceneIndex = 0;
__instance.category = HowToPlayScene.HowToPlayCategory.RolesSelection;
__instance.startPage.SetActive(false);
+1 -1
View File
@@ -58,7 +58,7 @@ public static class KillButtonPatches
return false;
}
PlayerControl.LocalPlayer.RpcCustomMurder(__instance.currentTarget);
PlayerControl.LocalPlayer.RpcCustomMurder(__instance.currentTarget, MeetingCheck.OutsideMeeting);
__instance.SetTarget(null);
return false;
@@ -39,7 +39,7 @@ public static class LogicRoleSelectionHnsPatch
foreach (RoleManager.RoleAssignmentData roleAssignmentData in guaranteedRoles.Select((x) =>
new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100)))
{
for (;;)
while (true)
{
RoleManager.RoleAssignmentData roleAssignmentData2 = roleAssignmentData;
int count = roleAssignmentData2.Count;
@@ -122,7 +122,7 @@ public static class LogicRoleSelectionHnsPatch
foreach (RoleManager.RoleAssignmentData roleAssignmentData in guaranteedRoles.Select((x) =>
new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100)))
{
for (;;)
while (true)
{
RoleManager.RoleAssignmentData roleAssignmentData2 = roleAssignmentData;
int count = roleAssignmentData2.Count;
@@ -36,7 +36,7 @@ public static class LogicRoleSelectionNormalPatch
foreach (RoleManager.RoleAssignmentData roleAssignmentData in guaranteedRoles.Select((x) =>
new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100)))
{
for (;;)
while (true)
{
RoleManager.RoleAssignmentData roleAssignmentData2 = roleAssignmentData;
int count = roleAssignmentData2.Count;
+22
View File
@@ -71,4 +71,26 @@ public class OptionPreset
CustomRoleManager.SyncAllRoleSettings();
}
/// <summary>
/// Resets the specified option with the preset provided.
/// </summary>
public void ResetOption(OptionBehaviour baseOption)
{
var selectedOpt = Plugin.InternalOptions.First(x => x.OptionBehaviour == baseOption);
selectedOpt.LoadFromPreset(PresetConfig);
/*ModdedOptionsManager.SyncAllOptions();
CustomRoleManager.SyncAllRoleSettings();*/
}
/// <summary>
/// Gets whether the specified option is included in the preset.
/// </summary>
/// <returns>>The value of whether the option is in the preset.</returns>
public bool IsOptionInPreset(OptionBehaviour baseOption)
{
var selectedOpt = Plugin.InternalOptions.First(x => x.OptionBehaviour == baseOption);
return selectedOpt.ConfigDefinition != null && PresetConfig.ContainsKey(selectedOpt.ConfigDefinition);
}
}
+2
View File
@@ -19,6 +19,7 @@ public static class PresetManager
/// </summary>
public static string PresetDirectory { get; } = Path.GetFullPath("mira_presets", Application.persistentDataPath);
internal static Dictionary<MiraPluginInfo, OptionPreset> DefaultPresets { get; } = [];
internal static List<OptionPreset> InternalMasterPresets { get; } = [];
/// <summary>
@@ -47,6 +48,7 @@ public static class PresetManager
}
presetConfig.Save();
DefaultPresets.Add(plugin, new OptionPreset("Default", plugin, presetConfig));
}
/// <summary>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

+4
View File
@@ -1,5 +1,6 @@
using AmongUs.GameOptions;
using System;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.Patches.Freeplay;
using MiraAPI.Utilities.Assets;
using UnityEngine;
@@ -77,16 +78,19 @@ public record struct CustomRoleConfiguration
/// <summary>
/// Gets or sets the Sprite used for the Role Options menu screenshot.
/// </summary>
[HideFromIl2Cpp]
public LoadableAsset<Sprite>? OptionsScreenshot { get; set; } = null;
/// <summary>
/// Gets or sets the Sprite used for the Role Icon.
/// </summary>
[HideFromIl2Cpp]
public LoadableAsset<Sprite>? Icon { get; set; } = null;
/// <summary>
/// Gets or sets the Intro sound for the Role.
/// </summary>
[HideFromIl2Cpp]
public LoadableAsset<AudioClip>? IntroSound { get; set; } = null;
/// <summary>
+14 -7
View File
@@ -89,15 +89,22 @@ public static class CustomRoleManager
foreach (var roleType in roles)
{
ClassInjector.RegisterTypeInIl2Cpp(roleType);
var role = RegisterRole(roleType, pluginInfo);
if (role is null)
try
{
continue;
}
ClassInjector.RegisterTypeInIl2Cpp(roleType);
pluginInfo.InternalRoles.Add((ushort)role.Role, role);
var role = RegisterRole(roleType, pluginInfo);
if (role is null)
{
continue;
}
pluginInfo.InternalRoles.Add((ushort)role.Role, role);
}
catch (Exception ex)
{
Error($"Failed to register role type {roleType.Name}: {ex}");
}
}
pluginInfo.PluginConfig.Save();
+5
View File
@@ -90,6 +90,11 @@ public static class MiraAssets
/// </summary>
public static LoadableResourceAsset CheckmarkBox { get; } = new("MiraAPI.Resources.CheckMarkBox.png");
/// <summary>
/// Gets the Reset Box used in the Settings Menu.
/// </summary>
public static LoadableResourceAsset ResetButton { get; } = new("MiraAPI.Resources.ResetButton.png");
/// <summary>
/// Gets the Checkmark used in the Settings Menu.
/// </summary>
-4
View File
@@ -546,10 +546,6 @@ public static class Helpers
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;
}
}