Compare commits

..
Author SHA1 Message Date
XtraCube a72fefe235 remove unnecessary icustomrole methods 2025-03-25 19:01:38 -04:00
112 changed files with 565 additions and 2615 deletions

No files matched your search

+2 -1
View File
@@ -1 +1,2 @@
[*.{cs,vb}]
[*.{cs,vb}]
dotnet_diagnostic.CA1707.severity = none
-1
View File
@@ -3,7 +3,6 @@
.vs/
obj/
bin/
.qodo/
*.user
*.lock.json
+2 -2
View File
@@ -2,10 +2,10 @@
<ItemGroup>
<PackageReference Include="Reactor" Version="2.3.1" />
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.697" Private="false" ExcludeAssets="runtime;native" />
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2025.3.25" PrivateAssets="all" />
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2024.10.29" PrivateAssets="all" />
<PackageReference Include="BepInEx.AutoPlugin" Version="1.1.0" PrivateAssets="all" />
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.1.0-rc.1" PrivateAssets="all" ExcludeAssets="runtime" />
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.0.1" PrivateAssets="all" ExcludeAssets="runtime" />
</ItemGroup>
<Target Name="Copy" AfterTargets="Build" Condition="'$(AmongUs)' != ''">
+1 -1
View File
@@ -34,7 +34,7 @@
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" PrivateAssets="all" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)\stylecop.json" Link="stylecop.json" Visible="false" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.7.0.110445">
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.32.0.97167">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
@@ -1,31 +0,0 @@
using System;
using BepInEx;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using MiraAPI.PluginLoading;
using Reactor;
using Reactor.Networking;
using Reactor.Networking.Attributes;
namespace MiraAPI.Example.Extension;
[BepInAutoPlugin("mira.example.extension", "MiraExampleExtensionMod")]
[BepInProcess("Among Us.exe")]
[BepInDependency(ReactorPlugin.Id)]
[BepInDependency(MiraApiPlugin.Id)]
[BepInDependency(ExamplePlugin.Id)]
[ReactorModFlags(ModFlags.RequireOnAllClients)]
public partial class ExampleExtensionPlugin : BasePlugin, IMiraExtension
{
public Harmony Harmony { get; } = new(Id);
public override void Load()
{
Harmony.PatchAll();
}
public Type GetBasePluginType()
{
return typeof(ExamplePlugin);
}
}
@@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>1.0.0</Version>
<Description>Example extension for Mira API</Description>
<Authors>Angxl, XtraCube</Authors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MiraAPI\MiraAPI.csproj" />
<ProjectReference Include="..\MiraAPI.Example\MiraAPI.Example.csproj" />
</ItemGroup>
<Import Project="..\AmongUs.props" />
</Project>
@@ -1,16 +0,0 @@
using MiraAPI.Example.Extension.Options;
using MiraAPI.GameOptions;
using MiraAPI.Modifiers.Types;
namespace MiraAPI.Example.Extension.Modifiers;
public class ExtensionModifier : GameModifier
{
public override string ModifierName => "Extension Modifier";
public override string GetDescription() => "This is from the extension mod!";
public override int GetAssignmentChance() => (int)OptionGroupSingleton<ExtensionModifierOptions>.Instance.ModifierChance;
public override int GetAmountPerGame() => (int)OptionGroupSingleton<ExtensionModifierOptions>.Instance.ModifierLimit;
}
@@ -1,13 +0,0 @@
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
namespace MiraAPI.Example.Extension.Options;
public class ExtendedFreezerOptions : AbstractOptionGroup<FreezerRole>
{
public override string GroupName => "Extended Freezer Role Options";
[ModdedNumberOption("Freeze Uses", 1, 5)]
public float FreezeUses { get; set; } = 1;
}
@@ -1,17 +0,0 @@
using MiraAPI.Example.Extension.Modifiers;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.Utilities;
namespace MiraAPI.Example.Extension.Options;
public class ExtensionModifierOptions : AbstractOptionGroup<ExtensionModifier>
{
public override string GroupName => "Extension Modifier Options";
[ModdedNumberOption("Modifier Chance", 0, 100, 10, MiraNumberSuffixes.Percent)]
public float ModifierChance { get; set; } = 100f;
[ModdedNumberOption("Modifier Limit", 0, 10)]
public float ModifierLimit { get; set; } = 0;
}
@@ -1,15 +0,0 @@
using HarmonyLib;
using MiraAPI.Example.Buttons.Freezer;
using MiraAPI.Example.Extension.Options;
using MiraAPI.GameOptions;
namespace MiraAPI.Example.Extension.Patches;
[HarmonyPatch(typeof(FreezeButton), nameof(FreezeButton.MaxUses), MethodType.Getter)]
public static class FreezeButtonPatch
{
public static void Postfix(ref int __result)
{
__result = (int)OptionGroupSingleton<ExtendedFreezerOptions>.Instance.FreezeUses;
}
}
@@ -16,7 +16,7 @@ public class FreezeButton : CustomActionButton<PlayerControl>
public override float Cooldown => OptionGroupSingleton<FreezerRoleSettings>.Instance.FreezeDuration;
public override int MaxUses => 1;
public override int MaxUses => (int)OptionGroupSingleton<FreezerRoleSettings>.Instance.FreezeUses;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
@@ -1,20 +1,17 @@
using MiraAPI.Example.Extension.GameOver;
using MiraAPI.Example.Extension.Roles;
using MiraAPI.Example.GameOver;
using MiraAPI.Example.Roles;
using MiraAPI.GameEnd;
using MiraAPI.Hud;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Example.Extension.Buttons;
namespace MiraAPI.Example.Buttons;
public class NeutralKillerButton : CustomActionButton
{
public override string Name => "Win Game";
public override float Cooldown => 0f;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
protected override void OnClick()
{
CustomGameOver.Trigger<NeutralKillerGameOver>([PlayerControl.LocalPlayer.Data]);
-1
View File
@@ -5,7 +5,6 @@ namespace MiraAPI.Example;
public static class ExampleAssets
{
public static LoadableResourceAsset ExampleButton { get; } = new("MiraAPI.Example.Resources.ExampleButton.png");
public static LoadableResourceAsset CallMeetingButton { get; } = new("MiraAPI.Example.Resources.CallMeeting.png");
// Credit to EpicHorrors for the teleport button asset.
public static LoadableResourceAsset TeleportButton { get; } = new("MiraAPI.Example.Resources.TeleportButton.png");
+2 -5
View File
@@ -18,7 +18,7 @@ public static class ExampleEventHandlers
public static void Initialize()
{
// You can register event handlers with the MiraEventManager class.
var handle = MiraEventManager.RegisterEventHandler<BeforeMurderEvent>(@event =>
MiraEventManager.RegisterEventHandler<BeforeMurderEvent>(@event =>
{
Logger<ExamplePlugin>.Info($"{@event.Source.Data.PlayerName} is about to kill {@event.Target.Data.PlayerName}");
});
@@ -32,9 +32,6 @@ public static class ExampleEventHandlers
{
Logger<ExamplePlugin>.Info($"{@event.Player.Data.PlayerName} completed {@event.Task.TaskType.ToString()}");
});
// You can also unregister event handlers using the handle returned from RegisterEventHandler.
MiraEventManager.UnregisterEventHandler(handle);
}
// If you want to add extra votes to a player, do something like this.
@@ -51,7 +48,7 @@ public static class ExampleEventHandlers
[RegisterEvent(15)]
public static void HandleVoteEvent(HandleVoteEvent @event)
{
if (@event.VoteData.Owner.Data.Role is not FreezerRole) return;
if (@event.VoteData.Owner.Data.Role is not NeutralKillerRole) return;
@event.VoteData.SetRemainingVotes(0);
@@ -1,9 +1,9 @@
using MiraAPI.Example.Extension.Roles;
using MiraAPI.Example.Roles;
using MiraAPI.GameEnd;
using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Example.Extension.GameOver;
namespace MiraAPI.Example.GameOver;
public class NeutralKillerGameOver : CustomGameOver
{
+5 -3
View File
@@ -9,9 +9,11 @@
<ProjectReference Include="..\MiraAPI\MiraAPI.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\**\*.*" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\ExampleButton.png" />
<EmbeddedResource Include="Resources\FortniteBanner.jpeg" />
<EmbeddedResource Include="Resources\TeleportButton.png" />
</ItemGroup>
<Import Project="..\AmongUs.props" />
</Project>
@@ -1,26 +1,12 @@
using MiraAPI.Example.Options.Modifiers;
using MiraAPI.GameOptions;
using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Example.Modifiers;
public class CaptainModifier : GameModifier
{
public override string ModifierName => "Captain";
public override LoadableAsset<Sprite>? ModifierIcon => ExampleAssets.CallMeetingButton;
public override void OnDeath(DeathReason reason)
{
Player.RemoveModifier(this);
}
public override string GetDescription()
{
return $"You can call a meeting from anywhere on the map.";
}
public override int GetAssignmentChance()
{
@@ -6,11 +6,6 @@ public class HighPriorityModifier : GameModifier
{
public override string ModifierName => "High Priority";
public override string GetDescription()
{
return "You are high priority! Idrk what this means either tbh...";
}
public override int GetAssignmentChance()
{
return 100;
@@ -11,4 +11,7 @@ public class FreezerRoleSettings : AbstractOptionGroup<FreezerRole>
[ModdedNumberOption("Freeze Duration", 1, 15, 1, MiraNumberSuffixes.Seconds)]
public float FreezeDuration { get; set; } = 5;
[ModdedNumberOption("Freeze Uses", 1, 5)]
public float FreezeUses { get; set; } = 1;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

+12 -40
View File
@@ -1,7 +1,6 @@
using AmongUs.GameOptions;
using MiraAPI.Patches.Stubs;
using MiraAPI.Roles;
using Reactor.Utilities;
using MiraAPI.Utilities;
using TMPro;
using UnityEngine;
@@ -15,64 +14,37 @@ public class ChameloenRole : CrewmateRole, ICustomRole
public Color RoleColor => Palette.AcceptedGreen;
public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate;
private bool _shouldHide;
public CustomRoleConfiguration Configuration => new(this)
{
OptionsScreenshot = ExampleAssets.Banner,
IntroSound = CustomRoleUtils.GetIntroSound(RoleTypes.Shapeshifter),
};
public override void Initialize(PlayerControl player)
public void PlayerControlFixedUpdate(PlayerControl playerControl)
{
Logger<ExamplePlugin>.Info("Initializing ChamelonRole for player: " + player.PlayerId);
RoleBehaviourStubs.Initialize(this, player);
_shouldHide = true;
}
public void FixedUpdate()
{
if (!Player || !_shouldHide)
if (playerControl.MyPhysics.Velocity.magnitude > 0)
{
return;
}
if (Player.MyPhysics.Velocity.magnitude > 0)
{
var rend = Player.cosmetics.currentBodySprite.BodySprite;
var tmp = Player.cosmetics.nameText;
SpriteRenderer rend = playerControl.cosmetics.currentBodySprite.BodySprite;
TextMeshPro tmp = playerControl.cosmetics.nameText;
tmp.color = Color.Lerp(tmp.color, new Color(tmp.color.r, tmp.color.g, tmp.color.b, 1), Time.deltaTime * 4f);
rend.color = Color.Lerp(rend.color, new Color(1, 1, 1, 1), Time.deltaTime * 4f);
foreach (var cosmetic in Player.cosmetics.transform.GetComponentsInChildren<SpriteRenderer>())
foreach (var cosmetic in playerControl.cosmetics.transform.GetComponentsInChildren<SpriteRenderer>())
{
cosmetic.color = Color.Lerp(cosmetic.color, new Color(1, 1, 1, 1), Time.deltaTime * 4f);
}
}
else
{
SpriteRenderer rend = Player.cosmetics.currentBodySprite.BodySprite;
TextMeshPro tmp = Player.cosmetics.nameText;
tmp.color = Color.Lerp(tmp.color, new Color(tmp.color.r, tmp.color.g, tmp.color.b, Player.AmOwner ? 0.3f : 0), Time.deltaTime * 4f);
rend.color = Color.Lerp(rend.color, new Color(1, 1, 1, Player.AmOwner ? 0.3f : 0), Time.deltaTime * 4f);
SpriteRenderer rend = playerControl.cosmetics.currentBodySprite.BodySprite;
TextMeshPro tmp = playerControl.cosmetics.nameText;
tmp.color = Color.Lerp(tmp.color, new Color(tmp.color.r, tmp.color.g, tmp.color.b, playerControl.AmOwner ? 0.3f : 0), Time.deltaTime * 4f);
rend.color = Color.Lerp(rend.color, new Color(1, 1, 1, playerControl.AmOwner ? 0.3f : 0), Time.deltaTime * 4f);
foreach (var cosmetic in Player.cosmetics.transform.GetComponentsInChildren<SpriteRenderer>())
foreach (var cosmetic in playerControl.cosmetics.transform.GetComponentsInChildren<SpriteRenderer>())
{
cosmetic.color = Color.Lerp(cosmetic.color, new Color(1, 1, 1, Player.AmOwner ? 0.3f : 0), Time.deltaTime * 4f);
cosmetic.color = Color.Lerp(cosmetic.color, new Color(1, 1, 1, playerControl.AmOwner ? 0.3f : 0), Time.deltaTime * 4f);
}
}
}
public override void Deinitialize(PlayerControl targetPlayer)
{
Logger<ExamplePlugin>.Info("Deinitializing ChamelonRole for player: " + targetPlayer.PlayerId);
RoleBehaviourStubs.Deinitialize(this, targetPlayer);
_shouldHide = false;
foreach (var cosmetic in Player.cosmetics.transform.GetComponentsInChildren<SpriteRenderer>(true))
{
cosmetic.color = Color.white;
}
Player.cosmetics.currentBodySprite.BodySprite.color = Color.white;
Player.cosmetics.nameText.color = Color.white;
}
}
@@ -1,20 +1,16 @@
using MiraAPI.Example.Extension.GameOver;
using MiraAPI.Example.GameOver;
using MiraAPI.GameEnd;
using MiraAPI.Roles;
using UnityEngine;
namespace MiraAPI.Example.Extension.Roles;
namespace MiraAPI.Example.Roles;
public class NeutralKillerRole : ImpostorRole, ICustomRole
{
public string RoleName => "Outcast Killer";
public string RoleDescription => "Outcast who can kill.";
public string RoleLongDescription => this.RoleDescription;
public string RoleLongDescription => RoleDescription;
public Color RoleColor => Color.magenta;
public ModdedRoleTeams Team => ModdedRoleTeams.Custom;
public CustomRoleConfiguration Configuration => new(this)
-6
View File
@@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiraAPI", "MiraAPI\MiraAPI.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiraAPI.Example", "MiraAPI.Example\MiraAPI.Example.csproj", "{19B76855-16CC-43CC-A0A6-5A7FFB43F077}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiraAPI.Example.Extension", "MiraAPI.Example.Extension\MiraAPI.Example.Extension.csproj", "{526FF017-80E1-4094-804D-741AF35AA226}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -23,10 +21,6 @@ Global
{19B76855-16CC-43CC-A0A6-5A7FFB43F077}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19B76855-16CC-43CC-A0A6-5A7FFB43F077}.Release|Any CPU.ActiveCfg = Release|Any CPU
{19B76855-16CC-43CC-A0A6-5A7FFB43F077}.Release|Any CPU.Build.0 = Release|Any CPU
{526FF017-80E1-4094-804D-741AF35AA226}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{526FF017-80E1-4094-804D-741AF35AA226}.Debug|Any CPU.Build.0 = Debug|Any CPU
{526FF017-80E1-4094-804D-741AF35AA226}.Release|Any CPU.ActiveCfg = Release|Any CPU
{526FF017-80E1-4094-804D-741AF35AA226}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
-22
View File
@@ -1,22 +0,0 @@
using System;
namespace MiraAPI.Events;
/// <summary>
/// Event handle to use when unregistering an event.
/// </summary>
public class MiraEventHandle
{
/// <summary>
/// Gets the type of event.
/// </summary>
public Type EventType { get; }
internal MiraEventWrapper EventWrapper { get; }
internal MiraEventHandle(Type type, MiraEventWrapper eventWrapper)
{
EventType = type;
EventWrapper = eventWrapper;
}
}
+24 -69
View File
@@ -21,21 +21,14 @@ public static class MiraEventManager
public static bool InvokeEvent<T>(T eventInstance) where T : MiraEvent
{
EventWrappers.TryGetValue(typeof(T), out var handlers);
if (handlers == null || handlers.Count == 0)
if (handlers == null)
{
return false;
}
foreach (var handler in handlers)
{
try
{
((Action<T>)handler.EventHandler).Invoke(eventInstance);
}
catch (Exception ex)
{
Logger<MiraApiPlugin>.Error($"Error invoking event handler for {typeof(T).Name}: {ex.ToString()}");
}
((Action<T>)handler.EventHandler).Invoke(eventInstance);
}
return true;
@@ -50,21 +43,14 @@ public static class MiraEventManager
public static bool InvokeEvent(MiraEvent eventInstance, Type type)
{
EventWrappers.TryGetValue(type, out var handlers);
if (handlers == null || handlers.Count == 0)
if (handlers == null)
{
return false;
}
foreach (var handler in handlers)
{
try
{
handler.EventHandler.DynamicInvoke(eventInstance);
}
catch (Exception ex)
{
Logger<MiraApiPlugin>.Error($"Error invoking event handler for {type.Name}: {ex.ToString()}");
}
handler.EventHandler.DynamicInvoke(eventInstance);
}
return true;
@@ -75,78 +61,47 @@ public static class MiraEventManager
/// </summary>
/// <param name="type">The type of event.</param>
/// <param name="methodInfo">The MethodInfo of the event handler.</param>
/// <param name="priority">The priority of the event handler. Lower values are called first.</param>
/// <returns>An event handle to use when unregistering the event.</returns>
public static MiraEventHandle RegisterEventHandler(Type type, MethodInfo methodInfo, int priority = 0)
/// <param name="priority">The priority of the event handler. Higher values are called first.</param>
public static void RegisterEventHandler(Type type, MethodInfo methodInfo, int priority = 0)
{
if (!type.IsSubclassOf(typeof(MiraEvent)))
{
throw new InvalidOperationException($"Type must be a subclass of MiraEvent: {type.FullName}");
Logger<MiraApiPlugin>.Error($"Type must be a subclass of MiraEvent: {nameof(type)}");
return;
}
EventWrappers.TryAdd(type, []);
var handlers = EventWrappers[type];
if (!EventWrappers.TryGetValue(type, out var handlers))
{
handlers = [];
EventWrappers.Add(type, handlers);
}
var @delegate = Delegate.CreateDelegate(typeof(Action<>).MakeGenericType(type), methodInfo);
var eventWrapper = new MiraEventWrapper(@delegate, priority);
var index = handlers.BinarySearch(eventWrapper, Comparer<MiraEventWrapper>.Create((a, b) => a.Priority.CompareTo(b.Priority)));
if (index < 0)
{
index = ~index;
}
handlers.Insert(index, eventWrapper);
return new MiraEventHandle(type, eventWrapper);
handlers.Add(new MiraEventWrapper(@delegate, priority));
}
/// <summary>
/// Register an event.
/// </summary>
/// <param name="handler">The callback method/handler for the event.</param>
/// <param name="priority">The priority of the event handler. Lower values are called first.</param>
/// <param name="priority">The priority of the event handler. Higher values are called first.</param>
/// <typeparam name="T">Type of event.</typeparam>
/// <returns>An event handle to use when unregistering the event.</returns>
public static MiraEventHandle RegisterEventHandler<T>(Action<T> handler, int priority = 0) where T : MiraEvent
public static void RegisterEventHandler<T>(Action<T> handler, int priority = 0) where T : MiraEvent
{
EventWrappers.TryAdd(typeof(T), []);
if (!EventWrappers.ContainsKey(typeof(T)))
{
EventWrappers.Add(typeof(T), []);
}
var handlers = EventWrappers[typeof(T)];
var eventWrapper = new MiraEventWrapper(handler, priority);
var index = handlers.BinarySearch(eventWrapper, Comparer<MiraEventWrapper>.Create((a, b) => a.Priority.CompareTo(b.Priority)));
if (index < 0)
{
index = ~index;
}
handlers.Insert(index, eventWrapper);
return new MiraEventHandle(typeof(T), eventWrapper);
handlers.Add(new MiraEventWrapper(handler, priority));
}
/// <summary>
/// Unregister an event using the event handle.
/// </summary>
/// <param name="eventHandle">A handle to the event.</param>
/// <returns>True if the event was unregistered successfully, false otherwise.</returns>
public static bool UnregisterEventHandler(MiraEventHandle eventHandle)
internal static void SortAllHandlers()
{
if (!EventWrappers.TryGetValue(eventHandle.EventType, out var handlers))
foreach (var handlers in EventWrappers.Values)
{
return false;
handlers.Sort((a, b) => a.Priority.CompareTo(b.Priority));
}
if (!handlers.Remove(eventHandle.EventWrapper))
{
return false;
}
if (handlers.Count == 0)
{
EventWrappers.Remove(eventHandle.EventType);
}
return true;
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ public class MiraEventWrapper
public Delegate EventHandler { get; }
/// <summary>
/// Gets the priority of the handler. Lower values are called first.
/// Gets the priority of the handler. Higher values are called first.
/// </summary>
public int Priority { get; }
+2 -2
View File
@@ -9,14 +9,14 @@ namespace MiraAPI.Events;
public class RegisterEventAttribute : Attribute
{
/// <summary>
/// Gets the priority of the event. Lower values are called first.
/// Gets the priority of the event.
/// </summary>
public int Priority { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RegisterEventAttribute"/> class.
/// </summary>
/// <param name="priority">The priority of the event. Lower values are called first.</param>
/// <param name="priority">The priority of the event.</param>
public RegisterEventAttribute(int priority = 0)
{
Priority = priority;
@@ -15,21 +15,14 @@ public class AfterMurderEvent : MiraEvent
/// </summary>
public PlayerControl Target { get; }
/// <summary>
/// Gets the player's body, if it exists.
/// </summary>
public DeadBody? DeadBody { get; }
/// <summary>
/// Initializes a new instance of the <see cref="AfterMurderEvent"/> class.
/// </summary>
/// <param name="source">The killer.</param>
/// <param name="target">The killed player.</param>
/// <param name="deadBody">The player's vody, if it exists.</param>
public AfterMurderEvent(PlayerControl source, PlayerControl target, DeadBody? deadBody)
public AfterMurderEvent(PlayerControl source, PlayerControl target)
{
Source = source;
Target = target;
DeadBody = deadBody;
}
}
@@ -1,12 +0,0 @@
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// The event that is invoked when the intro cutscene has finished playing.
/// </summary>
public class IntroEndEvent(IntroCutscene introCutscene) : MiraEvent
{
/// <summary>
/// Gets the instance of the IntroCutscene.
/// </summary>
public IntroCutscene IntroCutscene { get; } = introCutscene;
}
@@ -1,21 +0,0 @@
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// Start Round event, invoked on IntroCutscene.OnDestroy and ExileController.WrapUp.
/// </summary>
public class RoundStartEvent : MiraEvent
{
/// <summary>
/// Gets a value indicating whether the event was triggered by the IntroCutscene or ExileController.
/// </summary>
public bool TriggeredByIntro { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RoundStartEvent"/> class.
/// </summary>
/// <param name="triggeredByIntro">Whether the event was triggered by the intro or not.</param>
public RoundStartEvent(bool triggeredByIntro)
{
TriggeredByIntro = triggeredByIntro;
}
}
@@ -24,7 +24,6 @@ public class UpdateSystemEvent : MiraCancelableEvent
/// Initializes a new instance of the <see cref="UpdateSystemEvent"/> class.
/// </summary>
/// <param name="systemType">The SystemType being updated.</param>
/// <param name="player">The player that is updating the system.</param>
/// <param name="amount">Amount to update System to.</param>
public UpdateSystemEvent(SystemTypes systemType, PlayerControl player, byte amount)
{
@@ -1,12 +0,0 @@
namespace MiraAPI.Events.Vanilla.Meeting;
/// <summary>
/// The event that is invoked when the meeting has ended.
/// </summary>
public class EndMeetingEvent(MeetingHud meetingHud) : MiraEvent
{
/// <summary>
/// Gets the MeetingHud instance.
/// </summary>
public MeetingHud MeetingHud { get; } = meetingHud;
}
@@ -24,11 +24,6 @@ public class HandleVoteEvent : MiraCancelableEvent
/// </summary>
public byte TargetId { get; }
/// <summary>
/// Gets a value indicating whether to prevent the vote from commencing.
/// </summary>
public bool PreventVote { get; }
/// <summary>
/// Gets the player info of the target.
/// </summary>
@@ -44,7 +39,6 @@ public class HandleVoteEvent : MiraCancelableEvent
VoteData = playerVoteData;
Player = playerVoteData.Owner;
TargetId = targetId;
PreventVote = false;
TargetPlayerInfo = GameData.Instance.GetPlayerById(targetId);
}
}
@@ -1,13 +0,0 @@
namespace MiraAPI.Events.Vanilla.Meeting.Voting;
/// <summary>
/// The event that is invoked when the voting phase of a meeting is complete.
/// </summary>
/// <param name="meetingHud">The MeetingHud instance.</param>
public class VotingCompleteEvent(MeetingHud meetingHud) : MiraEvent
{
/// <summary>
/// Gets the MeetingHud instance.
/// </summary>
public MeetingHud MeetingHud { get; } = meetingHud;
}
@@ -15,21 +15,14 @@ public class PlayerDeathEvent : MiraEvent
/// </summary>
public DeathReason DeathReason { get; }
/// <summary>
/// Gets the dead body associated with the player, if any.
/// </summary>
public DeadBody? DeadBody { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PlayerDeathEvent"/> class.
/// </summary>
/// <param name="player">The player who died.</param>
/// <param name="reason">The reason the player died.</param>
/// <param name="deadBody">The player's body, if it exists.</param>
public PlayerDeathEvent(PlayerControl player, DeathReason reason, DeadBody? deadBody)
public PlayerDeathEvent(PlayerControl player, DeathReason reason)
{
Player = player;
DeathReason = reason;
DeadBody = deadBody;
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ public static class CustomGameModeManager
}
/// <summary>
/// Gets the current gamemode.
/// Current gamemode.
/// </summary>
public static CustomGameMode? ActiveMode { get; internal set; } = new DefaultMode();
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using UnityEngine;
namespace MiraAPI.GameOptions;
@@ -12,11 +11,6 @@ public abstract class AbstractOptionGroup
{
internal List<IModdedOption> Options { get; } = [];
/// <summary>
/// Gets a list of the options which are a part of this group.
/// </summary>
public ReadOnlyCollection<IModdedOption> Children => new(Options);
/// <summary>
/// Gets the name of the group. Visible in options menu.
/// </summary>
-58
View File
@@ -6,76 +6,18 @@ using UnityEngine;
namespace MiraAPI.GameOptions;
/// <summary>
/// Interface for modded options.
/// </summary>
public interface IModdedOption
{
/// <summary>
/// Gets the unique identifier for the option.
/// </summary>
uint Id { get; }
/// <summary>
/// Gets the title of the option.
/// </summary>
string Title { get; }
/// <summary>
/// Gets the StringName for the option, used for localization.
/// </summary>
StringNames StringName { get; }
/// <summary>
/// Gets or sets the MiraPlugin that created this option.
/// </summary>
IMiraPlugin? ParentMod { get; set; }
/// <summary>
/// Gets the game setting data for the option.
/// </summary>
BaseGameSetting Data { get; }
/// <summary>
/// Gets the OptionBehaviour object of the option.
/// </summary>
OptionBehaviour? OptionBehaviour { get; }
/// <summary>
/// Gets or sets the visibility function for the option.
/// </summary>
Func<bool> Visible { get; set; }
/// <summary>
/// Gets or sets the ConfigDefinition for the option, used for BepInEx configuration.
/// </summary>
ConfigDefinition? ConfigDefinition { get; set; }
/// <summary>
/// Creates the option behaviour for the modded option.
/// </summary>
/// <param name="toggleOpt">The ToggleOption template.</param>
/// <param name="numberOpt">The NumberOption template.</param>
/// <param name="stringOpt">The StringOption template.</param>
/// <param name="container">>The Transform container for the option.</param>
/// <returns>>The created OptionBehaviour object.</returns>
OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container);
/// <summary>
/// Gets the value as a float.
/// </summary>
/// <returns>>The value of the option as a float.</returns>
float GetFloatData();
/// <summary>
/// Gets the NetData for the option, used for network synchronization.
/// </summary>
/// <returns>>Returns the NetData object for the option.</returns>
NetData GetNetData();
/// <summary>
/// Handles incoming network data for the option.
/// </summary>
/// <param name="data">>The byte array representing the network data.</param>
void HandleNetData(byte[] data);
}
@@ -45,9 +45,7 @@ public static class ModdedOptionsManager
pluginInfo.OptionGroups.Add(group);
typeof(OptionGroupSingleton<>).MakeGenericType(type)
#pragma warning disable S3011
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
#pragma warning restore S3011
.SetValue(null, group);
return true;
@@ -175,7 +173,6 @@ public static class ModdedOptionsManager
/// </summary>
/// <param name="__originalMethod">The original setter method.</param>
/// <param name="value">The new object value.</param>
// ReSharper disable InconsistentNaming
public static void PropertySetterPatch(MethodBase __originalMethod, object value)
{
var attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod() == __originalMethod).Value;
@@ -189,7 +186,6 @@ public static class ModdedOptionsManager
/// <param name="__result">The result of the property getter.</param>
/// <returns>False so the original getter gets skipped.</returns>
public static bool PropertyGetterPatch(MethodBase __originalMethod, ref object __result)
// ReSharper restore InconsistentNaming
{
var attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod() == __originalMethod).Value;
__result = attribute.GetValue();
@@ -2,9 +2,7 @@
namespace MiraAPI.GameOptions;
#pragma warning disable CA1852
internal class ModifierOptionGroup : AbstractOptionGroup
#pragma warning restore CA1852
{
public override string GroupName { get; }
@@ -129,7 +129,7 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
Enum.GetNames(typeof(T)).Select(CustomStringName.CreateAndRegister).ToArray()
: values.Select(CustomStringName.CreateAndRegister).ToArray();
data.Index = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo);
data.Index = Convert.ToInt32(Value);
}
/// <inheritdoc />
@@ -32,9 +32,9 @@ public abstract class ModdedOption<T> : IModdedOption
public StringNames StringName { get; }
/// <summary>
/// Gets or sets the BaseGameSetting data of the option.
/// Gets the BaseGameSetting data of the option.
/// </summary>
public BaseGameSetting Data { get; protected set; } = null!;
public BaseGameSetting Data { get; protected init; }
/// <summary>
/// Gets or sets the parent mod of the option.
+23 -29
View File
@@ -180,7 +180,7 @@ public abstract class CustomActionButton
if (!moveButton) return;
if (HudManagerPatches.BottomLeft == null || HudManagerPatches.BottomRight == null)
if (HudManagerPatches._bottomLeft == null || HudManagerPatches._bottomRight == null)
{
return;
}
@@ -188,17 +188,17 @@ public abstract class CustomActionButton
switch (location)
{
case ButtonLocation.BottomLeft:
var gridArrange = HudManagerPatches.BottomLeft.GetComponent<GridArrange>();
var aspectPosition = HudManagerPatches.BottomLeft.GetComponent<AspectPosition>();
var gridArrange = HudManagerPatches._bottomLeft.GetComponent<GridArrange>();
var aspectPosition = HudManagerPatches._bottomLeft.GetComponent<AspectPosition>();
Button.transform.SetParent(HudManagerPatches.BottomLeft.transform);
Button.transform.SetParent(HudManagerPatches._bottomLeft.transform);
gridArrange.Start();
gridArrange.ArrangeChilds();
aspectPosition.AdjustPosition();
break;
case ButtonLocation.BottomRight:
Button.transform.SetParent(HudManagerPatches.BottomRight);
Button.transform.SetParent(HudManagerPatches._bottomRight);
break;
}
}
@@ -417,12 +417,9 @@ public abstract class CustomActionButton
/// <param name="playerControl">The local PlayerControl.</param>
public virtual void FixedUpdateHandler(PlayerControl playerControl)
{
if (Timer >= 0)
if (Timer >= 0 && !TimerPaused)
{
if (!TimerPaused)
{
Timer -= Time.deltaTime;
}
Timer -= Time.deltaTime;
}
else if (HasEffect && EffectActive)
{
@@ -431,28 +428,25 @@ public abstract class CustomActionButton
OnEffectEnd();
}
if (Button)
if (CanUse())
{
if (CanUse())
{
Button!.SetEnabled();
}
else
{
Button!.SetDisabled();
}
Button?.SetEnabled();
}
else
{
Button?.SetDisabled();
}
if (EffectActive)
{
Button.SetFillUp(Timer, EffectDuration);
if (EffectActive)
{
Button?.SetFillUp(Timer, EffectDuration);
Button.cooldownTimerText.text = Mathf.CeilToInt(Timer).ToString(NumberFormatInfo.InvariantInfo);
Button.cooldownTimerText.gameObject.SetActive(true);
}
else
{
Button.SetCoolDown(Timer, Cooldown);
}
Button!.cooldownTimerText.text = Mathf.CeilToInt(Timer).ToString(NumberFormatInfo.InvariantInfo);
Button!.cooldownTimerText.gameObject.SetActive(true);
}
else
{
Button?.SetCoolDown(Timer, Cooldown);
}
FixedUpdate(playerControl);
-2
View File
@@ -31,9 +31,7 @@ public static class CustomButtonManager
CustomButtons.Add(button);
pluginInfo.Buttons.Add(button);
typeof(CustomButtonSingleton<>).MakeGenericType(buttonType)
#pragma warning disable S3011
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
#pragma warning restore S3011
.SetValue(null, button);
ButtonEventTypes.Add(buttonType, typeof(MiraButtonClickEvent<>).MakeGenericType(buttonType));
+3 -3
View File
@@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq;
namespace MiraAPI.Hud;
@@ -14,6 +13,7 @@ public static class CustomButtonSingleton<T> where T : CustomActionButton
/// <summary>
/// Gets the instance of the button.
/// </summary>
[SuppressMessage("Design", "CA1000:Do not declare static members on generic types", Justification = "This is a utility class to get the instance of a custom action button.")]
#pragma warning disable CA1000
public static T Instance => _instance ??= CustomButtonManager.CustomButtons.OfType<T>().Single();
#pragma warning restore CA1000
}
+27 -27
View File
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using AmongUs.GameOptions;
using Il2CppInterop.Runtime.Attributes;
@@ -9,8 +8,6 @@ using Reactor.Utilities.Attributes;
using Reactor.Utilities.Extensions;
using UnityEngine;
using UnityEngine.Events;
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
namespace MiraAPI.Hud;
@@ -19,18 +16,22 @@ namespace MiraAPI.Hud;
/// </summary>
/// <param name="il2CppPtr">Used by Il2Cpp. Do not use constructor, this is a MonoBehaviour.</param>
[RegisterInIl2Cpp]
[SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Justification = "Unity Convention")]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Unity Convention")]
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Unity Convention")]
public class CustomPlayerMenu(IntPtr il2CppPtr) : Minigame(il2CppPtr)
{
public ShapeshifterPanel panelPrefab;
public float xStart = -0.8f;
public float yStart = 2.15f;
public float xOffset = 1.95f;
public float yOffset = -0.65f;
public UiElement backButton;
public UiElement defaultButtonSelected;
public ShapeshifterPanel PanelPrefab;
public float XStart = -0.8f;
public float YStart = 2.15f;
public float XOffset = 1.95f;
public float YOffset = -0.65f;
public UiElement BackButton;
public UiElement DefaultButtonSelected;
public List<ShapeshifterPanel> potentialVictims;
/// <summary>
@@ -41,17 +42,17 @@ public class CustomPlayerMenu(IntPtr il2CppPtr) : Minigame(il2CppPtr)
{
var shapeShifterRole = RoleManager.Instance.GetRole(RoleTypes.Shapeshifter);
var ogMenu = shapeShifterRole.TryCast<ShapeshifterRole>()!.ShapeshifterMenu;
var ogMenu = shapeShifterRole.TryCast<ShapeshifterRole>().ShapeshifterMenu;
var newMenu = Instantiate(ogMenu);
var customMenu = newMenu.gameObject.AddComponent<CustomPlayerMenu>();
customMenu.panelPrefab = newMenu.PanelPrefab;
customMenu.xStart = newMenu.XStart;
customMenu.yStart = newMenu.YStart;
customMenu.xOffset = newMenu.XOffset;
customMenu.yOffset = newMenu.YOffset;
customMenu.backButton = newMenu.BackButton;
var back = customMenu.backButton.GetComponent<PassiveButton>();
customMenu.PanelPrefab = newMenu.PanelPrefab;
customMenu.XStart = newMenu.XStart;
customMenu.YStart = newMenu.YStart;
customMenu.XOffset = newMenu.XOffset;
customMenu.YOffset = newMenu.YOffset;
customMenu.BackButton = newMenu.BackButton;
var back = customMenu.BackButton.GetComponent<PassiveButton>();
back.OnClick.RemoveAllListeners();
back.OnClick.AddListener((UnityAction)(() =>
{
@@ -64,7 +65,7 @@ public class CustomPlayerMenu(IntPtr il2CppPtr) : Minigame(il2CppPtr)
newMenu.DestroyImmediate();
customMenu.transform.SetParent(Camera.main!.transform, false);
customMenu.transform.SetParent(Camera.main.transform, false);
customMenu.transform.localPosition = new Vector3(0f, 0f, -50f);
return customMenu;
}
@@ -74,7 +75,6 @@ public class CustomPlayerMenu(IntPtr il2CppPtr) : Minigame(il2CppPtr)
ControllerManager.Instance.CloseOverlayMenu(name);
}
/// <inheritdoc />
public override void Begin(PlayerTask task)
{
throw new NotImplementedException("Use the other Begin method.");
@@ -90,7 +90,7 @@ public class CustomPlayerMenu(IntPtr il2CppPtr) : Minigame(il2CppPtr)
{
MinigameStubs.Begin(this, null);
var back = backButton.GetComponent<PassiveButton>();
var back = BackButton.GetComponent<PassiveButton>();
back.OnClick.AddListener((UnityAction)(() =>
{
onClick(null);
@@ -107,13 +107,13 @@ public class CustomPlayerMenu(IntPtr il2CppPtr) : Minigame(il2CppPtr)
var num = i % 3;
var num2 = i / 3;
var flag = PlayerControl.LocalPlayer.Data.Role.NameColor == player.Data.Role.NameColor;
var shapeshifterPanel = Instantiate(panelPrefab, transform);
shapeshifterPanel.transform.localPosition = new Vector3(xStart + num * xOffset, yStart + num2 * yOffset, -1f);
var shapeshifterPanel = Instantiate(PanelPrefab, transform);
shapeshifterPanel.transform.localPosition = new Vector3(XStart + num * XOffset, YStart + num2 * YOffset, -1f);
shapeshifterPanel.SetPlayer(i, player.Data, (Il2CppSystem.Action)(() => { onClick(player); }));
shapeshifterPanel.NameText.color = flag ? player.Data.Role.NameColor : Color.white;
potentialVictims.Add(shapeshifterPanel);
list2.Add(shapeshifterPanel.Button);
}
ControllerManager.Instance.OpenOverlayMenu(name, backButton, defaultButtonSelected, list2);
ControllerManager.Instance.OpenOverlayMenu(name, BackButton, DefaultButtonSelected, list2);
}
}
+9 -11
View File
@@ -1,12 +1,19 @@
using BepInEx;
using System;
using BepInEx;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using Il2CppInterop.Runtime;
using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using MonoMod.Utils;
using Reactor;
using Reactor.Networking;
using Reactor.Networking.Attributes;
using Reactor.Utilities;
using TMPro;
using UnityEngine;
using UnityEngine.ProBuilder;
using Delegate = Il2CppSystem.Delegate;
namespace MiraAPI;
@@ -16,20 +23,11 @@ namespace MiraAPI;
[BepInAutoPlugin("mira.api", "MiraAPI")]
[BepInProcess("Among Us.exe")]
[BepInDependency(ReactorPlugin.Id)]
[BepInDependency(ModCompatibility.SubmergedId, BepInDependency.DependencyFlags.SoftDependency)]
[ReactorModFlags(ModFlags.RequireOnAllClients)]
public partial class MiraApiPlugin : BasePlugin
{
/// <summary>
/// Gets the branding Mira API color.
/// </summary>
public static Color MiraColor { get; } = new Color32(238, 154, 112, 255);
/// <summary>
/// Gets the default color for headers in the options menu.
/// </summary>
public static Color DefaultHeaderColor { get; } = new Color32(77, 77, 77, 255);
public static Color DefaultHeaderColor = new Color32(77, 77, 77, 255);
private static MiraPluginManager? PluginManager { get; set; }
internal Harmony Harmony { get; } = new(Id);
-28
View File
@@ -1,28 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using BepInEx.Unity.IL2CPP;
namespace MiraAPI;
/// <summary>
/// Mod compatibility tools.
/// </summary>
public static class ModCompatibility
{
/// <summary>
/// The ID for the Submerged mod.
/// </summary>
public const string SubmergedId = "Submerged";
/// <summary>
/// Checks if the Submerged mod is loaded.
/// </summary>
/// <param name="submergedAssembly">Submerged mod assembly if loaded, null otherwise.</param>
/// <returns>True if the Submerged mod is loaded, false otherwise.</returns>
public static bool SubmergedLoaded([NotNullWhen(true)] out Assembly? submergedAssembly)
{
var result = IL2CPPChainloader.Instance.Plugins.TryGetValue(SubmergedId, out var plugin);
submergedAssembly = result ? plugin?.Instance.GetType().Assembly : null;
return result;
}
}
+5 -19
View File
@@ -1,8 +1,6 @@
using System;
using MiraAPI.GameOptions;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Modifiers;
@@ -50,14 +48,9 @@ public abstract class BaseModifier : IOptionable
public abstract string ModifierName { get; }
/// <summary>
/// Gets the modifier icon. Useless if HideOnUi is true.
/// Gets a value indicating whether the modifier is hidden on the UI.
/// </summary>
public virtual LoadableAsset<Sprite>? ModifierIcon => null;
/// <summary>
/// Gets a value indicating whether the modifier is hidden on the UI. Will be hidden either way if no description is provided.
/// </summary>
public virtual bool HideOnUi => GetDescription() == string.Empty;
public virtual bool HideOnUi => false;
/// <summary>
/// Gets a value indicating whether the modifier is shown in the freeplay menu.
@@ -70,10 +63,10 @@ public abstract class BaseModifier : IOptionable
public virtual bool Unique => true;
/// <summary>
/// Gets the HUD description for this modifier. Does nothing if <see cref="HideOnUi"/> is true. Required to be visible on UI.
/// Gets the HUD information for this modifier. Defaults to the modifier name. Does nothing if <see cref="HideOnUi"/> is true.
/// </summary>
/// <returns>The description string for the HUD.</returns>
public virtual string GetDescription() => string.Empty;
/// <returns>The information string for the HUD.</returns>
public virtual string GetHudString() => ModifierName;
/// <summary>
/// Called when the modifier is activated.
@@ -111,13 +104,6 @@ public abstract class BaseModifier : IOptionable
{
}
/// <summary>
/// Called when a meeting starts.
/// </summary>
public virtual void OnMeetingStart()
{
}
/// <summary>
/// Determines whether the player can vent.
/// </summary>
+43 -123
View File
@@ -3,11 +3,13 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.Modifiers.ModifierDisplay;
using MiraAPI.Modifiers.Types;
using MiraAPI.Utilities;
using Reactor.Utilities;
using Reactor.Utilities.Attributes;
using TMPro;
using UnityEngine;
namespace MiraAPI.Modifiers;
@@ -24,12 +26,12 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
[HideFromIl2Cpp]
public ImmutableList<BaseModifier> ActiveModifiers { get; private set; } = ImmutableList<BaseModifier>.Empty;
private ModifierDisplayComponent? ModifierDisplay { get; set; }
[HideFromIl2Cpp]
private List<BaseModifier> Modifiers { get; set; } = [];
private PlayerControl _player = null!;
private PlayerControl? _player;
private TextMeshPro? _modifierText;
private readonly List<BaseModifier> _toRemove = [];
@@ -40,18 +42,9 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
_toRemove.AddRange(Modifiers);
}
private void Awake()
private void Start()
{
_player = GetComponent<PlayerControl>();
if (_player == null)
{
Logger<MiraApiPlugin>.Error("ModifierComponent could not find PlayerControl on the same GameObject.");
Destroy(this);
return;
}
ModifierExtensions.ModifierComponents.Add(_player, this);
Modifiers = [];
if (!_player.AmOwner)
@@ -59,42 +52,23 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
return;
}
ModifierDisplay = ModifierDisplayComponent.CreateDisplay();
ModifierDisplay?.UpdateModifiersList(Modifiers);
_modifierText = Helpers.CreateTextLabel("ModifierText", HudManager.Instance.transform, AspectPosition.EdgeAlignments.RightTop, new Vector3(10.1f, 3.5f, 0), textAlignment: TextAlignmentOptions.Right);
_modifierText.verticalAlignment = VerticalAlignmentOptions.Top;
}
private void FixedUpdate()
{
var removed = _toRemove.Count > 0;
foreach (var modifier in _toRemove.ToArray())
{
_toRemove.Remove(modifier);
try
{
modifier.OnDeactivate();
}
catch (Exception e)
{
Logger<MiraApiPlugin>.Error($"Error while deactivating modifier {modifier.ModifierName}: {e.ToString()}");
}
modifier.OnDeactivate();
Modifiers.Remove(modifier);
}
var added = _toAdd.Count > 0;
foreach (var modifier in _toAdd.ToArray())
{
_toAdd.Remove(modifier);
Modifiers.Add(modifier);
modifier.Initialized = true;
try
{
modifier.OnActivate();
}
catch (Exception e)
{
Logger<MiraApiPlugin>.Error($"Error while activating modifier {modifier.ModifierName}: {e.ToString()}");
}
modifier.OnActivate();
if (modifier is TimedModifier { AutoStart: true } timer)
{
@@ -102,31 +76,21 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
}
}
if (removed || added)
if (_toAdd.Count > 0 || _toRemove.Count > 0)
{
if (_player.AmOwner && HudManager.InstanceExists)
if (_player?.AmOwner == true)
{
ModifierDisplay?.UpdateModifiersList(Modifiers);
HudManager.Instance?.SetHudActive(_player, _player.Data.Role, HudManager.Instance.TaskPanel.isActiveAndEnabled);
}
_toAdd.Clear();
_toRemove.Clear();
ActiveModifiers = Modifiers.ToImmutableList();
}
foreach (var modifier in ActiveModifiers)
{
try
{
modifier.FixedUpdate();
}
catch (Exception e)
{
Logger<MiraApiPlugin>.Error($"Error while (fixed) updating modifier {modifier.ModifierName}: {e.ToString()}");
}
}
if (_player.AmOwner && ModifierDisplay && HudManager.InstanceExists)
{
ModifierDisplay!.gameObject.SetActive(MeetingHud.Instance || HudManager.Instance.TaskPanel.isActiveAndEnabled);
modifier.FixedUpdate();
}
}
@@ -134,21 +98,34 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
{
foreach (var modifier in ActiveModifiers)
{
try
{
modifier.Update();
}
catch (Exception e)
{
Logger<MiraApiPlugin>.Error($"Error while updating modifier {modifier.ModifierName}: {e.ToString()}");
}
modifier.Update();
}
}
private void OnDestroy()
{
// Remove this component from the global list when destroyed
ModifierExtensions.ModifierComponents.Remove(_player);
if (!_modifierText || _player?.AmOwner == false)
{
return;
}
if (MeetingHud.Instance)
{
return;
}
var showText = false;
var builder = new StringBuilder("<b><size=130%>Modifiers:</b></size>\n");
foreach (var modifier in ActiveModifiers)
{
if (modifier.HideOnUi)
{
continue;
}
showText = true;
builder.AppendLine(modifier.GetHudString());
}
_modifierText!.text = showText ? builder.ToString() : string.Empty;
}
/// <summary>
@@ -515,61 +492,4 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
{
return ActiveModifiers.Exists(x => x.UniqueId == id);
}
/// <summary>
/// Checks if a player has an active modifier by its type.
/// </summary>
/// <param name="checkInactive">Whether to check inactive modifiers (those pending to be added).</param>
/// <param name="predicate">The predicate to check the modifier.</param>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
/// <returns>True if the Modifier is present, false otherwise.</returns>
[HideFromIl2Cpp]
public bool HasModifier<T>(bool checkInactive, Func<T, bool>? predicate=null) where T : BaseModifier
{
return ActiveModifiers.Exists(MatchExpr) || (checkInactive && _toAdd.Exists(MatchExpr));
bool MatchExpr(BaseModifier bm) => bm is T modifier && (predicate == null || predicate(modifier));
}
/// <summary>
/// Checks if a player has an active modifier by its type.
/// </summary>
/// <param name="type">The modifier type.</param>
/// <param name="checkInactive">Whether to check inactive modifiers (those pending to be added).</param>
/// <param name="predicate">The predicate to check the modifier.</param>
/// <returns>True if the Modifier is present, false otherwise.</returns>
[HideFromIl2Cpp]
public bool HasModifier(Type type, bool checkInactive, Func<BaseModifier, bool>? predicate=null)
{
return ActiveModifiers.Exists(MatchExpr) || (checkInactive && _toAdd.Exists(MatchExpr));
bool MatchExpr(BaseModifier bm) => bm.GetType() == type && (predicate == null || predicate(bm));
}
/// <summary>
/// Checks if a player has an active modifier by its type ID.
/// </summary>
/// <param name="id">The modifier's type ID.</param>
/// <param name="checkInactive">Whether to check inactive modifiers (those pending to be added).</param>
/// <param name="predicate">The predicate to check the modifier.</param>
/// <returns>True if the modifier is present, false otherwise.</returns>
[HideFromIl2Cpp]
public bool HasModifier(uint id, bool checkInactive, Func<BaseModifier, bool>? predicate=null)
{
var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
$"Cannot get modifier with id {id} because it is not registered.");
return HasModifier(type, checkInactive, predicate);
}
/// <summary>
/// Checks if a player has an active modifier by its unique ID.
/// </summary>
/// <param name="id">The modifier's guid.</param>
/// <param name="checkInactive">Whether to check inactive modifiers (those pending to be added).</param>
/// <returns>True if the modifier is present, false otherwise.</returns>
[HideFromIl2Cpp]
public bool HasModifier(Guid id, bool checkInactive)
{
return ActiveModifiers.Exists(MatchExpr) || (checkInactive && _toAdd.Exists(MatchExpr));
bool MatchExpr(BaseModifier bm) => bm.UniqueId == id;
}
}
@@ -1,212 +0,0 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using HarmonyLib;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities.Attributes;
using Reactor.Utilities.Extensions;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
namespace MiraAPI.Modifiers.ModifierDisplay;
/// <summary>
/// The code used to display Mira modifiers.
/// </summary>
[RegisterInIl2Cpp]
public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
{
/// <summary>
/// Gets the instance of the Modifier Display.
/// </summary>
public static ModifierDisplayComponent? Instance { get; private set; }
private RectTransform _children = null!;
private GameObject _modTemplate = null!;
private PassiveButton _toggleButton = null!;
private TextMeshPro _toggleBtnText = null!;
private RectTransform _pagination = null!;
private TextMeshPro _pageText = null!;
private PassiveButton _nextButton = null!;
private PassiveButton _backButton = null!;
private int _currentPage;
private const int ItemsPerPage = 3;
private readonly Dictionary<BaseModifier, ModifierUiComponent> _modifiers = [];
/// <summary>
/// Gets a read only dictionary of the created modifier UI components.
/// </summary>
[HideFromIl2Cpp]
public ReadOnlyDictionary<BaseModifier, ModifierUiComponent> Modifiers => new(_modifiers);
/// <summary>
/// Gets a value indicating whether the modifier display hud is currently open.
/// </summary>
public bool IsOpen { get; private set; }
private void Awake()
{
Instance = this;
_children = transform.GetChild(0).GetComponent<RectTransform>();
_modTemplate = transform.GetChild(2).gameObject;
_toggleButton = transform.GetChild(1).GetComponent<PassiveButton>();
_toggleButton.OnClick = new Button.ButtonClickedEvent();
_toggleButton.OnClick.AddListener((UnityAction)(() =>
{
IsOpen = !IsOpen;
_children.gameObject.SetActive(!_children.gameObject.activeSelf);
}));
_toggleButton.ClickSound = HudManager.Instance.GameMenu.StreamerModeButton.GetComponent<PassiveButton>().ClickSound;
_toggleButton.HoverSound = HudManager.Instance.GameMenu.StreamerModeButton.GetComponent<PassiveButton>().HoverSound;
_toggleButton.inactiveSprites = _toggleButton.transform.GetChild(1).gameObject;
_toggleBtnText = _toggleButton.transform.GetChild(0).GetComponent<TextMeshPro>();
_pagination = _children.transform.GetChild(0).GetComponent<RectTransform>();
_pageText = _pagination.transform.GetChild(0).GetComponent<TextMeshPro>();
_nextButton = _pagination.transform.GetChild(1).GetComponent<PassiveButton>();
_backButton = _pagination.transform.GetChild(2).GetComponent<PassiveButton>();
_toggleBtnText.font = _pageText.font = _nextButton.buttonText.font = _backButton.buttonText.font = HudManager.Instance.TaskPanel.taskText.font;
_toggleBtnText.fontMaterial = _pageText.fontMaterial = _nextButton.buttonText.fontMaterial = _backButton.buttonText.fontMaterial = HudManager.Instance.TaskPanel.taskText.fontMaterial;
_nextButton.OnClick = new Button.ButtonClickedEvent();
_nextButton.OnClick.AddListener((UnityAction)(() =>
{
_currentPage++;
UpdatePage();
}));
_backButton.OnClick = new Button.ButtonClickedEvent();
_backButton.OnClick.AddListener((UnityAction)(() =>
{
_currentPage--;
UpdatePage();
}));
IsOpen = false;
_children.gameObject.SetActive(false);
}
/// <summary>
/// Use this to refresh the modifiers UI. Useful if you hide modifiers uis based on options.
/// </summary>
public void RefreshModifiers()
{
UpdateModifiersList([.. PlayerControl.LocalPlayer.GetModifierComponent().ActiveModifiers]);
}
/// <summary>
/// Use this to toggle the visibility of the modifiers tab.
/// </summary>
public void ToggleTab()
{
IsOpen = !IsOpen;
_children.gameObject.SetActive(IsOpen);
RefreshModifiers();
}
internal void DestroyComponent(ModifierUiComponent component)
{
_modifiers.Remove(component.Modifier!);
component.gameObject.DestroyImmediate();
RefreshModifiers();
}
[HideFromIl2Cpp]
internal static ModifierDisplayComponent CreateDisplay()
{
var gameObject = Instantiate(MiraAssets.ModifierDisplay.LoadAsset(), HudManager.Instance.transform);
var minigame = gameObject.AddComponent<ModifierDisplayComponent>();
return minigame;
}
[HideFromIl2Cpp]
private ModifierUiComponent CreateForModifier(BaseModifier modifier)
{
var newMod = Instantiate(_modTemplate, _children.transform);
newMod.transform.name = modifier.ModifierName;
var comp = newMod.AddComponent<ModifierUiComponent>();
comp.Modifier = modifier;
return comp;
}
private void UpdatePage()
{
var totalPages = Mathf.CeilToInt((float)_modifiers.Count / ItemsPerPage);
_currentPage = Mathf.Clamp(_currentPage, 0, Mathf.Max(0, totalPages - 1));
if (_modifiers.Count <= ItemsPerPage)
{
_modifiers.Do(x => x.Value.gameObject.SetActive(true));
_pagination.gameObject.SetActive(false);
return;
}
_pagination.gameObject.SetActive(true);
if (_pageText != null)
{
_pageText.text = $"Page {_currentPage + 1}/{Mathf.Max(totalPages, 1)}";
}
_nextButton.gameObject.SetActive(true);
_backButton.gameObject.SetActive(true);
_modifiers.Do(x => x.Value.gameObject.SetActive(false));
var start = _currentPage * ItemsPerPage;
var end = Mathf.Min(start + ItemsPerPage, _modifiers.Count);
var modifiers = _modifiers.ToArray();
for (var i = start; i < end; i++)
{
modifiers[i].Value.gameObject.SetActive(true);
}
_pagination.SetAsLastSibling();
}
[HideFromIl2Cpp]
internal void UpdateModifiersList(List<BaseModifier> modifiers)
{
var filteredModifiers = modifiers.Where(x => !x.HideOnUi && x.GetDescription() != string.Empty).ToList();
var modifiersToAdd = filteredModifiers.ToList();
foreach (var mod in _modifiers.ToArray())
{
if (modifiersToAdd.Contains(mod.Key))
{
modifiersToAdd.Remove(mod.Key);
continue;
}
mod.Value.gameObject.Destroy();
_modifiers.Remove(mod.Key);
}
_toggleBtnText.text = $"Modifiers ({filteredModifiers.Count})";
if (filteredModifiers.Count == 0)
{
_toggleButton.gameObject.SetActive(false);
return;
}
_toggleButton.gameObject.SetActive(true);
foreach (var mod in modifiersToAdd)
{
_modifiers.Add(mod, CreateForModifier(mod));
}
UpdatePage();
}
}
@@ -1,72 +0,0 @@
using Il2CppInterop.Runtime.Attributes;
using Reactor.Utilities.Attributes;
using TMPro;
using UnityEngine;
namespace MiraAPI.Modifiers.ModifierDisplay;
/// <summary>
/// The code placed on every Modifier HUD object. Used to handle updating.
/// </summary>
[RegisterInIl2Cpp]
public class ModifierUiComponent(nint cppPtr) : MonoBehaviour(cppPtr)
{
/// <summary>
/// Gets the modifier which this component is for.
/// </summary>
[HideFromIl2Cpp]
public BaseModifier? Modifier { get; internal set; }
// ReSharper disable InconsistentNaming
private SpriteRenderer modBg = null!;
private TextMeshPro desc = null!;
private SpriteRenderer icon = null!;
private TextMeshPro nameText = null!;
private RectTransform descRect = null!;
// ReSharper restore InconsistentNaming
private void Awake()
{
if (Modifier == null) return;
modBg = transform.GetChild(0).GetComponent<SpriteRenderer>();
desc = modBg.transform.GetChild(0).GetComponent<TextMeshPro>();
icon = modBg.transform.GetChild(1).GetComponent<SpriteRenderer>();
var nameBg = transform.GetChild(1);
nameText = nameBg.GetChild(0).GetComponent<TextMeshPro>();
descRect = desc.gameObject.GetComponent<RectTransform>();
nameText.font = desc.font = HudManager.Instance.TaskPanel.taskText.font;
nameText.fontMaterial = desc.fontMaterial = HudManager.Instance.TaskPanel.taskText.fontMaterial;
}
// Might not be the best solution, but used to update name/description/icon if updated in the modifier.
private void FixedUpdate()
{
if (Modifier == null || ModifierDisplayComponent.Instance == null || !ModifierDisplayComponent.Instance.IsOpen) return;
if (Modifier.HideOnUi || Modifier.GetDescription() == string.Empty)
{
ModifierDisplayComponent.Instance.DestroyComponent(this);
return;
}
nameText.text = Modifier.ModifierName;
desc.text = Modifier.GetDescription();
modBg.gameObject.SetActive(true);
icon.gameObject.SetActive(Modifier.ModifierIcon != null);
if (icon.gameObject && icon.gameObject.activeSelf && Modifier.ModifierIcon != null)
{
icon.sprite = Modifier.ModifierIcon?.LoadAsset();
icon.size = new Vector2(0.7f, 0.7f);
icon.tileMode = SpriteTileMode.Continuous;
descRect.localPosition = new Vector3(0.38f, -0.1f, -5);
descRect.sizeDelta = new Vector2(2.38f, 0.65f);
}
else
{
descRect.localPosition = new Vector3(0, -0.1f, -5);
descRect.sizeDelta = new Vector2(3.1f, 0.65f);
}
}
}
+11 -11
View File
@@ -180,17 +180,6 @@ public static class ModifierExtensions
return player.GetModifierComponent().HasModifier(typeId, predicate);
}
/// <summary>
/// Checks if the player has a specific modifier by its GUID.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="uniqueId">The unique ID of the modifier.</param>
/// <returns>True if the player has the modifier, false otherwise.</returns>
public static bool HasModifier(this PlayerControl player, Guid uniqueId)
{
return player.GetModifierComponent().HasModifier(uniqueId);
}
/// <summary>
/// Clears all modifiers from a player.
/// </summary>
@@ -215,6 +204,17 @@ public static class ModifierExtensions
}
}
/// <summary>
/// Checks if the player has a specific modifier by its GUID.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="uniqueId">The unique ID of the modifier.</param>
/// <returns>True if the player has the modifier, false otherwise.</returns>
public static bool HasModifier(this PlayerControl player, Guid uniqueId)
{
return player.GetModifierComponent().HasModifier(uniqueId);
}
/// <summary>
/// Tries to get a modifier by its type.
/// </summary>
+4 -6
View File
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using MiraAPI.Utilities;
@@ -12,7 +11,7 @@ namespace MiraAPI.Modifiers;
/// </summary>
public static class ModifierFactory
{
private static readonly Dictionary<(Type, Type[]), Func<object[], BaseModifier>> ConstructorCache = [];
private static readonly Dictionary<(Type, Type[]), Func<object[], BaseModifier>> _constructorCache = [];
private static Func<object[], BaseModifier> CreateConstructor(Type type, params object[] args)
{
@@ -46,13 +45,13 @@ public static class ModifierFactory
/// <returns>An instance of the modifier.</returns>
public static BaseModifier CreateInstance(Type type, params object[] args)
{
var argTypes = args.Select(arg => arg.GetType()).ToArray();
var argTypes = args.Select(arg => arg?.GetType() ?? typeof(object)).ToArray();
var key = (type, argTypes);
if (!ConstructorCache.TryGetValue(key, out var constructor))
if (!_constructorCache.TryGetValue(key, out var constructor))
{
constructor = CreateConstructor(type, args);
ConstructorCache[key] = constructor;
_constructorCache[key] = constructor;
}
return constructor(args);
@@ -96,7 +95,6 @@ public static class ModifierFactory<T> where T : BaseModifier
/// </summary>
/// <param name="args">Parameters for the constructor.</param>
/// <returns>An instance of the modifier.</returns>
[SuppressMessage("Design", "CA1000:Do not declare static members on generic types", Justification = "This is a factory class.")]
public static T CreateInstance(params object[] args)
{
return Constructor(args);
+51 -39
View File
@@ -5,6 +5,7 @@ using System.Runtime.Serialization;
using MiraAPI.Modifiers.Types;
using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using Reactor.Utilities;
using Reactor.Utilities.Extensions;
using Random = System.Random;
@@ -102,57 +103,68 @@ public static class ModifierManager
{
var rand = new Random();
// Filter and sort modifiers by descending priority.
var modifiers = IdToTypeModifierMap
.Where(x=>x.Value.IsAssignableTo(typeof(GameModifier)))
.Select(x => Activator.CreateInstance(x.Value) as GameModifier)
.OfType<GameModifier>()
.Where(x => x.GetAmountPerGame() > 0 && x.GetAssignmentChance() > 0)
.OrderByDescending(x => x.Priority())
.ToArray();
foreach (var modifier in modifiers)
foreach (var priority in PrioritiesToIdsMap.Keys.OrderByDescending(x => x))
{
var assignments = modifier.GetAmountPerGame();
var filteredModifiers = new List<uint>();
var validPlayers = plrs.Where(x => IsGameModifierValid(x, modifier, modifier.TypeId)).ToList();
if (validPlayers.Count == 0)
foreach (var id in PrioritiesToIdsMap[priority])
{
Logger<MiraApiPlugin>.Warning($"No valid players for modifier {modifier.ModifierName}");
var mod = Activator.CreateInstance(IdToTypeModifierMap[id]) as GameModifier;
var chance = Math.Clamp(mod!.GetAssignmentChance(), 0, 100);
var count = mod.GetAmountPerGame();
if (chance == 0 || count == 0)
{
continue;
}
var maxCount = plrs.Count(x => IsGameModifierValid(x, mod, id));
if (maxCount == 0)
{
Logger<MiraApiPlugin>.Warning($"No players are valid for {mod.ModifierName}");
continue;
}
var num = Math.Clamp(count, 0, maxCount);
for (var i = 0; i < num; i++)
{
var randomNum = rand.Next(100);
if (randomNum < chance)
{
filteredModifiers.Add(id);
}
}
}
if (filteredModifiers.Count == 0)
{
Logger<MiraApiPlugin>.Warning($"No filtered modifiers for priority {priority}");
continue;
}
assignments = Math.Min(assignments, validPlayers.Count);
var availablePlayers = new List<PlayerControl>(validPlayers);
var shuffledList = filteredModifiers.Randomize();
for (var i = 0; i < assignments; i++)
if (shuffledList.Count > plrs.Count)
{
var chance = Math.Clamp(modifier.GetAssignmentChance(), 0, 100);
if (rand.Next(100) >= chance)
shuffledList = shuffledList.GetRange(0, plrs.Count);
}
foreach (var id in shuffledList)
{
var mod = Activator.CreateInstance(IdToTypeModifierMap[id]) as GameModifier;
var plr = plrs.Where(x => IsGameModifierValid(x, mod!, id)).Random();
if (!plr || plr == null)
{
continue;
Logger<MiraApiPlugin>.Warning($"Somehow valid players for modifier {mod!.ModifierName} disappeared");
}
var candidates = availablePlayers
.Where(x => IsGameModifierValid(x, modifier, modifier.TypeId))
.ToList();
if (candidates.Count == 0)
else
{
Logger<MiraApiPlugin>.Warning(
$"No available players for modifier {modifier.ModifierName} at assignment {i + 1}");
break;
plr.RpcAddModifier(id);
}
var plr = candidates.Random();
if (plr == null)
{
Logger<MiraApiPlugin>.Warning($"Valid player for modifier {modifier.ModifierName} disappeared");
continue;
}
plr.RpcAddModifier(modifier.TypeId);
availablePlayers.Remove(plr);
}
}
}
+1 -1
View File
@@ -34,7 +34,7 @@ public abstract class GameModifier : BaseModifier
public virtual bool IsModifierValidOn(RoleBehaviour role) => true;
/// <summary>
/// Determines whether the player won the game with this modifier. The modifier with the highest priority will override other modifiers.
/// Determines whether the player won the game with this modifier.
/// </summary>
/// <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>
+4 -4
View File
@@ -45,12 +45,12 @@ public abstract class TimedModifier : BaseModifier
public override bool HideOnUi => !TimerActive;
/// <summary>
/// Gets the HUD description for Timed Modifier, including the time remaining.
/// Gets the HUD information for Timed Modifier, including the time remaining.
/// </summary>
/// <returns>A string with the time remaining.</returns>
public override string GetDescription()
/// <returns>A string with the hud information + timer.</returns>
public override string GetHudString()
{
return $"Remaining Time: {Math.Round(Duration - TimeRemaining, 0)}s/{Duration}s";
return base.GetHudString() + $" <size=70%>({Math.Round(Duration - TimeRemaining, 0)}s/{Duration}s)</size>";
}
/// <summary>
+7 -8
View File
@@ -1,6 +1,5 @@
using System.Collections;
using System.Linq;
using AmongUs.Data;
using AmongUs.GameOptions;
using Assets.CoreScripts;
using BepInEx.Unity.IL2CPP.Utils;
@@ -97,7 +96,7 @@ public static class CustomMurderRpc
var flag = PlayerControl.LocalPlayer.Data.Role.Role == RoleTypes.GuardianAngel;
if (flag && PlayerControl.LocalPlayer.Data.PlayerId == target.protectedByGuardianId)
{
DataManager.Player.Stats.IncrementStat(StatID.Role_GuardianAngel_CrewmatesProtected);
StatsManager.Instance.IncrementStat(StringNames.StatsGuardianAngelCrewmatesProtected);
AchievementManager.Instance.OnProtectACrewmate();
}
@@ -128,14 +127,14 @@ public static class CustomMurderRpc
DebugAnalytics.Instance.Analytics.Kill(target.Data, source.Data);
if (source.AmOwner)
{
DataManager.Player.Stats.IncrementStat(
StatsManager.Instance.IncrementStat(
GameManager.Instance.IsHideAndSeek()
? StatID.HideAndSeek_ImpostorKills
: StatID.ImpostorKills);
? StringNames.StatsImpostorKills_HideAndSeek
: StringNames.StatsImpostorKills);
if (source.CurrentOutfitType == PlayerOutfitType.Shapeshifted)
{
DataManager.Player.Stats.IncrementStat(StatID.Role_Shapeshifter_ShiftedKills);
StatsManager.Instance.IncrementStat(StringNames.StatsShapeshifterShiftedKills);
}
if (Constants.ShouldPlaySfx() && playKillSound)
@@ -153,7 +152,7 @@ public static class CustomMurderRpc
target.gameObject.layer = LayerMask.NameToLayer("Ghost");
if (target.AmOwner)
{
DataManager.Player.Stats.IncrementStat(StatID.TimesMurdered);
StatsManager.Instance.IncrementStat(StringNames.StatsTimesMurdered);
if (Minigame.Instance)
{
try
@@ -264,7 +263,7 @@ public static class CustomMurderRpc
deadBody.enabled = true;
}
var afterMurderEvent = new AfterMurderEvent(source, target, deadBody);
var afterMurderEvent = new AfterMurderEvent(source, target);
MiraEventManager.InvokeEvent(afterMurderEvent);
if (!isParticipant)
-5
View File
@@ -44,9 +44,4 @@ public enum MiraRpc : uint
/// Custom RPC to undo a player's vote.
/// </summary>
RemoveVote,
/// <summary>
/// Custom RPC to replace Among Us CastVote.
/// </summary>
CastVote,
}
-5
View File
@@ -1,5 +0,0 @@
[*.{cs,vb}]
dotnet_diagnostic.CA1707.severity = none
dotnet_diagnostic.CS1591.severity = none
dotnet_diagnostic.SA1611.severity = none
dotnet_diagnostic.SA1615.severity = none
@@ -1,7 +1,6 @@
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
using MiraAPI.Utilities;
namespace MiraAPI.Patches.Events;
@@ -20,7 +19,7 @@ public static class KillAnimationPatches
[HarmonyPatch(typeof(KillAnimation), nameof(KillAnimation.CoPerformKill))]
public static void KillAnimPostfix(PlayerControl source, PlayerControl target)
{
var afterMurderEvent = new AfterMurderEvent(source, target, Helpers.GetBodyById(target.PlayerId));
var afterMurderEvent = new AfterMurderEvent(source, target);
MiraEventManager.InvokeEvent(afterMurderEvent);
}
}
@@ -1,15 +0,0 @@
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
namespace MiraAPI.Patches.Events;
[HarmonyPatch(typeof(SpawnInMinigame), nameof(SpawnInMinigame.Close))]
internal static class SpawnInMinigamePatch
{
public static void Postfix()
{
var @event = new RoundStartEvent(false);
MiraEventManager.InvokeEvent(@event);
}
}
@@ -1,31 +0,0 @@
using System.Collections.Generic;
using System.Reflection;
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
namespace MiraAPI.Patches.Events;
[HarmonyPatch]
internal static class SubmergedSpawnGamePatch
{
public static bool Prepare()
{
return ModCompatibility.SubmergedLoaded(out _);
}
public static IEnumerable<MethodBase> TargetMethods()
{
if (ModCompatibility.SubmergedLoaded(out var subAss) &&
subAss.GetType("Submerged.SpawnIn.SubmarineSelectSpawn") is { } selectSpawn)
{
yield return AccessTools.Method(selectSpawn, "OnDestroy");
}
}
public static void Postfix()
{
var @event = new RoundStartEvent(false);
MiraEventManager.InvokeEvent(@event);
}
}
+3 -3
View File
@@ -18,7 +18,7 @@ namespace MiraAPI.Patches.Freeplay;
[HarmonyPatch(typeof(TaskAdderGame))]
internal static class TaskAdderPatches
{
private static Scroller _scroller = null!;
private static Scroller? _scroller;
[HarmonyPostfix]
[HarmonyPatch(nameof(TaskAdderGame.Begin))]
@@ -293,9 +293,9 @@ internal static class TaskAdderPatches
chip.GetComponent<SpriteRenderer>().maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
}
if (_scroller)
if (_scroller && _scroller != null)
{
_scroller.CalculateAndSetYBounds(__instance.ActiveItems.Count, 6, 5, 1.4f);
_scroller.CalculateAndSetYBounds(__instance.ActiveItems.Count, 6, 3, 1f);
_scroller.SetYBoundsMin(0.0f);
_scroller.ScrollToTop();
}
+1 -1
View File
@@ -4,7 +4,7 @@ using MiraAPI.Hud;
namespace MiraAPI.Patches.Hud;
/// <summary>
/// Reset button patches.
/// Reset button patches
/// </summary>
[HarmonyPatch]
public static class ButtonResetPatches
+15 -15
View File
@@ -13,9 +13,9 @@ namespace MiraAPI.Patches;
public static class HudManagerPatches
{
// Custom buttons parent.
public static GameObject? BottomLeft { get; private set; }
public static Transform? BottomRight { get; private set; }
public static Transform? Buttons { get; private set; }
internal static GameObject? _bottomLeft;
internal static Transform? _bottomRight;
internal static Transform? _buttons;
/*
/// <summary>
@@ -36,30 +36,30 @@ public static class HudManagerPatches
[HarmonyPatch(nameof(HudManager.Start))]
public static void StartPostfix(HudManager __instance)
{
if (Buttons == null)
if (_buttons == null)
{
Buttons = __instance.transform.Find("Buttons");
_buttons = __instance.transform.Find("Buttons");
}
if (BottomRight == null)
if (_bottomRight == null)
{
BottomRight = Buttons.Find("BottomRight");
_bottomRight = _buttons.Find("BottomRight");
}
if (BottomLeft == null)
if (_bottomLeft == null)
{
BottomLeft = Object.Instantiate(BottomRight.gameObject, Buttons);
_bottomLeft = Object.Instantiate(_bottomRight.gameObject, _buttons);
}
foreach (var t in BottomLeft.GetComponentsInChildren<ActionButton>(true))
foreach (var t in _bottomLeft.GetComponentsInChildren<ActionButton>(true))
{
t.gameObject.Destroy();
}
var gridArrange = BottomLeft.GetComponent<GridArrange>();
var aspectPosition = BottomLeft.GetComponent<AspectPosition>();
var gridArrange = _bottomLeft.GetComponent<GridArrange>();
var aspectPosition = _bottomLeft.GetComponent<AspectPosition>();
BottomLeft.name = "BottomLeft";
_bottomLeft.name = "BottomLeft";
gridArrange.Alignment = GridArrange.StartAlign.Right;
aspectPosition.Alignment = AspectPosition.EdgeAlignments.LeftBottom;
@@ -67,8 +67,8 @@ public static class HudManagerPatches
{
var location = button.Location switch
{
ButtonLocation.BottomLeft => BottomLeft.transform,
ButtonLocation.BottomRight => BottomRight,
ButtonLocation.BottomLeft => _bottomLeft.transform,
ButtonLocation.BottomRight => _bottomRight,
_ => null,
};
+4 -4
View File
@@ -56,11 +56,11 @@ public static class IntroCutscenePatches
}
}
/*
[HarmonyPostfix]
[HarmonyPatch(nameof(IntroCutscene.OnDestroy))]
public static void GameBeginPatch(IntroCutscene __instance)
public static void GameBeginPatch()
{
MiraEventManager.InvokeEvent(new IntroEndEvent(__instance));
MiraEventManager.InvokeEvent(new RoundStartEvent(true));
}
CustomGameModeManager.ActiveMode?.Initialize();
}*/
}
-141
View File
@@ -1,141 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using AmongUs.Data;
using HarmonyLib;
using MiraAPI.Utilities;
using Reactor.Utilities;
using Reactor.Utilities.Extensions;
using TMPro;
using UnityEngine;
using Object = UnityEngine.Object;
namespace MiraAPI.Patches.Menu;
[HarmonyPatch(typeof(HatsTab))]
public static class HatsTabPatches
{
private static SortedList<string, List<HatData>> sortedHats = [];
private static int currentPage;
[HarmonyPatch(nameof(HatsTab.OnEnable))]
[HarmonyPrefix]
public static bool OnEnablePrefix(HatsTab __instance)
{
__instance.currentHat = HatManager.Instance.GetHatById(DataManager.Player.Customization.Hat);
var allHats = HatManager.Instance.GetUnlockedHats().ToImmutableList();
if (sortedHats.Count == 0)
{
var comparer = new ControllableComparer<string>(["vanilla"], [], StringComparer.InvariantCulture);
sortedHats = new SortedList<string, List<HatData>>(comparer);
foreach (var hat in allHats)
{
if (!sortedHats.ContainsKey(hat.StoreName)) sortedHats[hat.StoreName] = [];
sortedHats[hat.StoreName].Add(hat);
}
}
GenerateHats(__instance, currentPage);
return false;
}
[HarmonyPatch(typeof(HatsTab), nameof(HatsTab.Update))]
[HarmonyPrefix]
public static void UpdatePrefix(HatsTab __instance)
{
if (sortedHats.Count == 0) return;
if (Input.GetKeyDown(KeyCode.LeftControl) || Input.GetKeyDown(KeyCode.RightControl) || Input.GetKeyDown(KeyCode.LeftArrow))
{
currentPage--;
currentPage = currentPage < 0 ? sortedHats.Count - 1 : currentPage;
GenerateHats(__instance, currentPage);
}
else if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.RightArrow))
{
currentPage++;
currentPage = currentPage > sortedHats.Count - 1 ? 0 : currentPage;
GenerateHats(__instance, currentPage);
}
}
private static IEnumerator? loadRoutine;
private static int hatIndex;
private static void GenerateHats(HatsTab __instance, int page)
{
if (loadRoutine != null) Coroutines.Stop(loadRoutine);
hatIndex = 0;
foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy();
__instance.ColorChips.Clear();
__instance.scroller.Inner.GetComponentsInChildren<TextMeshPro>().Do(x => x.gameObject.Destroy());
var groupNameText = __instance.GetComponentInChildren<TextMeshPro>(false);
var (groupName, hats) = sortedHats.ToArray()[page];
var text = Object.Instantiate(groupNameText, __instance.scroller.Inner);
text.enabled = true;
text.gameObject.transform.localScale = Vector3.one;
text.GetComponent<TextTranslatorTMP>().Destroy();
text.EnableStencilMasking();
text.text = $"{groupName}\nPress Ctrl or Tab to cycle pages";
text.alignment = TextAlignmentOptions.Center;
text.fontSize = 3f;
text.fontSizeMax = 3f;
text.fontSizeMin = 0f;
var xLerp = __instance.XRange.Lerp(0.5f);
var yLerp = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
text.transform.localPosition = new Vector3(xLerp, yLerp, -1f);
hatIndex += 5;
loadRoutine = Coroutines.Start(CoGenerateChips(__instance, hats));
}
private static IEnumerator CoGenerateChips(HatsTab __instance, List<HatData> hats)
{
__instance.scroller.ScrollToTop();
var batchSize = 5;
for (var i = 0; i < hats.Count; i += batchSize)
{
var batch = hats.Skip(i).Take(batchSize).ToList();
foreach (var hat in batch.OrderBy(HatManager.Instance.allHats.IndexOf))
{
var hatXposition = __instance.XRange.Lerp(hatIndex % __instance.NumPerRow / (__instance.NumPerRow - 1f));
var hatYposition = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
GenerateColorChip(__instance, new Vector2(hatXposition, hatYposition), hat);
hatIndex += 1;
yield return null;
}
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f;
yield return new WaitForSeconds(0.01f);
}
__instance.currentHatIsEquipped = true;
loadRoutine = null;
}
private static void GenerateColorChip(HatsTab __instance, Vector2 position, HatData hat)
{
var colorChip = Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner);
colorChip.gameObject.name = hat.ProductId;
colorChip.Button.OnClick.AddListener((Action)(() => __instance.ClickEquip()));
colorChip.Button.OnMouseOver.AddListener((Action)(() => __instance.SelectHat(hat)));
colorChip.Button.OnMouseOut.AddListener((Action)(() => __instance.SelectHat(HatManager.Instance.GetHatById(DataManager.Player.Customization.Hat))));
colorChip.Inner.SetHat(hat, __instance.HasLocalPlayer() ? PlayerControl.LocalPlayer.Data.DefaultOutfit.ColorId : DataManager.Player.Customization.Color);
colorChip.Button.ClickMask = __instance.scroller.Hitbox;
colorChip.SelectionHighlight.gameObject.SetActive(false);
__instance.UpdateMaterials(colorChip.Inner.FrontLayer, hat);
colorChip.Inner.SetMaskType(PlayerMaterial.MaskType.SimpleUI);
colorChip.transform.localPosition = new Vector3(position.x, position.y, -1f);
colorChip.Inner.transform.localPosition = hat.ChipOffset + new Vector2(0f, -0.3f);
colorChip.Tag = hat;
__instance.ColorChips.Add(colorChip);
}
}
@@ -1,21 +0,0 @@
using HarmonyLib;
using MiraAPI.Utilities.Assets;
namespace MiraAPI.Patches.Menu;
/// <summary>
/// General MainMenuManage patches.
/// </summary>
[HarmonyPatch(typeof(MainMenuManager))]
public static class MainMenuManagerPatches
{
/// <summary>
/// A postifix on Awake to load all the addressables registered.
/// </summary>
[HarmonyPatch(nameof(MainMenuManager.Awake))]
[HarmonyPostfix]
public static void AwakePostfix()
{
AddressablesLoader.LoadAll();
}
}
@@ -1,118 +0,0 @@
using AmongUs.Data;
using HarmonyLib;
using Innersloth.Assets;
using MiraAPI.Utilities;
using Reactor.Utilities.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using Object = UnityEngine.Object;
namespace MiraAPI.Patches.Menu;
[HarmonyPatch(typeof(NameplatesTab))]
public static class NameplatesTabPatches
{
private static readonly SortedList<string, List<NamePlateData>> SortedNameplates = new(new ControllableComparer<string>(["vanilla"], [], StringComparer.InvariantCulture));
private static int currentPage;
internal static void AddRange(IEnumerable<(string Key, NamePlateData Visor)> data)
{
foreach (var item in data)
{
if (!SortedNameplates.ContainsKey(item.Key)) SortedNameplates.Add(item.Key, []);
SortedNameplates[item.Key].Add(item.Visor);
}
}
[HarmonyPatch(nameof(NameplatesTab.OnEnable))]
[HarmonyPrefix]
public static bool OnEnablePrefix(NameplatesTab __instance)
{
__instance.plateId = HatManager.Instance.GetNamePlateById(DataManager.Player.Customization.namePlate).ProdId;
if (!SortedNameplates.ContainsKey("Vanilla")) AddRange(DestroyableSingleton<HatManager>.Instance.GetUnlockedNamePlates().Select(x => ("Vanilla", x)));
GenerateHats(__instance, currentPage);
return false;
}
[HarmonyPatch(typeof(NameplatesTab), nameof(NameplatesTab.Update))]
[HarmonyPrefix]
public static void UpdatePrefix(NameplatesTab __instance)
{
if (Input.GetKeyDown(KeyCode.LeftControl) || Input.GetKeyDown(KeyCode.RightControl) || Input.GetKeyDown(KeyCode.LeftArrow))
{
currentPage--;
currentPage = currentPage < 0 ? SortedNameplates.Count - 1 : currentPage;
GenerateHats(__instance, currentPage);
}
else if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.RightArrow))
{
currentPage++;
currentPage = currentPage > SortedNameplates.Count - 1 ? 0 : currentPage;
GenerateHats(__instance, currentPage);
}
}
private static void GenerateHats(NameplatesTab __instance, int page)
{
foreach (ColorChip instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy();
__instance.ColorChips.Clear();
__instance.scroller.Inner.GetComponentsInChildren<TextMeshPro>().Do(x => x.gameObject.Destroy());
var groupNameText = __instance.GetComponentInChildren<TextMeshPro>(false);
int hatIndex = 0;
var (groupName, nameplates) = SortedNameplates.ToArray()[page];
var text = Object.Instantiate(groupNameText, __instance.scroller.Inner);
text.enabled = true;
text.gameObject.transform.localScale = Vector3.one;
text.GetComponent<TextTranslatorTMP>().Destroy();
text.EnableStencilMasking();
text.text = $"{groupName}\nPress Ctrl or Tab to cycle pages";
text.alignment = TextAlignmentOptions.Center;
text.fontSize = 3f;
text.fontSizeMax = 3f;
text.fontSizeMin = 0f;
float xLerp = __instance.XRange.Lerp(0.5f);
float yLerp = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
text.transform.localPosition = new Vector3(xLerp, yLerp, -1f);
hatIndex += 2;
foreach (var visor in nameplates.OrderBy(HatManager.Instance.allNamePlates.IndexOf))
{
float hatXposition = __instance.XRange.Lerp(hatIndex % __instance.NumPerRow / (__instance.NumPerRow - 1f));
float hatYposition = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
GenerateColorChip(__instance, new Vector2(hatXposition, hatYposition), visor);
hatIndex += 1;
}
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f;
__instance.currentNameplateIsEquipped = true;
}
private static void GenerateColorChip(NameplatesTab __instance, Vector2 position, NamePlateData namePlate)
{
var colorChip = Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner);
colorChip.gameObject.name = namePlate.ProductId;
colorChip.Button.OnClick.AddListener((Action)(() => __instance.ClickEquip()));
colorChip.Button.OnMouseOver.AddListener((Action)(() => __instance.SelectNameplate(namePlate)));
colorChip.Button.OnMouseOut.AddListener((Action)(() => __instance.SelectNameplate(HatManager.Instance.GetNamePlateById(DataManager.Player.Customization.NamePlate))));
colorChip.Button.ClickMask = __instance.scroller.Hitbox;
colorChip.ProductId = namePlate.ProdId;
var x = (NamePlateViewData viewdata) =>
{
colorChip.transform.GetChild(1).GetComponent<SpriteRenderer>().sprite = viewdata?.Image;
// (colorChip as NameplateChip).image.sprite = viewdata?.Image;
};
__instance.StartCoroutine(AddressableAssetExtensions.CoLoadAssetAsync<NamePlateViewData>(__instance, namePlate.GetAssetReference(), x));
colorChip.transform.localPosition = new Vector3(position.x, position.y, -1f);
colorChip.Tag = namePlate;
__instance.ColorChips.Add(colorChip);
}
}
-118
View File
@@ -1,118 +0,0 @@
using AmongUs.Data;
using HarmonyLib;
using MiraAPI.Utilities;
using Reactor.Utilities.Extensions;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using TMPro;
using UnityEngine;
using Object = UnityEngine.Object;
namespace MiraAPI.Patches.Menu;
[HarmonyPatch(typeof(SkinsTab))]
public static class SkinsTabPatches
{
private static SortedList<string, List<SkinData>> sortedSkins = [];
private static int currentPage;
[HarmonyPatch(nameof(SkinsTab.OnEnable))]
[HarmonyPrefix]
public static bool OnEnablePrefix(SkinsTab __instance)
{
__instance.skinId = HatManager.Instance.GetSkinById(DataManager.Player.Customization.Skin).ProdId;
var allSkins = HatManager.Instance.GetUnlockedSkins().ToImmutableList();
if (sortedSkins.Count == 0)
{
var comparer = new ControllableComparer<string>(["vanilla"], [], StringComparer.InvariantCulture);
sortedSkins = new SortedList<string, List<SkinData>>(comparer);
foreach (var skin in allSkins)
{
if (!sortedSkins.ContainsKey(skin.StoreName)) sortedSkins[skin.StoreName] = [];
sortedSkins[skin.StoreName].Add(skin);
}
}
GenerateHats(__instance, currentPage);
return false;
}
[HarmonyPatch(typeof(SkinsTab), nameof(SkinsTab.Update))]
[HarmonyPrefix]
public static void UpdatePrefix(SkinsTab __instance)
{
if (Input.GetKeyDown(KeyCode.LeftControl) || Input.GetKeyDown(KeyCode.RightControl) || Input.GetKeyDown(KeyCode.LeftArrow))
{
currentPage--;
currentPage = currentPage < 0 ? sortedSkins.Count - 1 : currentPage;
GenerateHats(__instance, currentPage);
}
else if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.RightArrow))
{
currentPage++;
currentPage = currentPage > sortedSkins.Count - 1 ? 0 : currentPage;
GenerateHats(__instance, currentPage);
}
}
private static void GenerateHats(SkinsTab __instance, int page)
{
foreach (ColorChip instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy();
__instance.ColorChips.Clear();
__instance.scroller.Inner.GetComponentsInChildren<TextMeshPro>().Do(x => x.gameObject.Destroy());
var groupNameText = __instance.GetComponentInChildren<TextMeshPro>(false);
int hatIndex = 0;
var (groupName, skins) = sortedSkins.ToArray()[page];
var text = Object.Instantiate(groupNameText, __instance.scroller.Inner);
text.enabled = true;
text.gameObject.transform.localScale = Vector3.one;
text.GetComponent<TextTranslatorTMP>().Destroy();
text.EnableStencilMasking();
text.text = $"{groupName}\nPress Ctrl or Tab to cycle pages";
text.alignment = TextAlignmentOptions.Center;
text.fontSize = 3f;
text.fontSizeMax = 3f;
text.fontSizeMin = 0f;
float xLerp = __instance.XRange.Lerp(0.5f);
float yLerp = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
text.transform.localPosition = new Vector3(xLerp, yLerp, -1f);
hatIndex += 5;
foreach (var skin in skins.OrderBy(HatManager.Instance.allSkins.IndexOf))
{
float hatXposition = __instance.XRange.Lerp(hatIndex % __instance.NumPerRow / (__instance.NumPerRow - 1f));
float hatYposition = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
GenerateColorChip(__instance, new Vector2(hatXposition, hatYposition), skin);
hatIndex += 1;
}
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f;
__instance.currentSkinIsEquipped = true;
}
private static void GenerateColorChip(SkinsTab __instance, Vector2 position, SkinData skin)
{
var colorChip = Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner);
colorChip.gameObject.name = skin.ProductId;
colorChip.Button.OnClick.AddListener((Action)(() => __instance.ClickEquip()));
colorChip.Button.OnMouseOver.AddListener((Action)(() => __instance.SelectSkin(skin)));
colorChip.Button.OnMouseOut.AddListener((Action)(() => __instance.SelectSkin(HatManager.Instance.GetSkinById(DataManager.Player.Customization.Skin))));
colorChip.Button.ClickMask = __instance.scroller.Hitbox;
colorChip.ProductId = skin.ProductId;
colorChip.SelectionHighlight.gameObject.SetActive(false);
__instance.UpdateMaterials(colorChip.Inner.FrontLayer, skin);
skin.SetPreview(colorChip.Inner.FrontLayer, __instance.HasLocalPlayer() ? PlayerControl.LocalPlayer.Data.DefaultOutfit.ColorId : DataManager.Player.Customization.Color);
colorChip.transform.localPosition = new Vector3(position.x, position.y, -1f);
colorChip.Inner.transform.localPosition = skin.ChipOffset + new Vector2(0f, -0.3f);
colorChip.Tag = skin;
__instance.ColorChips.Add(colorChip);
}
}
-114
View File
@@ -1,114 +0,0 @@
using AmongUs.Data;
using HarmonyLib;
using MiraAPI.Utilities;
using Reactor.Utilities.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using Object = UnityEngine.Object;
namespace MiraAPI.Patches.Menu;
[HarmonyPatch(typeof(VisorsTab))]
public static class VisorsTabPatches
{
private static readonly SortedList<string, List<VisorData>> SortedVisors = new(new ControllableComparer<string>(["vanilla"], [], StringComparer.InvariantCulture));
private static int currentPage;
internal static void AddRange(IEnumerable<(string Key, VisorData Visor)> data)
{
foreach (var item in data)
{
if (!SortedVisors.ContainsKey(item.Key)) SortedVisors.Add(item.Key, []);
SortedVisors[item.Key].Add(item.Visor);
}
}
[HarmonyPatch(nameof(VisorsTab.OnEnable))]
[HarmonyPrefix]
public static bool OnEnablePrefix(VisorsTab __instance)
{
__instance.visorId = HatManager.Instance.GetVisorById(DataManager.Player.Customization.Visor).ProdId;
if (!SortedVisors.ContainsKey("Vanilla")) AddRange(DestroyableSingleton<HatManager>.Instance.GetUnlockedVisors().Select(x => ("Vanilla", x)));
GenerateHats(__instance, currentPage);
return false;
}
[HarmonyPatch(typeof(VisorsTab), nameof(VisorsTab.Update))]
[HarmonyPrefix]
public static void UpdatePrefix(VisorsTab __instance)
{
if (Input.GetKeyDown(KeyCode.LeftControl) || Input.GetKeyDown(KeyCode.RightControl) || Input.GetKeyDown(KeyCode.LeftArrow))
{
currentPage--;
currentPage = currentPage < 0 ? SortedVisors.Count - 1 : currentPage;
GenerateHats(__instance, currentPage);
}
else if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.RightArrow))
{
currentPage++;
currentPage = currentPage > SortedVisors.Count - 1 ? 0 : currentPage;
GenerateHats(__instance, currentPage);
}
}
private static void GenerateHats(VisorsTab __instance, int page)
{
foreach (ColorChip instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy();
__instance.ColorChips.Clear();
__instance.scroller.Inner.GetComponentsInChildren<TextMeshPro>().Do(x => x.gameObject.Destroy());
var groupNameText = __instance.GetComponentInChildren<TextMeshPro>(false);
int hatIndex = 0;
var (groupName, visors) = SortedVisors.ToArray()[page];
var text = Object.Instantiate(groupNameText, __instance.scroller.Inner);
text.enabled = true;
text.gameObject.transform.localScale = Vector3.one;
text.GetComponent<TextTranslatorTMP>().Destroy();
text.EnableStencilMasking();
text.text = $"{groupName}\nPress Ctrl or Tab to cycle pages";
text.alignment = TextAlignmentOptions.Center;
text.fontSize = 3f;
text.fontSizeMax = 3f;
text.fontSizeMin = 0f;
float xLerp = __instance.XRange.Lerp(0.5f);
float yLerp = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
text.transform.localPosition = new Vector3(xLerp, yLerp, -1f);
hatIndex += 5;
foreach (var visor in visors.OrderBy(HatManager.Instance.allVisors.IndexOf))
{
float hatXposition = __instance.XRange.Lerp(hatIndex % __instance.NumPerRow / (__instance.NumPerRow - 1f));
float hatYposition = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
GenerateColorChip(__instance, new Vector2(hatXposition, hatYposition), visor);
hatIndex += 1;
}
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f;
__instance.currentVisorIsEquipped = true;
}
private static void GenerateColorChip(VisorsTab __instance, Vector2 position, VisorData visor)
{
var colorChip = Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner);
colorChip.gameObject.name = visor.ProductId;
colorChip.Button.OnClick.AddListener((Action)(() => __instance.ClickEquip()));
colorChip.Button.OnMouseOver.AddListener((Action)(() => __instance.SelectVisor(visor)));
colorChip.Button.OnMouseOut.AddListener((Action)(() => __instance.SelectVisor(HatManager.Instance.GetVisorById(DataManager.Player.Customization.Visor))));
colorChip.Button.ClickMask = __instance.scroller.Hitbox;
colorChip.ProductId = visor.ProductId;
colorChip.SelectionHighlight.gameObject.SetActive(false);
__instance.UpdateMaterials(colorChip.Inner.FrontLayer, visor);
visor.SetPreview(colorChip.Inner.FrontLayer, __instance.HasLocalPlayer() ? PlayerControl.LocalPlayer.Data.DefaultOutfit.ColorId : DataManager.Player.Customization.Color);
colorChip.transform.localPosition = new Vector3(position.x, position.y, -1f);
colorChip.Inner.transform.localPosition = visor.ChipOffset + new Vector2(0f, -0.3f);
colorChip.Tag = visor;
__instance.ColorChips.Add(colorChip);
}
}
@@ -1,19 +0,0 @@
using HarmonyLib;
namespace MiraAPI.Patches;
/// <summary>
/// Used to fix the Minigame crash errors (credits Submerged/PH Gaming for the fix).
/// </summary>
[HarmonyPatch]
public static class MinigameBeginErrorPatch
{
private static readonly Logger _logger = new(Logger.Category.Gameplay, "Minigame");
[HarmonyPatch(typeof(Minigame), nameof(Minigame.Begin))]
[HarmonyPrefix]
public static void Prefix(Minigame __instance)
{
__instance.logger ??= _logger;
}
}
+12 -35
View File
@@ -1,63 +1,40 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq;
using HarmonyLib;
using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
namespace MiraAPI.Patches.Modifiers;
[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameEnd))]
internal static class EndGameDidWinPatch
[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CoEndGame))]
public static class EndGameDidWinPatch
{
private static List<CachedPlayerData> CachedWinners { get; set; } = [];
public static void Prefix(EndGameResult endGameResult)
public static void Prefix()
{
CachedWinners.Clear();
var gameOverReason = endGameResult.GameOverReason;
var gameOverReason = EndGameResult.CachedGameOverReason;
EndGameResult.CachedWinners.Clear();
var players = GameData.Instance.AllPlayers.ToArray();
for (var i = 0; i < GameData.Instance.PlayerCount; i++)
{
var networkedPlayerInfo = players[i];
if (!networkedPlayerInfo)
if (networkedPlayerInfo == null)
{
continue;
}
var didWin = networkedPlayerInfo.Role.DidWin(gameOverReason);
if (!networkedPlayerInfo.Object)
var modifierWin = networkedPlayerInfo.Object?.GetModifierComponent().GetModifiers<GameModifier>()
.FirstOrDefault(x => x.DidWin(gameOverReason) != null)?.DidWin(gameOverReason);
if (modifierWin != null)
{
if (didWin)
{
CachedWinners.Add(new CachedPlayerData(networkedPlayerInfo));
}
continue;
}
foreach (var modifier in networkedPlayerInfo.Object.GetModifiers<GameModifier>().OrderByDescending(x => x.Priority()))
{
var result = modifier.DidWin(gameOverReason);
if (!result.HasValue) continue;
didWin = result.Value;
break;
didWin = modifierWin.Value;
}
if (didWin)
{
CachedWinners.Add(new CachedPlayerData(networkedPlayerInfo));
EndGameResult.CachedWinners.Add(new CachedPlayerData(networkedPlayerInfo));
}
}
}
public static void Postfix(EndGameResult endGameResult)
{
EndGameResult.CachedWinners.Clear();
foreach (var winner in CachedWinners)
{
EndGameResult.CachedWinners.Add(winner);
}
}
}
@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
using MiraAPI.Modifiers;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities;
using Reactor.Utilities.Extensions;
using TMPro;
using UnityEngine;
@@ -31,8 +31,6 @@ internal static class GameSettingMenuPatches
private static PassiveButton? _modifiersButton;
private static PassiveButton? _smallRolesButton;
private static Dictionary<int, Vector3> OptionsPositions { get; } = [];
[HarmonyPostfix]
[HarmonyPatch(nameof(GameSettingMenu.ChangeTab))]
public static void ChangeTabPostfix(GameSettingMenu __instance, int tabNum, bool previewOnly)
@@ -113,11 +111,6 @@ internal static class GameSettingMenuPatches
passiveButton.OnClick.AddListener(
(UnityAction)(() =>
{
if (!__instance.RoleSettingsTab.AdvancedRolesSettings.active)
{
RoleSettingMenuPatches.RolePositions[SelectedModIdx] = __instance.RoleSettingsTab.scrollBar.Inner.localPosition;
}
OptionsPositions[SelectedModIdx] = __instance.GameSettingsTab.scrollBar.Inner.localPosition;
SelectedModIdx += 1;
if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins().Length)
{
@@ -136,11 +129,6 @@ internal static class GameSettingMenuPatches
backButton.gameObject.GetComponent<PassiveButton>().OnClick.AddListener(
(UnityAction)(() =>
{
if (!__instance.RoleSettingsTab.AdvancedRolesSettings.active)
{
RoleSettingMenuPatches.RolePositions[SelectedModIdx] = __instance.RoleSettingsTab.scrollBar.Inner.localPosition;
}
OptionsPositions[SelectedModIdx] = __instance.GameSettingsTab.scrollBar.Inner.localPosition;
SelectedModIdx -= 1;
if (SelectedModIdx < 0)
{
@@ -420,15 +408,7 @@ internal static class GameSettingMenuPatches
rolesMenu.AdvancedRolesSettings.gameObject.SetActive(false);
rolesMenu.RoleChancesSettings.gameObject.SetActive(true);
rolesMenu.SetQuotaTab();
if (RoleSettingMenuPatches.RolePositions.TryGetValue(SelectedModIdx, out var pos))
{
rolesMenu.scrollBar.Inner.localPosition = pos;
rolesMenu.scrollBar.UpdateScrollBars();
}
else
{
rolesMenu.scrollBar.ScrollToTop();
}
rolesMenu.scrollBar.ScrollToTop();
}
void CleanupSettings(GameOptionsMenu gameOptMenu)
@@ -442,15 +422,7 @@ internal static class GameSettingMenuPatches
.ForEach(header => header.gameObject.DestroyImmediate());
gameOptMenu.Initialize();
if (OptionsPositions.TryGetValue(SelectedModIdx, out var pos))
{
gameOptMenu.scrollBar.Inner.localPosition = pos;
gameOptMenu.scrollBar.UpdateScrollBars();
}
else
{
gameOptMenu.scrollBar.ScrollToTop();
}
gameOptMenu.scrollBar.ScrollToTop();
}
if (roles.roleChances != null && SelectedMod?.CustomRoles.Count > 0)
@@ -260,7 +260,7 @@ public static class LobbyViewPanePatches
foreach (var grouping in sortedRoleGroups)
{
if (!grouping.Any() || grouping.All(x => x.Configuration.HideSettings))
if (!grouping.Any())
{
continue;
}
@@ -291,11 +291,6 @@ public static class LobbyViewPanePatches
foreach (var customRole in grouping)
{
if (customRole.Configuration.HideSettings)
{
continue;
}
var roleBehaviour = customRole as RoleBehaviour;
if (roleBehaviour == null)
{
@@ -340,7 +335,7 @@ public static class LobbyViewPanePatches
viewSettingsInfoPanelRoleVariant.chanceTitle.color =
viewSettingsInfoPanelRoleVariant.chanceBackground.color =
viewSettingsInfoPanelRoleVariant.background.color =
customRole.RoleColor.FindAlternateColor();
customRole.RoleColor.GetAlternateColor();
instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject);
num -= 0.664f;
}
@@ -2,8 +2,6 @@
using HarmonyLib;
using Il2CppSystem;
using MiraAPI.Roles;
using UnityEngine;
using Object = Il2CppSystem.Object;
namespace MiraAPI.Patches.Options;
@@ -25,13 +23,15 @@ public static class NotificationPopperPatch
return true;
}
var textColor = customRole.OptionsMenuColor.ToTextColor();
var text = teamType == RoleTeamTypes.Crewmate
? Palette.CrewmateSettingChangeText.ToTextColor()
: Palette.ImpostorRed.ToTextColor();
var item = TranslationController.Instance.GetString(
StringNames.LobbyChangeSettingNotificationRole,
string.Concat(
"<font=\"Barlow-Black SDF\" material=\"Barlow-Black Outline\">",
textColor,
text,
TranslationController.Instance.GetString(key, Array.Empty<Object>()),
"</color></font>"
),
@@ -22,11 +22,9 @@ namespace MiraAPI.Patches.Options;
[HarmonyPatch(typeof(RolesSettingsMenu))]
public static class RoleSettingMenuPatches
{
public static Dictionary<int, Vector3> RolePositions { get; set; } = [];
private static Dictionary<RoleOptionsGroup, bool> RoleGroupHidden { get; set; } = [];
private static List<GameObject> Headers { get; set; } = [];
private static List<RoleOptionSetting> RoleOptionSettings { get; set; } = [];
private static Dictionary<RoleOptionsGroup, bool> RoleGroupHidden { get; set; } = new();
private static List<GameObject> Headers { get; set; } = new();
private static List<RoleOptionSetting> RoleOptionSettings { get; set; } = new();
private static float ScrollerNum { get; set; } = 0.522f;
@@ -85,9 +83,10 @@ public static class RoleSettingMenuPatches
var quotaThing = __instance.categoryHeaderEditRoleOrigin.transform.FindChild("QuotaHeader");
var template = __instance.transform.parent.parent.GetComponent<GameSettingMenu>().GameSettingsTab.categoryHeaderOrigin;
foreach (var grouping in sortedRoleGroups)
{
if (!grouping.Any() || grouping.All(x=>x.Configuration.HideSettings))
if (!grouping.Any())
{
continue;
}
@@ -234,15 +233,6 @@ public static class RoleSettingMenuPatches
private static void SetScrollBounds(this Scroller scroller)
{
scroller.CalculateAndSetYBounds(1 + 1.5f * Headers.Count + RoleOptionSettings.Count, 1f, 6f, 0.43f);
if (RolePositions.TryGetValue(GameSettingMenuPatches.SelectedModIdx, out var scroll))
{
scroller.Inner.localPosition = scroll;
scroller.UpdateScrollBars();
}
else
{
scroller.ScrollToTop();
}
}
[HarmonyPrefix]
@@ -262,6 +252,7 @@ public static class RoleSettingMenuPatches
}
__instance.scrollBar.SetScrollBounds();
__instance.scrollBar.ScrollToTop();
}
private static void ValueChanged(OptionBehaviour obj)
@@ -366,8 +357,6 @@ public static class RoleSettingMenuPatches
return;
}
RolePositions[GameSettingMenuPatches.SelectedModIdx] = __instance.scrollBar.Inner.localPosition;
__instance.roleDescriptionText.text = customRole.RoleLongDescription;
__instance.roleTitleText.text = TranslationController.Instance.GetString(
role.StringName,
+37 -8
View File
@@ -5,7 +5,7 @@ using MiraAPI.Events.Vanilla.Gameplay;
using MiraAPI.Events.Vanilla.Player;
using MiraAPI.Hud;
using MiraAPI.Modifiers;
using MiraAPI.Utilities;
using MiraAPI.Roles;
using MiraAPI.Voting;
using Reactor.Utilities.Extensions;
@@ -15,11 +15,14 @@ namespace MiraAPI.Patches;
/// General patches for the PlayerControl class.
/// </summary>
[HarmonyPatch(typeof(PlayerControl))]
internal static class PlayerControlPatches
public static class PlayerControlPatches
{
/// <summary>
/// Adds the modifier component to the player on start.
/// </summary>
/// <param name="__instance">PlayerControl instance.</param>
[HarmonyPostfix]
[HarmonyPatch(nameof(PlayerControl.Start))]
// ReSharper disable once InconsistentNaming
public static void PlayerControlStartPostfix(PlayerControl __instance)
{
if (__instance.gameObject.TryGetComponent<ModifierComponent>(out var modifierComp))
@@ -36,12 +39,15 @@ internal static class PlayerControlPatches
__instance.gameObject.AddComponent<PlayerVoteData>();
}
/// <summary>
/// Calls the OnDeath method for all active modifiers.
/// </summary>
/// <param name="__instance">PlayerControl instance.</param>
[HarmonyPostfix]
[HarmonyPatch(nameof(PlayerControl.Die))]
// ReSharper disable once InconsistentNaming
public static void PlayerControlDiePostfix(PlayerControl __instance, DeathReason reason)
{
var deathEvent = new PlayerDeathEvent(__instance, reason, Helpers.GetBodyById(__instance.PlayerId));
var deathEvent = new PlayerDeathEvent(__instance, reason);
MiraEventManager.InvokeEvent(deathEvent);
var modifiersComponent = __instance.GetComponent<ModifierComponent>();
@@ -52,9 +58,13 @@ internal static class PlayerControlPatches
}
}
/// <summary>
/// Used to trigger the <see cref="CompleteTaskEvent"/>.
/// </summary>
/// <param name="__instance">PlayerControl instance.</param>
/// <param name="idx">The task id.</param>
[HarmonyPostfix]
[HarmonyPatch(nameof(PlayerControl.CompleteTask))]
// ReSharper disable once InconsistentNaming
public static void PlayerCompleteTaskPostfix(PlayerControl __instance, uint idx)
{
var playerTask = __instance.myTasks.ToArray().First(playerTask => playerTask.Id == idx);
@@ -65,9 +75,14 @@ internal static class PlayerControlPatches
}
}
/// <summary>
/// Used to trigger the <see cref="BeforeMurderEvent"/>.
/// </summary>
/// <param name="__instance">The source player.</param>
/// <param name="target">The target.</param>
/// <param name="didSucceed">Whether the kill succeeded.</param>
[HarmonyPrefix]
[HarmonyPatch(nameof(PlayerControl.RpcMurderPlayer))]
// ReSharper disable once InconsistentNaming
public static void PlayerControlMurderPrefix(PlayerControl __instance, PlayerControl target, ref bool didSucceed)
{
var beforeMurderEvent = new BeforeMurderEvent(__instance, target);
@@ -76,9 +91,12 @@ internal static class PlayerControlPatches
didSucceed = beforeMurderEvent.IsCancelled;
}
/// <summary>
/// FixedUpdate handler for custom roles and custom buttons.
/// </summary>
/// <param name="__instance">PlayerControl instance.</param>
[HarmonyPostfix]
[HarmonyPatch(nameof(PlayerControl.FixedUpdate))]
// ReSharper disable once InconsistentNaming
public static void PlayerControlFixedUpdatePostfix(PlayerControl __instance)
{
if (!__instance.AmOwner)
@@ -101,4 +119,15 @@ internal static class PlayerControlPatches
button.FixedUpdateHandler(__instance);
}
}
/// <summary>
/// Clear from modifier component cache.
/// </summary>
/// <param name="__instance">PlayerControl instance.</param>
[HarmonyPrefix]
[HarmonyPatch(nameof(PlayerControl.OnDestroy))]
public static void PlayerControlOnDestroyPrefix(PlayerControl __instance)
{
ModifierExtensions.ModifierComponents.Remove(__instance);
}
}
+2 -13
View File
@@ -1,6 +1,5 @@
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
using MiraAPI.Events.Vanilla.Meeting;
using MiraAPI.Roles;
@@ -10,7 +9,7 @@ namespace MiraAPI.Patches.Roles;
/// Patches for custom ejection messages.
/// </summary>
[HarmonyPatch(typeof(ExileController))]
internal static class EjectionPatches
public static class EjectionPatches
{
[HarmonyPostfix]
[HarmonyPatch(nameof(ExileController.Begin))]
@@ -25,21 +24,11 @@ internal static class EjectionPatches
return;
}
if (!GameManager.Instance.LogicOptions.GetConfirmImpostor() || role.GetCustomEjectionMessage(__instance.initData.networkedPlayer) == null)
if (role.GetCustomEjectionMessage(__instance.initData.networkedPlayer) == null)
{
return;
}
__instance.completeString = role.GetCustomEjectionMessage(__instance.initData.networkedPlayer);
}
[HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))]
public static class WrapUpPatch
{
public static void Postfix()
{
var @event = new RoundStartEvent(false);
MiraEventManager.InvokeEvent(@event);
}
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ public static class GameStartupPatch
private static bool _runOnce;
/// <summary>
/// This is used for registering roles when the game opens, might be a janky solution, but it works.
/// This is used for registering roles when the game opens, might be a janky solution, but it works
/// </summary>
[HarmonyPostfix]
[HarmonyPatch(nameof(MainMenuManager.Start))]
+1 -1
View File
@@ -24,4 +24,4 @@ public static class HauntMenuMinigamePatch
__instance.FilterText.text = role.NiceName;
__instance.FilterText.color = color;
}
}
}
+6 -22
View File
@@ -5,29 +5,15 @@ using Reactor.Utilities.Extensions;
namespace MiraAPI.Patches.Roles;
/// <summary>
/// HudManager patches for roles.
/// </summary>
[HarmonyPatch(typeof(HudManager))]
public static class HudManagerPatches
internal static class HudManagerPatches
{
// Custom role tab.
private static TaskPanelBehaviour? _roleTab;
/// <summary>
/// Fixes Kill Button not showing for Neutral killing role.
/// </summary>
/// <param name="__instance">HudManager instance.</param>
/// <param name="localPlayer">The local PlayerControl.</param>
/// <param name="role">The player's RoleBehaviour.</param>
/// <param name="isActive">Whether the Hud should be set active or not.</param>
[HarmonyPostfix]
[HarmonyPatch(nameof(HudManager.SetHudActive), typeof(PlayerControl), typeof(RoleBehaviour), typeof(bool))]
public static void SetHudActivePostfix(
HudManager __instance,
PlayerControl localPlayer,
RoleBehaviour role,
bool isActive)
// ReSharper disable once InconsistentNaming
public static void SetHudActivePostfix(HudManager __instance, PlayerControl localPlayer, RoleBehaviour role, bool isActive)
{
var flag = localPlayer.Data != null && localPlayer.Data.IsDead;
@@ -44,11 +30,9 @@ public static class HudManagerPatches
}
}
/// <summary>
/// Update custom role tab and custom role hud elements.
/// </summary>
[HarmonyPostfix]
[HarmonyPatch(nameof(HudManager.Update))]
// ReSharper disable once InconsistentNaming
public static void UpdatePostfix(HudManager __instance)
{
var local = PlayerControl.LocalPlayer;
@@ -69,9 +53,9 @@ public static class HudManagerPatches
CustomRoleManager.UpdateRoleTab(_roleTab, customRole);
}
else if (_roleTab)
else if (_roleTab != null)
{
_roleTab?.gameObject.Destroy();
_roleTab.gameObject.Destroy();
}
}
}
+2 -42
View File
@@ -5,58 +5,18 @@ using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
using MiraAPI.Modifiers;
using MiraAPI.Roles;
using UnityEngine;
namespace MiraAPI.Patches.Roles;
[HarmonyPatch(typeof(RoleManager))]
public static class RoleManagerPatches
{
[HarmonyPrefix]
[HarmonyPostfix]
[HarmonyPatch(nameof(RoleManager.SetRole))]
public static bool SetRolePatch(RoleManager __instance, PlayerControl targetPlayer, RoleTypes roleType)
public static void SetRolePatch(RoleManager __instance, PlayerControl targetPlayer, RoleTypes roleType)
{
if (!targetPlayer)
{
return false;
}
var data = targetPlayer.Data;
if (data == null)
{
Debug.LogError("It shouldn't be possible, but " + targetPlayer.name + " still doesn't have PlayerData during role selection.");
return false;
}
if (data.Role)
{
data.Role.Deinitialize(targetPlayer);
Object.Destroy(data.Role.gameObject);
}
var roleBehaviour = Object.Instantiate<RoleBehaviour>(__instance.AllRoles.First(r => r.Role == roleType), data.gameObject.transform);
roleBehaviour.Initialize(targetPlayer);
targetPlayer.Data.Role = roleBehaviour;
targetPlayer.Data.RoleType = roleType;
// basically everything is the same except this one if statement
// innersloth decided to check the roleType manually instead of the role behaviour.
if (!roleBehaviour.IsDead)
{
targetPlayer.Data.RoleWhenAlive = new Il2CppSystem.Nullable<RoleTypes>(roleType);
}
roleBehaviour.AdjustTasks(targetPlayer);
switch (roleBehaviour.IsDead)
{
case true when !targetPlayer.Data.IsDead:
targetPlayer.Die(DeathReason.Kill, false);
return false;
case false when targetPlayer.Data.IsDead:
targetPlayer.Revive();
break;
}
var @event = new SetRoleEvent(targetPlayer, roleType);
MiraEventManager.InvokeEvent(@event);
return false;
}
[HarmonyPostfix]
@@ -8,15 +8,15 @@ namespace MiraAPI.Patches.Roles;
/// <summary>
/// Patches to return the correct role counts.
/// </summary>
[HarmonyPatch(typeof(RoleOptionsCollectionV09))]
[HarmonyPatch(typeof(RoleOptionsCollectionV08))]
public static class RoleOptionsCollectionPatch
{
/// <summary>
/// Set the role chance for custom Launchpad roles based on config.
/// Set the role chance for custom Launchpad roles based on config
/// </summary>
/// <returns>Return false to skip original method, true to not.</returns>
[HarmonyPrefix]
[HarmonyPatch(nameof(RoleOptionsCollectionV09.GetChancePerGame))]
[HarmonyPatch(nameof(RoleOptionsCollectionV08.GetChancePerGame))]
public static bool GetChancePrefix(RoleTypes role, ref int __result)
{
if (!CustomRoleManager.GetCustomRoleBehaviour(role, out var customRole) || customRole == null)
@@ -42,11 +42,11 @@ public static class RoleOptionsCollectionPatch
}
/// <summary>
/// Set the amount for custom Launchpad roles based on config.
/// Set the amount for custom Launchpad roles based on config
/// </summary>
/// <returns>Return false to skip original method, true to not.</returns>
[HarmonyPrefix]
[HarmonyPatch(nameof(RoleOptionsCollectionV09.GetNumPerGame))]
[HarmonyPatch(nameof(RoleOptionsCollectionV08.GetNumPerGame))]
public static bool GetNumPrefix(RoleTypes role, ref int __result)
{
if (!CustomRoleManager.GetCustomRoleBehaviour(role, out var customRole) || customRole == null)
+1 -1
View File
@@ -4,7 +4,7 @@ using MiraAPI.Roles;
namespace MiraAPI.Patches.Roles;
[HarmonyPatch(typeof(SabotageButton))]
public static class SabotageButtonPatch
public class SabotageButtonPatch
{
/// <summary>
/// Patches the Sabotage button to check if the player's custom role can use sabotage.
+11
View File
@@ -33,4 +33,15 @@ public static class MinigameStubs
{
// nothing needed
}
/// <summary>
/// Fixes the error when creating custom minigames. Credit to Submerged for discovering this fix.
/// </summary>
/// <param name="__instance">The Minigame instance.</param>
[HarmonyPrefix]
[HarmonyPatch(typeof(Minigame), nameof(Minigame.Begin))]
public static void MinigameErrorFixPatch(Minigame __instance)
{
__instance.logger ??= new Logger("Minigame", Logger.Level.Info, Logger.Category.Gameplay);
}
}
+5 -8
View File
@@ -4,9 +4,6 @@ using Il2CppSystem.Text;
namespace MiraAPI.Patches.Stubs;
/// <summary>
/// Stub methods for the RoleBehaviour class. Needed because of Il2Cpp limitations on injected classes.
/// </summary>
[HarmonyPatch]
public static class RoleBehaviourStubs
{
@@ -60,7 +57,7 @@ public static class RoleBehaviourStubs
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool CanUse(RoleBehaviour instance, IUsable console)
{
return false;
return default;
}
/// <summary>
@@ -87,7 +84,7 @@ public static class RoleBehaviourStubs
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool DidWin(RoleBehaviour instance, GameOverReason gameOverReason)
{
return false;
return default;
}
/// <summary>
@@ -100,7 +97,7 @@ public static class RoleBehaviourStubs
[MethodImpl(MethodImplOptions.NoInlining)]
public static PlayerControl FindClosestTarget(RoleBehaviour instance)
{
return null!;
return null;
}
/// <summary>
@@ -113,7 +110,7 @@ public static class RoleBehaviourStubs
[MethodImpl(MethodImplOptions.NoInlining)]
public static float GetAbilityDistance(RoleBehaviour instance)
{
return 0;
return default;
}
/// <summary>
@@ -127,7 +124,7 @@ public static class RoleBehaviourStubs
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool IsValidTarget(RoleBehaviour instance, NetworkedPlayerInfo target)
{
return false;
return default;
}
/// <summary>
@@ -8,13 +8,17 @@ using UnityEngine;
namespace MiraAPI.Patches;
/// <summary>
/// Used to change vent behaviour for the event system, custom roles, and modifiers.
/// Vent patches to make sure the player is able to use the vent.
/// </summary>
[HarmonyPatch(typeof(Vent), nameof(Vent.CanUse))]
internal static class VentCanUsePatch
[HarmonyPatch(typeof(Vent))]
public static class VentPatches
{
// ReSharper disable InconsistentNaming
public static void Postfix(Vent __instance, ref float __result, [HarmonyArgument(0)] NetworkedPlayerInfo pc, [HarmonyArgument(1)] ref bool canUse, [HarmonyArgument(2)] ref bool couldUse)
/// <summary>
/// CanUse patch.
/// </summary>
[HarmonyPostfix]
[HarmonyPatch(nameof(Vent.CanUse))]
public static void VentCanUsePostfix(Vent __instance, ref float __result, [HarmonyArgument(0)] NetworkedPlayerInfo pc, [HarmonyArgument(1)] ref bool canUse, [HarmonyArgument(2)] ref bool couldUse)
{
var @event = new PlayerCanUseEvent(__instance.Cast<IUsable>());
MiraEventManager.InvokeEvent(@event);
+80 -64
View File
@@ -1,18 +1,15 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Meeting;
using MiraAPI.Events.Vanilla.Meeting.Voting;
using MiraAPI.Modifiers;
using MiraAPI.Utilities;
using MiraAPI.Voting;
namespace MiraAPI.Patches.Voting;
[HarmonyPatch(typeof(MeetingHud))]
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony Convention")]
internal static class MeetingHudPatches
{
[HarmonyPostfix]
@@ -29,62 +26,11 @@ internal static class MeetingHudPatches
{
voteData.SetRemainingVotes(0);
}
foreach (var modifier in plr.GetModifierComponent().ActiveModifiers)
{
modifier.OnMeetingStart();
}
}
var @event = new StartMeetingEvent(__instance);
MiraEventManager.InvokeEvent(@event);
}
[HarmonyPostfix]
[HarmonyPatch(nameof(MeetingHud.OnDestroy))]
public static void OnDestroyPatch(MeetingHud __instance)
{
MiraEventManager.InvokeEvent(new EndMeetingEvent(__instance));
}
[HarmonyPostfix]
[HarmonyPatch(nameof(MeetingHud.VotingComplete))]
public static void VotingCompletePatch(MeetingHud __instance)
{
MiraEventManager.InvokeEvent(new VotingCompleteEvent(__instance));
}
// this is necessary because the actual ForceSkipAll method is inlined.
[HarmonyPrefix]
[HarmonyPatch(nameof(MeetingHud.Update))]
public static void ForceSkipPatch(MeetingHud __instance)
{
if (__instance.state is not (MeetingHud.VoteStates.NotVoted or MeetingHud.VoteStates.Voted))
{
return;
}
var logicOptionsNormal = GameManager.Instance.LogicOptions.Cast<LogicOptionsNormal>();
var votingTime = logicOptionsNormal.GetVotingTime();
if (votingTime <= 0)
{
return;
}
var num2 = __instance.discussionTimer - logicOptionsNormal.GetDiscussionTime();
if (!AmongUsClient.Instance.AmHost || num2 < votingTime)
{
return;
}
foreach (var plr in PlayerControl.AllPlayerControls)
{
var voteData = plr.GetVoteData();
voteData.SetRemainingVotes(0);
}
}
[HarmonyPrefix]
[HarmonyPatch(nameof(MeetingHud.Select))]
public static bool SelectPatch(MeetingHud __instance, int suspectStateIdx)
@@ -140,6 +86,84 @@ internal static class MeetingHudPatches
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(MeetingHud.CastVote))]
public static bool CastVotePatch(MeetingHud __instance, byte srcPlayerId, byte suspectPlayerId)
{
var plr = GameData.Instance.GetPlayerById(srcPlayerId);
if (plr == null)
{
return false;
}
var voteData = plr.Object.GetVoteData();
if (voteData == null || voteData.VotesRemaining == 0 ||
voteData.VotedFor(suspectPlayerId))
{
return false;
}
VotingUtils.HandleVote(voteData, suspectPlayerId);
__instance.SetDirtyBit(1U);
__instance.CheckForEndVoting();
if (voteData.VotesRemaining != 0)
{
return false;
}
__instance.playerStates.First(x => x.TargetPlayerId == srcPlayerId).SetVote(suspectPlayerId);
PlayerControl.LocalPlayer.RpcSendChatNote(srcPlayerId, ChatNoteTypes.DidVote);
return false;
}
[HarmonyPostfix]
[HarmonyPatch(nameof(MeetingHud.CmdCastVote))]
public static void CmdCastVotePatch(MeetingHud __instance, byte playerId, byte suspectIdx)
{
var plr = GameData.Instance.GetPlayerById(playerId);
var voteData = plr.Object.GetVoteData();
if (!AmongUsClient.Instance.AmHost)
{
if (voteData == null || voteData.VotesRemaining == 0 ||
voteData.VotedFor(suspectIdx))
{
return;
}
VotingUtils.HandleVote(voteData, suspectIdx);
if (voteData.VotesRemaining == 0)
{
__instance.playerStates.First(x => x.TargetPlayerId == playerId).SetVote(suspectIdx);
}
}
if (PlayerControl.LocalPlayer.PlayerId != playerId)
{
return;
}
SoundManager.Instance.PlaySound(__instance.VoteLockinSound, false);
foreach (var playerVoteArea in __instance.playerStates)
{
playerVoteArea.ClearButtons();
}
__instance.SkipVoteButton.ClearButtons();
var localVoteData = PlayerControl.LocalPlayer.GetVoteData();
if (localVoteData == null || localVoteData.VotesRemaining != 0) return;
__instance.SkipVoteButton.voteComplete = true;
__instance.SkipVoteButton.gameObject.SetActive(false);
}
[HarmonyPrefix]
[HarmonyPatch(nameof(MeetingHud.CheckForEndVoting))]
public static bool EndCheck(MeetingHud __instance)
@@ -202,20 +226,12 @@ internal static class MeetingHudPatches
return false;
}
// TODO: figure out a way to do host-authorization since right now any player can send RpcCastVote
[HarmonyPrefix]
[HarmonyPatch(nameof(MeetingHud.CmdCastVote))]
public static bool CmdCastVoteOverridePatch(MeetingHud __instance, byte playerId, byte suspectIdx)
{
VotingUtils.RpcCastVote(PlayerControl.LocalPlayer, playerId, suspectIdx);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(MeetingHud.Confirm))]
public static bool ConfirmPatch(MeetingHud __instance, [HarmonyArgument(0)] byte suspect)
{
__instance.CmdCastVote(PlayerControl.LocalPlayer.PlayerId, suspect);
return false;
}
}
-8
View File
@@ -1,8 +0,0 @@
using System;
namespace MiraAPI.PluginLoading;
public interface IMiraExtension
{
public Type GetBasePluginType();
}
+63 -109
View File
@@ -4,7 +4,6 @@ using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using BepInEx;
using BepInEx.Unity.IL2CPP;
using MiraAPI.Colors;
using MiraAPI.Events;
@@ -36,26 +35,75 @@ public sealed class MiraPluginManager
Instance = this;
IL2CPPChainloader.Instance.PluginLoad += (pluginInfo, assembly, plugin) =>
{
if (plugin is IMiraPlugin miraPlugin)
if (plugin is not IMiraPlugin miraPlugin)
{
var miraInfo = new MiraPluginInfo(miraPlugin, pluginInfo);
RegisterPlugin(miraInfo, assembly);
_registeredPlugins.Add(assembly, miraInfo);
return;
}
if (plugin is IMiraExtension miraExtension)
var info = new MiraPluginInfo(miraPlugin, pluginInfo);
var roles = new List<Type>();
foreach (var type in assembly.GetTypes())
{
var basePlugin = miraExtension.GetBasePluginType();
var basePluginInfo = _registeredPlugins.Values.FirstOrDefault(x => x.MiraPlugin.GetType() == basePlugin);
if (basePluginInfo == null)
if (type.GetCustomAttribute<MiraIgnoreAttribute>() != null)
{
Logger<MiraApiPlugin>.Error($"Base plugin {basePlugin.Name} not found for extension {pluginInfo.Metadata.GUID}.");
return;
continue;
}
RegisterPlugin(basePluginInfo, assembly);
foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public))
{
var eventAttribute = method.GetCustomAttribute<RegisterEventAttribute>();
if (eventAttribute != null)
{
var parameters = method.GetParameters();
if (parameters.Length != 1 || !parameters[0].ParameterType.IsSubclassOf(typeof(MiraEvent)))
{
Logger<MiraApiPlugin>.Error($"Invalid event registration method {method.Name} in {type.Name}");
continue;
}
var paramType = parameters[0].ParameterType;
MiraEventManager.RegisterEventHandler(paramType, method, eventAttribute.Priority);
}
}
if (RegisterModifier(type, info))
{
continue;
}
if (RegisterOptions(type, info))
{
continue;
}
if (RegisterRoleAttribute(type, info, out var role))
{
roles.Add(role);
continue;
}
if (RegisterButtonAttribute(type, info))
{
continue;
}
if (RegisterGameOver(type))
{
continue;
}
RegisterColorClasses(type);
}
info.OptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority));
QueuedRoleRegistrations.Add(info, roles);
_registeredPlugins.Add(assembly, info);
Logger<MiraApiPlugin>.Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API.");
};
IL2CPPChainloader.Instance.Finished += PaletteManager.RegisterAllColors;
IL2CPPChainloader.Instance.Finished += MiraEventManager.SortAllHandlers;
IL2CPPChainloader.Instance.Finished += () =>
{
CustomButtonManager.Buttons = new ReadOnlyCollection<CustomActionButton>(CustomButtonManager.CustomButtons);
@@ -72,84 +120,6 @@ public sealed class MiraPluginManager
return Instance._registeredPlugins.Values.FirstOrDefault(plugin => plugin.PluginId == pluginId);
}
private void RegisterPlugin(MiraPluginInfo info, Assembly assembly)
{
var roles = new List<Type>();
var oldConfigSetting = info.PluginConfig.SaveOnConfigSet;
info.PluginConfig.SaveOnConfigSet = false;
foreach (var type in assembly.GetTypes())
{
if (type.GetCustomAttribute<MiraIgnoreAttribute>() != null)
{
continue;
}
foreach (var method in type.GetMethods())
{
var eventAttribute = method.GetCustomAttribute<RegisterEventAttribute>();
if (eventAttribute == null)
{
continue;
}
if (!method.IsStatic)
{
Logger<MiraApiPlugin>.Error($"Event method {method.Name} in {type.Name} must be static.");
continue;
}
var parameters = method.GetParameters();
if (parameters.Length != 1 || !parameters[0].ParameterType.IsSubclassOf(typeof(MiraEvent)))
{
Logger<MiraApiPlugin>.Error($"Invalid event registration method {method.Name} in {type.Name}");
continue;
}
var paramType = parameters[0].ParameterType;
MiraEventManager.RegisterEventHandler(paramType, method, eventAttribute.Priority);
}
if (RegisterModifier(type, info))
{
continue;
}
if (RegisterOptions(type, info))
{
continue;
}
if (RegisterRole(type, info, out var role))
{
roles.Add(role);
continue;
}
if (RegisterButton(type, info))
{
continue;
}
if (RegisterGameOver(type))
{
continue;
}
RegisterColorClasses(type);
}
info.PluginConfig.Save();
info.PluginConfig.SaveOnConfigSet = oldConfigSetting;
info.OptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority));
QueuedRoleRegistrations.TryAdd(info, []);
QueuedRoleRegistrations[info].AddRange(roles);
Logger<MiraApiPlugin>.Info($"Registering mod {info.PluginId} with Mira API.");
}
private static bool RegisterGameOver(Type type)
{
try
@@ -179,12 +149,6 @@ public sealed class MiraPluginManager
foreach (var property in type.GetProperties())
{
if (property.GetMethod?.IsStatic == true)
{
Logger<MiraApiPlugin>.Error($"Option property {property.Name} in {type.Name} must not be static.");
continue;
}
if (property.PropertyType.IsAssignableTo(typeof(IModdedOption)))
{
ModdedOptionsManager.RegisterPropertyOption(type, property, pluginInfo);
@@ -200,21 +164,16 @@ public sealed class MiraPluginManager
ModdedOptionsManager.RegisterAttributeOption(type, attribute, property, pluginInfo);
}
foreach (var field in type.GetFields().Where(f=>f.FieldType.IsAssignableTo(typeof(IModdedOption))))
{
Logger<MiraApiPlugin>.Error($"{field.Name} is a field, not a property. Use properties for options.");
}
return true;
}
catch (Exception e)
{
Logger<MiraApiPlugin>.Error($"Failed to register options for {type.Name}: {e.ToString()}");
Logger<MiraApiPlugin>.Error($"Failed to register options for {type.Name}: {e}");
}
return false;
}
private static bool RegisterRole(Type type, MiraPluginInfo pluginInfo, [NotNullWhen(true)] out Type? role)
private static bool RegisterRoleAttribute(Type type, MiraPluginInfo pluginInfo, [NotNullWhen(true)] out Type? role)
{
role = null;
try
@@ -270,11 +229,6 @@ public sealed class MiraPluginManager
PaletteManager.CustomColors.Add(color);
}
foreach (var field in type.GetFields().Where(f=>f.FieldType.IsAssignableTo(typeof(CustomColor))))
{
Logger<MiraApiPlugin>.Error($"{field.Name} is a field, not a property. Use properties for colors.");
}
}
catch (Exception e)
{
@@ -295,7 +249,7 @@ public sealed class MiraPluginManager
}
}
private static bool RegisterButton(Type type, MiraPluginInfo pluginInfo)
private static bool RegisterButtonAttribute(Type type, MiraPluginInfo pluginInfo)
{
try
{
Binary file not shown.
+2
View File
@@ -1,6 +1,8 @@
using AmongUs.GameOptions;
using Il2CppSystem.Web.Util;
using MiraAPI.Utilities.Assets;
using UnityEngine;
using Helpers = MiraAPI.Utilities.Helpers;
namespace MiraAPI.Roles;
+1 -6
View File
@@ -14,6 +14,7 @@ using Reactor.Utilities;
using Reactor.Utilities.Extensions;
using TMPro;
using UnityEngine;
using UnityEngine.TextCore;
using Object = UnityEngine.Object;
namespace MiraAPI.Roles;
@@ -59,9 +60,6 @@ public static class CustomRoleManager
{
roles.ForEach(x => RoleIds.Add(x, GetNextRoleId()));
var oldConfigSetting = pluginInfo.PluginConfig.SaveOnConfigSet;
pluginInfo.PluginConfig.SaveOnConfigSet = false;
foreach (var roleType in roles)
{
ClassInjector.RegisterTypeInIl2Cpp(roleType);
@@ -74,9 +72,6 @@ public static class CustomRoleManager
pluginInfo.CustomRoles.Add((ushort)role.Role, role);
}
pluginInfo.PluginConfig.Save();
pluginInfo.PluginConfig.SaveOnConfigSet = oldConfigSetting;
}
private static RoleBehaviour? RegisterRole(Type roleType, MiraPluginInfo parentMod)
+5 -5
View File
@@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq;
namespace MiraAPI.Roles;
@@ -9,11 +8,12 @@ namespace MiraAPI.Roles;
/// <typeparam name="T">The role you are trying to access.</typeparam>
public static class CustomRoleSingleton<T> where T : ICustomRole
{
private static T? _instance;
private static ICustomRole? _instance;
/// <summary>
/// Gets the instance of the role.
/// </summary>
[SuppressMessage("Design", "CA1000:Do not declare static members on generic types", Justification = "This is a utility class to get the instance of a custom role.")]
public static T Instance => _instance ??= CustomRoleManager.CustomRoles.Values.OfType<T>().Single();
#pragma warning disable CA1000
public static ICustomRole Instance => _instance ??= CustomRoleManager.CustomRoles.Values.OfType<T>().Single();
#pragma warning restore CA1000
}
+1
View File
@@ -3,6 +3,7 @@ using BepInEx.Configuration;
using MiraAPI.GameOptions;
using MiraAPI.Modifiers;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities;
using Reactor.Utilities;
using UnityEngine;
-14
View File
@@ -1,22 +1,8 @@
namespace MiraAPI.Roles;
/// <summary>
/// Enum for modded role teams.
/// </summary>
public enum ModdedRoleTeams
{
/// <summary>
/// The crewmate team.
/// </summary>
Crewmate,
/// <summary>
/// The impostor team.
/// </summary>
Impostor,
/// <summary>
/// Custom team, catchall for any other teams.
/// </summary>
Custom,
}
+1 -16
View File
@@ -2,18 +2,9 @@
namespace MiraAPI.Roles;
/// <summary>
/// Utility class to get the ID of a role.
/// </summary>
public static class RoleId
{
/// <summary>
/// Gets the ID of a role.
/// </summary>
/// <typeparam name="T">The type of the role.</typeparam>
/// <returns>The role ID as a ushort.</returns>
/// <exception cref="InvalidOperationException">Thrown when the role is not registered.</exception>
public static ushort Get<T>() where T : ICustomRole
public static ushort Get<T>() where T : RoleBehaviour
{
if (!CustomRoleManager.RoleIds.TryGetValue(typeof(T), out var roleId))
{
@@ -23,12 +14,6 @@ public static class RoleId
return roleId;
}
/// <summary>
/// Gets the ID of a role.
/// </summary>
/// <param name="type">The type of the role.</param>
/// <returns>The ID as a ushort.</returns>
/// <exception cref="InvalidOperationException">Thrown when the role is not registered.</exception>
public static ushort Get(Type type)
{
if (!CustomRoleManager.RoleIds.TryGetValue(type, out var roleId))
@@ -1,222 +0,0 @@
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.Patches.Menu;
using Reactor.Utilities;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
namespace MiraAPI.Utilities.Assets;
/// <summary>
/// The component for loading Addressables, and cosmetics.
/// </summary>
public static class AddressablesLoader
{
private static bool _isInitialized;
private static readonly List<(string Location, string ProviderSuffix)> CatalogLocations = [];
private static readonly List<string> LoadedLocations = [];
private static readonly List<string> RegisteredHatKeys = [];
private static readonly List<(string, string)> RegisteredVisorKeys = [];
private static readonly List<(string, string)> RegisteredNameplateKeys = [];
private static readonly List<string> RegisteredSkinKeys = [];
/// <summary>
/// Registers a specific addressables package to load asynchronously at the start of the game, when possible.
/// </summary>
/// <param name="location">The location, remote or otherwise, of the addressables.</param>
/// <param name="providerSuffix">The suffix of the provider for an addressables package.</param>
public static void RegisterCatalog(string location, string providerSuffix = "")
{
if (_isInitialized)
{
Logger<MiraApiPlugin>.Error("AddressablesLoader has already been initialized, cannot register more catalogs.");
return;
}
CatalogLocations.Add((location, providerSuffix));
}
/// <summary>
/// Registers a specific addressables key as only containing <see cref="HatData"/>'s to load.
/// </summary>
/// <param name="addressablesKey">The key/label/group for a List <see cref="HatData"/>.</param>
public static void RegisterHats(string addressablesKey)
{
if (_isInitialized)
{
Logger<MiraApiPlugin>.Error("AddressablesLoader has already been initialized, cannot register more keys for hats.");
return;
}
RegisteredHatKeys.Add(addressablesKey);
}
/// <summary>
/// Registers a specific addressables key as only containing <see cref="SkinData"/>'s to load.
/// </summary>
/// <param name="addressablesKey">The key/label/group for a List <see cref="SkinData"/>.</param>
public static void RegisterSkins(string addressablesKey)
{
if (_isInitialized)
{
Logger<MiraApiPlugin>.Error("AddressablesLoader has already been initialized, cannot register more keys for skins.");
return;
}
RegisteredSkinKeys.Add(addressablesKey);
}
/// <summary>
/// Registers a specific addressables key as only containing <see cref="NamePlateData"/>'s to load.
/// </summary>
/// <param name="addressablesKey">The key/label/group for a List <see cref="NamePlateData"/>.</param>
/// /// <param name="groupTitle">The title of the group for visors.</param>
public static void RegisterNameplates(string addressablesKey, string groupTitle)
{
if (_isInitialized)
{
Logger<MiraApiPlugin>.Error("AddressablesLoader has already been initialized, cannot register more keys for nameplates.");
return;
}
RegisteredNameplateKeys.Add((addressablesKey, groupTitle));
}
/// <summary>
/// Registers a specific addressables key as only containing <see cref="VisorData"/>'s to load.
/// </summary>
/// <param name="addressablesKey">The key/label/group for a List <see cref="VisorData"/>.</param>
/// <param name="groupTitle">The title of the group for visors.</param>
public static void RegisterVisors(string addressablesKey, string groupTitle)
{
if (_isInitialized)
{
Logger<MiraApiPlugin>.Error("AddressablesLoader has already been initialized, cannot register more keys for visors.");
return;
}
RegisteredVisorKeys.Add((addressablesKey, groupTitle));
}
internal static void LoadAll()
{
if (_isInitialized)
{
Logger<MiraApiPlugin>.Error("AddressablesLoader has already been initialized.");
return;
}
_isInitialized = true;
foreach (var (location, providerSuffix) in CatalogLocations)
{
Coroutines.Start(CoLoadAddressables(location, providerSuffix));
}
Coroutines.Start(LoadCosmetics());
}
internal static IEnumerator CoLoadAddressables(string location, string suffix = "")
{
while (!AmongUsClient.Instance) yield return null;
// Load the local/remote content catalog
var catalogOperation = Addressables.LoadContentCatalog(location, suffix);
yield return catalogOperation;
// Check for errors
if (catalogOperation.Status != AsyncOperationStatus.Succeeded)
{
Logger<MiraApiPlugin>.Error($"Failed to load catalog {location}.");
}
Logger<MiraApiPlugin>.Info($"Loaded addressables {location}.");
LoadedLocations.Add(location);
}
[HideFromIl2Cpp]
internal static IEnumerator LoadCosmetics()
{
while (!AmongUsClient.Instance || CatalogLocations.Select(x=>x.Location).Any(x=>!LoadedLocations.Contains(x))) yield return null;
var hatBehaviours = DiscoverData<HatData>(RegisteredHatKeys);
var skinBehaviours = DiscoverData<SkinData>(RegisteredSkinKeys);
var namePlateBehaviours = DiscoverAndReportData<NamePlateData>(RegisteredNameplateKeys);
var visorBehaviours = DiscoverAndReportData<VisorData>(RegisteredVisorKeys);
var hatData = new List<HatData>();
hatData.AddRange(DestroyableSingleton<HatManager>.Instance.allHats);
hatData.ForEach(x => x.StoreName = "Vanilla");
DestroyableSingleton<HatManager>.Instance.allHats = PrepareArray(hatData, hatBehaviours);
var skinData = new List<SkinData>();
skinData.AddRange(DestroyableSingleton<HatManager>.Instance.allSkins);
skinData.ForEach(x => x.StoreName = "Vanilla");
DestroyableSingleton<HatManager>.Instance.allSkins = PrepareArray(skinData, skinBehaviours);
var visorData = new List<VisorData>();
visorData.AddRange(DestroyableSingleton<HatManager>.Instance.allVisors);
VisorsTabPatches.AddRange(visorBehaviours);
DestroyableSingleton<HatManager>.Instance.allVisors = PrepareArray(visorData, visorBehaviours.Select(x=>x.Data).ToList());
var namePlateData = new List<NamePlateData>();
namePlateData.AddRange(DestroyableSingleton<HatManager>.Instance.allNamePlates);
NameplatesTabPatches.AddRange(namePlateBehaviours);
DestroyableSingleton<HatManager>.Instance.allNamePlates = PrepareArray(namePlateData, namePlateBehaviours.Select(x => x.Data).ToList());
}
private static T[] PrepareArray<T>(List<T> data, List<T> behaviours) where T : CosmeticData
{
var count = data.Count;
for (int i = 0; i < behaviours.Count; i++)
{
behaviours[i].displayOrder = count + i;
data.Add(behaviours[i]);
}
return data.ToArray();
}
private static List<T> DiscoverData<T>(List<string> tags)
{
var behaviours = new List<T>();
foreach (var tag in tags)
{
try
{
var allLocations = Addressables.LoadResourceLocationsAsync(tag).WaitForCompletion();
var assets = Addressables.LoadAssetsAsync<T>(allLocations, null, false).WaitForCompletion();
var array = new Il2CppSystem.Collections.Generic.List<T>(assets.Pointer);
behaviours.AddRange(array.ToArray());
}
catch
{
Logger<MiraApiPlugin>.Error($"Failed to find tag {tag}");
}
}
return behaviours;
}
private static List<(string Category, T Data)> DiscoverAndReportData<T>(List<(string Tag, string Category)> tags)
{
var behaviours = new List<(string, T)>();
foreach (var tag in tags)
{
try
{
var allLocations = Addressables.LoadResourceLocationsAsync(tag.Tag).WaitForCompletion();
var assets = Addressables.LoadAssetsAsync<T>(allLocations, null, false).WaitForCompletion();
var array = new Il2CppSystem.Collections.Generic.List<T>(assets.Pointer);
behaviours.AddRange(array.ToArray().Select(x => (tag.Category, x)));
}
catch
{
Logger<MiraApiPlugin>.Error($"Failed to find tag {tag}");
}
}
return behaviours;
}
}
Loaded 100 of 112 files, more files were not shown because too many files have changed in this diff. Show more