From a88646465b3e676549c871f56efe93cad2b16a10 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Mon, 26 Aug 2024 20:02:05 -0400 Subject: [PATCH] GameModifiers, and fixes --- MiraAPI.Example/ExampleButton.cs | 5 +- MiraAPI.Example/ModifierExample.cs | 23 +++--- MiraAPI/Hud/CustomActionButton.cs | 8 +-- MiraAPI/Modifiers/BaseModifier.cs | 4 +- MiraAPI/Modifiers/ModifierComponent.cs | 18 ++++- MiraAPI/Modifiers/ModifierManager.cs | 78 +++++++++++++++++---- MiraAPI/Modifiers/Types/GameModifier.cs | 1 + MiraAPI/Patches/Modifiers/VentPatches.cs | 46 ++++++++++++ MiraAPI/Patches/PlayerControlPatches.cs | 8 ++- MiraAPI/Patches/Roles/RoleManagerPatches.cs | 11 +++ MiraAPI/Roles/ICustomRole.cs | 22 +++--- MiraAPI/Utilities/Extensions.cs | 25 +++++-- MiraAPI/Utilities/Helpers.cs | 3 +- 13 files changed, 202 insertions(+), 50 deletions(-) create mode 100644 MiraAPI/Patches/Modifiers/VentPatches.cs diff --git a/MiraAPI.Example/ExampleButton.cs b/MiraAPI.Example/ExampleButton.cs index d7058bc..df45015 100644 --- a/MiraAPI.Example/ExampleButton.cs +++ b/MiraAPI.Example/ExampleButton.cs @@ -1,4 +1,5 @@ using MiraAPI.Hud; +using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Utilities; using UnityEngine; @@ -13,7 +14,7 @@ public class ExampleButton : CustomActionButton public override float EffectDuration => 0f; public override int MaxUses => 5; public override LoadableAsset Sprite { get; } = new LoadableResourceAsset("MiraAPI.Resources.ExampleButton.png"); - + protected override void OnClick() { Logger.Info("Example button clicked!"); @@ -21,6 +22,6 @@ public class ExampleButton : CustomActionButton public override bool Enabled(RoleBehaviour role) { - return true; + return PlayerControl.LocalPlayer.HasModifier(); } } \ No newline at end of file diff --git a/MiraAPI.Example/ModifierExample.cs b/MiraAPI.Example/ModifierExample.cs index 54a474e..bb2ecb7 100644 --- a/MiraAPI.Example/ModifierExample.cs +++ b/MiraAPI.Example/ModifierExample.cs @@ -3,17 +3,22 @@ namespace MiraAPI.Example; [RegisterModifier] -public class ModifierExample : TimedModifier +public class ModifierExample : GameModifier { - public override string ModifierName => "Timed modifier example"; - public override float Duration => 1.5f; - public override bool AutoStart => true; + public override string ModifierName => "Example"; - private System.Random _rand = new(); - - public override void OnTimerComplete() + public override bool CanVent() { - Player.SetColor((byte)_rand.Next(0, 18)); - StartTimer(); + return true; + } + + public override int GetAmountPerGame() + { + return 1; + } + + public override int GetAssignmentChance() + { + return 100; } } \ No newline at end of file diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs index 4acef2b..05c933f 100644 --- a/MiraAPI/Hud/CustomActionButton.cs +++ b/MiraAPI/Hud/CustomActionButton.cs @@ -60,7 +60,7 @@ public abstract class CustomActionButton /// The button object in game. This is created by Mira API automatically. /// protected ActionButton Button { get; private set; } - + internal void CreateButton(Transform parent) { if (Button) @@ -99,10 +99,10 @@ public abstract class CustomActionButton { OnEffectEnd(); } - + EffectActive = false; } - + /// /// A utility function to override the sprite of the button. /// @@ -225,7 +225,7 @@ public abstract class CustomActionButton { Button.SetDisabled(); } - + Button.SetCoolDown(Timer, EffectActive ? EffectDuration : Cooldown); FixedUpdate(playerControl); diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs index 07bac31..61f27bb 100644 --- a/MiraAPI/Modifiers/BaseModifier.cs +++ b/MiraAPI/Modifiers/BaseModifier.cs @@ -2,11 +2,11 @@ public abstract class BaseModifier { public abstract string ModifierName { get; } - 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 OnDeath() { } + public virtual bool CanVent() => Player.Data.Role.CanVent; } \ No newline at end of file diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index ac7c20a..32e20ce 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -13,13 +13,14 @@ namespace MiraAPI.Modifiers; [RegisterInIl2Cpp] public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr) { - public readonly List ActiveModifiers = []; + public List ActiveModifiers { get; private set; } public PlayerControl player; public void Start() { player = GetComponent(); + ActiveModifiers = new List(); } public void Update() @@ -50,6 +51,11 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr) modifier.OnDeactivate(); modifierComponent.ActiveModifiers.Remove(modifier); + + if (target.AmOwner) + { + HudManager.Instance.SetHudActive(true); + } } public static void AddModifier(PlayerControl target, uint modifierId) @@ -72,11 +78,17 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr) modifierComponent.ActiveModifiers.Add(modifier); modifier.Player = modifierComponent.player; + modifier.ModifierId = modifierId; modifier.OnActivate(); - if (modifier is TimedModifier { AutoStart: true } timer && target.AmOwner) + if (target.AmOwner) { - timer.StartTimer(); + if (modifier is TimedModifier { AutoStart: true } timer) + { + timer.StartTimer(); + } + + HudManager.Instance.SetHudActive(true); } } diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs index f647e8d..afe81b1 100644 --- a/MiraAPI/Modifiers/ModifierManager.cs +++ b/MiraAPI/Modifiers/ModifierManager.cs @@ -1,8 +1,12 @@ -using MiraAPI.Networking; -using System; -using System.Collections.Generic; +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; @@ -30,10 +34,58 @@ public static class ModifierManager TypeToIdModifiers.Add(modifierType, _nextId); } + public static void AssignModifiers(List plrs) + { + Random rand = new Random(); + + List 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(); - + if (targetId == -1) { foreach (var player in GameData.Instance.AllPlayers) @@ -44,24 +96,24 @@ public static class ModifierManager else { var player = GameData.Instance.GetPlayerById((byte)targetId).Object; - + data.Add(GetPlayerModifiers(player)); } - - + + Rpc.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(); - + if (!modifierComponent) { continue; @@ -72,7 +124,7 @@ public static class ModifierManager modifier.OnDeactivate(); } modifierComponent.ActiveModifiers.Clear(); - + foreach (var id in ids) { ModifierComponent.AddModifier(plr, id); @@ -88,12 +140,12 @@ public static class ModifierManager { 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()); } } \ No newline at end of file diff --git a/MiraAPI/Modifiers/Types/GameModifier.cs b/MiraAPI/Modifiers/Types/GameModifier.cs index d1339ea..cda0844 100644 --- a/MiraAPI/Modifiers/Types/GameModifier.cs +++ b/MiraAPI/Modifiers/Types/GameModifier.cs @@ -3,4 +3,5 @@ public abstract class GameModifier : BaseModifier { public abstract int GetAssignmentChance(); public abstract int GetAmountPerGame(); + public virtual bool IsModifierValidOn(RoleBehaviour role) => true; } \ No newline at end of file diff --git a/MiraAPI/Patches/Modifiers/VentPatches.cs b/MiraAPI/Patches/Modifiers/VentPatches.cs new file mode 100644 index 0000000..e2c6e61 --- /dev/null +++ b/MiraAPI/Patches/Modifiers/VentPatches.cs @@ -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 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; + } + } +} \ No newline at end of file diff --git a/MiraAPI/Patches/PlayerControlPatches.cs b/MiraAPI/Patches/PlayerControlPatches.cs index 55fd72e..2be9a6d 100644 --- a/MiraAPI/Patches/PlayerControlPatches.cs +++ b/MiraAPI/Patches/PlayerControlPatches.cs @@ -1,17 +1,19 @@ using HarmonyLib; using MiraAPI.Hud; using MiraAPI.Modifiers; +using Reactor.Utilities.Extensions; namespace MiraAPI.Patches; [HarmonyPatch(typeof(PlayerControl))] public static class PlayerControlPatches { - - [HarmonyPostfix] + [HarmonyPrefix] [HarmonyPatch(nameof(PlayerControl.Start))] public static void PlayerControlStartPostfix(PlayerControl __instance) { + if (__instance.gameObject.TryGetComponent(out var comp)) comp.DestroyImmediate(); + __instance.gameObject.AddComponent(); } @@ -23,7 +25,7 @@ public static class PlayerControlPatches foreach (var button in CustomButtonManager.CustomButtons) { - if (!button.Enabled(__instance.Data.Role)) + if (!button.Enabled(__instance.Data?.Role)) { continue; } diff --git a/MiraAPI/Patches/Roles/RoleManagerPatches.cs b/MiraAPI/Patches/Roles/RoleManagerPatches.cs index a7cd867..9e84bf9 100644 --- a/MiraAPI/Patches/Roles/RoleManagerPatches.cs +++ b/MiraAPI/Patches/Roles/RoleManagerPatches.cs @@ -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) diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index 67e6506..e1915a5 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -1,12 +1,13 @@ -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.Text; -using HarmonyLib; -using MiraAPI.Networking; using Reactor.Utilities; +using System; +using System.Text; using UnityEngine; namespace MiraAPI.Roles; @@ -22,7 +23,7 @@ public interface ICustomRole Color RoleColor { get; } ModdedRoleTeams Team { get; } - + LoadableAsset OptionsScreenshot { get; } LoadableAsset Icon => MiraAssets.Empty; @@ -69,12 +70,17 @@ public interface ICustomRole var taskStringBuilder = Helpers.CreateForRole(this); return taskStringBuilder; } - + + bool IsModifierApplicable(BaseModifier modifier) + { + return true; + } + NetData GetNetData(RoleBehaviour roleBehaviour) { PluginSingleton.Instance.Config.TryGetEntry(NumConfigDefinition, out var numEntry); PluginSingleton.Instance.Config.TryGetEntry(ChanceConfigDefinition, out var chanceEntry); - + return new NetData((uint)roleBehaviour.Role, BitConverter.GetBytes(numEntry.Value).AddRangeToArray(BitConverter.GetBytes(chanceEntry.Value))); } } \ No newline at end of file diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 7187b5b..667cc5d 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -1,9 +1,9 @@ -using System; -using MiraAPI.GameOptions; +using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.Roles; using Reactor.Utilities; using Reactor.Utilities.Extensions; +using System.Collections.Generic; using System.Linq; using UnityEngine; using Object = UnityEngine.Object; @@ -19,12 +19,29 @@ public static class Extensions public static ModifierComponent GetModifierComponent(this PlayerControl player) { - return player.gameObject.GetComponent(); + return player.GetComponent(); + } + + public static List Randomize(this List list) + { + List randomizedList = new List(); + 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(this PlayerControl player) where T : BaseModifier { - return player.GetModifierComponent().ActiveModifiers.Exists(x => x is T); + return player is not null && player.GetModifierComponent() is not null && player.GetModifierComponent().ActiveModifiers.Exists(x => x is T); + } + public static bool HasModifier(this PlayerControl player, uint id) + { + return player is not null && player.GetModifierComponent() is not null && player.GetModifierComponent().ActiveModifiers.Exists(x => x.ModifierId == id); } public static void AddModifier(this PlayerControl player) where T : BaseModifier diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index 765aaec..5e922cc 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -8,7 +8,6 @@ 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)); @@ -18,7 +17,7 @@ public static class Helpers { return Object.FindObjectsOfType().FirstOrDefault(body => body.ParentId == id); } - + public static string GetSuffix(NumberSuffixes suffix) { return suffix switch