From dd8d3df2b03665ccb3fe2eab1ec849dc89e67bfc Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Wed, 28 Aug 2024 12:39:05 -0400 Subject: [PATCH 1/9] refactor access and management methods for modifiers --- .../Buttons/Freezer/FreezeButton.cs | 2 +- MiraAPI.Example/Buttons/MeetingButton.cs | 2 +- MiraAPI/Modifiers/ModifierComponent.cs | 111 ++++++++++++------ MiraAPI/Modifiers/ModifierManager.cs | 10 +- MiraAPI/Modifiers/Types/TimedModifier.cs | 5 +- MiraAPI/Utilities/Extensions.cs | 52 ++++++-- 6 files changed, 130 insertions(+), 52 deletions(-) diff --git a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs index c04d4a4..3926316 100644 --- a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs +++ b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs @@ -18,7 +18,7 @@ public class FreezeButton : CustomActionButton protected override void OnClick() { - Target.AddModifier(); + Target?.RpcAddModifier(); } public override PlayerControl GetTarget() diff --git a/MiraAPI.Example/Buttons/MeetingButton.cs b/MiraAPI.Example/Buttons/MeetingButton.cs index c7f202d..639e663 100644 --- a/MiraAPI.Example/Buttons/MeetingButton.cs +++ b/MiraAPI.Example/Buttons/MeetingButton.cs @@ -41,7 +41,7 @@ protected override void OnClick() if (UsesLeft == 0 && PlayerControl.LocalPlayer.HasModifier()) { - PlayerControl.LocalPlayer.RemoveModifier(); + PlayerControl.LocalPlayer.RpcRemoveModifier(); } } } diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index 55c3f30..6da6580 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -1,7 +1,5 @@ using MiraAPI.Modifiers.Types; -using MiraAPI.Networking; using MiraAPI.Utilities; -using Reactor.Networking.Attributes; using Reactor.Utilities; using Reactor.Utilities.Attributes; using System; @@ -54,7 +52,7 @@ public void Update() var filteredModifiers = ActiveModifiers.Where(mod => !mod.HideOnUi); var baseModifiers = filteredModifiers as BaseModifier[] ?? filteredModifiers.ToArray(); - + if (player.AmOwner && baseModifiers.Any()) { var stringBuild = new StringBuilder(); @@ -74,76 +72,111 @@ public void Update() } } - public static void RemoveModifier(PlayerControl target, uint modifierId) + public void RemoveModifier(Type type) { - if (!ModifierManager.IdToTypeModifiers.TryGetValue(modifierId, out var type)) + var modifier = ActiveModifiers.Find(x => x.GetType() == type); + + if (modifier is null) { - Logger.Error($"Cannot remove modifier with id {modifierId} because it is not registered."); + Logger.Error($"Cannot remove modifier {type.Name} because it is not active."); return; } + + RemoveModifier(modifier); + } - var modifierComponent = target.GetModifierComponent(); + public void RemoveModifier() where T : BaseModifier + { + RemoveModifier(typeof(T)); + } - var modifier = modifierComponent?.ActiveModifiers.Find(x => x.GetType() == type); + public void RemoveModifier(uint modifierId) + { + var modifier = ActiveModifiers.Find(x => x.ModifierId == modifierId); if (modifier is null) { - Logger.Error($"Cannot remove modifier {type.Name} because it is not active."); + Logger.Error($"Cannot remove modifier with id {modifierId} because it is not active."); return; } + + RemoveModifier(modifier); + } + public void RemoveModifier(BaseModifier modifier) + { + if (!ActiveModifiers.Contains(modifier)) + { + Logger.Error($"Cannot remove modifier {modifier.ModifierName} because it is not active on this player."); + return; + } + modifier.OnDeactivate(); - modifierComponent.ActiveModifiers.Remove(modifier); + ActiveModifiers.Remove(modifier); - if (target.AmOwner) + if (player.AmOwner) { HudManager.Instance.SetHudActive(true); } } - public static void AddModifier(PlayerControl target, uint modifierId) + public BaseModifier AddModifier(BaseModifier modifier) { - if (!ModifierManager.IdToTypeModifiers.TryGetValue(modifierId, out var type)) + if (ActiveModifiers.Contains(modifier)) { - Logger.Error($"Cannot add modifier with id {modifierId} because it is not registered."); - return; + Logger.Error($"Player already has modifier with id {modifier.ModifierId}!"); + return null; } + + ActiveModifiers.Add(modifier); + modifier.Player = player; + modifier.ModifierId = ModifierManager.TypeToIdModifiers[modifier.GetType()]; + modifier.OnActivate(); - if (target.HasModifier(modifierId)) + if (!player.AmOwner) { - Logger.Error($"Player already has modifier with id {modifierId}!"); - return; + return modifier; } - var modifier = (BaseModifier)Activator.CreateInstance(type); + if (modifier is TimedModifier { AutoStart: true } timer) + { + timer.StartTimer(); + } - var modifierComponent = target.GetModifierComponent(); + HudManager.Instance.SetHudActive(true); - if (modifier is null) + return modifier; + } + + public BaseModifier AddModifier(Type type) + { + if (!ModifierManager.TypeToIdModifiers.TryGetValue(type, out var modifierId)) { - Logger.Error($"Cannot add modifier {type.Name} because it is null."); - return; + Logger.Error($"Cannot add modifier {type.Name} because it is not registered."); + return null; } - modifierComponent.ActiveModifiers.Add(modifier); - modifier.Player = modifierComponent.player; - modifier.ModifierId = modifierId; - modifier.OnActivate(); - - if (target.AmOwner) + if (ActiveModifiers.Find(x=>x.ModifierId == modifierId) != null) { - if (modifier is TimedModifier { AutoStart: true } timer) - { - timer.StartTimer(); - } + Logger.Error($"Player already has modifier with id {modifierId}!"); + return null; + } - HudManager.Instance.SetHudActive(true); + var modifier = (BaseModifier)Activator.CreateInstance(type); + + if (modifier is null) + { + Logger.Error($"Cannot add modifier {type.Name} because it is null."); + return null; } - } - [MethodRpc((uint)MiraRpc.RemoveModifier)] - public static void RpcRemoveModifier(PlayerControl target, uint modifierId) => RemoveModifier(target, modifierId); + AddModifier(modifier); + + return modifier; + } - [MethodRpc((uint)MiraRpc.AddModifier)] - public static void RpcAddModifier(PlayerControl target, uint modifierId) => AddModifier(target, modifierId); + public T AddModifier() where T : BaseModifier + { + return AddModifier(typeof(T)) as T; + } } \ No newline at end of file diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs index c45cc01..ad0d71d 100644 --- a/MiraAPI/Modifiers/ModifierManager.cs +++ b/MiraAPI/Modifiers/ModifierManager.cs @@ -106,7 +106,7 @@ internal static void AssignModifiers(List plrs) } shuffledModifiers.RemoveAt(0); - ModifierComponent.RpcAddModifier(plr, id); + plr.RpcAddModifier(id); } } @@ -152,7 +152,13 @@ internal static void HandleSyncModifiers(NetData[] data) foreach (var id in ids) { - ModifierComponent.AddModifier(plr, id); + if (!IdToTypeModifiers.TryGetValue(id, out var type)) + { + Logger.Error($"Cannot add modifier with id {id} because it is not registered."); + continue; + } + + modifierComponent.AddModifier(type); } } } diff --git a/MiraAPI/Modifiers/Types/TimedModifier.cs b/MiraAPI/Modifiers/Types/TimedModifier.cs index b37c80e..44e73f7 100644 --- a/MiraAPI/Modifiers/Types/TimedModifier.cs +++ b/MiraAPI/Modifiers/Types/TimedModifier.cs @@ -1,4 +1,5 @@ -using Reactor.Utilities; +using MiraAPI.Utilities; +using Reactor.Utilities; using UnityEngine; namespace MiraAPI.Modifiers.Types; @@ -32,7 +33,7 @@ public override void FixedUpdate() if (RemoveOnComplete) { - ModifierComponent.RpcRemoveModifier(Player, ModifierId); + Player.RpcRemoveModifier(ModifierId); } } } diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index f075ce0..157fcd7 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -6,6 +6,7 @@ using Reactor.Utilities; using System.Linq; using MiraAPI.Networking; +using Reactor.Networking.Attributes; using UnityEngine; namespace MiraAPI.Utilities; @@ -71,9 +72,22 @@ public static bool IsCustom(this OptionBehaviour optionBehaviour) return ModdedOptionsManager.ModdedOptions.Values.Any(opt => opt.OptionBehaviour && opt.OptionBehaviour.Equals(optionBehaviour)); } + public static readonly Dictionary ModifierComponents = new(); + public static ModifierComponent? GetModifierComponent(this PlayerControl player) { - return player.GetComponent(); + if (ModifierComponents.TryGetValue(player, out var component)) + { + return component; + } + + component = player.GetComponent(); + if (component) + { + ModifierComponents[player] = component; + } + + return component; } public static List Randomize(this List list) @@ -88,28 +102,52 @@ public static List Randomize(this List list) } return randomizedList; } - + + public static T? GetModifier(this PlayerControl? player) where T : BaseModifier + { + return player?.GetModifierComponent()?.ActiveModifiers.Find(x => x is T) as T; + } + public static bool HasModifier(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(this PlayerControl player) where T : BaseModifier + [MethodRpc((uint)MiraRpc.RemoveModifier)] + public static void RpcRemoveModifier(this PlayerControl target, uint modifierId) + { + target.GetModifierComponent()?.RemoveModifier(modifierId); + } + + [MethodRpc((uint)MiraRpc.AddModifier)] + public static BaseModifier? RpcAddModifier(this PlayerControl target, uint modifierId) + { + if (ModifierManager.IdToTypeModifiers.TryGetValue(modifierId, out var type)) + { + return target.GetModifierComponent()?.AddModifier(type); + } + + Logger.Error($"Cannot add modifier with id {modifierId} because it is not registered."); + return null; + } + + public static void RpcAddModifier(this PlayerControl player) where T : BaseModifier { if (!ModifierManager.TypeToIdModifiers.TryGetValue(typeof(T), out var id)) { Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered."); return; } - - ModifierComponent.RpcAddModifier(player, id); + + player.RpcAddModifier(id); } - public static void RemoveModifier(this PlayerControl player) where T : BaseModifier + public static void RpcRemoveModifier(this PlayerControl player) where T : BaseModifier { if (!ModifierManager.TypeToIdModifiers.TryGetValue(typeof(T), out var id)) { @@ -117,7 +155,7 @@ public static void RemoveModifier(this PlayerControl player) where T : BaseMo return; } - ModifierComponent.RpcRemoveModifier(player, id); + player.RpcAddModifier(id); } public static Color DarkenColor(this Color color) From 7cb103f318f2951c4e80f597f0c511f4d4348f20 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Wed, 28 Aug 2024 13:45:32 -0400 Subject: [PATCH 2/9] save config file once when handling netdata --- MiraAPI/GameOptions/ModdedOptionsManager.cs | 14 ++++++++++++++ MiraAPI/GameOptions/OptionTypes/ModdedOption.cs | 10 +++++----- MiraAPI/Roles/CustomRoleManager.cs | 16 ++++++++++++++++ 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/MiraAPI/GameOptions/ModdedOptionsManager.cs b/MiraAPI/GameOptions/ModdedOptionsManager.cs index f130864..231d36a 100644 --- a/MiraAPI/GameOptions/ModdedOptionsManager.cs +++ b/MiraAPI/GameOptions/ModdedOptionsManager.cs @@ -120,6 +120,14 @@ internal static void SyncAllOptions(int targetId=-1) internal static void HandleSyncOptions(NetData[] data) { + // necessary to disable then re-enable this setting + // we dont know how other plugins handle their configs + // this way, all the options are saved at once, instead of one by one + foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values) + { + plugin.PluginConfig.SaveOnConfigSet = false; + } + foreach (var netData in data) { if (!ModdedOptions.TryGetValue(netData.Id, out var option)) @@ -129,6 +137,12 @@ internal static void HandleSyncOptions(NetData[] data) option.HandleNetData(netData.Data); } + + foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values) + { + plugin.PluginConfig.Save(); + plugin.PluginConfig.SaveOnConfigSet = true; + } } diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs index a990667..d93522d 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -66,14 +66,14 @@ public void SetValue(T newValue) if (AmongUsClient.Instance.AmHost) { + if (ParentMod?.GetConfigFile().TryGetEntry(ConfigDefinition, out var entry) == true) + { + entry.Value = Value; + } + Rpc.Instance.Send(PlayerControl.LocalPlayer, [GetNetData()], true); } - if (ParentMod?.GetConfigFile().TryGetEntry(ConfigDefinition, out var entry) == true) - { - entry.Value = Value; - } - OnValueChanged(newValue); } diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index 74e0bcf..e21568d 100644 --- a/MiraAPI/Roles/CustomRoleManager.cs +++ b/MiraAPI/Roles/CustomRoleManager.cs @@ -172,6 +172,16 @@ internal static void SyncAllRoleSettings(int targetId=-1) internal static void HandleSyncRoleOptions(NetData[] data) { + // necessary to disable then re-enable this setting + // we dont know how other plugins handle their configs + // this way, all the options are saved at once, instead of one by one + var oldConfigSetting = new Dictionary(); + foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values) + { + oldConfigSetting.Add(plugin, plugin.PluginConfig.SaveOnConfigSet); + plugin.PluginConfig.SaveOnConfigSet = false; + } + foreach (var netData in data) { if (!CustomRoles.TryGetValue((ushort)netData.Id, out var role)) @@ -205,5 +215,11 @@ internal static void HandleSyncRoleOptions(NetData[] data) Logger.Error(e); } } + + foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values) + { + plugin.PluginConfig.Save(); + plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin]; + } } } \ No newline at end of file From 7af85d9fbb4f1a8c610e439df8012b3739e50152 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Wed, 28 Aug 2024 15:18:36 -0400 Subject: [PATCH 3/9] Implement custom murders & extension method --- MiraAPI/Networking/CustomMurderRpcs.cs | 186 +++++++++++++++++++++++++ MiraAPI/Networking/MiraRpc.cs | 3 +- MiraAPI/Utilities/Extensions.cs | 75 ++++------ MiraAPI/Utilities/Helpers.cs | 57 ++++++++ 4 files changed, 271 insertions(+), 50 deletions(-) create mode 100644 MiraAPI/Networking/CustomMurderRpcs.cs diff --git a/MiraAPI/Networking/CustomMurderRpcs.cs b/MiraAPI/Networking/CustomMurderRpcs.cs new file mode 100644 index 0000000..9f216ab --- /dev/null +++ b/MiraAPI/Networking/CustomMurderRpcs.cs @@ -0,0 +1,186 @@ +using AmongUs.GameOptions; +using Assets.CoreScripts; +using Reactor.Networking.Attributes; +using Reactor.Utilities; +using Reactor.Utilities.Extensions; +using System.Collections; +using System.Linq; +using UnityEngine; + +namespace MiraAPI.Networking; +public static class CustomMurderRpcs +{ + [MethodRpc((uint)MiraRpc.CustomMurder)] + public static void RpcCustomMurder(this PlayerControl source, PlayerControl target, bool didSucceed = true, + bool resetKillTimer = true, bool createDeadBody = true, bool teleportMurderer = true, bool showKillAnim = true, bool playKillSound = true) + { + MurderResultFlags murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError; + MurderResultFlags murderResultFlags2 = MurderResultFlags.DecisionByHost | murderResultFlags; + + source.CustomMurder(target, murderResultFlags2, resetKillTimer, createDeadBody, teleportMurderer, showKillAnim, playKillSound); + } + + public static void CustomMurder(this PlayerControl source, PlayerControl target, MurderResultFlags resultFlags, + bool resetKillTimer = true, bool createDeadBody = true, bool teleportMurderer = true, bool showKillAnim = true, bool playKillSound = true) + { + source.isKilling = false; + source.logger.Debug(string.Format("{0} trying to murder {1}", source.PlayerId, target.PlayerId), null); + NetworkedPlayerInfo data = target.Data; + if (resultFlags.HasFlag(MurderResultFlags.FailedError)) + { + return; + } + if (resultFlags.HasFlag(MurderResultFlags.FailedProtected) || (resultFlags.HasFlag(MurderResultFlags.DecisionByHost) && target.protectedByGuardianId > -1)) + { + target.protectedByGuardianThisRound = true; + bool flag = PlayerControl.LocalPlayer.Data.Role.Role == RoleTypes.GuardianAngel; + if (flag && PlayerControl.LocalPlayer.Data.PlayerId == target.protectedByGuardianId) + { + StatsManager.Instance.IncrementStat(StringNames.StatsGuardianAngelCrewmatesProtected); + DestroyableSingleton.Instance.OnProtectACrewmate(); + } + if (source.AmOwner || flag) + { + target.ShowFailedMurder(); + + if (resetKillTimer) + { + source.SetKillTimer(GameOptionsManager.Instance.CurrentGameOptions.GetFloat(FloatOptionNames.KillCooldown) / 2f); + } + } + else + { + target.RemoveProtection(); + } + source.logger.Debug(string.Format("{0} failed to murder {1} due to guardian angel protection", source.PlayerId, target.PlayerId), null); + return; + } + if (resultFlags.HasFlag(MurderResultFlags.Succeeded) || resultFlags.HasFlag(MurderResultFlags.DecisionByHost)) + { + DestroyableSingleton.Instance.Analytics.Kill(target.Data, source.Data); + if (source.AmOwner) + { + if (GameManager.Instance.IsHideAndSeek()) + { + StatsManager.Instance.IncrementStat(StringNames.StatsImpostorKills_HideAndSeek); + } + else + { + StatsManager.Instance.IncrementStat(StringNames.StatsImpostorKills); + } + if (source.CurrentOutfitType == PlayerOutfitType.Shapeshifted) + { + StatsManager.Instance.IncrementStat(StringNames.StatsShapeshifterShiftedKills); + } + if (Constants.ShouldPlaySfx() && playKillSound) + { + SoundManager.Instance.PlaySound(source.KillSfx, false, 0.8f, null); + } + + if (resetKillTimer) + { + source.SetKillTimer(GameOptionsManager.Instance.CurrentGameOptions.GetFloat(FloatOptionNames.KillCooldown)); + } + } + DestroyableSingleton.Instance.WriteMurder(); + target.gameObject.layer = LayerMask.NameToLayer("Ghost"); + if (target.AmOwner) + { + StatsManager.Instance.IncrementStat(StringNames.StatsTimesMurdered); + if (Minigame.Instance) + { + try + { + Minigame.Instance.Close(); + Minigame.Instance.Close(); + } + catch + { + } + } + + if (showKillAnim) + { + DestroyableSingleton.Instance.KillOverlay.ShowKillAnimation(source.Data, data); + } + + target.cosmetics.SetNameMask(false); + target.RpcSetScanner(false); + } + DestroyableSingleton.Instance.OnMurder(source.AmOwner, target.AmOwner, source.CurrentOutfitType == PlayerOutfitType.Shapeshifted, source.shapeshiftTargetPlayerId, (int)target.PlayerId); + Coroutines.Start(source.KillAnimations.Random().CoPerformCustomKill(source, target, createDeadBody, teleportMurderer)); + source.logger.Debug(string.Format("{0} succeeded in murdering {1}", source.PlayerId, target.PlayerId), null); + } + } + + public static IEnumerator CoPerformCustomKill(this KillAnimation anim, PlayerControl source, PlayerControl target, + bool createDeadBody = true, bool teleportMurderer = true) + { + FollowerCamera cam = Camera.main.GetComponent(); + bool isParticipant = PlayerControl.LocalPlayer == source || PlayerControl.LocalPlayer == target; + PlayerPhysics sourcePhys = source.MyPhysics; + + if (teleportMurderer) + { + KillAnimation.SetMovement(source, false); + } + + KillAnimation.SetMovement(target, false); + + if (isParticipant) + { + PlayerControl.LocalPlayer.isKilling = true; + source.isKilling = true; + } + + DeadBody deadBody = null; + + if (createDeadBody) + { + deadBody = Object.Instantiate(GameManager.Instance.DeadBodyPrefab); + deadBody.enabled = false; + deadBody.ParentId = target.PlayerId; + deadBody.bodyRenderers.ToArray().ToList().ForEach(target.SetPlayerMaterialColors); + + target.SetPlayerMaterialColors(deadBody.bloodSplatter); + Vector3 vector = target.transform.position + anim.BodyOffset; + vector.z = vector.y / 1000f; + deadBody.transform.position = vector; + } + + if (isParticipant) + { + cam.Locked = true; + ConsoleJoystick.SetMode_Task(); + if (PlayerControl.LocalPlayer.AmOwner) + { + PlayerControl.LocalPlayer.MyPhysics.inputHandler.enabled = true; + } + } + + target.Die(DeathReason.Kill, true); + yield return source.MyPhysics.Animations.CoPlayCustomAnimation(anim.BlurAnim); + sourcePhys.Animations.PlayIdleAnimation(); + + if (teleportMurderer) + { + source.NetTransform.SnapTo(target.transform.position); + KillAnimation.SetMovement(source, true); + } + KillAnimation.SetMovement(target, true); + + if (deadBody) + { + deadBody.enabled = true; + } + + if (isParticipant) + { + cam.Locked = false; + PlayerControl.LocalPlayer.isKilling = false; + source.isKilling = false; + } + + yield break; + } +} \ No newline at end of file diff --git a/MiraAPI/Networking/MiraRpc.cs b/MiraAPI/Networking/MiraRpc.cs index 50625bc..40861ce 100644 --- a/MiraAPI/Networking/MiraRpc.cs +++ b/MiraAPI/Networking/MiraRpc.cs @@ -6,5 +6,6 @@ public enum MiraRpc : uint SyncRoleOptions, AddModifier, RemoveModifier, - SyncModifiers + SyncModifiers, + CustomMurder, } \ No newline at end of file diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 157fcd7..fa86da6 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -1,12 +1,12 @@ #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.Networking.Attributes; +using Reactor.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; using UnityEngine; namespace MiraAPI.Utilities; @@ -17,47 +17,47 @@ public static bool IsStatic(this Type type) { return type is { IsClass: true, IsAbstract: true, IsSealed: true }; } - + public static Color32 GetShadowColor(this Color32 color, byte darknessAmount) { return new Color32((byte)Mathf.Clamp(color.r - darknessAmount, 0, 255), (byte)Mathf.Clamp(color.g - darknessAmount, 0, 255), (byte)Mathf.Clamp(color.b - darknessAmount, 0, 255), byte.MaxValue); } - + public static string? Truncate(this string? value, int maxLength, string truncationSuffix = "…") { return value?.Length > maxLength ? value[..maxLength] + truncationSuffix : value; } - + public static Queue ChunkNetData(this IEnumerable dataCollection, int chunkSize) { Queue chunks = []; List current = []; - + var count = 0; foreach (var netData in dataCollection) { var length = netData.GetLength(); - + if (length > chunkSize) { Logger.Error($"NetData length is greater than chunk size: {length} > {chunkSize}"); continue; } - + if (count + length > chunkSize) { chunks.Enqueue(current.ToArray()); current.Clear(); count = 0; } - + current.Add(netData); } - + if (current.Count > 0) { chunks.Enqueue(current.ToArray()); @@ -65,22 +65,22 @@ public static Queue ChunkNetData(this IEnumerable dataCollec return chunks; } - - + + public static bool IsCustom(this OptionBehaviour optionBehaviour) { return ModdedOptionsManager.ModdedOptions.Values.Any(opt => opt.OptionBehaviour && opt.OptionBehaviour.Equals(optionBehaviour)); } public static readonly Dictionary ModifierComponents = new(); - + public static ModifierComponent? GetModifierComponent(this PlayerControl player) { if (ModifierComponents.TryGetValue(player, out var component)) { return component; } - + component = player.GetComponent(); if (component) { @@ -102,17 +102,17 @@ public static List Randomize(this List list) } return randomizedList; } - + public static T? GetModifier(this PlayerControl? player) where T : BaseModifier { return player?.GetModifierComponent()?.ActiveModifiers.Find(x => x is T) as T; } - + public static bool HasModifier(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); @@ -143,7 +143,7 @@ public static void RpcAddModifier(this PlayerControl player) where T : BaseMo Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered."); return; } - + player.RpcAddModifier(id); } @@ -187,7 +187,7 @@ public static bool IsColorDark(this Color color) .FirstOrDefault(component => component && !component.Reported); } - public static T? GetNearestObjectOfType(this PlayerControl playerControl, float radius, string? colliderTag=null, Func? predicate=null) where T : Component + public static T? GetNearestObjectOfType(this PlayerControl playerControl, float radius, string? colliderTag = null, Func? predicate = null) where T : Component { var results = new Il2CppSystem.Collections.Generic.List(); Physics2D.OverlapCircle(playerControl.GetTruePosition(), radius, Helpers.Filter, results); @@ -197,40 +197,17 @@ public static bool IsColorDark(this Color color) .FirstOrDefault(predicate ?? (component => component)); } - public static PlayerControl? GetClosestPlayer(this PlayerControl playerControl, bool includeImpostors, float distance) + public static PlayerControl? GetClosestPlayer(this PlayerControl playerControl, bool includeImpostors, float distance, bool ignoreColliders = false) { - PlayerControl? result = null; if (!ShipStatus.Instance) { return null; } - var truePosition = playerControl.GetTruePosition(); + var filteredPlayers = Helpers.GetClosestPlayers(playerControl, distance, ignoreColliders) + .Where(playerInfo => !playerInfo.Data.Disconnected && playerInfo.PlayerId != playerControl.PlayerId && !playerInfo.Data.IsDead && + (includeImpostors || !playerInfo.Data.Role.IsImpostor)); - var filteredPlayers = GameData.Instance.AllPlayers.ToArray() - .Where(playerInfo => !playerInfo.Disconnected && playerInfo.PlayerId != playerControl.PlayerId && !playerInfo.IsDead && - (includeImpostors || !playerInfo.Role.IsImpostor)); - - foreach (var playerInfo in filteredPlayers) - { - var @object = playerInfo.Object; - if (!@object) - { - continue; - } - - var vector = @object.GetTruePosition() - truePosition; - var magnitude = vector.magnitude; - if (!(magnitude <= distance) || PhysicsHelpers.AnyNonTriggersBetween(truePosition, - vector.normalized, - magnitude, LayerMask.GetMask("Ship", "Objects"))) - { - continue; - } - - result = @object; - distance = magnitude; - } - return result; + return filteredPlayers.First(); } } \ No newline at end of file diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index 353c95d..6fbac3c 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -1,4 +1,5 @@ using MiraAPI.Roles; +using System.Collections.Generic; using System.Linq; using System.Text; using TMPro; @@ -14,6 +15,62 @@ public static PlainShipRoom GetRoom(Vector3 pos) return ShipStatus.Instance.AllRooms.ToList().Find(room => room.roomArea.OverlapPoint(pos)); } + public static List GetClosestPlayers(PlayerControl source, float distance = 2f, bool ignoreColliders = true, bool ignoreSource = true) + { + if (!ShipStatus.Instance) + { + return null; + } + + Vector2 myPos = source.GetTruePosition(); + List players = GetClosestPlayers(myPos, distance, ignoreColliders); + + return ignoreSource ? players.Where(plr => plr.PlayerId != source.PlayerId).ToList() : players; + } + + public static List GetClosestPlayers(Vector2 source, float distance = 2f, bool ignoreColliders = true) + { + if (!ShipStatus.Instance) + { + return null; + } + + List outputList = new List(); + outputList.Clear(); + List allPlayers = GameData.Instance.AllPlayers.ToArray().ToList(); + + for (int i = 0; i < allPlayers.Count; i++) + { + NetworkedPlayerInfo networkedPlayerInfo = allPlayers[i]; + + PlayerControl @object = networkedPlayerInfo.Object; + if (@object && @object.Collider.enabled) + { + Vector2 vector = @object.GetTruePosition() - source; + float magnitude = vector.magnitude; + if (magnitude <= distance && (ignoreColliders || !PhysicsHelpers.AnyNonTriggersBetween(source, vector.normalized, magnitude, Constants.ShipAndObjectsMask))) + { + outputList.Add(@object); + } + } + } + outputList.Sort(delegate (PlayerControl a, PlayerControl b) + { + float magnitude2 = (a.GetTruePosition() - source).magnitude; + float magnitude3 = (b.GetTruePosition() - source).magnitude; + if (magnitude2 > magnitude3) + { + return 1; + } + if (magnitude2 < magnitude3) + { + return -1; + } + return 0; + }); + return outputList; + } + public static TextMeshPro CreateTextLabel(string name, Transform parent, AspectPosition.EdgeAlignments alignment, Vector3 distance, float fontSize = 2f, TextAlignmentOptions textAlignment = TextAlignmentOptions.Center) From eafc740acae8d630780f0083986dc07d9a98693f Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Wed, 28 Aug 2024 20:40:56 -0400 Subject: [PATCH 4/9] new CI, build, and stylecop skidded from reactor :p --- .github/workflows/main.yaml | 31 +++- .gitignore | 3 +- AmongUs.props | 14 ++ Directory.Build.props | 40 +++++ MiraAPI.Example/MiraAPI.Example.csproj | 22 +-- MiraAPI/MiraAPI.csproj | 35 +--- stylecop.json | 16 ++ stylecop.ruleset | 219 +++++++++++++++++++++++++ 8 files changed, 327 insertions(+), 53 deletions(-) create mode 100644 AmongUs.props create mode 100644 Directory.Build.props create mode 100644 stylecop.json create mode 100644 stylecop.ruleset diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 925679a..ca76373 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -1,19 +1,28 @@ name: CI -on: workflow_dispatch +on: ["workflow_dispatch", "push", "pull_request"] jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 with: submodules: true + - uses: actions/cache@v3 + with: + path: | + ~/.nuget/packages + ~/.cache/bepinex + key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }} + restore-keys: | + ${{ runner.os }}-nuget- + - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 6.x + dotnet-version: 8.0.x - name: Run the Cake script uses: cake-build/cake-action@v2 @@ -24,3 +33,19 @@ jobs: with: name: MiraAPI.dll path: MiraAPI/bin/Release/net6.0/MiraAPI.dll + + - uses: actions/upload-artifact@v3 + with: + name: AllOfUs.MiraAPI.nupkg + path: MiraAPI/bin/Release/AllOfUs.MiraAPI.*.nupkg + + - uses: softprops/action-gh-release@v1 + if: github.ref_type == 'tag' + with: + draft: true + files: MiraAPI/bin/Release/net6.0/MiraAPI.dll + + - name: Push NuGet package + if: github.repository == 'All-Of-Us-Mods/MiraAPI' && github.ref == 'refs/heads/master' && github.ref_type == 'tag' + run: | + dotnet nuget push {MiraAPI/bin/Release/AllOfUs.MiraAPI.*.nupkg} --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 963a6b5..80750dc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ obj/ bin/ -*.user \ No newline at end of file +*.user +*.lock.json \ No newline at end of file diff --git a/AmongUs.props b/AmongUs.props new file mode 100644 index 0000000..2575d6a --- /dev/null +++ b/AmongUs.props @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..861c3c7 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,40 @@ + + + net6.0 + latest + embedded + enable + true + + true + + 0.1.2 + dev + + All Of Us, XtraCube, Angxl + All Of Us + git + https://github.com/All-Of-Us-Mods/MiraAPI + LGPL-2.1-or-later + + + true + false + true + + + + true + latest + recommended + true + + + + + + + + $(MSBuildThisFileDirectory)\stylecop.ruleset + + \ No newline at end of file diff --git a/MiraAPI.Example/MiraAPI.Example.csproj b/MiraAPI.Example/MiraAPI.Example.csproj index 87e735a..c5d55fe 100644 --- a/MiraAPI.Example/MiraAPI.Example.csproj +++ b/MiraAPI.Example/MiraAPI.Example.csproj @@ -1,25 +1,9 @@  - net6.0 - latest - embedded - - 1.0.0 - dev + 1.0.0 Example mod for Mira API Angxl, XtraCube - - - - - - - - - compile; build; native; contentfiles; analyzers; buildtransitive - - @@ -30,7 +14,5 @@ - - - + diff --git a/MiraAPI/MiraAPI.csproj b/MiraAPI/MiraAPI.csproj index 72516ae..ec090f0 100644 --- a/MiraAPI/MiraAPI.csproj +++ b/MiraAPI/MiraAPI.csproj @@ -1,15 +1,10 @@  - net6.0 latest - embedded - AllOfUs.MiraAPI - 0.1.2 Simple and easy to use Among Us modding API. - Angxl, XtraCube - All Of Us - LGPL-2.1-or-later - README.md + AllOfUs.MiraAPI + true + true @@ -17,27 +12,9 @@ - - - - - - - - - - - - - - - - - compile; build; native; contentfiles; analyzers; buildtransitive - + - - - + + diff --git a/stylecop.json b/stylecop.json new file mode 100644 index 0000000..21a7e94 --- /dev/null +++ b/stylecop.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "orderingRules": { + "usingDirectivesPlacement": "outsideNamespace" + }, + "layoutRules": { + "newlineAtEndOfFile": "require", + "allowDoWhileOnClosingBrace": true + }, + "documentationRules": { + "xmlHeader": false, + "documentInterfaces": false + } + } +} \ No newline at end of file diff --git a/stylecop.ruleset b/stylecop.ruleset new file mode 100644 index 0000000..73b4f35 --- /dev/null +++ b/stylecop.ruleset @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 03ef684691b9c292933e3d804c428d1722b84734 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Wed, 28 Aug 2024 20:50:34 -0400 Subject: [PATCH 5/9] New --- MiraAPI/Utilities/Extensions.cs | 33 +++++++++----------------- MiraAPI/Utilities/Helpers.cs | 41 +++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index fa86da6..ffe0c23 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -4,7 +4,6 @@ using MiraAPI.Networking; using Reactor.Networking.Attributes; using Reactor.Utilities; -using System; using System.Collections.Generic; using System.Linq; using UnityEngine; @@ -13,7 +12,7 @@ namespace MiraAPI.Utilities; public static class Extensions { - public static bool IsStatic(this Type type) + public static bool IsStatic(this System.Type type) { return type is { IsClass: true, IsAbstract: true, IsSealed: true }; } @@ -158,18 +157,18 @@ public static void RpcRemoveModifier(this PlayerControl player) where T : Bas player.RpcAddModifier(id); } - public static Color DarkenColor(this Color color) + public static Color DarkenColor(this Color color, float amount = 0.45f) { - return new Color(color.r - 0.45f, color.g - 0.45f, color.b - 0.45f); + return new Color(color.r - amount, color.g - amount, color.b - amount); } - public static Color GetAlternateColor(this Color color) + public static Color GetAlternateColor(this Color color, float amount = 0.45f) { - return color.IsColorDark() ? LightenColor(color) : DarkenColor(color); + return color.IsColorDark() ? LightenColor(color, amount) : DarkenColor(color, amount); } - public static Color LightenColor(this Color color) + public static Color LightenColor(this Color color, float amount = 0.45f) { - return new Color(color.r + 0.45f, color.g + 0.45f, color.b + 0.45f); + return new Color(color.r + amount, color.g + amount, color.b + amount); } public static bool IsColorDark(this Color color) @@ -177,24 +176,14 @@ public static bool IsColorDark(this Color color) return color.r < 0.5f && color is { g: < 0.5f, b: < 0.5f }; } - public static DeadBody? NearestDeadBody(this PlayerControl playerControl, float radius) + public static DeadBody? GetNearestDeadBody(this PlayerControl playerControl, float radius) { - var results = new Il2CppSystem.Collections.Generic.List(); - Physics2D.OverlapCircle(playerControl.GetTruePosition(), radius, Helpers.Filter, results); - return results.ToArray() - .Where(collider2D => collider2D.CompareTag("DeadBody")) - .Select(collider2D => collider2D.GetComponent()) - .FirstOrDefault(component => component && !component.Reported); + return Helpers.GetNearestDeadBodies(playerControl.GetTruePosition(), radius).FirstOrDefault(component => component && !component.Reported); } - public static T? GetNearestObjectOfType(this PlayerControl playerControl, float radius, string? colliderTag = null, Func? predicate = null) where T : Component + public static T? GetNearestObjectOfType(this PlayerControl playerControl, float radius, string? colliderTag = null, System.Func? predicate = null) where T : Component { - var results = new Il2CppSystem.Collections.Generic.List(); - Physics2D.OverlapCircle(playerControl.GetTruePosition(), radius, Helpers.Filter, results); - return results.ToArray() - .Where(collider2D => colliderTag == null || collider2D.CompareTag(colliderTag)) - .Select(collider2D => collider2D.GetComponent()) - .FirstOrDefault(predicate ?? (component => component)); + return Helpers.GetNearestObjectsOfType(playerControl.GetTruePosition(), radius, colliderTag).FirstOrDefault(predicate ?? (component => component)); } public static PlayerControl? GetClosestPlayer(this PlayerControl playerControl, bool includeImpostors, float distance, bool ignoreColliders = false) diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index 6fbac3c..10085f4 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -4,6 +4,7 @@ using System.Text; using TMPro; using UnityEngine; +using Object = UnityEngine.Object; namespace MiraAPI.Utilities; @@ -15,6 +16,46 @@ public static PlainShipRoom GetRoom(Vector3 pos) return ShipStatus.Instance.AllRooms.ToList().Find(room => room.roomArea.OverlapPoint(pos)); } + public static List GetNearestDeadBodies(Vector2 source, float radius) + { + var results = new Il2CppSystem.Collections.Generic.List(); + Physics2D.OverlapCircle(source, radius, Filter, results); + return results.ToArray() + .Where(collider2D => collider2D.CompareTag("DeadBody")) + .Select(collider2D => collider2D.GetComponent()).ToList(); + } + + public static List GetNearestObjectsOfType(Vector2 source, float radius, string colliderTag = null) where T : Component + { + var results = new Il2CppSystem.Collections.Generic.List(); + Physics2D.OverlapCircle(source, radius, Filter, results); + return results.ToArray() + .Where(collider2D => colliderTag == null || collider2D.CompareTag(colliderTag)) + .Select(collider2D => collider2D.GetComponent()).ToList(); + } + + public static List GetClosestPlayersInCircle(Vector2 source, float radius, bool ignoreColliders = true) + { + List newList = GetNearestObjectsOfType(source, radius); + + if (ignoreColliders) + { + List filteredList = new List(); + foreach (var player in newList) + { + Vector2 vector = player.GetTruePosition() - source; + float magnitude = vector.magnitude; + if (!PhysicsHelpers.AnyNonTriggersBetween(source, vector.normalized, magnitude, Constants.ShipAndObjectsMask)) + { + filteredList.Add(player); + } + } + + return filteredList; + } + + return newList; + } public static List GetClosestPlayers(PlayerControl source, float distance = 2f, bool ignoreColliders = true, bool ignoreSource = true) { if (!ShipStatus.Instance) From acf1d44b52be9049f6c901563ec338f9c89e9767 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Thu, 29 Aug 2024 15:47:54 -0400 Subject: [PATCH 6/9] Changes --- MiraAPI.Example/Options/ExampleOptions.cs | 12 +++---- MiraAPI.Example/Roles/FreezerRole.cs | 2 +- .../Attributes/ModdedEnumOptionAttribute.cs | 4 +-- .../OptionTypes/ModdedEnumOption.cs | 17 ++++++---- MiraAPI/Hud/CustomActionButton.cs | 29 +++++++++++++---- .../Patches/Options/RoleSettingMenuPatches.cs | 8 ++--- MiraAPI/Roles/CustomRoleManager.cs | 32 +++++++++---------- MiraAPI/Roles/ICustomRole.cs | 10 +++--- 8 files changed, 68 insertions(+), 46 deletions(-) diff --git a/MiraAPI.Example/Options/ExampleOptions.cs b/MiraAPI.Example/Options/ExampleOptions.cs index 480b357..13a7dd7 100644 --- a/MiraAPI.Example/Options/ExampleOptions.cs +++ b/MiraAPI.Example/Options/ExampleOptions.cs @@ -22,13 +22,13 @@ public class ExampleOptions : AbstractOptionGroup [ModdedStringOption("String Opt", ["Hello", "Hey", "Bye"])] public int StringOpt { get; set; } = 2; - [ModdedEnumOption("Enum Opt", typeof(TestEnum))] - public TestEnum EnumOpt { get; set; } = TestEnum.Mira; + [ModdedEnumOption("Best API", typeof(BestApiEnum), ["Mira API", "Mitochondria", "Reactor"])] + public BestApiEnum EnumOpt { get; set; } = BestApiEnum.MiraAPI; } -public enum TestEnum +public enum BestApiEnum { - Welcome, - To, - Mira + MiraAPI, + Mitochondria, + Reactor } \ No newline at end of file diff --git a/MiraAPI.Example/Roles/FreezerRole.cs b/MiraAPI.Example/Roles/FreezerRole.cs index 7415dd6..e649b1a 100644 --- a/MiraAPI.Example/Roles/FreezerRole.cs +++ b/MiraAPI.Example/Roles/FreezerRole.cs @@ -13,5 +13,5 @@ public class FreezerRole : ImpostorRole, ICustomRole public Color RoleColor => Palette.Blue; public ModdedRoleTeams Team => ModdedRoleTeams.Impostor; public LoadableAsset OptionsScreenshot => ExampleAssets.Banner; - + public int MaxPlayers => 2; } \ No newline at end of file diff --git a/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs index 8225c7e..cc861dd 100644 --- a/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs @@ -4,12 +4,12 @@ namespace MiraAPI.GameOptions.Attributes; -public class ModdedEnumOptionAttribute(string title, Type enumType, Type roleType = null) +public class ModdedEnumOptionAttribute(string title, Type enumType, string[] values = null, Type roleType = null) : ModdedOptionAttribute(title, roleType) { internal override IModdedOption CreateOption(object value, PropertyInfo property) { - var opt = new ModdedEnumOption(Title, (int)value, enumType, RoleType); + var opt = new ModdedEnumOption(Title, (int)value, enumType, values, RoleType); return opt; } diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs index 1e43a2b..0be085f 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs @@ -1,7 +1,7 @@ -using Reactor.Localization.Utilities; +using MiraAPI.Networking; +using Reactor.Localization.Utilities; using System; using System.Linq; -using MiraAPI.Networking; using UnityEngine; using Object = UnityEngine.Object; @@ -11,15 +11,18 @@ public class ModdedEnumOption : ModdedOption { public string[] Values { get; } - public ModdedEnumOption(string title, int defaultValue, Type enumType, Type roleType=null) : base(title, defaultValue, roleType) + public ModdedEnumOption(string title, int defaultValue, Type enumType, string[] values = null, Type roleType = null) : base(title, defaultValue, roleType) { - Values = Enum.GetNames(enumType); + Values = values is null ? Enum.GetNames(enumType) : values; Data = ScriptableObject.CreateInstance(); var data = (StringGameSetting)Data; - + data.Title = StringName; data.Type = global::OptionTypes.String; - data.Values = Values.Select(CustomStringName.CreateAndRegister).ToArray(); + data.Values = values is null ? + Enum.GetNames(enumType).Select(CustomStringName.CreateAndRegister).ToArray() + : values.Select(CustomStringName.CreateAndRegister).ToArray(); + data.Index = Value; } @@ -29,7 +32,7 @@ public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOptio stringOption.SetUpFromData(Data, 20); stringOption.OnValueChanged = (Il2CppSystem.Action)ValueChanged; - + // SetUpFromData method doesnt work correctly so we must set the values manually stringOption.Title = StringName; stringOption.Values = ((StringGameSetting)Data).Values; diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs index c24f5ea..e571134 100644 --- a/MiraAPI/Hud/CustomActionButton.cs +++ b/MiraAPI/Hud/CustomActionButton.cs @@ -1,5 +1,4 @@ #nullable enable -using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using UnityEngine; using UnityEngine.Events; @@ -55,17 +54,17 @@ public abstract class CustomActionButton /// /// Returns true if the effect is currently active. /// - protected bool EffectActive; + public bool EffectActive { get; protected set; } /// - /// A timer variable to measure cooldowns and effects. + /// Returns the amount of uses left. /// - protected float Timer; + public int UsesLeft { get; set; } /// - /// Returns the amount of uses left. + /// A timer variable to measure cooldowns and effects. /// - protected int UsesLeft; + protected float Timer; /// /// The button object in game. This is created by Mira API automatically. @@ -135,6 +134,24 @@ public void OverrideName(string name) Button?.OverrideText(name); } + /// + /// Increase the amount of uses this button has left. + /// + /// The amount you want to increase by. Default: 1 + public void IncreaseUses(int amount = 1) + { + UsesLeft += amount; + } + + /// + /// Decrease the amount of uses this button has left. + /// + /// The amount you want to decrease by. Default: 1 + public void DecreaseUses(int amount = 1) + { + UsesLeft -= amount; + } + /// /// A utility function that runs with the local PlayerControl's FixedUpdate if the button is enabled. /// diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index b65cc2b..e355438 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -1,4 +1,5 @@ using HarmonyLib; +using Il2CppInterop.Runtime.InteropTypes.Arrays; using MiraAPI.Networking; using MiraAPI.Roles; using MiraAPI.Utilities; @@ -8,7 +9,6 @@ using Reactor.Utilities.Extensions; using System; using System.Linq; -using Il2CppInterop.Runtime.InteropTypes.Arrays; using TMPro; using UnityEngine; using UnityEngine.Events; @@ -224,7 +224,7 @@ private static void ChangeTab(RoleBehaviour role, RolesSettingsMenu __instance) __instance.roleScreenshot.drawMode = SpriteDrawMode.Sliced; __instance.roleHeaderSprite.color = customRole.RoleColor; __instance.roleHeaderText.color = customRole.RoleColor.GetAlternateColor(); - + var bg = __instance.AdvancedRolesSettings.transform.Find("Background"); bg.localPosition = new Vector3(1.4041f, -7.08f, 0); bg.GetComponent().size = new Vector2(89.4628f, 100); @@ -237,7 +237,7 @@ private static void ChangeTab(RoleBehaviour role, RolesSettingsMenu __instance) { continue; } - + optionBehaviour.OnValueChanged = new Action(__instance.ValueChanged); if (AmongUsClient.Instance && !AmongUsClient.Instance.AmHost) { @@ -263,7 +263,7 @@ public static void CreateQuotaOption(RolesSettingsMenu __instance, RoleBehaviour roleOptionSetting.titleText.transform.localPosition = new Vector3(-0.5376f, -0.2923f, 0f); roleOptionSetting.titleText.color = customRole.RoleColor.GetAlternateColor(); roleOptionSetting.titleText.horizontalAlignment = HorizontalAlignmentOptions.Left; - + if (GameSettingMenuPatches.SelectedMod is null || GameSettingMenuPatches.SelectedMod.Options.Any(x => x.AdvancedRole != null && x.AdvancedRole.IsInstanceOfType(role))) { diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index f124a30..798c662 100644 --- a/MiraAPI/Roles/CustomRoleManager.cs +++ b/MiraAPI/Roles/CustomRoleManager.cs @@ -1,7 +1,9 @@ using AmongUs.GameOptions; using Il2CppInterop.Runtime; +using Il2CppInterop.Runtime.Injection; using MiraAPI.Networking; using MiraAPI.PluginLoading; +using MiraAPI.Utilities; using Reactor.Localization.Utilities; using Reactor.Networking.Rpc; using Reactor.Utilities; @@ -9,8 +11,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Il2CppInterop.Runtime.Injection; -using MiraAPI.Utilities; using TMPro; using UnityEngine; using Object = UnityEngine.Object; @@ -20,11 +20,11 @@ namespace MiraAPI.Roles; public static class CustomRoleManager { public static readonly Dictionary CustomRoles = new(); - + public static readonly Dictionary RoleIds = new(); - + private static ushort _roleId = 100; - + private static ushort GetNextRoleId() { return _roleId++; @@ -37,8 +37,8 @@ internal static void RegisterInRoleManager() public static void RegisterRoleTypes(List roles, MiraPluginInfo pluginInfo) { - roles.ForEach(x=>RoleIds.Add(x, GetNextRoleId())); - + roles.ForEach(x => RoleIds.Add(x, GetNextRoleId())); + foreach (var roleType in roles) { ClassInjector.RegisterTypeInIl2Cpp(roleType); @@ -68,7 +68,7 @@ private static RoleBehaviour RegisterRole(Type roleType, MiraPluginInfo parentMo roleBehaviour.gameObject.Destroy(); return null; } - + var roleId = RoleIds[roleType]; roleBehaviour.Role = (RoleTypes)roleId; @@ -83,7 +83,7 @@ private static RoleBehaviour RegisterRole(Type roleType, MiraPluginInfo parentMo roleBehaviour.TasksCountTowardProgress = customRole.TasksCount; roleBehaviour.CanVent = customRole.CanUseVent; roleBehaviour.DefaultGhostRole = customRole.GhostRole; - roleBehaviour.MaxCount = 15; + roleBehaviour.MaxCount = customRole.MaxPlayers; roleBehaviour.RoleScreenshot = customRole.OptionsScreenshot.LoadAsset(); if (customRole.IsGhostRole) @@ -101,7 +101,7 @@ private static RoleBehaviour RegisterRole(Type roleType, MiraPluginInfo parentMo var config = parentMod.PluginConfig; config.Bind(customRole.NumConfigDefinition, 1); config.Bind(customRole.ChanceConfigDefinition, 100); - + return roleBehaviour; } @@ -156,8 +156,8 @@ internal static void UpdateRoleTab(TaskPanelBehaviour panel, ICustomRole role) panel.SetTaskText(role.SetTabText().ToString()); } - - internal static void SyncAllRoleSettings(int targetId=-1) + + internal static void SyncAllRoleSettings(int targetId = -1) { var data = CustomRoles.Values .Where(x => x is ICustomRole { HideSettings: false }) @@ -181,7 +181,7 @@ internal static void HandleSyncRoleOptions(NetData[] data) oldConfigSetting.Add(plugin, plugin.PluginConfig.SaveOnConfigSet); plugin.PluginConfig.SaveOnConfigSet = false; } - + foreach (var netData in data) { if (!CustomRoles.TryGetValue((ushort)netData.Id, out var role)) @@ -197,7 +197,7 @@ internal static void HandleSyncRoleOptions(NetData[] data) var num = BitConverter.ToInt32(netData.Data, 0); var chance = BitConverter.ToInt32(netData.Data, 4); - + DestroyableSingleton.Instance.Notifier.AddRoleSettingsChangeMessage(role.StringName, num, chance, role.TeamType, false); try @@ -215,13 +215,13 @@ internal static void HandleSyncRoleOptions(NetData[] data) Logger.Error(e); } } - + foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values) { plugin.PluginConfig.Save(); plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin]; } - + if (LobbyInfoPane.Instance) { LobbyInfoPane.Instance.RefreshPane(); diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index f6c74e3..f8c3e8d 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -3,11 +3,11 @@ using HarmonyLib; using MiraAPI.Modifiers; using MiraAPI.Networking; +using MiraAPI.PluginLoading; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using System; using System.Text; -using MiraAPI.PluginLoading; using UnityEngine; namespace MiraAPI.Roles; @@ -24,6 +24,8 @@ public interface ICustomRole ModdedRoleTeams Team { get; } + int MaxPlayers => 15; + LoadableAsset OptionsScreenshot => MiraAssets.Empty; LoadableAsset Icon => MiraAssets.Empty; @@ -53,7 +55,7 @@ public interface ICustomRole RoleTypes GhostRole => Team == ModdedRoleTeams.Crewmate ? RoleTypes.CrewmateGhost : RoleTypes.ImpostorGhost; MiraPluginInfo ParentMod => CustomRoleManager.FindParentMod(this); - + /// /// Runs on the PlayerControl FixedUpdate method for any player with this role /// @@ -76,7 +78,7 @@ StringBuilder SetTabText() var taskStringBuilder = Helpers.CreateForRole(this); return taskStringBuilder; } - + bool IsModifierApplicable(BaseModifier modifier) { return true; @@ -86,7 +88,7 @@ NetData GetNetData() { ParentMod.PluginConfig.TryGetEntry(NumConfigDefinition, out var numEntry); ParentMod.PluginConfig.TryGetEntry(ChanceConfigDefinition, out var chanceEntry); - + return new NetData(RoleId.Get(GetType()), BitConverter.GetBytes(numEntry.Value).AddRangeToArray(BitConverter.GetBytes(chanceEntry.Value))); } } \ No newline at end of file From 2f09baa371b6c25ac274388b287dd9b98c963722 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Thu, 29 Aug 2024 16:31:07 -0400 Subject: [PATCH 7/9] Bug fixes and new teleport button --- .../Buttons/Teleporter/TeleportButton.cs | 2 +- MiraAPI.Example/ExampleAssets.cs | 3 +++ MiraAPI.Example/MiraAPI.Example.csproj | 4 ++++ MiraAPI.Example/Resources/TeleportButton.png | Bin 0 -> 12116 bytes MiraAPI/Modifiers/ModifierComponent.cs | 20 ++++++++++++------ 5 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 MiraAPI.Example/Resources/TeleportButton.png diff --git a/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs b/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs index 8491b99..99199b8 100644 --- a/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs +++ b/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs @@ -19,7 +19,7 @@ public class TeleportButton : CustomActionButton public override int MaxUses => 0; - public override LoadableAsset Sprite => ExampleAssets.ExampleButton; + public override LoadableAsset Sprite => ExampleAssets.TeleportButton; public static bool IsZoom { get; private set; } public override bool Enabled(RoleBehaviour role) diff --git a/MiraAPI.Example/ExampleAssets.cs b/MiraAPI.Example/ExampleAssets.cs index 005d106..a96dfc8 100644 --- a/MiraAPI.Example/ExampleAssets.cs +++ b/MiraAPI.Example/ExampleAssets.cs @@ -5,5 +5,8 @@ namespace MiraAPI.Example; public static class ExampleAssets { public static LoadableResourceAsset ExampleButton { get; } = new("MiraAPI.Example.Resources.ExampleButton.png"); + + // Credit to EpicHorrors for the teleport button asset. + public static LoadableResourceAsset TeleportButton { get; } = new("MiraAPI.Example.Resources.TeleportButton.png"); public static LoadableResourceAsset Banner { get; } = new("MiraAPI.Example.Resources.FortniteBanner.jpeg"); } \ No newline at end of file diff --git a/MiraAPI.Example/MiraAPI.Example.csproj b/MiraAPI.Example/MiraAPI.Example.csproj index 87e735a..250d437 100644 --- a/MiraAPI.Example/MiraAPI.Example.csproj +++ b/MiraAPI.Example/MiraAPI.Example.csproj @@ -9,6 +9,9 @@ Example mod for Mira API Angxl, XtraCube + + + @@ -28,6 +31,7 @@ + diff --git a/MiraAPI.Example/Resources/TeleportButton.png b/MiraAPI.Example/Resources/TeleportButton.png new file mode 100644 index 0000000000000000000000000000000000000000..cfe9f0a7e8478f0efbf80df406404823b1996f5e GIT binary patch literal 12116 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 IgLAqU5pvfz{9P$y=5 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 KMNUMnLSTX=3+9dh literal 0 HcmV?d00001 diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index 6da6580..ba6f14a 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -36,16 +36,24 @@ public void Start() public void FixedUpdate() { + if (ActiveModifiers is null) return; + foreach (var modifier in ActiveModifiers) { + if (modifier is null) continue; + modifier.FixedUpdate(); } } public void Update() { + if (ActiveModifiers is null) return; + foreach (var modifier in ActiveModifiers) { + if (modifier is null) continue; + modifier.Update(); } @@ -81,7 +89,7 @@ public void RemoveModifier(Type type) Logger.Error($"Cannot remove modifier {type.Name} because it is not active."); return; } - + RemoveModifier(modifier); } @@ -99,7 +107,7 @@ public void RemoveModifier(uint modifierId) Logger.Error($"Cannot remove modifier with id {modifierId} because it is not active."); return; } - + RemoveModifier(modifier); } @@ -110,7 +118,7 @@ public void RemoveModifier(BaseModifier modifier) Logger.Error($"Cannot remove modifier {modifier.ModifierName} because it is not active on this player."); return; } - + modifier.OnDeactivate(); ActiveModifiers.Remove(modifier); @@ -127,7 +135,7 @@ public BaseModifier AddModifier(BaseModifier modifier) Logger.Error($"Player already has modifier with id {modifier.ModifierId}!"); return null; } - + ActiveModifiers.Add(modifier); modifier.Player = player; modifier.ModifierId = ModifierManager.TypeToIdModifiers[modifier.GetType()]; @@ -156,7 +164,7 @@ public BaseModifier AddModifier(Type type) return null; } - if (ActiveModifiers.Find(x=>x.ModifierId == modifierId) != null) + if (ActiveModifiers.Find(x => x.ModifierId == modifierId) != null) { Logger.Error($"Player already has modifier with id {modifierId}!"); return null; @@ -171,7 +179,7 @@ public BaseModifier AddModifier(Type type) } AddModifier(modifier); - + return modifier; } From 2cbbcca1617e9c9b8a6bd290da72562f31f558fd Mon Sep 17 00:00:00 2001 From: ang-xd Date: Thu, 29 Aug 2024 16:46:38 -0400 Subject: [PATCH 8/9] oops --- MiraAPI.Example/MiraAPI.Example.csproj | 5 +-- MiraAPI/Hud/CustomActionButton.cs | 45 +++++++++++++++++++++++--- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/MiraAPI.Example/MiraAPI.Example.csproj b/MiraAPI.Example/MiraAPI.Example.csproj index 250d437..b9e314e 100644 --- a/MiraAPI.Example/MiraAPI.Example.csproj +++ b/MiraAPI.Example/MiraAPI.Example.csproj @@ -1,4 +1,4 @@ - + net6.0 latest @@ -9,9 +9,6 @@ Example mod for Mira API Angxl, XtraCube - - - diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs index e571134..6cae97d 100644 --- a/MiraAPI/Hud/CustomActionButton.cs +++ b/MiraAPI/Hud/CustomActionButton.cs @@ -59,12 +59,12 @@ public abstract class CustomActionButton /// /// Returns the amount of uses left. /// - public int UsesLeft { get; set; } + public int UsesLeft { get; protected set; } /// /// A timer variable to measure cooldowns and effects. /// - protected float Timer; + public float Timer { get; protected set; } /// /// The button object in game. This is created by Mira API automatically. @@ -134,13 +134,50 @@ public void OverrideName(string name) Button?.OverrideText(name); } + /// + /// Set the button's timer. + /// + /// The amount you want to set to. + public void SetTimer(float time) + { + Timer = Mathf.Clamp(time, -1, float.MaxValue); + } + + /// + /// Increase the button's timer. + /// + /// The amount you want to increase by. + public void IncreaseTimer(float amount) + { + SetTimer(Timer + amount); + } + + /// + /// Decrease the button's timer. + /// + /// The amount you want to decrease by. + public void DecreaseTimer(float amount) + { + SetTimer(Timer - amount); + } + + + /// + /// Set the amount of uses this button has left. + /// + /// The amount you want to set to. + public void SetUses(int amount) + { + UsesLeft = Mathf.Clamp(amount, 0, int.MaxValue); + } + /// /// Increase the amount of uses this button has left. /// /// The amount you want to increase by. Default: 1 public void IncreaseUses(int amount = 1) { - UsesLeft += amount; + SetUses(UsesLeft + amount); } /// @@ -149,7 +186,7 @@ public void IncreaseUses(int amount = 1) /// The amount you want to decrease by. Default: 1 public void DecreaseUses(int amount = 1) { - UsesLeft -= amount; + SetUses(UsesLeft - amount); } /// From ab5f997fb6a3563be9ad13115153ee4bc5dc8f58 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Thu, 29 Aug 2024 16:51:24 -0400 Subject: [PATCH 9/9] Bump ver --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 861c3c7..097874d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,7 +8,7 @@ true - 0.1.2 + 0.1.3 dev All Of Us, XtraCube, Angxl