Merge pull request #6 from All-Of-Us-Mods/modifiers

Modifiers
This commit is contained in:
XtraCube authored and GitHub committed 2024-08-27 14:59:53 -04:00
commit 36a419d721
26 files changed
+694 -26

No files matched your search

+4 -2
View File
@@ -1,4 +1,6 @@
using MiraAPI.Hud;
using MiraAPI.Example.Modifiers;
using MiraAPI.Hud;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities;
using UnityEngine;
@@ -22,6 +24,6 @@ public class ExampleButton : CustomActionButton
public override bool Enabled(RoleBehaviour role)
{
return true;
return PlayerControl.LocalPlayer.HasModifier<GameModifierExample>();
}
}
+1
View File
@@ -22,5 +22,6 @@ public partial class ExamplePlugin : BasePlugin, IMiraPlugin
public override void Load()
{
Harmony.PatchAll();
AddComponent<MiraDebugWindow>();
}
}
+34
View File
@@ -0,0 +1,34 @@
using MiraAPI.Utilities;
using Reactor.Utilities.Attributes;
using Reactor.Utilities.ImGui;
using System;
using MiraAPI.Example.Modifiers;
using UnityEngine;
namespace MiraAPI.Example;
[RegisterInIl2Cpp]
public class MiraDebugWindow : MonoBehaviour
{
public DragWindow DebuggingWindow;
public MiraDebugWindow(IntPtr ptr) : base(ptr)
{
DebuggingWindow = new DragWindow(new Rect(10, 10, 0, 0), "MIRA API DEBUGGING", () =>
{
if (GUILayout.Button("Test modifier"))
{
PlayerControl.LocalPlayer.AddModifier<ModifierTimerExample>();
}
if (GUILayout.Button("Remove modifier"))
{
PlayerControl.LocalPlayer.RemoveModifier<ModifierTimerExample>();
}
});
DebuggingWindow.Enabled = true;
}
public void OnGUI()
{
DebuggingWindow.OnGUI();
}
}
-8
View File
@@ -1,8 +0,0 @@
namespace MiraAPI.Example;
public class ModifierExample
{
//name, role specific, etc
// mono behaviour stuff here (ModdedPlayerModifier inherits monobehaviour)
}
@@ -0,0 +1,25 @@
using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
namespace MiraAPI.Example.Modifiers;
[RegisterModifier]
public class GameModifierExample : GameModifier
{
public override string ModifierName => "Example Game Modifier";
public override bool CanVent()
{
return true;
}
public override int GetAmountPerGame()
{
return 1;
}
public override int GetAssignmentChance()
{
return 100;
}
}
@@ -0,0 +1,21 @@
using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
namespace MiraAPI.Example.Modifiers;
[RegisterModifier]
public class ModifierTimerExample : TimedModifier
{
public override string ModifierName => "Color Changer";
public override bool HideOnUi => true;
public override float Duration => 10f;
public override bool AutoStart => true;
private System.Random _rand = new();
public override void OnTimerComplete()
{
Player.SetColor((byte)_rand.Next(0, Palette.PlayerColors.Count));
StartTimer();
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ using Reactor.Networking.Rpc;
namespace MiraAPI.GameOptions;
public class ModdedOptionsManager
public static class ModdedOptionsManager
{
private static readonly Dictionary<PropertyInfo, ModdedOptionAttribute> OptionAttributes = new();
private static readonly Dictionary<Type, AbstractOptionGroup> TypeToGroup = new();
+14
View File
@@ -0,0 +1,14 @@
namespace MiraAPI.Modifiers;
public abstract class BaseModifier
{
public abstract string ModifierName { get; }
public virtual bool HideOnUi => false;
public PlayerControl Player { get; internal set; }
public uint ModifierId { get; internal set; }
public virtual void OnActivate() { }
public virtual void OnDeactivate() { }
public virtual void Update() { }
public virtual void FixedUpdate() { }
public virtual void OnDeath() { }
public virtual bool CanVent() => Player.Data.Role.CanVent;
}
+146
View File
@@ -0,0 +1,146 @@
using MiraAPI.Modifiers.Types;
using MiraAPI.Networking;
using MiraAPI.Utilities;
using Reactor.Networking.Attributes;
using Reactor.Utilities;
using Reactor.Utilities.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TMPro;
using UnityEngine;
namespace MiraAPI.Modifiers;
[RegisterInIl2Cpp]
public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
{
public List<BaseModifier> ActiveModifiers { get; private set; }
public PlayerControl player;
private TextMeshPro _modifierText;
public void Start()
{
player = GetComponent<PlayerControl>();
ActiveModifiers = [];
if (!player.AmOwner) return;
_modifierText = Helpers.CreateTextLabel("ModifierText", HudManager.Instance.transform, AspectPosition.EdgeAlignments.RightTop, new Vector3(10.1f, 3.5f, 0), textAlignment: TextAlignmentOptions.Right);
_modifierText.verticalAlignment = VerticalAlignmentOptions.Top;
}
public void FixedUpdate()
{
foreach (var modifier in ActiveModifiers)
{
modifier.FixedUpdate();
}
}
public void Update()
{
foreach (var modifier in ActiveModifiers)
{
modifier.Update();
}
var filteredModifiers = ActiveModifiers.Where(mod => !mod.HideOnUi);
var baseModifiers = filteredModifiers as BaseModifier[] ?? filteredModifiers.ToArray();
if (player.AmOwner && baseModifiers.Any())
{
var stringBuild = new StringBuilder();
foreach (var mod in baseModifiers)
{
stringBuild.Append($"\n{mod.ModifierName}");
if (mod is TimedModifier timer)
{
stringBuild.Append($" <size=70%>({Math.Round(timer.Duration - timer.TimeRemaining, 0)}s/{timer.Duration}s)</size>");
}
}
_modifierText.text = $"<b><size=130%>Modifiers:</b></size>{stringBuild}";
}
else if (player.AmOwner && !baseModifiers.Any() && _modifierText.text != string.Empty)
{
_modifierText.text = string.Empty;
}
}
public static void RemoveModifier(PlayerControl target, uint modifierId)
{
if (!ModifierManager.IdToTypeModifiers.TryGetValue(modifierId, out var type))
{
Logger<MiraApiPlugin>.Error($"Cannot remove modifier with id {modifierId} because it is not registered.");
return;
}
var modifierComponent = target.GetModifierComponent();
var modifier = modifierComponent.ActiveModifiers.Find(x => x.GetType() == type);
if (modifier is null)
{
Logger<MiraApiPlugin>.Error($"Cannot remove modifier {type.Name} because it is not active.");
return;
}
modifier.OnDeactivate();
modifierComponent.ActiveModifiers.Remove(modifier);
if (target.AmOwner)
{
HudManager.Instance.SetHudActive(true);
}
}
public static void AddModifier(PlayerControl target, uint modifierId)
{
if (!ModifierManager.IdToTypeModifiers.TryGetValue(modifierId, out var type))
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier with id {modifierId} because it is not registered.");
return;
}
if (target.HasModifier(modifierId))
{
Logger<MiraApiPlugin>.Error($"Player already has modifier with id {modifierId}!");
return;
}
var modifier = (BaseModifier)Activator.CreateInstance(type);
var modifierComponent = target.GetModifierComponent();
if (modifier is null)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {type.Name} because it is null.");
return;
}
modifierComponent.ActiveModifiers.Add(modifier);
modifier.Player = modifierComponent.player;
modifier.ModifierId = modifierId;
modifier.OnActivate();
if (target.AmOwner)
{
if (modifier is TimedModifier { AutoStart: true } timer)
{
timer.StartTimer();
}
HudManager.Instance.SetHudActive(true);
}
}
[MethodRpc((uint)MiraRpc.RemoveModifier)]
public static void RpcRemoveModifier(PlayerControl target, uint modifierId) => RemoveModifier(target, modifierId);
[MethodRpc((uint)MiraRpc.AddModifier)]
public static void RpcAddModifier(PlayerControl target, uint modifierId) => AddModifier(target, modifierId);
}
+151
View File
@@ -0,0 +1,151 @@
using MiraAPI.Modifiers.Types;
using MiraAPI.Networking;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using Reactor.Networking.Rpc;
using Reactor.Utilities.Extensions;
using System;
using System.Collections.Generic;
using Random = System.Random;
namespace MiraAPI.Modifiers;
public static class ModifierManager
{
public static readonly Dictionary<uint, Type> IdToTypeModifiers = [];
public static readonly Dictionary<Type, uint> TypeToIdModifiers = [];
private static uint _nextId;
private static uint GetNextId()
{
_nextId++;
return _nextId;
}
public static void RegisterModifier(Type modifierType)
{
if (!typeof(BaseModifier).IsAssignableFrom(modifierType))
{
return;
}
IdToTypeModifiers.Add(GetNextId(), modifierType);
TypeToIdModifiers.Add(modifierType, _nextId);
}
public static void AssignModifiers(List<PlayerControl> plrs)
{
Random rand = new Random();
List<uint> filteredModifiers = [];
foreach (var modifier in IdToTypeModifiers)
{
var mod = (GameModifier)Activator.CreateInstance(modifier.Value);
var num = mod.GetAmountPerGame();
var chance = mod.GetAssignmentChance();
for (int i = 0; i < num; i++)
{
int randomNum = rand.Next(100);
if (randomNum < Math.Clamp(chance, 0, 100))
{
filteredModifiers.Add(TypeToIdModifiers[modifier.Value]);
}
}
}
var shuffledModifiers = filteredModifiers.Randomize();
if (shuffledModifiers.Count > plrs.Count)
{
shuffledModifiers = shuffledModifiers.GetRange(0, plrs.Count);
}
while (shuffledModifiers.Count > 0)
{
var id = shuffledModifiers[0];
var mod = (GameModifier)Activator.CreateInstance(IdToTypeModifiers[id]);
var plr = plrs.Random();
if (plr.Data.Role is ICustomRole modRole)
{
if (!modRole.IsModifierApplicable(mod)) continue;
}
if (plr.HasModifier(id) || mod.IsModifierValidOn(plr.Data.Role)) continue;
shuffledModifiers.RemoveAt(0);
ModifierComponent.RpcAddModifier(plr, id);
}
}
public static void SyncAllModifiers(int targetId = -1)
{
var data = new List<NetData>();
if (targetId == -1)
{
foreach (var player in GameData.Instance.AllPlayers)
{
data.Add(GetPlayerModifiers(player.Object));
}
}
else
{
var player = GameData.Instance.GetPlayerById((byte)targetId).Object;
data.Add(GetPlayerModifiers(player));
}
Rpc<SyncModifiersRpc>.Instance.Send(PlayerControl.LocalPlayer, data.ToArray(), true);
}
public static void HandleSyncModifiers(NetData[] data)
{
foreach (var netData in data)
{
var ids = new uint[netData.Data.Length / 4];
Buffer.BlockCopy(netData.Data, 0, ids, 0, netData.Data.Length);
var plr = GameData.Instance.GetPlayerById((byte)netData.Id).Object;
var modifierComponent = plr.GetComponent<ModifierComponent>();
if (!modifierComponent)
{
continue;
}
foreach (var modifier in modifierComponent.ActiveModifiers)
{
modifier.OnDeactivate();
}
modifierComponent.ActiveModifiers.Clear();
foreach (var id in ids)
{
ModifierComponent.AddModifier(plr, id);
}
}
}
private static NetData GetPlayerModifiers(PlayerControl player)
{
var bytes = new List<byte>();
var modifierComponent = player.GetComponent<ModifierComponent>();
if (!modifierComponent)
{
return new NetData(player.PlayerId, []);
}
foreach (var modifier in modifierComponent.ActiveModifiers)
{
bytes.AddRange(BitConverter.GetBytes(TypeToIdModifiers[modifier.GetType()]));
}
return new NetData(player.PlayerId, bytes.ToArray());
}
}
@@ -0,0 +1,6 @@
using System;
namespace MiraAPI.Modifiers;
[AttributeUsage(AttributeTargets.Class)]
public class RegisterModifierAttribute : Attribute;
+7
View File
@@ -0,0 +1,7 @@
namespace MiraAPI.Modifiers.Types;
public abstract class GameModifier : BaseModifier
{
public abstract int GetAssignmentChance();
public abstract int GetAmountPerGame();
public virtual bool IsModifierValidOn(RoleBehaviour role) => true;
}
+41
View File
@@ -0,0 +1,41 @@
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI.Modifiers.Types;
public abstract class TimedModifier : BaseModifier
{
public abstract float Duration { get; }
public virtual bool AutoStart => false;
public abstract void OnTimerComplete();
public bool TimerActive = false;
public float TimeRemaining;
public override void FixedUpdate()
{
if (!Player.AmOwner) return;
if (TimeRemaining > 0 && TimerActive)
{
TimeRemaining -= Time.fixedDeltaTime;
}
else
{
TimerActive = false;
TimeRemaining = Duration;
OnTimerComplete();
}
}
public void StartTimer()
{
if (TimerActive)
{
Logger<MiraApiPlugin>.Error("Can't start a timer that has already been started.");
return;
}
TimerActive = true;
TimeRemaining = Duration;
}
}
+4 -1
View File
@@ -3,5 +3,8 @@
public enum MiraRpc : uint
{
SyncGameOptions,
SyncRoleOptions
SyncRoleOptions,
AddModifier,
RemoveModifier,
SyncModifiers
}
+46
View File
@@ -0,0 +1,46 @@
using Hazel;
using MiraAPI.Modifiers;
using Reactor.Networking.Attributes;
using Reactor.Networking.Rpc;
namespace MiraAPI.Networking;
[RegisterCustomRpc((uint)MiraRpc.SyncModifiers)]
public class SyncModifiersRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, NetData[]>(plugin, id)
{
public override RpcLocalHandling LocalHandling => RpcLocalHandling.None;
public override void Write(MessageWriter writer, NetData[] data)
{
writer.WritePacked((uint)data.Length);
foreach (var netData in data)
{
writer.WritePacked(netData.Id);
writer.WriteBytesAndSize(netData.Data);
}
}
public override NetData[] Read(MessageReader reader)
{
var length = reader.ReadPackedUInt32();
var data = new NetData[length];
for (var i = 0; i < length; i++)
{
var id = reader.ReadPackedUInt32();
var bytes = reader.ReadBytesAndSize();
data[i] = new NetData(id, bytes);
}
return data;
}
public override void Handle(PlayerControl playerControl, NetData[] data)
{
if (AmongUsClient.Instance.HostId != playerControl.OwnerId)
{
return;
}
ModifierManager.HandleSyncModifiers(data);
}
}
+2 -1
View File
@@ -14,7 +14,7 @@ public static class AmongUsClientSyncPatch
{
return;
}
if (clientData.Id == AmongUsClient.Instance.HostId)
{
return;
@@ -22,5 +22,6 @@ public static class AmongUsClientSyncPatch
ModdedOptionsManager.SyncAllOptions(clientData.Id);
CustomRoleManager.SyncAllRoleSettings(clientData.Id);
// ModifierManager.SyncAllModifiers(clientData.Id);
}
}
+46
View File
@@ -0,0 +1,46 @@
using HarmonyLib;
using MiraAPI.Modifiers;
using MiraAPI.Utilities;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace MiraAPI.Patches.Modifiers;
[HarmonyPatch(typeof(Vent))]
public static class VentPatches
{
[HarmonyPostfix]
[HarmonyPatch(nameof(Vent.CanUse))]
public static void CanUseVentPatch(Vent __instance, ref float __result, [HarmonyArgument(0)] NetworkedPlayerInfo pc, [HarmonyArgument(1)] ref bool canUse, [HarmonyArgument(2)] ref bool couldUse)
{
var @object = pc.Object;
var role = @object.Data.Role;
List<BaseModifier> modifiers = @object.GetModifierComponent().ActiveModifiers;
if (modifiers.Count > 0)
{
if (role.CanVent && modifiers.Any(x => !x.CanVent()))
{
couldUse = canUse = false;
return;
}
else if (!role.CanVent && modifiers.Any(x => x.CanVent()))
{
couldUse = true;
}
var num = float.MaxValue;
canUse = couldUse;
if (canUse)
{
var center = @object.Collider.bounds.center;
var position = __instance.transform.position;
num = Vector2.Distance(center, position);
canUse &= num <= __instance.UsableDistance && !PhysicsHelpers.AnythingBetween(@object.Collider, center, position, Constants.ShipOnlyMask, false);
}
__result = num;
}
}
}
@@ -223,7 +223,7 @@ public static class RoleSettingMenuPatches
{
ICustomRole customRole = role as ICustomRole;
__instance.roleDescriptionText.text = customRole.RoleLongDescription;
__instance.roleTitleText.text = DestroyableSingleton<TranslationController>.Instance.GetString(role.StringName, Array.Empty<Il2CppSystem.Object>());
__instance.roleTitleText.text = DestroyableSingleton<TranslationController>.Instance.GetString(role.StringName, []);
__instance.roleScreenshot.sprite = Sprite.Create(customRole.OptionsScreenshot.LoadAsset().texture, new Rect(0, 0, 370, 230), Vector2.one / 2, 100);
__instance.roleScreenshot.drawMode = SpriteDrawMode.Sliced;
__instance.roleHeaderSprite.color = customRole.RoleColor;
+11 -1
View File
@@ -1,13 +1,23 @@
using HarmonyLib;
using MiraAPI.Hud;
using MiraAPI.Roles;
using MiraAPI.Modifiers;
using Reactor.Utilities.Extensions;
namespace MiraAPI.Patches;
[HarmonyPatch(typeof(PlayerControl))]
public static class PlayerControlPatches
{
[HarmonyPrefix]
[HarmonyPatch(nameof(PlayerControl.Start))]
public static void PlayerControlStartPostfix(PlayerControl __instance)
{
if (__instance.gameObject.TryGetComponent<ModifierComponent>(out var comp)) comp.DestroyImmediate();
__instance.gameObject.AddComponent<ModifierComponent>();
}
[HarmonyPostfix]
[HarmonyPatch(nameof(PlayerControl.FixedUpdate))]
public static void PlayerControlFixedUpdatePostfix(PlayerControl __instance)
@@ -1,6 +1,8 @@
using AmongUs.GameOptions;
using HarmonyLib;
using MiraAPI.Modifiers;
using MiraAPI.Roles;
using System.Linq;
using UnityEngine;
namespace MiraAPI.Patches.Roles;
@@ -8,6 +10,15 @@ namespace MiraAPI.Patches.Roles;
[HarmonyPatch(typeof(RoleManager))]
public static class RoleManagerPatches
{
[HarmonyPostfix]
[HarmonyPatch(nameof(RoleManager.SelectRoles))]
public static void ModifierSelectionPatches(RoleManager __instance)
{
if (!AmongUsClient.Instance.AmHost) return;
ModifierManager.AssignModifiers(PlayerControl.AllPlayerControls.ToArray().Where(plr => !plr.Data.IsDead && !plr.Data.Disconnected).ToList());
}
[HarmonyPrefix]
[HarmonyPatch(nameof(RoleManager.AssignRoleOnDeath))]
public static bool AssignRoleOnDeath(RoleManager __instance, [HarmonyArgument(0)] PlayerControl plr)
+15 -1
View File
@@ -1,8 +1,8 @@
using BepInEx.Unity.IL2CPP;
using Il2CppInterop.Runtime.Injection;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.Hud;
using MiraAPI.Modifiers;
using MiraAPI.Roles;
using MiraAPI.Utilities.Colors;
using Reactor.Utilities;
@@ -39,6 +39,7 @@ internal class MiraPluginManager
var info = new MiraPluginInfo(plugin as IMiraPlugin, pluginInfo);
RegisterModifierAttribute(assembly);
RegisterAllOptions(assembly, info);
RegisterRoleAttribute(assembly, info);
@@ -150,6 +151,19 @@ internal class MiraPluginManager
}
}
private static void RegisterModifierAttribute(Assembly assembly)
{
foreach (var type in assembly.GetTypes())
{
var attribute = type.GetCustomAttribute<RegisterModifierAttribute>();
if (attribute != null)
{
ModifierManager.RegisterModifier(type);
}
}
}
private static void RegisterButtonAttribute(Assembly assembly)
{
foreach (var type in assembly.GetTypes())
+10 -4
View File
@@ -1,11 +1,12 @@
using System;
using AmongUs.GameOptions;
using AmongUs.GameOptions;
using BepInEx.Configuration;
using HarmonyLib;
using MiraAPI.Modifiers;
using MiraAPI.Networking;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using System;
using System.Text;
using HarmonyLib;
using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using UnityEngine;
@@ -76,6 +77,11 @@ public interface ICustomRole
return taskStringBuilder;
}
bool IsModifierApplicable(BaseModifier modifier)
{
return true;
}
NetData GetNetData()
{
ParentMod.PluginConfig.TryGetEntry<int>(NumConfigDefinition, out var numEntry);
-1
View File
@@ -1,5 +1,4 @@
using System;
using System.Linq;
namespace MiraAPI.Roles;
+52 -2
View File
@@ -1,10 +1,11 @@
#nullable enable
#nullable enable
using System;
using System.Collections.Generic;
using MiraAPI.GameOptions;
using MiraAPI.Modifiers;
using Reactor.Utilities;
using System.Linq;
using MiraAPI.Networking;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI.Utilities;
@@ -70,6 +71,55 @@ public static class Extensions
return ModdedOptionsManager.ModdedOptions.Values.Any(opt => opt.OptionBehaviour && opt.OptionBehaviour.Equals(optionBehaviour));
}
public static ModifierComponent? GetModifierComponent(this PlayerControl player)
{
return player.GetComponent<ModifierComponent>();
}
public static List<T> Randomize<T>(this List<T> list)
{
List<T> randomizedList = [];
System.Random rnd = new();
while (list.Count > 0)
{
int index = rnd.Next(0, list.Count);
randomizedList.Add(list[index]);
list.RemoveAt(index);
}
return randomizedList;
}
public static bool HasModifier<T>(this PlayerControl? player) where T : BaseModifier
{
return player?.GetModifierComponent() != null && player.GetModifierComponent().ActiveModifiers.Exists(x => x is T);
}
public static bool HasModifier(this PlayerControl? player, uint id)
{
return player?.GetModifierComponent() != null && player.GetModifierComponent().ActiveModifiers.Exists(x => x.ModifierId == id);
}
public static void AddModifier<T>(this PlayerControl player) where T : BaseModifier
{
if (!ModifierManager.TypeToIdModifiers.TryGetValue(typeof(T), out var id))
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
return;
}
ModifierComponent.RpcAddModifier(player, id);
}
public static void RemoveModifier<T>(this PlayerControl player) where T : BaseModifier
{
if (!ModifierManager.TypeToIdModifiers.TryGetValue(typeof(T), out var id))
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
return;
}
ModifierComponent.RpcRemoveModifier(player, id);
}
public static Color DarkenColor(this Color color)
{
return new Color(color.r - 0.45f, color.g - 0.45f, color.b - 0.45f);
+29 -2
View File
@@ -1,6 +1,7 @@
using MiraAPI.Roles;
using System.Linq;
using System.Text;
using TMPro;
using UnityEngine;
namespace MiraAPI.Utilities;
@@ -8,17 +9,43 @@ namespace MiraAPI.Utilities;
public static class Helpers
{
public static readonly ContactFilter2D Filter = ContactFilter2D.CreateLegacyFilter(Constants.NotShipMask, float.MinValue, float.MaxValue);
public static PlainShipRoom GetRoom(Vector3 pos)
{
return ShipStatus.Instance.AllRooms.ToList().Find(room => room.roomArea.OverlapPoint(pos));
}
public static TextMeshPro CreateTextLabel(string name, Transform parent,
AspectPosition.EdgeAlignments alignment, Vector3 distance, float fontSize = 2f,
TextAlignmentOptions textAlignment = TextAlignmentOptions.Center)
{
var textObj = new GameObject(name)
{
transform =
{
parent = parent
},
layer = LayerMask.NameToLayer("UI")
};
var textMeshPro = textObj.AddComponent<TextMeshPro>();
textMeshPro.fontSize = fontSize;
textMeshPro.alignment = textAlignment;
textMeshPro.font = HudManager.Instance.TaskPanel.taskText.font;
textMeshPro.fontMaterial = HudManager.Instance.TaskPanel.taskText.fontMaterial;
var aspectPosition = textObj.AddComponent<AspectPosition>();
aspectPosition.Alignment = alignment;
aspectPosition.DistanceFromEdge = distance;
aspectPosition.AdjustPosition();
return textMeshPro;
}
public static DeadBody GetBodyById(byte id)
{
return Object.FindObjectsOfType<DeadBody>().FirstOrDefault(body => body.ParentId == id);
}
public static string GetSuffix(NumberSuffixes suffix)
{
return suffix switch
+16 -1
View File
@@ -5,10 +5,10 @@
A thorough, but simple, Among Us modding API and utility library that covers:
- Roles
- Options
- Modifiers
- Buttons
- Assets
- Compatibility
- ~~Modifiers~~ (coming soon)
- ~~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.
@@ -62,6 +62,21 @@ Mira API handles everything else, from adding the proper options to the settings
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 provides 3 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
- `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.
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.
An example Game modifier can be found [here](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Modifiers/ExampleGameModifier.cs).
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.