mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-20 01:46:15 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd818a18cf | ||
|
|
56d4e399d9 | ||
|
|
0d41ebbc13 | ||
|
|
9388ff487a | ||
|
|
720491e5cf | ||
|
|
9884df356e | ||
|
|
7ec4addacc | ||
|
|
bc9bc0ce24 | ||
|
|
aff1c311fa | ||
|
|
0d2a9d9700 | ||
|
|
669c541279 | ||
|
|
2e5f9639b3 | ||
|
|
e6d1f440c8 | ||
|
|
2ac8bdc240 | ||
|
|
d5d99001ea | ||
|
|
5628b79814 | ||
|
|
72a44f65f1 | ||
|
|
db6a336a63 | ||
|
|
90b23ec2ca | ||
|
|
20e927208c | ||
|
|
13ef3bdd49 | ||
|
|
b3ff6755bb | ||
|
|
67604600cd | ||
|
|
6e35de6185 | ||
|
|
a26ec51c7d | ||
|
|
3f6ae4cc41 | ||
|
|
15a2cdcd5b | ||
|
|
372a9e3aa9 | ||
|
|
10a9dcfa34 | ||
|
|
d46fdb0185 | ||
|
|
a361afbcbf | ||
|
|
24cdac0243 | ||
|
|
5fe75843ad | ||
|
|
a0436dcbba | ||
|
|
35be381a77 | ||
|
|
1298beb517 | ||
|
|
d41c19a176 | ||
|
|
476f4400ad | ||
|
|
2b46d90570 | ||
|
|
ae4aaf7af9 | ||
|
|
757b9436ca | ||
|
|
7ea10afffb | ||
|
|
0ab80c42f9 | ||
|
|
bc144568f4 | ||
|
|
07f402e659 | ||
|
|
1a6aed539a | ||
|
|
023cdaa470 | ||
|
|
98dd5ca056 | ||
|
|
1f3e21ce19 | ||
|
|
15e441dd71 | ||
|
|
8c67bbbd44 | ||
|
|
aa584a8f44 | ||
|
|
a8a584b61b | ||
|
|
acc4b1c8dd | ||
|
|
f9600bc906 | ||
|
|
f3da06870f | ||
|
|
9180dec9e1 | ||
|
|
ef6dedbd55 | ||
|
|
5d3c3ed319 | ||
|
|
5b22d667fd | ||
|
|
fb894c9f2c | ||
|
|
d0449f57b5 | ||
|
|
5ffd591258 | ||
|
|
cc0ac1b988 | ||
|
|
00b96f6e61 | ||
|
|
6cb7f9f145 | ||
|
|
f5b4bdd1d4 | ||
|
|
cd6c4f5390 | ||
|
|
5cc29d903a |
No files matched your search
+1
-2
@@ -1,2 +1 @@
|
||||
[*.{cs,vb}]
|
||||
dotnet_diagnostic.CA1707.severity = none
|
||||
[*.{cs,vb}]
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
<Project>
|
||||
<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="BepInEx.Unity.IL2CPP" Version="6.0.0-be.735" Private="false" ExcludeAssets="runtime;native" />
|
||||
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2025.4.15" 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" />
|
||||
|
||||
@@ -9,10 +9,7 @@ using MiraAPI.Events.Vanilla.Player;
|
||||
using MiraAPI.Example.Buttons.Freezer;
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example;
|
||||
|
||||
@@ -21,7 +18,7 @@ public static class ExampleEventHandlers
|
||||
public static void Initialize()
|
||||
{
|
||||
// You can register event handlers with the MiraEventManager class.
|
||||
MiraEventManager.RegisterEventHandler<BeforeMurderEvent>(@event =>
|
||||
var handle = MiraEventManager.RegisterEventHandler<BeforeMurderEvent>(@event =>
|
||||
{
|
||||
Logger<ExamplePlugin>.Info($"{@event.Source.Data.PlayerName} is about to kill {@event.Target.Data.PlayerName}");
|
||||
});
|
||||
@@ -35,6 +32,9 @@ 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.
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using Il2CppSystem.Collections.Generic;
|
||||
using MiraAPI.Example.Options.Modifiers;
|
||||
using MiraAPI.Example.Options.Modifiers;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using UnityEngine;
|
||||
using Random = System.Random;
|
||||
|
||||
namespace MiraAPI.Example.Modifiers;
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Modifiers;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using AmongUs.GameOptions;
|
||||
using MiraAPI.Patches.Stubs;
|
||||
using MiraAPI.Roles;
|
||||
using Reactor.Utilities;
|
||||
using TMPro;
|
||||
@@ -14,6 +15,8 @@ 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,
|
||||
@@ -22,35 +25,54 @@ public class ChameloenRole : CrewmateRole, ICustomRole
|
||||
|
||||
public override void Initialize(PlayerControl player)
|
||||
{
|
||||
Player = player;
|
||||
Logger<ExamplePlugin>.Error("Initializing ChamelonRole for player: " + player.PlayerId);
|
||||
Logger<ExamplePlugin>.Info("Initializing ChamelonRole for player: " + player.PlayerId);
|
||||
RoleBehaviourStubs.Initialize(this, player);
|
||||
_shouldHide = true;
|
||||
}
|
||||
|
||||
public void PlayerControlFixedUpdate(PlayerControl playerControl)
|
||||
public void FixedUpdate()
|
||||
{
|
||||
if (playerControl.MyPhysics.Velocity.magnitude > 0)
|
||||
if (!Player || !_shouldHide)
|
||||
{
|
||||
SpriteRenderer rend = playerControl.cosmetics.currentBodySprite.BodySprite;
|
||||
TextMeshPro tmp = playerControl.cosmetics.nameText;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Player.MyPhysics.Velocity.magnitude > 0)
|
||||
{
|
||||
var rend = Player.cosmetics.currentBodySprite.BodySprite;
|
||||
var tmp = Player.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 playerControl.cosmetics.transform.GetComponentsInChildren<SpriteRenderer>())
|
||||
foreach (var cosmetic in Player.cosmetics.transform.GetComponentsInChildren<SpriteRenderer>())
|
||||
{
|
||||
cosmetic.color = Color.Lerp(cosmetic.color, new Color(1, 1, 1, 1), Time.deltaTime * 4f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
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);
|
||||
|
||||
foreach (var cosmetic in playerControl.cosmetics.transform.GetComponentsInChildren<SpriteRenderer>())
|
||||
foreach (var cosmetic in Player.cosmetics.transform.GetComponentsInChildren<SpriteRenderer>())
|
||||
{
|
||||
cosmetic.color = Color.Lerp(cosmetic.color, new Color(1, 1, 1, playerControl.AmOwner ? 0.3f : 0), Time.deltaTime * 4f);
|
||||
cosmetic.color = Color.Lerp(cosmetic.color, new Color(1, 1, 1, Player.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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,14 @@ public static class MiraEventManager
|
||||
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
((Action<T>)handler.EventHandler).Invoke(eventInstance);
|
||||
try
|
||||
{
|
||||
((Action<T>)handler.EventHandler).Invoke(eventInstance);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error invoking event handler for {typeof(T).Name}: {ex.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -50,7 +57,14 @@ public static class MiraEventManager
|
||||
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
handler.EventHandler.DynamicInvoke(eventInstance);
|
||||
try
|
||||
{
|
||||
handler.EventHandler.DynamicInvoke(eventInstance);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error invoking event handler for {type.Name}: {ex.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -62,12 +76,12 @@ public static class MiraEventManager
|
||||
/// <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>
|
||||
public static void RegisterEventHandler(Type type, MethodInfo methodInfo, int priority = 0)
|
||||
/// <returns>An event handle to use when unregistering the event.</returns>
|
||||
public static MiraEventHandle RegisterEventHandler(Type type, MethodInfo methodInfo, int priority = 0)
|
||||
{
|
||||
if (!type.IsSubclassOf(typeof(MiraEvent)))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Type must be a subclass of MiraEvent: {type.FullName}");
|
||||
return;
|
||||
throw new InvalidOperationException($"Type must be a subclass of MiraEvent: {type.FullName}");
|
||||
}
|
||||
|
||||
EventWrappers.TryAdd(type, []);
|
||||
@@ -84,6 +98,7 @@ public static class MiraEventManager
|
||||
}
|
||||
|
||||
handlers.Insert(index, eventWrapper);
|
||||
return new MiraEventHandle(type, eventWrapper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -92,7 +107,8 @@ public static class MiraEventManager
|
||||
/// <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>
|
||||
/// <typeparam name="T">Type of event.</typeparam>
|
||||
public static void RegisterEventHandler<T>(Action<T> handler, int priority = 0) where T : MiraEvent
|
||||
/// <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
|
||||
{
|
||||
EventWrappers.TryAdd(typeof(T), []);
|
||||
|
||||
@@ -107,5 +123,30 @@ public static class MiraEventManager
|
||||
}
|
||||
|
||||
handlers.Insert(index, eventWrapper);
|
||||
return new MiraEventHandle(typeof(T), eventWrapper);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (!EventWrappers.TryGetValue(eventHandle.EventType, out var handlers))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!handlers.Remove(eventHandle.EventWrapper))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (handlers.Count == 0)
|
||||
{
|
||||
EventWrappers.Remove(eventHandle.EventType);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -15,14 +15,21 @@ 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>
|
||||
public AfterMurderEvent(PlayerControl source, PlayerControl target)
|
||||
/// <param name="deadBody">The player's vody, if it exists.</param>
|
||||
public AfterMurderEvent(PlayerControl source, PlayerControl target, DeadBody? deadBody)
|
||||
{
|
||||
Source = source;
|
||||
Target = target;
|
||||
DeadBody = deadBody;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace MiraAPI.Events.Vanilla.Gameplay;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked before RpcEndGame is called, allowing cancellation of the game end entirely.
|
||||
/// </summary>
|
||||
public class BeforeGameEndEvent : MiraCancelableEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the reason for the game end.
|
||||
/// </summary>
|
||||
public GameOverReason Reason { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BeforeGameEndEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="reason">The reason for the game end.</param>
|
||||
public BeforeGameEndEvent(GameOverReason reason)
|
||||
{
|
||||
Reason = reason;
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ 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)
|
||||
{
|
||||
|
||||
@@ -15,14 +15,21 @@ 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>
|
||||
public PlayerDeathEvent(PlayerControl player, DeathReason reason)
|
||||
/// <param name="deadBody">The player's body, if it exists.</param>
|
||||
public PlayerDeathEvent(PlayerControl player, DeathReason reason, DeadBody? deadBody)
|
||||
{
|
||||
Player = player;
|
||||
DeathReason = reason;
|
||||
DeadBody = deadBody;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
[*.cs]
|
||||
dotnet_diagnostic.CS1591.severity = none
|
||||
@@ -21,7 +21,7 @@ public static class CustomGameModeManager
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current gamemode.
|
||||
/// Gets the current gamemode.
|
||||
/// </summary>
|
||||
public static CustomGameMode? ActiveMode { get; internal set; } = new DefaultMode();
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using BepInEx.Configuration;
|
||||
@@ -45,7 +46,9 @@ public static class ModdedOptionsManager
|
||||
pluginInfo.InternalOptionGroups.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;
|
||||
@@ -173,8 +176,10 @@ public static class ModdedOptionsManager
|
||||
/// </summary>
|
||||
/// <param name="__originalMethod">The original setter method.</param>
|
||||
/// <param name="value">The new object value.</param>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
#pragma warning disable CA1707
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony naming convention")]
|
||||
public static void PropertySetterPatch(MethodBase __originalMethod, object value)
|
||||
#pragma warning restore CA1707
|
||||
{
|
||||
var attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod() == __originalMethod).Value;
|
||||
attribute.SetValue(value);
|
||||
@@ -186,8 +191,10 @@ public static class ModdedOptionsManager
|
||||
/// <param name="__originalMethod">The original getter method.</param>
|
||||
/// <param name="__result">The result of the property getter.</param>
|
||||
/// <returns>False so the original getter gets skipped.</returns>
|
||||
// ReSharper disable InconsistentNaming
|
||||
#pragma warning disable CA1707
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony naming convention")]
|
||||
public static bool PropertyGetterPatch(MethodBase __originalMethod, ref object __result)
|
||||
#pragma warning restore CA1707
|
||||
{
|
||||
var attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod() == __originalMethod).Value;
|
||||
__result = attribute.GetValue();
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace MiraAPI.GameOptions;
|
||||
|
||||
#pragma warning disable CA1852
|
||||
internal class ModifierOptionGroup : AbstractOptionGroup
|
||||
#pragma warning restore CA1852
|
||||
{
|
||||
public override string GroupName { get; }
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Globalization;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Mira;
|
||||
using MiraAPI.Patches;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
@@ -36,6 +37,11 @@ public abstract class CustomActionButton
|
||||
/// </summary>
|
||||
public abstract LoadableAsset<Sprite> Sprite { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the format string for the cooldown timer.
|
||||
/// </summary>
|
||||
public virtual string CooldownTimerFormatString => "0";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the button's effect duration in seconds. If the button has no effect, set to 0.
|
||||
/// </summary>
|
||||
@@ -178,9 +184,12 @@ public abstract class CustomActionButton
|
||||
|
||||
this.Location = location;
|
||||
|
||||
if (!moveButton) return;
|
||||
if (!moveButton)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (HudManagerPatches._bottomLeft == null || HudManagerPatches._bottomRight == null)
|
||||
if (HudManagerPatches.BottomLeft == null || HudManagerPatches.BottomRight == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -188,17 +197,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;
|
||||
}
|
||||
}
|
||||
@@ -435,23 +444,23 @@ public abstract class CustomActionButton
|
||||
{
|
||||
if (CanUse())
|
||||
{
|
||||
Button.SetEnabled();
|
||||
Button!.SetEnabled();
|
||||
}
|
||||
else
|
||||
{
|
||||
Button.SetDisabled();
|
||||
Button!.SetDisabled();
|
||||
}
|
||||
|
||||
if (EffectActive)
|
||||
{
|
||||
Button.SetFillUp(Timer, EffectDuration);
|
||||
|
||||
Button.cooldownTimerText.text = Mathf.CeilToInt(Timer).ToString(NumberFormatInfo.InvariantInfo);
|
||||
Button.cooldownTimerText.text = Timer.ToString(CooldownTimerFormatString, NumberFormatInfo.InvariantInfo);
|
||||
Button.cooldownTimerText.gameObject.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Button.SetCoolDown(Timer, Cooldown);
|
||||
Button.SetCooldownFormat(Timer, Cooldown, CooldownTimerFormatString);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,9 @@ public static class CustomButtonManager
|
||||
CustomButtons.Add(button);
|
||||
pluginInfo.InternalButtons.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));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.Hud;
|
||||
|
||||
@@ -13,7 +14,6 @@ public static class CustomButtonSingleton<T> where T : CustomActionButton
|
||||
/// <summary>
|
||||
/// Gets the instance of the button.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1000
|
||||
[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.")]
|
||||
public static T Instance => _instance ??= CustomButtonManager.CustomButtons.OfType<T>().Single();
|
||||
#pragma warning restore CA1000
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
using System;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace MiraAPI.Hud;
|
||||
|
||||
/// <summary>
|
||||
/// Simple input box for user input.
|
||||
/// </summary>
|
||||
[RegisterInIl2Cpp]
|
||||
public class InputBox : MonoBehaviour
|
||||
{
|
||||
public InputField inputField = null!;
|
||||
|
||||
public void CreateDialog(string title, string placeholder, string submitText, Action<string> onSubmit)
|
||||
{
|
||||
var canvasObj = new GameObject("DialogCanvas");
|
||||
canvasObj.transform.SetParent(transform);
|
||||
canvasObj.layer = LayerMask.NameToLayer("UI");
|
||||
|
||||
var canvas = canvasObj.AddComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.overrideSorting = true;
|
||||
canvas.sortingOrder = 1000; // Ensure the canvas is on top of other UI elements
|
||||
|
||||
canvasObj.AddComponent<CanvasScaler>().uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
canvasObj.AddComponent<GraphicRaycaster>();
|
||||
|
||||
var panelObj = new GameObject("DialogPanel");
|
||||
panelObj.transform.SetParent(canvasObj.transform);
|
||||
var panelRect = panelObj.AddComponent<RectTransform>();
|
||||
var img = panelObj.AddComponent<Image>();
|
||||
|
||||
img.color = new Color(1, 1, 1, 0.9f);
|
||||
img.raycastTarget = true;
|
||||
img.sprite = MiraAssets.RoundedBox.LoadAsset();
|
||||
img.type = Image.Type.Sliced;
|
||||
|
||||
panelRect.sizeDelta = new Vector2(300, 200);
|
||||
panelRect.anchoredPosition = Vector2.zero;
|
||||
|
||||
var titleText = CreateText(panelObj, title);
|
||||
titleText.alignment = TextAnchor.MiddleCenter;
|
||||
titleText.fontSize = 26;
|
||||
titleText.fontStyle = FontStyle.Bold;
|
||||
titleText.color = Color.white;
|
||||
|
||||
var titleRect = titleText.GetComponent<RectTransform>();
|
||||
titleRect.sizeDelta = new Vector2(280, 50);
|
||||
titleRect.anchoredPosition = new Vector2(0, 70);
|
||||
|
||||
var inputObj = new GameObject("InputField");
|
||||
inputObj.transform.SetParent(panelObj.transform);
|
||||
var inputRect = inputObj.AddComponent<RectTransform>();
|
||||
inputRect.sizeDelta = new Vector2(220, 40);
|
||||
inputRect.anchoredPosition = new Vector2(0, 20);
|
||||
|
||||
var inputBg = inputObj.AddComponent<Image>();
|
||||
inputBg.color = new Color(1, 1, 1, 0.8f);
|
||||
|
||||
inputField = inputObj.AddComponent<InputField>();
|
||||
inputField.textComponent = CreateText(inputObj, string.Empty, 18, Color.black);
|
||||
inputField.placeholder = CreateText(inputObj, placeholder, 18, new Color(0.5f, 0.5f, 0.5f));
|
||||
|
||||
var buttonObj = new GameObject("SubmitButton");
|
||||
buttonObj.transform.SetParent(panelObj.transform);
|
||||
var button = buttonObj.AddComponent<Button>();
|
||||
var buttonRect = buttonObj.AddComponent<RectTransform>();
|
||||
buttonRect.sizeDelta = new Vector2(150, 50);
|
||||
buttonRect.anchoredPosition = new Vector2(0, -50);
|
||||
|
||||
var buttonImg = buttonObj.AddComponent<Image>();
|
||||
buttonImg.color = new Color(1f, 1f, 1f, 1f);
|
||||
buttonImg.sprite = MiraAssets.RoundedBox.LoadAsset();
|
||||
buttonImg.type = Image.Type.Sliced;
|
||||
|
||||
var buttonText = CreateText(buttonObj, submitText, 20, Color.white);
|
||||
buttonText.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
var colors = button.colors;
|
||||
colors.highlightedColor = Color.green;
|
||||
colors.pressedColor = MiraAssets.AcceptedTeal;
|
||||
button.colors = colors;
|
||||
|
||||
button.onClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
onSubmit(inputField.text);
|
||||
DestroyImmediate(canvasObj);
|
||||
}));
|
||||
|
||||
inputField.ActivateInputField();
|
||||
}
|
||||
|
||||
private static Text CreateText(GameObject parent, string textContent = "", int fontSize = 20, Color? textColor = null, Font? font = null)
|
||||
{
|
||||
var textObj = new GameObject("Text");
|
||||
textObj.transform.SetParent(parent.transform);
|
||||
|
||||
var text = textObj.AddComponent<Text>();
|
||||
text.font = font ?? Resources.GetBuiltinResource<Font>("Arial.ttf");
|
||||
text.text = textContent;
|
||||
text.color = textColor ?? Color.white;
|
||||
text.fontSize = fontSize;
|
||||
text.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
var rect = textObj.GetComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(200, 40);
|
||||
rect.anchoredPosition = Vector2.zero;
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,11 @@ 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;
|
||||
@@ -72,7 +69,15 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
foreach (var modifier in _toRemove.ToArray())
|
||||
{
|
||||
_toRemove.Remove(modifier);
|
||||
modifier.OnDeactivate();
|
||||
try
|
||||
{
|
||||
modifier.OnDeactivate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error while deactivating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
}
|
||||
|
||||
Modifiers.Remove(modifier);
|
||||
}
|
||||
|
||||
@@ -82,7 +87,14 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
_toAdd.Remove(modifier);
|
||||
Modifiers.Add(modifier);
|
||||
modifier.Initialized = true;
|
||||
modifier.OnActivate();
|
||||
try
|
||||
{
|
||||
modifier.OnActivate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error while activating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
}
|
||||
|
||||
if (modifier is TimedModifier { AutoStart: true } timer)
|
||||
{
|
||||
@@ -102,7 +114,14 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
|
||||
foreach (var modifier in ActiveModifiers)
|
||||
{
|
||||
modifier.FixedUpdate();
|
||||
try
|
||||
{
|
||||
modifier.FixedUpdate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error while (fixed) updating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
if (_player.AmOwner && ModifierDisplay && HudManager.InstanceExists)
|
||||
@@ -115,7 +134,14 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
{
|
||||
foreach (var modifier in ActiveModifiers)
|
||||
{
|
||||
modifier.Update();
|
||||
try
|
||||
{
|
||||
modifier.Update();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error while updating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace MiraAPI.Modifiers.ModifierDisplay;
|
||||
/// The code used to display Mira modifiers.
|
||||
/// </summary>
|
||||
[RegisterInIl2Cpp]
|
||||
public class ModifierDisplayComponent(nint ptr) : MonoBehaviour(ptr)
|
||||
public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the instance of the Modifier Display.
|
||||
@@ -34,8 +34,8 @@ public class ModifierDisplayComponent(nint ptr) : MonoBehaviour(ptr)
|
||||
private PassiveButton _nextButton = null!;
|
||||
private PassiveButton _backButton = null!;
|
||||
|
||||
private int _currentPage = 0;
|
||||
private const int _itemsPerPage = 3;
|
||||
private int _currentPage;
|
||||
private const int ItemsPerPage = 3;
|
||||
|
||||
private readonly Dictionary<BaseModifier, ModifierUiComponent> _modifiers = [];
|
||||
|
||||
@@ -142,10 +142,10 @@ public class ModifierDisplayComponent(nint ptr) : MonoBehaviour(ptr)
|
||||
|
||||
private void UpdatePage()
|
||||
{
|
||||
var totalPages = Mathf.CeilToInt((float)_modifiers.Count / _itemsPerPage);
|
||||
var totalPages = Mathf.CeilToInt((float)_modifiers.Count / ItemsPerPage);
|
||||
_currentPage = Mathf.Clamp(_currentPage, 0, Mathf.Max(0, totalPages - 1));
|
||||
|
||||
if (_modifiers.Count <= _itemsPerPage)
|
||||
if (_modifiers.Count <= ItemsPerPage)
|
||||
{
|
||||
_modifiers.Do(x => x.Value.gameObject.SetActive(true));
|
||||
_pagination.gameObject.SetActive(false);
|
||||
@@ -162,8 +162,8 @@ public class ModifierDisplayComponent(nint ptr) : MonoBehaviour(ptr)
|
||||
_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 start = _currentPage * ItemsPerPage;
|
||||
var end = Mathf.Min(start + ItemsPerPage, _modifiers.Count);
|
||||
var modifiers = _modifiers.ToArray();
|
||||
|
||||
for (var i = start; i < end; i++)
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace MiraAPI.Modifiers.ModifierDisplay;
|
||||
/// The code placed on every Modifier HUD object. Used to handle updating.
|
||||
/// </summary>
|
||||
[RegisterInIl2Cpp]
|
||||
public class ModifierUiComponent(nint ptr) : MonoBehaviour(ptr)
|
||||
public class ModifierUiComponent(nint cppPtr) : MonoBehaviour(cppPtr)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the modifier which this component is for.
|
||||
@@ -17,12 +17,13 @@ public class ModifierUiComponent(nint ptr) : MonoBehaviour(ptr)
|
||||
[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;
|
||||
|
||||
@@ -30,7 +30,7 @@ public static class ModifierExtensions
|
||||
_ = ModifierManager.GetModifierType(typeId) ?? throw new InvalidOperationException(
|
||||
$"Modifier with ID {typeId} is not registered.");
|
||||
|
||||
Rpc<AddModifierRpc>.Instance.Send(target, new ModifierData(typeId, Guid.NewGuid(), args));
|
||||
Rpc<AddModifierRpc>.Instance.Send(target, new ModifierData(typeId, Guid.NewGuid(), args), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -63,7 +63,7 @@ public static class ModifierExtensions
|
||||
/// </summary>
|
||||
/// <param name="target">The player to remove the modifier from.</param>
|
||||
/// <param name="uniqueId">The unique ID of the modifier.</param>
|
||||
[MethodRpc((uint)MiraRpc.RemoveModifier)]
|
||||
[MethodRpc((uint)MiraRpc.RemoveModifier, SendImmediately = true)]
|
||||
public static void RpcRemoveModifier(this PlayerControl target, Guid uniqueId)
|
||||
{
|
||||
target.RemoveModifier(uniqueId);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using MiraAPI.Utilities;
|
||||
@@ -11,7 +12,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)
|
||||
{
|
||||
@@ -45,13 +46,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() ?? typeof(object)).ToArray();
|
||||
var argTypes = args.Select(arg => arg.GetType()).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);
|
||||
@@ -95,6 +96,7 @@ 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);
|
||||
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
@@ -17,6 +16,11 @@ namespace MiraAPI.Modifiers;
|
||||
/// </summary>
|
||||
public static class ModifierManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether modifiers should be assigned to players.
|
||||
/// </summary>
|
||||
public static bool MiraAssignsModifiers { get; set; } = true;
|
||||
|
||||
private static readonly Dictionary<uint, Type> IdToTypeModifierMap = [];
|
||||
private static readonly Dictionary<Type, uint> TypeToIdModifierMap = [];
|
||||
private static readonly Dictionary<int, List<uint>> PrioritiesToIdsMap = [];
|
||||
@@ -99,7 +103,11 @@ public static class ModifierManager
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void AssignModifiers(List<PlayerControl> plrs)
|
||||
/// <summary>
|
||||
/// Assigns modifiers to players. ONLY CALL THIS METHOD IF YOU KNOW WHAT YOU ARE DOING.
|
||||
/// </summary>
|
||||
/// <param name="plrs">The players to assign modifiers to.</param>
|
||||
public static void AssignModifiers(List<PlayerControl> plrs)
|
||||
{
|
||||
var rand = new Random();
|
||||
|
||||
|
||||
@@ -264,7 +264,7 @@ public static class CustomMurderRpc
|
||||
deadBody.enabled = true;
|
||||
}
|
||||
|
||||
var afterMurderEvent = new AfterMurderEvent(source, target);
|
||||
var afterMurderEvent = new AfterMurderEvent(source, target, deadBody);
|
||||
MiraEventManager.InvokeEvent(afterMurderEvent);
|
||||
|
||||
if (!isParticipant)
|
||||
|
||||
@@ -46,7 +46,7 @@ public enum MiraRpc : uint
|
||||
RemoveVote,
|
||||
|
||||
/// <summary>
|
||||
/// Custom RPC to replace Among Us CastVote
|
||||
/// Custom RPC to replace Among Us CastVote.
|
||||
/// </summary>
|
||||
CastVote,
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
[*.{cs,vb}]
|
||||
dotnet_diagnostic.CA1707.severity = none
|
||||
dotnet_diagnostic.CS1591.severity = none
|
||||
dotnet_diagnostic.SA1611.severity = none
|
||||
dotnet_diagnostic.SA1615.severity = none
|
||||
@@ -5,29 +5,44 @@ using MiraAPI.GameEnd;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
[HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))]
|
||||
internal static class EndGamePatches
|
||||
{
|
||||
public static bool Prefix(EndGameManager __instance)
|
||||
[HarmonyPatch(typeof(GameManager), nameof(GameManager.RpcEndGame))]
|
||||
public static class GameManagerPatches
|
||||
{
|
||||
if (CustomGameOver.Instance is not { } gameOver)
|
||||
public static bool Prefix(GameOverReason endReason)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var @event = new BeforeGameEndEvent(endReason);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
|
||||
var result = gameOver.BeforeEndGameSetup(__instance);
|
||||
return result;
|
||||
return !@event.IsCancelled;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Postfix(EndGameManager __instance)
|
||||
[HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))]
|
||||
public static class EndGameManagerPatches
|
||||
{
|
||||
if (CustomGameOver.Instance is { } gameOver)
|
||||
public static bool Prefix(EndGameManager __instance)
|
||||
{
|
||||
gameOver.AfterEndGameSetup(__instance);
|
||||
CustomGameOver.Instance = null;
|
||||
if (CustomGameOver.Instance is not { } gameOver)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var result = gameOver.BeforeEndGameSetup(__instance);
|
||||
return result;
|
||||
}
|
||||
|
||||
var @event = new GameEndEvent(__instance);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
public static void Postfix(EndGameManager __instance)
|
||||
{
|
||||
if (CustomGameOver.Instance is { } gameOver)
|
||||
{
|
||||
gameOver.AfterEndGameSetup(__instance);
|
||||
CustomGameOver.Instance = null;
|
||||
}
|
||||
|
||||
var @event = new GameEndEvent(__instance);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using MiraAPI.Utilities;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
@@ -13,13 +14,17 @@ public static class KillAnimationPatches
|
||||
/// <summary>
|
||||
/// Used to trigger the <see cref="AfterMurderEvent"/>.
|
||||
/// </summary>
|
||||
/// <param name="source">The killer.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(KillAnimation), nameof(KillAnimation.CoPerformKill))]
|
||||
public static void KillAnimPostfix(PlayerControl source, PlayerControl target)
|
||||
[HarmonyPatch(typeof(KillAnimation._CoPerformKill_d__2), nameof(KillAnimation._CoPerformKill_d__2.MoveNext))]
|
||||
public static void KillAnimPostfix(KillAnimation._CoPerformKill_d__2 __instance)
|
||||
{
|
||||
var afterMurderEvent = new AfterMurderEvent(source, target);
|
||||
// Checking if state is -1 to wait for the end of coroutine.
|
||||
if (__instance.__1__state != -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var afterMurderEvent = new AfterMurderEvent(__instance.source, __instance.target, Helpers.GetBodyById(__instance.target.PlayerId));
|
||||
MiraEventManager.InvokeEvent(afterMurderEvent);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,52 @@
|
||||
using HarmonyLib;
|
||||
using AmongUs.InnerNet.GameDataMessages;
|
||||
using HarmonyLib;
|
||||
using InnerNet;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Meeting;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class ReportBodyEventPatch
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdReportDeadBody))]
|
||||
public static bool PlayerControlCmdReportDeadBodyPrefix(PlayerControl __instance, NetworkedPlayerInfo? target)
|
||||
private static bool ReportBodyEventHook(PlayerControl source, NetworkedPlayerInfo? target)
|
||||
{
|
||||
var body = target != null ? Helpers.GetBodyById(target.PlayerId) : null;
|
||||
|
||||
var @event = new ReportBodyEvent(__instance, target, body);
|
||||
var @event = new ReportBodyEvent(source, target, body);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
return !@event.IsCancelled;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ReportDeadBody))]
|
||||
public static bool PlayerControlCmdReportDeadBodyPrefix(PlayerControl __instance, NetworkedPlayerInfo? target)
|
||||
{
|
||||
return ReportBodyEventHook(__instance, target);
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.LateBroadcastReliableMessage))]
|
||||
public static bool LateBroadcastReliableMessagePrefix(IGameDataMessage rpcMessage)
|
||||
{
|
||||
var reportBodyMessage = rpcMessage.TryCast<RpcReportDeadBodyMessage>();
|
||||
|
||||
if (reportBodyMessage == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var source = AmongUsClient.Instance.FindObjectByNetId<PlayerControl>(reportBodyMessage.rpcObjectNetId);
|
||||
if (source == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("Failed to find source player for RpcReportDeadBodyMessage with NetId: " + reportBodyMessage.rpcObjectNetId);
|
||||
return true;
|
||||
}
|
||||
|
||||
var target = GameData.Instance.GetPlayerById(reportBodyMessage.targetPlayerId);
|
||||
|
||||
return ReportBodyEventHook(source, target);
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,10 @@ public static class VentEventPatches
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.RpcEnterVent))]
|
||||
public static bool PlayerPhysicsRpcEnterVentPrefix(PlayerPhysics __instance, int id)
|
||||
public static bool PlayerPhysicsRpcEnterVentPrefix(PlayerPhysics __instance, int ventId)
|
||||
{
|
||||
var pc = __instance.myPlayer;
|
||||
var vent = ShipStatus.Instance.AllVents.FirstOrDefault(v => v.Id == id);
|
||||
var vent = ShipStatus.Instance.AllVents.FirstOrDefault(v => v.Id == ventId);
|
||||
|
||||
var @event = new EnterVentEvent(pc, vent);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
@@ -40,10 +40,10 @@ public static class VentEventPatches
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.RpcExitVent))]
|
||||
public static bool PlayerPhysicsRpcExitVentPrefix(PlayerPhysics __instance, int id)
|
||||
public static bool PlayerPhysicsRpcExitVentPrefix(PlayerPhysics __instance, int ventId)
|
||||
{
|
||||
var pc = __instance.myPlayer;
|
||||
var vent = ShipStatus.Instance.AllVents.First(v => v.Id == id);
|
||||
var vent = ShipStatus.Instance.AllVents.First(v => v.Id == ventId);
|
||||
|
||||
var @event = new ExitVentEvent(pc, vent);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[*.cs]
|
||||
dotnet_diagnostic.S125.severity = none
|
||||
@@ -4,7 +4,7 @@ using MiraAPI.Hud;
|
||||
namespace MiraAPI.Patches.Hud;
|
||||
|
||||
/// <summary>
|
||||
/// Reset button patches
|
||||
/// Reset button patches.
|
||||
/// </summary>
|
||||
[HarmonyPatch]
|
||||
public static class ButtonResetPatches
|
||||
|
||||
@@ -13,9 +13,9 @@ namespace MiraAPI.Patches;
|
||||
public static class HudManagerPatches
|
||||
{
|
||||
// Custom buttons parent.
|
||||
internal static GameObject? _bottomLeft;
|
||||
internal static Transform? _bottomRight;
|
||||
internal static Transform? _buttons;
|
||||
public static GameObject? BottomLeft { get; private set; }
|
||||
public static Transform? BottomRight { get; private set; }
|
||||
public static Transform? Buttons { get; private set; }
|
||||
|
||||
/*
|
||||
/// <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,
|
||||
};
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ public static class NameplatesTabPatches
|
||||
var x = (NamePlateViewData viewdata) =>
|
||||
{
|
||||
colorChip.transform.GetChild(1).GetComponent<SpriteRenderer>().sprite = viewdata?.Image;
|
||||
//(colorChip as NameplateChip).image.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);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace MiraAPI.Patches;
|
||||
|
||||
/// <summary>
|
||||
/// Used to fix the Minigame crash errors (credits Submerged/PH Gaming for the fix)
|
||||
/// Used to fix the Minigame crash errors (credits Submerged/PH Gaming for the fix).
|
||||
/// </summary>
|
||||
[HarmonyPatch]
|
||||
public static class MinigameBeginErrorPatch
|
||||
|
||||
@@ -89,7 +89,7 @@ internal static class GamePresetsTabPatches
|
||||
|
||||
var prefab = GameSettingMenu.Instance.GameSettingsButton;
|
||||
|
||||
foreach (var preset in MiraPluginManager.Instance.RegisteredPlugins.SelectMany(x => x.Presets))
|
||||
foreach (var preset in MiraPluginManager.Instance.RegisteredPlugins.SelectMany(x => x.InternalPresets))
|
||||
{
|
||||
var button = Object.Instantiate(prefab, _presetHolder.transform);
|
||||
button.buttonText.text = preset.Name;
|
||||
@@ -112,7 +112,7 @@ internal static class GamePresetsTabPatches
|
||||
|
||||
foreach (var mod in MiraPluginManager.Instance.RegisteredPlugins)
|
||||
{
|
||||
foreach (var button in mod.Presets.Select(x => x.PresetButton))
|
||||
foreach (var button in mod.InternalPresets.Select(x => x.PresetButton))
|
||||
{
|
||||
if (button != null)
|
||||
{
|
||||
@@ -154,7 +154,7 @@ internal static class GamePresetsTabPatches
|
||||
|
||||
foreach (var mod in MiraPluginManager.Instance.RegisteredPlugins)
|
||||
{
|
||||
foreach (var button in mod.Presets.Select(x => x.PresetButton))
|
||||
foreach (var button in mod.InternalPresets.Select(x => x.PresetButton))
|
||||
{
|
||||
if (button != null)
|
||||
{
|
||||
|
||||
@@ -32,11 +32,30 @@ internal static class GameSettingMenuPatches
|
||||
private static PassiveButton? _modifiersButton;
|
||||
private static PassiveButton? _smallRolesButton;
|
||||
|
||||
private static Dictionary<int, Vector3> OptionScrolls = [];
|
||||
private static Dictionary<int, Vector3> OptionsPositions { get; } = [];
|
||||
private static Dictionary<int, Vector3> ModifiersPositions { get; } = [];
|
||||
|
||||
private static void SaveScrollPositions(GameSettingMenu gameSettingMenu)
|
||||
{
|
||||
if (_modifiersTab)
|
||||
{
|
||||
ModifiersPositions[SelectedModIdx] = _modifiersTab!.scrollBar.Inner.localPosition;
|
||||
}
|
||||
|
||||
RoleSettingMenuPatches.RolePositions[SelectedModIdx] = gameSettingMenu.RoleSettingsTab.scrollBar.Inner.localPosition;
|
||||
OptionsPositions[SelectedModIdx] = gameSettingMenu.GameSettingsTab.scrollBar.Inner.localPosition;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(GameSettingMenu.ChangeTab))]
|
||||
|
||||
public static void ChangeTabPrefix(GameSettingMenu __instance)
|
||||
{
|
||||
SaveScrollPositions(__instance);
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(GameSettingMenu.ChangeTab))]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static void ChangeTabPostfix(GameSettingMenu __instance, int tabNum, bool previewOnly)
|
||||
{
|
||||
if ((previewOnly && Controller.currentTouchType == Controller.TouchType.Joystick) || !previewOnly)
|
||||
@@ -90,7 +109,6 @@ internal static class GameSettingMenuPatches
|
||||
/// <param name="__instance">The GameSettingMenu instance.</param>
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(GameSettingMenu.Start))]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static void StartPrefix(GameSettingMenu __instance)
|
||||
{
|
||||
_roleBtnOgPos = __instance.RoleSettingsButton.transform.localPosition;
|
||||
@@ -123,11 +141,7 @@ internal static class GameSettingMenuPatches
|
||||
passiveButton.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
RoleSettingMenuPatches.Scrolls[SelectedModIdx] =
|
||||
__instance.RoleSettingsTab.scrollBar.Inner.localPosition;
|
||||
OptionScrolls[SelectedModIdx] =
|
||||
__instance.GameSettingsTab.scrollBar.Inner.localPosition;
|
||||
|
||||
SaveScrollPositions(__instance);
|
||||
SelectedModIdx += 1;
|
||||
if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins.Length)
|
||||
{
|
||||
@@ -146,11 +160,7 @@ internal static class GameSettingMenuPatches
|
||||
backButton.gameObject.GetComponent<PassiveButton>().OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
RoleSettingMenuPatches.Scrolls[SelectedModIdx] =
|
||||
__instance.RoleSettingsTab.scrollBar.Inner.localPosition;
|
||||
OptionScrolls[SelectedModIdx] =
|
||||
__instance.GameSettingsTab.scrollBar.Inner.localPosition;
|
||||
|
||||
SaveScrollPositions(__instance);
|
||||
SelectedModIdx -= 1;
|
||||
if (SelectedModIdx < 0)
|
||||
{
|
||||
@@ -436,29 +446,7 @@ internal static class GameSettingMenuPatches
|
||||
|
||||
if (_modifiersTab?.Children?.Count > 0)
|
||||
{
|
||||
CleanupSettings(_modifiersTab);
|
||||
}
|
||||
|
||||
void CleanupSettings(GameOptionsMenu gameOptMenu)
|
||||
{
|
||||
ClearOptions(gameOptMenu.Children);
|
||||
gameOptMenu.Children = null;
|
||||
|
||||
gameOptMenu.settingsContainer
|
||||
.GetComponentsInChildren<CategoryHeaderMasked>()
|
||||
.ToList()
|
||||
.ForEach(header => header.gameObject.DestroyImmediate());
|
||||
|
||||
gameOptMenu.Initialize();
|
||||
if (OptionScrolls.TryGetValue(SelectedModIdx, out var pos))
|
||||
{
|
||||
gameOptMenu.scrollBar.Inner.localPosition = pos;
|
||||
gameOptMenu.scrollBar.UpdateScrollBars();
|
||||
}
|
||||
else
|
||||
{
|
||||
gameOptMenu.scrollBar.ScrollToTop();
|
||||
}
|
||||
CleanupSettings(_modifiersTab, true);
|
||||
}
|
||||
|
||||
void CleanupRoleSettings(RolesSettingsMenu rolesMenu)
|
||||
@@ -485,17 +473,37 @@ internal static class GameSettingMenuPatches
|
||||
rolesMenu.AdvancedRolesSettings.gameObject.SetActive(false);
|
||||
rolesMenu.RoleChancesSettings.gameObject.SetActive(true);
|
||||
rolesMenu.SetQuotaTab();
|
||||
if (SelectedModIdx == 0)
|
||||
if (RoleSettingMenuPatches.RolePositions.TryGetValue(SelectedModIdx, out var pos))
|
||||
{
|
||||
if (RoleSettingMenuPatches.Scrolls.TryGetValue(0, out var pos))
|
||||
{
|
||||
rolesMenu.scrollBar.Inner.localPosition = pos;
|
||||
rolesMenu.scrollBar.UpdateScrollBars();
|
||||
}
|
||||
else
|
||||
{
|
||||
rolesMenu.scrollBar.ScrollToTop();
|
||||
}
|
||||
rolesMenu.scrollBar.Inner.localPosition = pos;
|
||||
rolesMenu.scrollBar.UpdateScrollBars();
|
||||
}
|
||||
else
|
||||
{
|
||||
rolesMenu.scrollBar.ScrollToTop();
|
||||
}
|
||||
}
|
||||
|
||||
void CleanupSettings(GameOptionsMenu gameOptMenu, bool modifiers = false)
|
||||
{
|
||||
ClearOptions(gameOptMenu.Children);
|
||||
gameOptMenu.Children = null;
|
||||
|
||||
gameOptMenu.settingsContainer
|
||||
.GetComponentsInChildren<CategoryHeaderMasked>()
|
||||
.ToList()
|
||||
.ForEach(header => header.gameObject.DestroyImmediate());
|
||||
|
||||
gameOptMenu.Initialize();
|
||||
var positions = modifiers ? ModifiersPositions : OptionsPositions;
|
||||
if (positions.TryGetValue(SelectedModIdx, out var pos))
|
||||
{
|
||||
gameOptMenu.scrollBar.Inner.localPosition = pos;
|
||||
gameOptMenu.scrollBar.UpdateScrollBars();
|
||||
}
|
||||
else
|
||||
{
|
||||
gameOptMenu.scrollBar.ScrollToTop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,24 +113,27 @@ public static class LobbyViewPanePatches
|
||||
menu.scrollBar.ScrollToTop();
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(LobbyViewSettingsPane.SetTab))]
|
||||
public static bool SetTabPatch(LobbyViewSettingsPane __instance)
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(LobbyViewSettingsPane.ChangeTab))]
|
||||
[HarmonyPatch(nameof(LobbyViewSettingsPane.RefreshTab))]
|
||||
// CHANGED BECAUSE OF INLINING
|
||||
public static void SetTabPatch(LobbyViewSettingsPane __instance)
|
||||
{
|
||||
if (__instance.currentTab != ModifiersTabName || SelectedMod == null)
|
||||
{
|
||||
ModifiersTabButton?.SelectButton(false);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
__instance.taskTabButton.SelectButton(false);
|
||||
__instance.rolesTabButton.SelectButton(false);
|
||||
|
||||
ModifiersTabButton?.SelectButton(true);
|
||||
|
||||
var filteredGroups = SelectedMod.InternalOptionGroups
|
||||
.Where(x => x.GroupVisible() && (x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true));
|
||||
.Where(x => x.GroupVisible() && (x.ShowInModifiersMenu || (x.OptionableType != null && x.OptionableType.IsAssignableTo(typeof(BaseModifier)))));
|
||||
|
||||
DrawOptions(__instance, filteredGroups);
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
@@ -142,8 +145,14 @@ public static class LobbyViewPanePatches
|
||||
return true;
|
||||
}
|
||||
|
||||
if (__instance.currentTab == ModifiersTabName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var filteredGroups = SelectedMod.InternalOptionGroups
|
||||
.Where(x => x.OptionableType == null && x.GroupVisible.Invoke());
|
||||
.Where(x => x is { ShowInModifiersMenu: false, OptionableType: null } && x.GroupVisible());
|
||||
|
||||
DrawOptions(__instance, filteredGroups);
|
||||
return false;
|
||||
}
|
||||
@@ -157,6 +166,11 @@ public static class LobbyViewPanePatches
|
||||
return true;
|
||||
}
|
||||
|
||||
if (__instance.currentTab == ModifiersTabName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DrawRolesTab(__instance);
|
||||
return false;
|
||||
}
|
||||
@@ -165,12 +179,15 @@ public static class LobbyViewPanePatches
|
||||
{
|
||||
var num = 1.44f;
|
||||
|
||||
foreach (var group in groups)
|
||||
var groupArray = groups.Where(x => x.GroupVisible() && x.Options.Any(y => y.Visible())).ToArray();
|
||||
|
||||
foreach (var group in groupArray)
|
||||
{
|
||||
var categoryHeaderMasked = Object.Instantiate(
|
||||
menu.categoryHeaderOrigin,
|
||||
menu.settingsContainer,
|
||||
true);
|
||||
|
||||
categoryHeaderMasked.SetHeader(StringNames.Name, 61);
|
||||
categoryHeaderMasked.Title.text = group.GroupName;
|
||||
categoryHeaderMasked.transform.localScale = Vector3.one;
|
||||
@@ -182,7 +199,7 @@ public static class LobbyViewPanePatches
|
||||
|
||||
foreach (var option in group.Options)
|
||||
{
|
||||
if (!option.Visible.Invoke())
|
||||
if (!option.Visible())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -191,6 +208,7 @@ public static class LobbyViewPanePatches
|
||||
menu.infoPanelOrigin,
|
||||
menu.settingsContainer,
|
||||
true);
|
||||
|
||||
viewSettingsInfoPanel.transform.localScale = Vector3.one;
|
||||
float num2;
|
||||
if (i % 2 == 0)
|
||||
@@ -229,7 +247,7 @@ public static class LobbyViewPanePatches
|
||||
num -= 0.85f;
|
||||
}
|
||||
|
||||
menu.scrollBar.CalculateAndSetYBounds(menu.settingsInfo.Count + 10, 2f, 6f, 0.85f);
|
||||
menu.scrollBar.SetYBoundsMax(-num - 2);
|
||||
}
|
||||
|
||||
private static void DrawRolesTab(LobbyViewSettingsPane instance)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using HarmonyLib;
|
||||
using Il2CppSystem;
|
||||
using MiraAPI.Roles;
|
||||
using Object = Il2CppSystem.Object;
|
||||
|
||||
namespace MiraAPI.Patches.Options;
|
||||
|
||||
@@ -23,15 +24,13 @@ public static class NotificationPopperPatch
|
||||
return true;
|
||||
}
|
||||
|
||||
var text = teamType == RoleTeamTypes.Crewmate
|
||||
? Palette.CrewmateSettingChangeText.ToTextColor()
|
||||
: Palette.ImpostorRed.ToTextColor();
|
||||
var textColor = customRole.OptionsMenuColor.ToTextColor();
|
||||
|
||||
var item = TranslationController.Instance.GetString(
|
||||
StringNames.LobbyChangeSettingNotificationRole,
|
||||
string.Concat(
|
||||
"<font=\"Barlow-Black SDF\" material=\"Barlow-Black Outline\">",
|
||||
text,
|
||||
textColor,
|
||||
TranslationController.Instance.GetString(key, Array.Empty<Object>()),
|
||||
"</color></font>"
|
||||
),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using HarmonyLib;
|
||||
using Il2CppSystem;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
|
||||
namespace MiraAPI.Patches.Options;
|
||||
@@ -7,6 +8,138 @@ namespace MiraAPI.Patches.Options;
|
||||
[HarmonyPatch]
|
||||
public static class OptionsPatches
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.IncreaseChance))]
|
||||
public static bool RoleIncreaseChancePrefix(RoleOptionSetting __instance)
|
||||
{
|
||||
if (!__instance.Role.IsCustomRole())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (__instance.roleChance == 0)
|
||||
{
|
||||
__instance.roleMaxCount = 1;
|
||||
}
|
||||
|
||||
__instance.roleChance += 10;
|
||||
if (__instance.roleChance > 100)
|
||||
{
|
||||
__instance.roleChance = 0;
|
||||
}
|
||||
|
||||
__instance.OnValueChanged.Invoke(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.DecreaseChance))]
|
||||
public static bool RoleDecreaseChancePrefix(RoleOptionSetting __instance)
|
||||
{
|
||||
if (!__instance.Role.IsCustomRole())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (__instance.roleChance == 0)
|
||||
{
|
||||
__instance.roleMaxCount = 1;
|
||||
}
|
||||
|
||||
__instance.roleChance -= 10;
|
||||
if (__instance.roleChance < 0)
|
||||
{
|
||||
__instance.roleChance = 100;
|
||||
}
|
||||
|
||||
__instance.OnValueChanged.Invoke(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.IncreaseCount))]
|
||||
public static bool RoleIncreaseCountPrefix(RoleOptionSetting __instance)
|
||||
{
|
||||
if (!__instance.Role.IsCustomRole())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (__instance.roleMaxCount == 0)
|
||||
{
|
||||
__instance.roleChance = 50;
|
||||
}
|
||||
|
||||
__instance.roleMaxCount += 1;
|
||||
if (__instance.roleMaxCount > __instance.role.MaxCount)
|
||||
{
|
||||
__instance.roleMaxCount = 0;
|
||||
}
|
||||
|
||||
__instance.OnValueChanged.Invoke(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.DecreaseCount))]
|
||||
public static bool RoleDecreaseCountPrefix(RoleOptionSetting __instance)
|
||||
{
|
||||
if (!__instance.Role.IsCustomRole())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (__instance.roleMaxCount == 0)
|
||||
{
|
||||
__instance.roleChance = 50;
|
||||
}
|
||||
|
||||
__instance.roleMaxCount -= 1;
|
||||
if (__instance.roleMaxCount < 0)
|
||||
{
|
||||
__instance.roleMaxCount = __instance.role.MaxCount;
|
||||
}
|
||||
|
||||
__instance.OnValueChanged.Invoke(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.AdjustButtonsActiveState))]
|
||||
[HarmonyPatch(typeof(StringOption), nameof(StringOption.AdjustButtonsActiveState))]
|
||||
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.AdjustChanceButtonsActiveState))]
|
||||
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.AdjustCountButtonsActiveState))]
|
||||
public static bool AdjustButtonsPrefix(OptionBehaviour __instance)
|
||||
{
|
||||
if (__instance.IsCustom())
|
||||
{
|
||||
if (__instance.TryCast<NumberOption>() is { } numberOption)
|
||||
{
|
||||
numberOption.MinusBtn.SetInteractable(true);
|
||||
numberOption.PlusBtn.SetInteractable(true);
|
||||
}
|
||||
if (__instance.TryCast<StringOption>() is { } stringOption)
|
||||
{
|
||||
stringOption.MinusBtn.SetInteractable(true);
|
||||
stringOption.PlusBtn.SetInteractable(true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (__instance.TryCast<RoleOptionSetting>() is { } roleOptionSetting && roleOptionSetting.Role.IsCustomRole())
|
||||
{
|
||||
roleOptionSetting.CountMinusBtn.SetInteractable(true);
|
||||
roleOptionSetting.CountPlusBtn.SetInteractable(true);
|
||||
roleOptionSetting.ChanceMinusBtn.SetInteractable(true);
|
||||
roleOptionSetting.ChancePlusBtn.SetInteractable(true);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(ToggleOption), nameof(ToggleOption.Initialize))]
|
||||
public static bool ToggleInit(ToggleOption __instance)
|
||||
@@ -16,15 +149,30 @@ public static class OptionsPatches
|
||||
return true;
|
||||
}
|
||||
|
||||
__instance.TitleText.text =
|
||||
TranslationController.Instance.GetString(__instance.Title, Array.Empty<Object>());
|
||||
__instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title, Array.Empty<Object>());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(ToggleOption), nameof(ToggleOption.UpdateValue))]
|
||||
[HarmonyPatch(typeof(ToggleOption), nameof(ToggleOption.Toggle))]
|
||||
// UpdateValue was inlined.
|
||||
public static bool ToggleUpdate(ToggleOption __instance)
|
||||
{
|
||||
if (!__instance.IsCustom())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
__instance.CheckMark.enabled = !__instance.CheckMark.enabled;
|
||||
__instance.OnValueChanged.Invoke(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(StringOption), nameof(StringOption.UpdateValue))]
|
||||
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.UpdateValue))]
|
||||
public static bool UpdateValuePrefix(OptionBehaviour __instance)
|
||||
{
|
||||
return !__instance.IsCustom();
|
||||
}
|
||||
@@ -38,17 +186,84 @@ public static class OptionsPatches
|
||||
return true;
|
||||
}
|
||||
|
||||
__instance.TitleText.text =
|
||||
TranslationController.Instance.GetString(__instance.Title, Array.Empty<Object>());
|
||||
|
||||
__instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title, Array.Empty<Object>());
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.UpdateValue))]
|
||||
public static bool NumberUpdate(NumberOption __instance)
|
||||
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.Increase))]
|
||||
public static bool NumberIncrease(NumberOption __instance)
|
||||
{
|
||||
return !__instance.IsCustom();
|
||||
if (!__instance.IsCustom())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
__instance.Value += __instance.Increment;
|
||||
if (__instance.Value > __instance.ValidRange.max)
|
||||
{
|
||||
__instance.Value = __instance.ValidRange.min;
|
||||
}
|
||||
__instance.OnValueChanged.Invoke(__instance);
|
||||
__instance.AdjustButtonsActiveState();
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.Decrease))]
|
||||
public static bool NumberDecrease(NumberOption __instance)
|
||||
{
|
||||
if (!__instance.IsCustom())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
__instance.Value -= __instance.Increment;
|
||||
if (__instance.Value < __instance.ValidRange.min)
|
||||
{
|
||||
__instance.Value = __instance.ValidRange.max;
|
||||
}
|
||||
__instance.OnValueChanged.Invoke(__instance);
|
||||
__instance.AdjustButtonsActiveState();
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(StringOption), nameof(StringOption.Increase))]
|
||||
public static bool StringOptionIncrease(StringOption __instance)
|
||||
{
|
||||
if (!__instance.IsCustom())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
__instance.Value += 1;
|
||||
if (__instance.Value >= __instance.Values.Length)
|
||||
{
|
||||
__instance.Value = 0;
|
||||
}
|
||||
__instance.OnValueChanged.Invoke(__instance);
|
||||
__instance.AdjustButtonsActiveState();
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(StringOption), nameof(StringOption.Decrease))]
|
||||
public static bool StringOptionDecrease(StringOption __instance)
|
||||
{
|
||||
if (!__instance.IsCustom())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
__instance.Value -= 1;
|
||||
if (__instance.Value < 0)
|
||||
{
|
||||
__instance.Value = __instance.Values.Length - 1;
|
||||
}
|
||||
__instance.OnValueChanged.Invoke(__instance);
|
||||
__instance.AdjustButtonsActiveState();
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
@@ -60,19 +275,9 @@ public static class OptionsPatches
|
||||
return true;
|
||||
}
|
||||
|
||||
__instance.TitleText.text =
|
||||
TranslationController.Instance.GetString(__instance.Title, Array.Empty<Object>());
|
||||
__instance.ValueText.text = TranslationController.Instance.GetString(
|
||||
__instance.Values[__instance.Value],
|
||||
Array.Empty<Object>());
|
||||
__instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title, Array.Empty<Object>());
|
||||
__instance.ValueText.text = TranslationController.Instance.GetString(__instance.Values[__instance.Value], Array.Empty<Object>());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(StringOption), nameof(StringOption.UpdateValue))]
|
||||
public static bool StringUpdate(StringOption __instance)
|
||||
{
|
||||
return !__instance.IsCustom();
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,21 @@ namespace MiraAPI.Patches.Options;
|
||||
[HarmonyPatch(typeof(RolesSettingsMenu))]
|
||||
public static class RoleSettingMenuPatches
|
||||
{
|
||||
public static Dictionary<int, Vector3> Scrolls { get; } = [];
|
||||
private static Dictionary<RoleOptionsGroup, bool> RoleGroupHidden { get; } = [];
|
||||
private static List<GameObject> Headers { get; } = [];
|
||||
private static List<RoleOptionSetting> RoleOptionSettings { get; } = [];
|
||||
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 float ScrollerNum { get; set; } = 0.522f;
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))]
|
||||
public static void SetQuotaTabPostfix(RolesSettingsMenu __instance)
|
||||
{
|
||||
__instance.scrollBar.SetScrollBounds(__instance);
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))]
|
||||
public static bool SetQuotaTabPatch(RolesSettingsMenu __instance)
|
||||
@@ -203,9 +211,10 @@ public static class RoleSettingMenuPatches
|
||||
headerBtn.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
if (RoleGroupHidden.TryGetValue(group, out var value))
|
||||
RolePositions[GameSettingMenuPatches.SelectedModIdx] = __instance.scrollBar.Inner.localPosition;
|
||||
if (RoleGroupHidden.TryGetValue(group, out var groupHidden))
|
||||
{
|
||||
RoleGroupHidden[group] = !value;
|
||||
RoleGroupHidden[group] = !groupHidden;
|
||||
}
|
||||
foreach (var header in Headers)
|
||||
{
|
||||
@@ -226,14 +235,21 @@ public static class RoleSettingMenuPatches
|
||||
ScrollerNum -= 0.4f;
|
||||
}
|
||||
}
|
||||
__instance.scrollBar.SetScrollBounds();
|
||||
|
||||
__instance.scrollBar.SetScrollBounds(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void SetScrollBounds(this Scroller scroller)
|
||||
private static void SetScrollBounds(this Scroller scroller, RolesSettingsMenu roleMenu)
|
||||
{
|
||||
scroller.CalculateAndSetYBounds(1 + 1.5f * Headers.Count + RoleOptionSettings.Count, 1f, 6f, 0.43f);
|
||||
if (Scrolls.TryGetValue(GameSettingMenuPatches.SelectedModIdx, out var scroll))
|
||||
if (GameSettingMenuPatches.SelectedModIdx == 0)
|
||||
{
|
||||
scroller.CalculateAndSetYBounds(roleMenu.roleChances.Count + 3, 1f, 6f, 0.43f);
|
||||
return;
|
||||
}
|
||||
|
||||
scroller.SetYBoundsMax(-ScrollerNum - 2);
|
||||
if (RolePositions.TryGetValue(GameSettingMenuPatches.SelectedModIdx, out var scroll))
|
||||
{
|
||||
scroller.Inner.localPosition = scroll;
|
||||
scroller.UpdateScrollBars();
|
||||
@@ -255,12 +271,12 @@ public static class RoleSettingMenuPatches
|
||||
[HarmonyPatch(nameof(RolesSettingsMenu.OpenChancesTab))]
|
||||
public static void OpenChancesTabPostfix(RolesSettingsMenu __instance)
|
||||
{
|
||||
if (GameSettingMenuPatches.SelectedModIdx == 0)
|
||||
if (!__instance.scrollBar)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
__instance.scrollBar.SetScrollBounds();
|
||||
__instance.scrollBar.SetScrollBounds(__instance);
|
||||
}
|
||||
|
||||
private static void ValueChanged(OptionBehaviour obj)
|
||||
@@ -319,8 +335,9 @@ public static class RoleSettingMenuPatches
|
||||
|
||||
// TODO: create sub groups under the role settings.
|
||||
var filteredOptions = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||
.Where(x=> x.OptionableType == role.GetType())
|
||||
.SelectMany(x=>x.Options)
|
||||
.Where(x => x.GroupVisible() && x.OptionableType == role.GetType())
|
||||
.SelectMany(x => x.Options)
|
||||
.Where(x => x.Visible())
|
||||
.ToList() ?? [];
|
||||
|
||||
foreach (var option in filteredOptions)
|
||||
@@ -353,8 +370,7 @@ public static class RoleSettingMenuPatches
|
||||
newOpt.Initialize();
|
||||
}
|
||||
|
||||
Scrolls[GameSettingMenuPatches.SelectedModIdx] = __instance.scrollBar.Inner.localPosition;
|
||||
__instance.scrollBar.CalculateAndSetYBounds(__instance.advancedSettingChildren.Count + 3, 1f, 6f, 0.45f);
|
||||
__instance.scrollBar.SetYBoundsMax(-num - 3);
|
||||
__instance.scrollBar.ScrollToTop();
|
||||
}
|
||||
|
||||
@@ -366,7 +382,7 @@ public static class RoleSettingMenuPatches
|
||||
return;
|
||||
}
|
||||
|
||||
Scrolls[GameSettingMenuPatches.SelectedModIdx] = __instance.scrollBar.Inner.localPosition;
|
||||
RolePositions[GameSettingMenuPatches.SelectedModIdx] = __instance.scrollBar.Inner.localPosition;
|
||||
|
||||
__instance.roleDescriptionText.text = customRole.RoleLongDescription;
|
||||
__instance.roleTitleText.text = TranslationController.Instance.GetString(
|
||||
@@ -478,6 +494,10 @@ public static class RoleSettingMenuPatches
|
||||
roleOptionSetting.labelSprite.color = customRole.OptionsMenuColor;
|
||||
roleOptionSetting.OnValueChanged = new Action<OptionBehaviour>(ValueChanged);
|
||||
roleOptionSetting.SetClickMask(__instance.ButtonClickMask);
|
||||
roleOptionSetting.ChanceMinusBtn.SetInteractable(true);
|
||||
roleOptionSetting.ChancePlusBtn.SetInteractable(true);
|
||||
roleOptionSetting.CountMinusBtn.SetInteractable(true);
|
||||
roleOptionSetting.CountPlusBtn.SetInteractable(true);
|
||||
__instance.roleChances.Add(roleOptionSetting);
|
||||
|
||||
roleOptionSetting.titleText.transform.localPosition = new Vector3(-0.5376f, -0.2923f, 0f);
|
||||
|
||||
@@ -5,7 +5,7 @@ using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using MiraAPI.Events.Vanilla.Player;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Voting;
|
||||
using Reactor.Utilities.Extensions;
|
||||
|
||||
@@ -41,7 +41,7 @@ internal static class PlayerControlPatches
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static void PlayerControlDiePostfix(PlayerControl __instance, DeathReason reason)
|
||||
{
|
||||
var deathEvent = new PlayerDeathEvent(__instance, reason);
|
||||
var deathEvent = new PlayerDeathEvent(__instance, reason, Helpers.GetBodyById(__instance.PlayerId));
|
||||
MiraEventManager.InvokeEvent(deathEvent);
|
||||
|
||||
var modifiersComponent = __instance.GetComponent<ModifierComponent>();
|
||||
@@ -68,6 +68,7 @@ internal static class PlayerControlPatches
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(PlayerControl.RpcMurderPlayer))]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
// Note: This method is partially inlined in 2025.5.20 but we already use custom murder RPCs.
|
||||
public static void PlayerControlMurderPrefix(PlayerControl __instance, PlayerControl target, ref bool didSucceed)
|
||||
{
|
||||
var beforeMurderEvent = new BeforeMurderEvent(__instance, target);
|
||||
@@ -81,11 +82,6 @@ internal static class PlayerControlPatches
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static void PlayerControlFixedUpdatePostfix(PlayerControl __instance)
|
||||
{
|
||||
if (__instance.Data?.Role is ICustomRole customRole)
|
||||
{
|
||||
customRole.PlayerControlFixedUpdate(__instance);
|
||||
}
|
||||
|
||||
if (!__instance.AmOwner)
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using MiraAPI.Events.Vanilla.Meeting;
|
||||
|
||||
@@ -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))]
|
||||
|
||||
@@ -7,7 +7,8 @@ namespace MiraAPI.Patches.Roles;
|
||||
/// <summary>
|
||||
/// Patches the haunt menu to show the actual role name rather then team.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(HauntMenuMinigame), nameof(HauntMenuMinigame.SetFilterText))]
|
||||
[HarmonyPatch(typeof(HauntMenuMinigame), nameof(HauntMenuMinigame.SetHauntTarget))]
|
||||
// SetFilterText was INLINED
|
||||
public static class HauntMenuMinigamePatch
|
||||
{
|
||||
public static void Postfix(HauntMenuMinigame __instance)
|
||||
@@ -24,4 +25,4 @@ public static class HauntMenuMinigamePatch
|
||||
__instance.FilterText.text = role.NiceName;
|
||||
__instance.FilterText.color = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,8 +62,6 @@ public static class HudManagerPatches
|
||||
|
||||
if (role is ICustomRole { Configuration.RoleHintType: RoleHintType.RoleTab } customRole)
|
||||
{
|
||||
customRole.HudUpdate(__instance);
|
||||
|
||||
if (_roleTab == null)
|
||||
{
|
||||
_roleTab = CustomRoleManager.CreateRoleTab(customRole);
|
||||
|
||||
@@ -14,6 +14,8 @@ public static class RoleManagerPatches
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(RoleManager.SetRole))]
|
||||
// NOTE: As of 2025.5.20 update, this is INLINED in ONE place: GameManager::ReviveEveryoneFreeplay(void).
|
||||
// Should not affect normal gameplay, but may cause issues in Freeplay.
|
||||
public static bool SetRolePatch(RoleManager __instance, PlayerControl targetPlayer, RoleTypes roleType)
|
||||
{
|
||||
if (!targetPlayer)
|
||||
@@ -63,7 +65,7 @@ public static class RoleManagerPatches
|
||||
[HarmonyPatch(nameof(RoleManager.SelectRoles))]
|
||||
public static void ModifierSelectionPatches(RoleManager __instance)
|
||||
{
|
||||
if (!AmongUsClient.Instance.AmHost)
|
||||
if (!AmongUsClient.Instance.AmHost || !ModifierManager.MiraAssignsModifiers)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using AmongUs.GameOptions;
|
||||
using HarmonyLib;
|
||||
using Il2CppSystem.Collections.Generic;
|
||||
using MiraAPI.Roles;
|
||||
using Reactor.Utilities;
|
||||
|
||||
@@ -12,7 +13,24 @@ namespace MiraAPI.Patches.Roles;
|
||||
public static class RoleOptionsCollectionPatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Set the role chance for custom Launchpad roles based on config
|
||||
/// This patch fixes GetNumPerGame being inlined (2025.5.20) in the original code.
|
||||
/// </summary>
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(RoleOptionsCollectionV09.AnyRolesEnabled))]
|
||||
public static bool AnyRolesEnabledPrefix(RoleOptionsCollectionV09 __instance, ref bool __result)
|
||||
{
|
||||
foreach (KeyValuePair<RoleTypes, RoleDataV09> keyValuePair in __instance.roles)
|
||||
{
|
||||
if (__instance.GetNumPerGame(keyValuePair.Key) > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the role chance for custom Launchpad roles based on config.
|
||||
/// </summary>
|
||||
/// <returns>Return false to skip original method, true to not.</returns>
|
||||
[HarmonyPrefix]
|
||||
@@ -42,7 +60,7 @@ 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]
|
||||
|
||||
@@ -4,7 +4,7 @@ using MiraAPI.Roles;
|
||||
namespace MiraAPI.Patches.Roles;
|
||||
|
||||
[HarmonyPatch(typeof(SabotageButton))]
|
||||
public class SabotageButtonPatch
|
||||
public static class SabotageButtonPatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Patches the Sabotage button to check if the player's custom role can use sabotage.
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace MiraAPI.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(ServerDropdown), nameof(ServerDropdown.FillServerOptions))]
|
||||
public static class ServerDropdownPatch
|
||||
{
|
||||
public static bool Prefix(ServerDropdown __instance)
|
||||
{
|
||||
var num = 0;
|
||||
__instance.background.size = new Vector2(8.4f, 4.8f);
|
||||
|
||||
foreach (var regionInfo in DestroyableSingleton<ServerManager>.Instance.AvailableRegions)
|
||||
{
|
||||
if (DestroyableSingleton<ServerManager>.Instance.CurrentRegion.Equals(regionInfo))
|
||||
{
|
||||
__instance.defaultButtonSelected = __instance.firstOption;
|
||||
__instance.firstOption.ChangeButtonText(
|
||||
DestroyableSingleton<TranslationController>.Instance.GetStringWithDefault(
|
||||
regionInfo.TranslateName,
|
||||
regionInfo.Name));
|
||||
}
|
||||
else
|
||||
{
|
||||
var region = regionInfo;
|
||||
var serverListButton = __instance.ButtonPool.Get<ServerListButton>();
|
||||
var x = num % 2 == 0 ? -2 : 2;
|
||||
var y = -0.55f * (num / 2);
|
||||
serverListButton.transform.localPosition = new Vector3(x, __instance.y_posButton + y, -1f);
|
||||
serverListButton.transform.localScale = Vector3.one;
|
||||
serverListButton.Text.text =
|
||||
DestroyableSingleton<TranslationController>.Instance.GetStringWithDefault(
|
||||
regionInfo.TranslateName,
|
||||
regionInfo.Name);
|
||||
serverListButton.Text.ForceMeshUpdate();
|
||||
serverListButton.Button.OnClick.RemoveAllListeners();
|
||||
serverListButton.Button.OnClick.AddListener((UnityAction)(() => { __instance.ChooseOption(region); }));
|
||||
__instance.controllerSelectable.Add(serverListButton.Button);
|
||||
__instance.background.transform.localPosition = new Vector3(
|
||||
0f,
|
||||
__instance.initialYPos + (-0.3f * (num / 2)),
|
||||
0f);
|
||||
__instance.background.size = new Vector2(__instance.background.size.x, 1.2f + (0.6f * (num / 2)));
|
||||
num++;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ using MiraAPI.Events.Vanilla.Meeting.Voting;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Voting;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.Patches.Voting;
|
||||
|
||||
@@ -206,6 +205,7 @@ internal static class MeetingHudPatches
|
||||
// TODO: figure out a way to do host-authorization since right now any player can send RpcCastVote
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(MeetingHud.CmdCastVote))]
|
||||
// Although this method is inlined in MeetingHud.Confirm, the next patch fixes that.
|
||||
public static bool CmdCastVoteOverridePatch(MeetingHud __instance, byte playerId, byte suspectIdx)
|
||||
{
|
||||
VotingUtils.RpcCastVote(PlayerControl.LocalPlayer, playerId, suspectIdx);
|
||||
|
||||
@@ -2,8 +2,19 @@
|
||||
|
||||
namespace MiraAPI.PluginLoading;
|
||||
|
||||
/// <summary>
|
||||
/// The interface that all Mira plugins must implement.
|
||||
/// </summary>
|
||||
public interface IMiraPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name to display on the options menu.
|
||||
/// </summary>
|
||||
string OptionsTitleText { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the BepInEx configuration file for the plugin.
|
||||
/// </summary>
|
||||
/// <returns>The BepInEx configuration file for the plugin.</returns>
|
||||
public ConfigFile GetConfigFile();
|
||||
}
|
||||
@@ -55,21 +55,29 @@ public sealed class MiraPluginManager
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public))
|
||||
foreach (var method in type.GetMethods())
|
||||
{
|
||||
var eventAttribute = method.GetCustomAttribute<RegisterEventAttribute>();
|
||||
if (eventAttribute != null)
|
||||
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);
|
||||
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))
|
||||
@@ -82,13 +90,13 @@ public sealed class MiraPluginManager
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterRoleAttribute(type, info, out var role))
|
||||
if (RegisterRole(type, info, out var role))
|
||||
{
|
||||
roles.Add(role);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterButtonAttribute(type, info))
|
||||
if (RegisterButton(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -164,6 +172,12 @@ 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);
|
||||
@@ -179,16 +193,21 @@ 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}");
|
||||
Logger<MiraApiPlugin>.Error($"Failed to register options for {type.Name}: {e.ToString()}");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool RegisterRoleAttribute(Type type, MiraPluginInfo pluginInfo, [NotNullWhen(true)] out Type? role)
|
||||
private static bool RegisterRole(Type type, MiraPluginInfo pluginInfo, [NotNullWhen(true)] out Type? role)
|
||||
{
|
||||
role = null;
|
||||
try
|
||||
@@ -244,6 +263,11 @@ 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)
|
||||
{
|
||||
@@ -264,7 +288,7 @@ public sealed class MiraPluginManager
|
||||
}
|
||||
}
|
||||
|
||||
private static bool RegisterButtonAttribute(Type type, MiraPluginInfo pluginInfo)
|
||||
private static bool RegisterButton(Type type, MiraPluginInfo pluginInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -69,7 +69,8 @@ public static class PresetManager
|
||||
|
||||
foreach (var file in Directory.GetFiles(pluginPresetPath, "*.cfg"))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Info($"Loading preset file {file}");
|
||||
var fileName = Path.GetFileName(file);
|
||||
Logger<MiraApiPlugin>.Info($"Loading preset file {fileName}");
|
||||
var presetName = Path.GetFileNameWithoutExtension(file);
|
||||
var presetConfig = new ConfigFile(file, false)
|
||||
{
|
||||
@@ -90,6 +91,6 @@ public static class PresetManager
|
||||
|
||||
plugin.InternalPresets.Add(new OptionPreset(presetName, plugin, presetConfig));
|
||||
}
|
||||
plugin.Presets = [.. plugin.InternalPresets];
|
||||
plugin.Presets = [..plugin.InternalPresets];
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Fields;
|
||||
using MiraAPI.Modifiers.ModifierDisplay;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities.Attributes;
|
||||
@@ -9,16 +7,21 @@ using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
[RegisterInIl2Cpp]
|
||||
public class SavePresetPopup(nint ptr) : Minigame(ptr)
|
||||
#pragma warning disable S3903
|
||||
#pragma warning disable CA1050
|
||||
public class SavePresetPopup(nint cppPtr) : Minigame(cppPtr)
|
||||
#pragma warning restore CA1050
|
||||
#pragma warning restore S3903
|
||||
{
|
||||
private PassiveButton saveButton;
|
||||
private PassiveButton closeButton;
|
||||
#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.
|
||||
private TextMeshPro textBoxText;
|
||||
private TextBoxTMP textBox;
|
||||
|
||||
private Action<string> onSave;
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
|
||||
// Cleanup holder object
|
||||
public void OnDestroy()
|
||||
@@ -28,13 +31,14 @@ public class SavePresetPopup(nint ptr) : Minigame(ptr)
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
var textboxHolder = transform.GetChild(1).GetChild(1);
|
||||
saveButton = transform.FindChild("SaveButton").GetComponent<PassiveButton>();
|
||||
closeButton = transform.FindChild("CloseButton").GetComponent<PassiveButton>();
|
||||
var saveButton = transform.FindChild("SaveButton").GetComponent<PassiveButton>();
|
||||
var closeButton = transform.FindChild("CloseButton").GetComponent<PassiveButton>();
|
||||
textBox = textboxHolder.GetChild(0).GetComponent<TextBoxTMP>();
|
||||
textBoxText = textBox.transform.GetChild(0).GetComponent<TextMeshPro>();
|
||||
|
||||
@@ -64,7 +68,11 @@ public class SavePresetPopup(nint ptr) : Minigame(ptr)
|
||||
|
||||
foreach (var tmp in gameObject.GetComponentsInChildren<TextMeshPro>())
|
||||
{
|
||||
if (tmp.gameObject.transform.parent.name == "Textbox") continue;
|
||||
if (tmp.gameObject.transform.parent.name == "Textbox")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var text = tmp.text;
|
||||
tmp.text = $"<font=\"LiberationSans SDF\" material=\"LiberationSans SDF RadialMenu Material\">{text}</font>";
|
||||
}
|
||||
@@ -78,9 +86,9 @@ public class SavePresetPopup(nint ptr) : Minigame(ptr)
|
||||
transform =
|
||||
{
|
||||
parent = Camera.main!.transform,
|
||||
localPosition = new Vector3(0, -1f, -500),
|
||||
},
|
||||
};
|
||||
holder.transform.localPosition = new Vector3(0, -1f, -500);
|
||||
|
||||
var gameObject = Instantiate(MiraAssets.PresetSavePopup.LoadAsset(), holder.transform);
|
||||
var popup = gameObject.GetComponent<SavePresetPopup>();
|
||||
@@ -89,3 +97,5 @@ public class SavePresetPopup(nint ptr) : Minigame(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
File renamed without changes.
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.Roles;
|
||||
|
||||
@@ -8,12 +9,11 @@ namespace MiraAPI.Roles;
|
||||
/// <typeparam name="T">The role you are trying to access.</typeparam>
|
||||
public static class CustomRoleSingleton<T> where T : ICustomRole
|
||||
{
|
||||
private static ICustomRole? _instance;
|
||||
private static T? _instance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance of the role.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1000
|
||||
public static ICustomRole Instance => _instance ??= CustomRoleManager.CustomRoles.Values.OfType<T>().Single();
|
||||
#pragma warning restore CA1000
|
||||
[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();
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using AmongUs.GameOptions;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Roles;
|
||||
@@ -42,7 +43,7 @@ public static class CustomRoleUtils
|
||||
public static StringBuilder CreateForRole(ICustomRole role)
|
||||
{
|
||||
var taskStringBuilder = new StringBuilder();
|
||||
taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleColor.ToTextColor()}You are a <b>{role.RoleName}.</b></color>");
|
||||
taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleColor.ToTextColor()}Your role is <b>{role.RoleName}.</b></color>");
|
||||
taskStringBuilder.Append("<size=70%>");
|
||||
taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleLongDescription}");
|
||||
return taskStringBuilder;
|
||||
@@ -56,7 +57,21 @@ public static class CustomRoleUtils
|
||||
public static LoadableAsset<AudioClip>? GetIntroSound(RoleTypes roleType)
|
||||
{
|
||||
var role = RoleManager.Instance.AllRoles.FirstOrDefault(role => role.Role == roleType);
|
||||
if (role is ICustomRole customRole) return customRole.Configuration.IntroSound;
|
||||
if (role is ICustomRole customRole)
|
||||
{
|
||||
return customRole.Configuration.IntroSound;
|
||||
}
|
||||
|
||||
return new PreloadedAsset<AudioClip>(role!.IntroSound);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a role is a custom role or not.
|
||||
/// </summary>
|
||||
/// <param name="role">The RoleBehaviour to check.</param>
|
||||
/// <returns>True if the role is a custom role, false otherwise.</returns>
|
||||
public static bool IsCustomRole(this RoleBehaviour role)
|
||||
{
|
||||
return CustomRoleManager.CustomRoles.ContainsKey((ushort)role.Role);
|
||||
}
|
||||
}
|
||||
@@ -73,14 +73,6 @@ public interface ICustomRole : IOptionable
|
||||
/// </summary>
|
||||
MiraPluginInfo ParentMod => CustomRoleManager.FindParentMod(this);
|
||||
|
||||
/// <summary>
|
||||
/// This method runs on the PlayerControl.FixedUpdate method for ALL players with this role.
|
||||
/// </summary>
|
||||
/// <param name="playerControl">The PlayerControl that has this role.</param>
|
||||
void PlayerControlFixedUpdate(PlayerControl playerControl)
|
||||
{
|
||||
}
|
||||
|
||||
internal ConfigDefinition NumConfigDefinition => new("Roles", $"Num {GetType().FullName}");
|
||||
internal ConfigDefinition ChanceConfigDefinition => new("Roles", $"Chance {GetType().FullName}");
|
||||
|
||||
@@ -222,14 +214,6 @@ public interface ICustomRole : IOptionable
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method runs on the HudManager.Update method ONLY when the LOCAL player has this role.
|
||||
/// </summary>
|
||||
/// <param name="hudManager">Reference to HudManager instance.</param>
|
||||
void HudUpdate(HudManager hudManager)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a custom ejection message for the role. Return null to use the default message.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
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,
|
||||
}
|
||||
+16
-1
@@ -2,9 +2,18 @@
|
||||
|
||||
namespace MiraAPI.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Utility class to get the ID of a role.
|
||||
/// </summary>
|
||||
public static class RoleId
|
||||
{
|
||||
public static ushort Get<T>() where T : RoleBehaviour
|
||||
/// <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
|
||||
{
|
||||
if (!CustomRoleManager.RoleIds.TryGetValue(typeof(T), out var roleId))
|
||||
{
|
||||
@@ -14,6 +23,12 @@ 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))
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace MiraAPI.Utilities.Assets;
|
||||
/// A utility class for loading assets from embedded resources.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the embedded resource.</param>
|
||||
/// <param name="pixelsPerUnit">The pixels per unit for the loaded sprite.</param>
|
||||
public class LoadableResourceAsset(string path, float pixelsPerUnit=100f) : LoadableAsset<Sprite>
|
||||
{
|
||||
private readonly Assembly _assembly = Assembly.GetCallingAssembly();
|
||||
@@ -14,9 +15,9 @@ public class LoadableResourceAsset(string path, float pixelsPerUnit=100f) : Load
|
||||
/// <inheritdoc />
|
||||
public override Sprite LoadAsset()
|
||||
{
|
||||
if (LoadedAsset != null)
|
||||
if (LoadedAsset)
|
||||
{
|
||||
return LoadedAsset;
|
||||
return LoadedAsset!;
|
||||
}
|
||||
|
||||
return LoadedAsset = SpriteTools.LoadSpriteFromPath(path, _assembly, pixelsPerUnit);
|
||||
|
||||
@@ -4,7 +4,7 @@ using UnityEngine;
|
||||
namespace MiraAPI.Utilities.Assets;
|
||||
|
||||
/// <summary>
|
||||
/// Various assets used throughout Mira API.
|
||||
/// A static class that contains various assets used in the Mira API.
|
||||
/// </summary>
|
||||
public static class MiraAssets
|
||||
{
|
||||
@@ -25,21 +25,40 @@ public static class MiraAssets
|
||||
RoundedBox = new LoadableAssetWrapper<Sprite>(boxSprite);
|
||||
}
|
||||
|
||||
internal static readonly AssetBundle MiraAssetBundle = AssetBundleManager.Load("mirabundle");
|
||||
internal static readonly LoadableAsset<GameObject> ModifierDisplay = new LoadableBundleAsset<GameObject>("Modifiers", MiraAssetBundle);
|
||||
internal static readonly LoadableAsset<GameObject> PresetSavePopup = new LoadableBundleAsset<GameObject>("PresetSavePopup", MiraAssetBundle);
|
||||
internal static readonly LoadableAsset<Sprite> RefreshIcon = new LoadableBundleAsset<Sprite>("refresh", MiraAssetBundle);
|
||||
internal static readonly LoadableAsset<Sprite> FolderIcon = new LoadableBundleAsset<Sprite>("freePlay_folderTaskRoom", MiraAssetBundle);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the accepted teal color used for various selections in the UI.
|
||||
/// Gets the color used for teal highlighting in UI.
|
||||
/// </summary>
|
||||
public static Color32 AcceptedTeal { get; } = new(43, 233, 198, 255);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the empty sprite.
|
||||
/// Gets the Mira API asset bundle.
|
||||
/// </summary>
|
||||
public static LoadableAsset<Sprite> Empty { get; } = new LoadableResourceAsset("MiraAPI.Resources.Empty.png");
|
||||
public static AssetBundle MiraAssetBundle { get; } = AssetBundleManager.Load("mirabundle");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ModifierDisplay prefab.
|
||||
/// </summary>
|
||||
public static LoadableAsset<GameObject> ModifierDisplay { get; } = new LoadableBundleAsset<GameObject>("Modifiers", MiraAssetBundle);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Popup prefab for saving presets.
|
||||
/// </summary>
|
||||
public static LoadableAsset<GameObject> PresetSavePopup { get; } = new LoadableBundleAsset<GameObject>("PresetSavePopup", MiraAssetBundle);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Refresh Icon sprite.
|
||||
/// </summary>
|
||||
public static LoadableAsset<Sprite> RefreshIcon { get; } = new LoadableBundleAsset<Sprite>("refresh", MiraAssetBundle);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Folder Icon sprite.
|
||||
/// </summary>
|
||||
public static LoadableAsset<Sprite> FolderIcon { get; } = new LoadableBundleAsset<Sprite>("freePlay_folderTaskRoom", MiraAssetBundle);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the empty sprite asset.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset Empty { get; } = new("MiraAPI.Resources.Empty.png");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RoundedBox sprite, which is a rounded rectangle used for UI elements.
|
||||
@@ -47,32 +66,32 @@ public static class MiraAssets
|
||||
public static LoadableAsset<Sprite> RoundedBox { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NextButton sprite.
|
||||
/// Gets the Next Button sprite.
|
||||
/// </summary>
|
||||
public static LoadableAsset<Sprite> NextButton { get; } = new LoadableResourceAsset("MiraAPI.Resources.NextButton.png");
|
||||
public static LoadableResourceAsset NextButton { get; } = new("MiraAPI.Resources.NextButton.png");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the active variant of the NextButton sprite.
|
||||
/// Gets the highlighted Next Button sprite.
|
||||
/// </summary>
|
||||
public static LoadableAsset<Sprite> NextButtonActive { get; } = new LoadableResourceAsset("MiraAPI.Resources.NextButtonActive.png");
|
||||
public static LoadableResourceAsset NextButtonActive { get; } = new("MiraAPI.Resources.NextButtonActive.png");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Cog icon sprite.
|
||||
/// Gets the Cog icon used in Role Settings Menu.
|
||||
/// </summary>
|
||||
public static LoadableAsset<Sprite> Cog { get; } = new LoadableResourceAsset("MiraAPI.Resources.Cog.png");
|
||||
public static LoadableResourceAsset Cog { get; } = new("MiraAPI.Resources.Cog.png");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Checkmark icon sprite.
|
||||
/// Gets the Checkmark Box used in the Settings Menu.
|
||||
/// </summary>
|
||||
public static LoadableAsset<Sprite> Checkmark { get; } = new LoadableResourceAsset("MiraAPI.Resources.Checkmark.png");
|
||||
public static LoadableResourceAsset CheckmarkBox { get; } = new("MiraAPI.Resources.CheckMarkBox.png");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Checkmark Box sprite.
|
||||
/// Gets the Checkmark used in the Settings Menu.
|
||||
/// </summary>
|
||||
public static LoadableAsset<Sprite> CheckmarkBox { get; } = new LoadableResourceAsset("MiraAPI.Resources.CheckMarkBox.png");
|
||||
public static LoadableResourceAsset Checkmark { get; } = new("MiraAPI.Resources.Checkmark.png");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the custom category header sprite used in the options menu.
|
||||
/// Gets the white CategoryHeader used in the Settings Menu.
|
||||
/// </summary>
|
||||
public static LoadableAsset<Sprite> CategoryHeader { get; } = new LoadableResourceAsset("MiraAPI.Resources.CategoryHeader.png");
|
||||
public static LoadableResourceAsset CategoryHeader { get; } = new("MiraAPI.Resources.CategoryHeader.png");
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -83,7 +84,52 @@ public static class Extensions
|
||||
analytics.MinigameClosed(data, taskType, realtimeSinceStartup, isComplete);
|
||||
self.StartCoroutine(self.CoDestroySelf());
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the cooldown of a button with a formatted string.
|
||||
/// </summary>
|
||||
/// <param name="button">The ActionButton to set the cooldown for.</param>
|
||||
/// <param name="timer">The current timer value.</param>
|
||||
/// <param name="maxTimer">The maximum timer value.</param>
|
||||
/// <param name="format">The format string to use for the timer text.</param>
|
||||
public static void SetCooldownFormat(this ActionButton button, float timer, float maxTimer, string format="0")
|
||||
{
|
||||
var num = Mathf.Clamp(timer / maxTimer, 0f, 1f);
|
||||
button.isCoolingDown = num > 0f;
|
||||
button.SetCooldownFill(num);
|
||||
if (button.isCoolingDown)
|
||||
{
|
||||
button.cooldownTimerText.text = timer.ToString(format, NumberFormatInfo.InvariantInfo);
|
||||
button.cooldownTimerText.gameObject.SetActive(true);
|
||||
return;
|
||||
}
|
||||
button.cooldownTimerText.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the fill-up variant of a cooldown button with a formatted string.
|
||||
/// </summary>
|
||||
/// <param name="button">The ActionButton to set the cooldown for.</param>
|
||||
/// <param name="timer">The current timer value.</param>
|
||||
/// <param name="maxTimer">The maximum timer value.</param>
|
||||
/// <param name="format">The format string to use for the timer text.</param>
|
||||
public static void SetFillUpFormat(this ActionButton button, float timer, float maxTimer, string format="0")
|
||||
{
|
||||
var num = Mathf.Clamp(timer / maxTimer, 0f, 1f);
|
||||
button.isCoolingDown = num > 0f;
|
||||
if (button.isCoolingDown && timer < 3f)
|
||||
{
|
||||
button.graphic.transform.localPosition = button.position + (Vector3)UnityEngine.Random.insideUnitCircle * 0.05f;
|
||||
button.cooldownTimerText.text = timer.ToString(format, NumberFormatInfo.InvariantInfo);
|
||||
button.cooldownTimerText.gameObject.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
button.graphic.transform.localPosition = button.position;
|
||||
}
|
||||
button.SetCooldownFill(num);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a PlayerControl from their PlayerVoteArea in a meeting.
|
||||
/// </summary>
|
||||
@@ -145,6 +191,12 @@ public static class Extensions
|
||||
return component;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum value from a dictionary of integers, returning the key and value.
|
||||
/// </summary>
|
||||
/// <param name="self">The dictionary to search.</param>
|
||||
/// <param name="tie">Whether there is a tie for the maximum value.</param>
|
||||
/// <returns>The key-value pair with the maximum value.</returns>
|
||||
public static KeyValuePair<byte, int> MaxPair(this Dictionary<byte, int> self, out bool tie)
|
||||
{
|
||||
tie = true;
|
||||
@@ -164,6 +216,12 @@ public static class Extensions
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum value from a dictionary of floats, returning the key and value.
|
||||
/// </summary>
|
||||
/// <param name="self">The dictionary to search.</param>
|
||||
/// <param name="tie">Whether there is a tie for the maximum value.</param>
|
||||
/// <returns>The key-value pair with the maximum value.</returns>
|
||||
public static KeyValuePair<byte, float> MaxPair(this Dictionary<byte, float> self, out bool tie)
|
||||
{
|
||||
tie = true;
|
||||
@@ -385,18 +443,6 @@ public static class Extensions
|
||||
return new Color(color.r - amount, color.g - amount, color.b - amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an alternate color based on the original color.
|
||||
/// </summary>
|
||||
/// <param name="color">The original color.</param>
|
||||
/// <param name="amount">The amount to darken or lighten the original color by between 0.0 and 1.0.</param>
|
||||
/// <returns>An alternate color that has been darkened or lightened.</returns>
|
||||
[Obsolete("Use FindAlternateColor for WACG compliance.")]
|
||||
public static Color GetAlternateColor(this Color color, float amount = 0.45f)
|
||||
{
|
||||
return color.IsColorDark() ? LightenColor(color, amount) : DarkenColor(color, amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightens a color by a specified amount.
|
||||
/// </summary>
|
||||
@@ -408,16 +454,6 @@ public static class Extensions
|
||||
return new Color(color.r + amount, color.g + amount, color.b + amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a color is dark.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to check.</param>
|
||||
/// <returns>True if the color is dark, false otherwise.</returns>
|
||||
public static bool IsColorDark(this Color color)
|
||||
{
|
||||
return color.r < 0.5f && color is { g: < 0.5f, b: < 0.5f };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the nearest dead body to a player.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,8 +1,27 @@
|
||||
namespace MiraAPI.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Suffixes used for options numbers.
|
||||
/// </summary>
|
||||
public enum MiraNumberSuffixes
|
||||
{
|
||||
/// <summary>
|
||||
/// No suffix.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Multiplier suffix (x).
|
||||
/// </summary>
|
||||
Multiplier,
|
||||
|
||||
/// <summary>
|
||||
/// Seconds suffix (s).
|
||||
/// </summary>
|
||||
Seconds,
|
||||
|
||||
/// <summary>
|
||||
/// Percentage suffix (%).
|
||||
/// </summary>
|
||||
Percent,
|
||||
}
|
||||
@@ -71,7 +71,7 @@ public static class VotingUtils
|
||||
/// <param name="source">The player who is sending the RPC. Should be the host.</param>
|
||||
/// <param name="voterId">The player who voted.</param>
|
||||
/// <param name="votedFor">The player who the voter voted for.</param>
|
||||
[MethodRpc((uint)MiraRpc.RemoveVote)]
|
||||
[MethodRpc((uint)MiraRpc.RemoveVote, SendImmediately = true)]
|
||||
public static void RpcRemoveVote(PlayerControl source, byte voterId, byte votedFor)
|
||||
{
|
||||
if (!source.IsHost()) return;
|
||||
@@ -107,7 +107,7 @@ public static class VotingUtils
|
||||
/// <param name="source">The player who sent this RPC.</param>
|
||||
/// <param name="srcPlayerId">The id of the player who casted the vote.</param>
|
||||
/// <param name="suspectPlayerId">The voted player's id.</param>
|
||||
[MethodRpc((uint)MiraRpc.CastVote)]
|
||||
[MethodRpc((uint)MiraRpc.CastVote, SendImmediately = true)]
|
||||
public static void RpcCastVote(PlayerControl source, byte srcPlayerId, byte suspectPlayerId)
|
||||
{
|
||||
CustomCastVote(srcPlayerId, suspectPlayerId);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
[](https://discord.gg/all-of-us-launchpad-794950428756410429)
|
||||
[](https://discord.gg/AEfHJGwggC)
|
||||
|
||||
> This mod is not affiliated with Among Us or Innersloth LLC, and the content contained therein is not endorsed or otherwise sponsored by Innersloth LLC. Portions of the materials contained herein are property of Innersloth LLC. © Innersloth LLC.
|
||||
|
||||
# Mira API
|
||||
|
||||
@@ -8,27 +10,32 @@ A thorough, but simple, Among Us modding API and utility library that covers:
|
||||
- Modifiers
|
||||
- Buttons
|
||||
- Custom Colors
|
||||
- Events
|
||||
- Voting
|
||||
- Assets
|
||||
- Compatibility
|
||||
- ~~Game Modes~~ (coming soon)
|
||||
|
||||
Mira API strives to be simple and easy to use, while also using as many base game elements as possible. The result is a less intrusive, better modding API that covers general use cases.
|
||||
Mira API strives to be comprehensive, yet straightforward, while also using as many base game elements as possible.
|
||||
The result is a less intrusive, better modding API that covers general use cases.
|
||||
|
||||
**Join the [Discord](https://discord.gg/all-of-us-launchpad-794950428756410429) for support and to stay updated on the latest releases**
|
||||
**Join the [Discord](https://discord.gg/FYYqJU2bvp) for support and to stay updated on the latest releases**
|
||||
|
||||
# Usage
|
||||
|
||||
To start using Mira API, you need to:
|
||||
1. Add a reference to Mira API either through a [DLL](https://github.com/All-Of-Us-Mods/MiraAPI/releases), project reference, or [NuGet package](https://www.nuget.org/packages/AllOfUs.MiraAPI).
|
||||
2. Add a BepInDependency on your plugin class like this: `[BepInDependency(MiraApiPlugin.Id)]`
|
||||
3. Implement the IMiraPlugin interface on your plugin class.
|
||||
3. Implement the IMiraPlugin interface in your plugin class.
|
||||
|
||||
Mira API also depends on [Reactor](https://github.com/NuclearPowered/Reactor) in order to function properly! Do not forget to include it as a reference and `BepInDependency`!
|
||||
Mira API also depends on [Reactor](https://github.com/NuclearPowered/Reactor) to function properly!
|
||||
Remember to include it as a reference and `BepInDependency`!
|
||||
|
||||
For a full example, see [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/ExamplePlugin.cs).
|
||||
|
||||
## Recommended Project Structure
|
||||
It is highly recommended to follow this project structure when using Mira API in order to keep your code clean and organized. You can also view the Example Mod in this repository for some guidance.
|
||||
It is highly recommended to follow this project structure when using Mira API to keep your code clean and organized.
|
||||
You can also view the Example Mod in this repository for some guidance.
|
||||
```
|
||||
MyMiraMod/
|
||||
├── Buttons/
|
||||
@@ -54,28 +61,36 @@ MyMiraMod/
|
||||
```
|
||||
|
||||
## Roles
|
||||
Roles are very simple in Mira API. There are 3 things you need to do to create a custom role:
|
||||
1. Create a class that inherits from a base game role (like `CrewmateRole`, `ImpostorRole`, etc)
|
||||
Roles are straightforward in Mira API. There are two things you need to do to create a custom role:
|
||||
1. Create a class that inherits from a base game role (like `CrewmateRole`, `ImpostorRole`, etc.)
|
||||
2. Implement the `ICustomRole` interface from Mira API.
|
||||
3. Add the `[RegisterCustomRole]` attribute to the class.
|
||||
|
||||
**Disclaimer: Make sure your plugin class has the following attribute `[ReactorModFlags(ModFlags.RequireOnAllClients)]` or else your roles will not register correctly.**
|
||||
|
||||
Note: For step 1, if you are making neutral roles, choose either `CrewmateRole` or `ImpostorRole` as the base depending on if it can kill or not!
|
||||
Note: For step 1, if you are making neutral roles,
|
||||
you can choose either `CrewmateRole` or `ImpostorRole`,
|
||||
or even `RoleBehaviour` if you are okay with starting from scratch.
|
||||
|
||||
Mira API handles everything else, from adding the proper options to the settings menu, to managing the role assignment at the start of the game. There are no extra steps on the developer's part.
|
||||
Mira API handles everything else, from adding the proper options to the settings menu,
|
||||
to managing the role assignment at the start of the game.
|
||||
There are no extra steps on the developer's part.
|
||||
|
||||
See [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Roles/CustomRole.cs) for a code example.
|
||||
|
||||
## Modifiers
|
||||
Mira API uses a different definition of 'modifiers' than other Among Us mods. For example, in Town Of Us, a modifier is an extra "ability" that is applied on top of the base role. However, in Mira API, modifiers are very flexible. A modifier is anything that "modifiers" a player's abilities or interactions.
|
||||
Mira API uses a different definition of 'modifiers' than other Among Us mods.
|
||||
For example, in Town Of Us, a modifier is an extra "ability" that is applied on top of the base role.
|
||||
However, in Mira API, modifiers are very flexible.
|
||||
A modifier is anything that "modifiers" a player's abilities or interactions.
|
||||
|
||||
Mira provides 3 classes for working with modifiers:
|
||||
Mira provides three classes for working with modifiers:
|
||||
- `BaseModifier`: The base for every modifier. You MUST add and remove this modifier from a player manually!
|
||||
- `TimedModifier`: A modifier that has a time limit. This modifier has to be added manually, but will automatically remove itself after the time limit
|
||||
- `TimedModifier`: A modifier that has a time limit. This modifier has to be added manually, but will automatically remove itself after the time limit.
|
||||
- `GameModifier`: This works like the typical TOU modifier, where it is automatically applied at the beginning of the game, then removed at the end.
|
||||
|
||||
Modifiers provide various overridable functions and properties for custom behaviour. They can also be used for "tagging" a player. You can check if a player has a modifier through the extension method `HasModifier` on a `PlayerControl` object.
|
||||
Modifiers provide various overridable functions and properties for custom behavior.
|
||||
They can also be used for "tagging" a player.
|
||||
You can check if a player has a modifier through the extension method `HasModifier` on a `PlayerControl` object.
|
||||
|
||||
To start using a modifier, pick one of the base classes above and create a class that inherits from it. Implement the properties and methods you would like, then add the `[RegisterModifier]` attribute to the class.
|
||||
|
||||
@@ -83,9 +98,17 @@ An example Game modifier can be found [here](https://github.com/All-Of-Us-Mods/M
|
||||
An example Timer modifier can be found [here](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Modifiers/ModifierTimerExample.cs).
|
||||
|
||||
## Options
|
||||
Options are also very simple in Mira API. Mira API handles all the hard work behind the scenes, so developers only have to follow a few steps to create their custom options. The Options API is split up into Groups and Options. Every Option needs to be in a Group.
|
||||
Options are also simple in Mira API.
|
||||
Mira API handles all the hard work behind the scenes,
|
||||
so developers only have to follow a few steps to create their custom options.
|
||||
The Options API is split up into Groups and Options.
|
||||
Every Option needs to be in a Group.
|
||||
|
||||
To create a group, you need to create a class that inherits from the `AbstractOptionGroup` abstract class. Groups contain 4 properties, `GroupName`, `GroupColor`, `GroupVisible`, and `AdvancedRole`. Only the `GroupName` is required.
|
||||
To create a group, you need to create a class that inherits from the `AbstractOptionGroup` abstract class.
|
||||
Groups contain some properties for developers to control,
|
||||
including `GroupName`, `GroupColor`, `GroupVisible`, and `OptionableType`.
|
||||
The full list of properties can be found in the `AbstractOptionGroup` class.
|
||||
Only the `GroupName` is required.
|
||||
|
||||
Here is an example of a group class:
|
||||
```csharp
|
||||
@@ -141,46 +164,39 @@ And this is an example of a ModdedOption property:
|
||||
public ModdedToggleOption YeezusAbility { get; } = new ModdedToggleOption("Yeezus Ability", false);
|
||||
```
|
||||
|
||||
Here is a full list of ModdedOption classes you can use:
|
||||
There are currently three types of ModdedOptions you can create:
|
||||
- `ModdedEnumOption`
|
||||
- `ModdedNumberOption`
|
||||
- `ModdedToggleOption`
|
||||
|
||||
To see a full example of an options class, see [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Options/ExampleOptions.cs).
|
||||
To see a full example of an option class,
|
||||
see [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Options/ExampleOptions.cs).
|
||||
|
||||
### Role Options
|
||||
|
||||
You can also specify a role type for an option or option group.
|
||||
|
||||
To set the role type for an entire group, set the `AdvancedRole` property on that group like this:
|
||||
To set the role type for an entire group, set the `OptionableType` property on that group like this:
|
||||
```csharp
|
||||
public class MyOptionsGroup : AbstractOptionGroup
|
||||
{
|
||||
public override string GroupName => "My Options";
|
||||
public override Type AdvancedRole => typeof(MyRole); // this is the role that will have these options
|
||||
public override Type? OptionableType => typeof(MyRole); // this is the role that will have these options
|
||||
|
||||
[ModdedNumberOption("Ability Uses", min: 0, max: 10)]
|
||||
public float AbilityUses { get; set; } = 5f;
|
||||
}
|
||||
```
|
||||
|
||||
To set the role type for individual options, specify the `roleType` parameter in the option like this:
|
||||
```csharp
|
||||
// this group doesnt specify a role, so it will show up in the global settings
|
||||
public class MyOptionsGroup : AbstractOptionGroup
|
||||
{
|
||||
public override string GroupName => "My Options";
|
||||
|
||||
// this option will only show up in the settings for MyRole
|
||||
[ModdedNumberOption("Ability Uses", min: 0, max: 10, roleType: typeof(MyRole))]
|
||||
public float AbilityUses { get; set; } = 5f;
|
||||
}
|
||||
```
|
||||
You can also use a modifier type for the `OptionableType` property.
|
||||
This will associate that option group with the specified modifier type
|
||||
and display it in the modifier options menu.
|
||||
|
||||
An example can be found [here](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Options/Roles/CustomRoleSettings.cs).
|
||||
|
||||
## Custom Murders
|
||||
Mira API provides it's own implementation for murders. Our implementation allows for more customization on kills, and helps bypass server checks.
|
||||
Mira API provides its own implementation for murders.
|
||||
Our implementation allows for more customization on kills, and helps bypass server checks.
|
||||
You can use `PlayerControl.RpcCustomMurder` to perform a networked custom murder, or `PlayerControl.CustomMurder` to normally perform a custom murder.
|
||||
For example:
|
||||
```cs
|
||||
@@ -190,18 +206,23 @@ This will kill a player without creating a dead body and without teleporting the
|
||||
|
||||
## Buttons
|
||||
|
||||
Mira API provides a simple interface for adding ability buttons to the game. There is only 2 steps:
|
||||
1. Create a class that inherits from the `CustomActionButton` class and implement the properties and methods.
|
||||
2. Add the `[RegisterCustomButton]` attribute to the class.
|
||||
Mira API provides a simple interface for adding ability buttons to the game.
|
||||
|
||||
All other tasks and logic required to add the button to the game are handled by Mira API.
|
||||
The only thing you need to do is create a class
|
||||
that inherits from the `CustomActionButton` class and implement the properties and methods.
|
||||
|
||||
In case you need to access your `CustomActionButton` instance from another class, you can use the `CustomButtonSingleton` class like this:
|
||||
Mira API handles all other tasks and logic required to add the button to the game.
|
||||
|
||||
In case you need to access your `CustomActionButton` instance from another class,
|
||||
you can use the `CustomButtonSingleton` class like this:
|
||||
```csharp
|
||||
var myButton = CustomButtonSingleton<MyCoolButton>.Instance;
|
||||
```
|
||||
|
||||
The button API is simple, but provides a lot of flexibility. There are various methods you can override to customize the behaviour of your button. See [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI/Hud/CustomActionButton.cs) for a full list of methods you can override.
|
||||
The button API is straightforward, but provides a lot of flexibility.
|
||||
There are various methods you can override to customize the behavior of your button.
|
||||
See [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI/Hud/CustomActionButton.cs)
|
||||
for a full list of methods you can override.
|
||||
|
||||
An example button can be found [here](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Buttons/ExampleButton.cs).
|
||||
|
||||
@@ -209,7 +230,7 @@ An example button can be found [here](https://github.com/All-Of-Us-Mods/MiraAPI/
|
||||
|
||||
Mira provides a simple Custom Color API that allows you to add custom player colors to the game.
|
||||
|
||||
Creating custom colors isn't difficult, but there are some requirements for your colors to be registered by Mira.
|
||||
Creating custom colors isn't difficult, but there are some requirements for Mira to register your colors.
|
||||
|
||||
1. Create a ***STATIC*** class to house all your `CustomColor` objects.
|
||||
2. Inside this class, create a `CustomColor` property for each color you intend to add.
|
||||
@@ -230,13 +251,17 @@ public static class MyCustomColors
|
||||
|
||||
## Assets
|
||||
|
||||
Mira API provides a simple, but expandable asset system. The core of the system is the `LoadableAsset<T>` class. This is a generic abstract class that provides a pattern for loading assets.
|
||||
Mira API provides a simple, but expandable asset system.
|
||||
The core of the system is the `LoadableAsset<T>` class.
|
||||
This is a generic abstract class that provides a pattern for loading assets.
|
||||
|
||||
Mira API comes with two asset loaders:
|
||||
Mira API comes with a few asset loaders:
|
||||
1. `LoadableBundleAsset<T>`: This is used for loading assets from AssetBundles.
|
||||
2. `LoadableResourceAsset`: This is used for loading **only sprites** from the Embedded Resources within a mod.
|
||||
2. `LoadableAddressableAsset<T>`: This is used for loading assets from Addressables.
|
||||
3. `LoadableResourceAsset`: This is used for loading **only sprites** from the Embedded Resources within a mod.
|
||||
4. `LoadableAudioResourceAsset`: This is used for loading **only audio clips** from the Embedded Resources within a mod.
|
||||
|
||||
The code below shows how to use these asset loaders:
|
||||
The code below shows how to use an asset loaders:
|
||||
```csharp
|
||||
// Load a sprite from an AssetBundle
|
||||
AssetBundle bundle = AssetBundleManager.Load("MyBundle"); // AssetBundleManager is a utility provided by Reactor
|
||||
@@ -252,7 +277,3 @@ Sprite button = buttonSpriteAsset.LoadAsset();
|
||||
You can view an example file [here](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/ExampleAssets.cs).
|
||||
|
||||
You can create your own asset loaders by inheriting from `LoadableAsset<T>` and implementing the `LoadAsset` method.
|
||||
|
||||
# Disclaimer
|
||||
|
||||
> This mod is not affiliated with Among Us or Innersloth LLC, and the content contained therein is not endorsed or otherwise sponsored by Innersloth LLC. Portions of the materials contained herein are property of Innersloth LLC. © Innersloth LLC.
|
||||
Reference in new issue
Block a user