mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-20 01:46:15 +00:00
26 files changed
+694
-26
No files matched your search
@@ -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>();
|
||||
}
|
||||
}
|
||||
@@ -22,5 +22,6 @@ public partial class ExamplePlugin : BasePlugin, IMiraPlugin
|
||||
public override void Load()
|
||||
{
|
||||
Harmony.PatchAll();
|
||||
AddComponent<MiraDebugWindow>();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,8 @@
|
||||
public enum MiraRpc : uint
|
||||
{
|
||||
SyncGameOptions,
|
||||
SyncRoleOptions
|
||||
SyncRoleOptions,
|
||||
AddModifier,
|
||||
RemoveModifier,
|
||||
SyncModifiers
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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,5 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.Roles;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
Reference in new issue
Block a user