diff --git a/NewMod/Buttons/Shade/DeployShadow.cs b/NewMod/Buttons/Shade/DeployShadow.cs new file mode 100644 index 0000000..b14db52 --- /dev/null +++ b/NewMod/Buttons/Shade/DeployShadow.cs @@ -0,0 +1,68 @@ +using MiraAPI.GameOptions; +using MiraAPI.Hud; +using MiraAPI.Keybinds; +using MiraAPI.Utilities.Assets; +using UnityEngine; +using NewMod.Components; +using SH = NewMod.Roles.NeutralRoles.Shade; +using NewMod.Options.Roles.ShadeOptions; + +namespace NewMod.Buttons.Shade +{ + /// + /// Custom action button for the Shade role. + /// Deploys a Shadow Zone where the Shade becomes invisible and gains kill power. + /// + public class ShadeButton : CustomActionButton + { + /// + /// Display name of the button. + /// + public override string Name => "Deploy Shadow"; + + /// + /// Cooldown duration between zone deployments. + /// + public override float Cooldown => OptionGroupSingleton.Instance.Cooldown; + + /// + /// Maximum number of uses. + /// + public override int MaxUses => (int)OptionGroupSingleton.Instance.MaxUses; + + /// + /// Button HUD placement. + /// + public override ButtonLocation Location => ButtonLocation.BottomLeft; + + /// + /// Default keybind. + /// + public override MiraKeybind Keybind => MiraGlobalKeybinds.PrimaryAbility; + + /// + /// Button icon. + /// + public override LoadableAsset Sprite => NewModAsset.DeployZone; + + /// + /// Button enabled only for the Shade role. + /// + public override bool Enabled(RoleBehaviour role) => role is SH; + + /// + /// Deploys a shadow zone at the Shade's position. + /// + protected override void OnClick() + { + var player = PlayerControl.LocalPlayer; + + Vector2 pos = player.GetTruePosition(); + + float radius = OptionGroupSingleton.Instance.Radius; + float dur = OptionGroupSingleton.Instance.Duration; + + ShadowZone.RpcDeployZone(player, pos, radius, dur); + } + } +} diff --git a/NewMod/Components/ScreenEffects/DistorationWaveEffect.cs b/NewMod/Components/ScreenEffects/DistorationWaveEffect.cs new file mode 100644 index 0000000..1c6aaaf --- /dev/null +++ b/NewMod/Components/ScreenEffects/DistorationWaveEffect.cs @@ -0,0 +1,42 @@ +using System; +using Reactor.Utilities.Attributes; +using UnityEngine; + +namespace NewMod.Components.ScreenEffects +{ + [RegisterInIl2Cpp] + public class DistorationWaveEffect(IntPtr ptr) : MonoBehaviour(ptr) + { + public float amplitude = 0.05f; + public float frequency = 5f; + public float speed = 1f; + public float radius = 0.25f; + public float falloff = 1f; + public Vector2 center = new Vector2(0.5f, 0.5f); + public Color tint = Color.white; + private readonly Shader _shader = NewModAsset.DistorationWaveShader.LoadAsset(); + public Material _mat; + + public void OnEnable() + { + _mat = new Material(_shader) { hideFlags = HideFlags.DontSave }; + } + + public void OnDisable() + { + Destroy(_mat); + } + + public void OnRenderImage(RenderTexture src, RenderTexture dst) + { + _mat.SetFloat("_Amplitude", amplitude); + _mat.SetFloat("_Frequency", frequency); + _mat.SetFloat("_Speed", speed); + _mat.SetFloat("_Radius", radius); + _mat.SetFloat("_Falloff", falloff); + _mat.SetVector("_Center", center); + _mat.SetColor("_Tint", tint); + Graphics.Blit(src, dst, _mat); + } + } +} diff --git a/NewMod/Components/ScreenEffects/GlitchEffect.cs b/NewMod/Components/ScreenEffects/GlitchEffect.cs index 23db7e6..0efbe74 100644 --- a/NewMod/Components/ScreenEffects/GlitchEffect.cs +++ b/NewMod/Components/ScreenEffects/GlitchEffect.cs @@ -14,6 +14,7 @@ public class GlitchEffect(IntPtr ptr) : MonoBehaviour(ptr) public float speed = 4f; private readonly Shader _shader = NewModAsset.GlitchShader.LoadAsset(); public Material _mat; + public void OnEnable() { _mat = new Material(_shader) { hideFlags = HideFlags.DontSave }; diff --git a/NewMod/Components/ScreenEffects/ShadowFluxEffect.cs b/NewMod/Components/ScreenEffects/ShadowFluxEffect.cs new file mode 100644 index 0000000..e55085e --- /dev/null +++ b/NewMod/Components/ScreenEffects/ShadowFluxEffect.cs @@ -0,0 +1,42 @@ +using System; +using Reactor.Utilities.Attributes; +using UnityEngine; + +namespace NewMod.Components.ScreenEffects +{ + [RegisterInIl2Cpp] + public class ShadowFluxEffect(IntPtr ptr) : MonoBehaviour(ptr) + { + public Texture2D texture = NewModAsset.NoiseTex.LoadAsset(); + public float noiseScale = 2f; + public float speed = 0.3f; + public float edgeWidth = 0.25f; + public float threshold = 0.55f; + public float opacity = 0.75f; + public float darkness = 0.8f; + public Color tint = Color.white; + public static Shader _shader = NewModAsset.ShadowFluxShader.LoadAsset(); + public Material _mat; + + public void OnEnable() + { + _mat = new Material(_shader) { hideFlags = HideFlags.DontSave }; + _mat.SetTexture("_NoiseTex", texture); + } + public void OnDisable() + { + Destroy(_mat); + } + public void OnRenderImage(RenderTexture src, RenderTexture dst) + { + _mat.SetFloat("_NoiseScale", noiseScale); + _mat.SetFloat("_Speed", speed); + _mat.SetFloat("_EdgeWidth", edgeWidth); + _mat.SetFloat("_Threshold", threshold); + _mat.SetFloat("_Opacity", opacity); + _mat.SetFloat("_Darkness", darkness); + _mat.SetColor("_Tint", tint); + Graphics.Blit(src, dst, _mat); + } + } +} diff --git a/NewMod/Components/ShadowZone.cs b/NewMod/Components/ShadowZone.cs new file mode 100644 index 0000000..9a5cb36 --- /dev/null +++ b/NewMod/Components/ShadowZone.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MiraAPI.GameOptions; +using NewMod.Components.ScreenEffects; +using NewMod.Options.Roles.ShadeOptions; +using NewMod.Roles.NeutralRoles; +using NewMod.Utilities; +using Reactor.Networking.Attributes; +using Reactor.Utilities; +using Reactor.Utilities.Attributes; +using UnityEngine; + +namespace NewMod.Components +{ + [RegisterInIl2Cpp] + public class ShadowZone(IntPtr ptr) : MonoBehaviour(ptr) + { + public byte shadeId; + public float radius; + public float duration; + private float timer; + private bool active; + public static readonly List zones = new(); + + public void Awake() + { + if (!zones.Contains(this)) + zones.Add(this); + } + + public void OnDestroy() + { + zones.Remove(this); + } + + private bool Contains(Vector2 pos) + { + return Vector2.Distance(pos, (Vector2)transform.position) <= radius; + } + + public void Update() + { + timer += Time.deltaTime; + if (timer >= duration) + { + Coroutines.Start(CoroutinesHelper.RemoveCameraEffect(Camera.main, 0f)); + active = false; + Destroy(gameObject); + return; + } + + var lp = PlayerControl.LocalPlayer; + var hud = HudManager.Instance; + var killButton = hud.KillButton; + + bool inside = Contains(lp.GetTruePosition()); + var mode = OptionGroupSingleton.Instance.Behavior; + var cam = Camera.main; + + if (inside && !active) + { + cam.gameObject.AddComponent(); + if (lp.PlayerId == shadeId && lp.Data.Role is Shade) + { + if (mode is ShadeOptions.ShadowMode.Invisible or ShadeOptions.ShadowMode.Both) + { + lp.cosmetics.SetPhantomRoleAlpha(0); + lp.cosmetics.nameText.gameObject.SetActive(false); + } + + killButton.gameObject.SetActive(true); + killButton.currentTarget = null; + } + active = true; + } + + if (inside && active && lp.PlayerId == shadeId && lp.Data.Role is Shade) + { + if (mode is ShadeOptions.ShadowMode.KillEnabled or ShadeOptions.ShadowMode.Both) + { + var list = new Il2CppSystem.Collections.Generic.List(); + lp.Data.Role.GetPlayersInAbilityRangeSorted(list); + var players = list.ToArray().Where(p => p.PlayerId != lp.PlayerId && !p.Data.IsDead).ToList(); + var closest = players.Count > 0 ? players[0] : null; + + if (killButton.currentTarget && killButton.currentTarget != closest) + killButton.currentTarget.ToggleHighlight(false, RoleTeamTypes.Impostor); + + killButton.currentTarget = closest; + if (closest != null) + closest.ToggleHighlight(true, RoleTeamTypes.Impostor); + } + } + else if (!inside && active) + { + Coroutines.Start(CoroutinesHelper.RemoveCameraEffect(cam, 0f)); + if (lp.PlayerId == shadeId) + { + lp.cosmetics.SetPhantomRoleAlpha(1); + lp.cosmetics.nameText.gameObject.SetActive(true); + + if (killButton.currentTarget) + { + killButton.currentTarget.ToggleHighlight(false, RoleTeamTypes.Impostor); + killButton.currentTarget = null; + } + killButton.gameObject.SetActive(false); + } + active = false; + } + } + + public static ShadowZone Create(byte id, Vector2 pos, float r, float dur) + { + var go = new GameObject("ShadowZone"); + var z = go.AddComponent(); + z.shadeId = id; + z.radius = r; + z.duration = dur; + go.transform.position = pos; + return z; + } + + [MethodRpc((uint)CustomRPC.DeployZone)] + public static void RpcDeployZone(PlayerControl source, Vector2 pos, float radius, float duration) + { + Create(source.PlayerId, pos, radius, duration); + } + + public static bool IsInsideAny(Vector2 pos) + { + return zones.Any(z => z && z.Contains(pos)); + } + } +} diff --git a/NewMod/CustomRPC.cs b/NewMod/CustomRPC.cs index db951e5..88d1919 100644 --- a/NewMod/CustomRPC.cs +++ b/NewMod/CustomRPC.cs @@ -14,5 +14,7 @@ public enum CustomRPC SuppressionDome, WitnessTrap, NotifyChampion, - SummonNPC + SummonNPC, + BeaconPulse, + DeployZone } \ No newline at end of file diff --git a/NewMod/DebugWindow.cs b/NewMod/DebugWindow.cs index 8bf96cb..9afd784 100644 --- a/NewMod/DebugWindow.cs +++ b/NewMod/DebugWindow.cs @@ -1,5 +1,4 @@ using UnityEngine; -using System.Linq; using System; using MiraAPI.Hud; using MiraAPI.Modifiers; @@ -18,126 +17,163 @@ namespace NewMod { - [RegisterInIl2Cpp] - public class DebugWindow(nint ptr) : MonoBehaviour(ptr) - { - [HideFromIl2Cpp] public bool EnableDebugger { get; set; } = false; - public float Zoom = 3f; - public const float ZoomMin = 3f; - public const float ZoomMax = 15f; - public bool ScrollZoomWhileOpen = true; - public static DebugWindow Instance; - - public void ApplyZoom(float size) - { - size = Mathf.Clamp(size, ZoomMin, ZoomMax); - if (Camera.main) Camera.main.orthographicSize = size; - foreach (var cam in Camera.allCameras) if (cam) cam.orthographicSize = size; - ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, Screen.fullScreen); - if (HudManager.Instance && HudManager.Instance.ShadowQuad) - { - bool zoomingOut = size > 3f; - HudManager.Instance.ShadowQuad.gameObject.SetActive(!zoomingOut); - } - } - - private static bool AllowDebug() - { - return AmongUsClient.Instance && AmongUsClient.Instance.NetworkMode == NetworkModes.FreePlay; - } - - public readonly DragWindow DebuggingWindow = new(new Rect(10, 10, 0, 0), "NewMod Debug Window", () => - { - bool allow = AllowDebug(); - - GUILayout.BeginVertical(GUI.skin.box); - GUILayout.Label("Camera Zoom"); - var newZoom = GUILayout.HorizontalSlider(Instance.Zoom, ZoomMin, ZoomMax, GUILayout.Width(220f)); - GUILayout.BeginHorizontal(); - if (GUILayout.Button("-", GUILayout.Width(28f)) && allow) newZoom = Mathf.Clamp(Instance.Zoom / 1.25f, ZoomMin, ZoomMax); - if (GUILayout.Button("+", GUILayout.Width(28f)) && allow) newZoom = Mathf.Clamp(Instance.Zoom * 1.25f, ZoomMin, ZoomMax); - if (GUILayout.Button("Reset", GUILayout.Width(64f)) && allow) newZoom = 3f; - Instance.ScrollZoomWhileOpen = GUILayout.Toggle(Instance.ScrollZoomWhileOpen, "Scroll-wheel zoom"); - GUILayout.EndHorizontal(); - if (!Mathf.Approximately(newZoom, Instance.Zoom) && allow) - { - Instance.Zoom = newZoom; - Instance.ApplyZoom(Instance.Zoom); - } - GUILayout.Label($"Size: {Instance.Zoom:0.00}"); - GUILayout.EndVertical(); - - GUILayout.Space(6); - - if (GUILayout.Button("Become Explosive Modifier") && allow) PlayerControl.LocalPlayer.RpcAddModifier(); - if (GUILayout.Button("Remove Explosive Modifier") && allow) PlayerControl.LocalPlayer.RpcRemoveModifier(); - if (GUILayout.Button("Become Necromancer") && allow) PlayerControl.LocalPlayer.RpcSetRole((RoleTypes)RoleId.Get(), false); - if (GUILayout.Button("Become DoubleAgent") && allow) PlayerControl.LocalPlayer.RpcSetRole((RoleTypes)RoleId.Get(), false); - if (GUILayout.Button("Become EnergyThief") && allow) PlayerControl.LocalPlayer.RpcSetRole((RoleTypes)RoleId.Get(), false); - if (GUILayout.Button("Become SpecialAgent") && allow) PlayerControl.LocalPlayer.RpcSetRole((RoleTypes)RoleId.Get(), false); - if (GUILayout.Button("Force Start Game") && allow) if (GameOptionsManager.Instance.CurrentGameOptions.NumImpostors is not 1) AmongUsClient.Instance.StartGame(); - if (GUILayout.Button("Increases Uses by 3") && allow) foreach (var button in CustomButtonManager.Buttons) button.SetUses(3); - if (GUILayout.Button("Randomly Cast a Vote") && allow && MeetingHud.Instance) - { - var randPlayer = Utils.GetRandomPlayer(p => !p.Data.IsDead && !p.Data.Disconnected); - MeetingHud.Instance.CmdCastVote(PlayerControl.LocalPlayer.PlayerId, randPlayer.PlayerId); - } - if (GUILayout.Button("End Meeting") && allow && MeetingHud.Instance) - { - MeetingHud.Instance.Close(); - } - if (GUILayout.Button("Apply Glitch Effect to Main Camera") && allow) - { - Camera.main.gameObject.AddComponent(); - } - if (GUILayout.Button("Apply Earthquake Effect to Main Camera") && allow) - { - Camera.main.gameObject.AddComponent(); - } - if (GUILayout.Button("Apply Slow Hue Pulse Effect to Main Camera") && allow) - { - Camera.main.gameObject.AddComponent(); - } - if (GUILayout.Button("Reset Camera Effects") && allow) - { - Coroutines.Start(CoroutinesHelper.RemoveCameraEffect(Camera.main, 1f)); - } - if (GUILayout.Button("Show Toast") && LobbyBehaviour.Instance) - { - var toast = Toast.CreateToast(); - toast.ShowToast(string.Empty, "NewMod v1.2.6", Color.red, 5f); - } - /*if (GUILayout.Button("Spawn General NPC") && allow) - { - var npc = new GameObject("GeneralNPC").AddComponent(); - npc.Initialize(PlayerControl.LocalPlayer); - }*/ - }); - - public void OnGUI() - { - if (EnableDebugger) DebuggingWindow.OnGUI(); - } - - public void Start() - { - Instance = this; - if (Camera.main) Zoom = Mathf.Clamp(Camera.main.orthographicSize, ZoomMin, ZoomMax); - } - - public void Update() - { - if (Input.GetKeyDown(KeyCode.F3)) EnableDebugger = !EnableDebugger; - if (EnableDebugger && ScrollZoomWhileOpen && AllowDebug()) - { - float wheel = Input.GetAxis("Mouse ScrollWheel"); - if (Mathf.Abs(wheel) > 0.0001f) + [RegisterInIl2Cpp] + public class DebugWindow(nint ptr) : MonoBehaviour(ptr) + { + [HideFromIl2Cpp] public bool EnableDebugger { get; set; } = false; + public float Zoom = 3f; + public const float ZoomMin = 3f; + public const float ZoomMax = 15f; + public bool ScrollZoomWhileOpen = true; + public static DebugWindow Instance; + public int tab; + + //ShadowFlux + public float noiseScale = 2f; + public float fluxSpeed = 0.3f; + public float edgeWidth = 0.25f; + public float threshold = 0.55f; + public float opacity = 0.75f; + public float darkness = 0.8f; + + //DistorationWave + public float amplitude = 0.05f; + public float frequency = 5f; + public float distoSpeed = 1f; + public float radius = 0.25f; + public float falloff = 1f; + + public void ApplyZoom(float size) + { + size = Mathf.Clamp(size, ZoomMin, ZoomMax); + if (Camera.main) Camera.main.orthographicSize = size; + foreach (var cam in Camera.allCameras) if (cam) cam.orthographicSize = size; + ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, Screen.fullScreen); + if (HudManager.Instance && HudManager.Instance.ShadowQuad) + HudManager.Instance.ShadowQuad.gameObject.SetActive(size <= 3f); + } + + public static bool AllowDebug() => AmongUsClient.Instance && AmongUsClient.Instance.NetworkMode == NetworkModes.FreePlay; + public readonly DragWindow DebuggingWindow = new(new Rect(10, 10, 0, 0), "NewMod Debug Window", () => + { + bool allow = AllowDebug(); + + GUILayout.BeginHorizontal(); + if (GUILayout.Toggle(Instance.tab == 0, "Game", GUI.skin.button)) Instance.tab = 0; + if (GUILayout.Toggle(Instance.tab == 1, "Effects", GUI.skin.button)) Instance.tab = 1; + GUILayout.EndHorizontal(); + + if (Instance.tab == 0) + { + GUILayout.BeginVertical(GUI.skin.box); + GUILayout.Label("Camera Zoom"); + var newZoom = GUILayout.HorizontalSlider(Instance.Zoom, ZoomMin, ZoomMax, GUILayout.Width(220f)); + if (!Mathf.Approximately(newZoom, Instance.Zoom) && allow) + { + Instance.Zoom = newZoom; + Instance.ApplyZoom(Instance.Zoom); + } + GUILayout.Label($"Size: {Instance.Zoom:0.00}"); + GUILayout.EndVertical(); + + GUILayout.Space(6); + + if (GUILayout.Button("Become Necromancer") && allow) PlayerControl.LocalPlayer.RpcSetRole((RoleTypes)RoleId.Get(), false); + if (GUILayout.Button("Become DoubleAgent") && allow) PlayerControl.LocalPlayer.RpcSetRole((RoleTypes)RoleId.Get(), false); + if (GUILayout.Button("Become EnergyThief") && allow) PlayerControl.LocalPlayer.RpcSetRole((RoleTypes)RoleId.Get(), false); + if (GUILayout.Button("Become SpecialAgent") && allow) PlayerControl.LocalPlayer.RpcSetRole((RoleTypes)RoleId.Get(), false); + if (GUILayout.Button("Increase Uses by 3") && allow) foreach (var b in CustomButtonManager.Buttons) b.SetUses(3); + if (GUILayout.Button("Random Vote") && allow && MeetingHud.Instance) + { + var p = Utils.GetRandomPlayer(x => !x.Data.IsDead && !x.Data.Disconnected); + MeetingHud.Instance.CmdCastVote(PlayerControl.LocalPlayer.PlayerId, p.PlayerId); + } + if (GUILayout.Button("End Meeting") && allow && MeetingHud.Instance) MeetingHud.Instance.Close(); + if (GUILayout.Button("Apply Glitch Effect") && allow) Camera.main.gameObject.AddComponent(); + if (GUILayout.Button("Apply Earthquake Effect") && allow) Camera.main.gameObject.AddComponent(); + if (GUILayout.Button("Apply PulseHue Effect") && allow) Camera.main.gameObject.AddComponent(); + if (GUILayout.Button("Apply DistortionWave Effect") && allow) Camera.main.gameObject.AddComponent(); + if (GUILayout.Button("Apply ShadowFlux Effect") && allow) Camera.main.gameObject.AddComponent(); + if (GUILayout.Button("Reset Effects") && allow) Coroutines.Start(CoroutinesHelper.RemoveCameraEffect(Camera.main, 1f)); + } + + if (Instance.tab == 1) + { + var cam = Camera.main; + + if (cam.gameObject.TryGetComponent(out ShadowFluxEffect flux) && flux._mat) + { + GUILayout.Label("ShadowFlux"); + Instance.noiseScale = Slider("NoiseScale", Instance.noiseScale, 0f, 5f); + Instance.fluxSpeed = Slider("Speed", Instance.fluxSpeed, 0f, 3f); + Instance.edgeWidth = Slider("EdgeWidth", Instance.edgeWidth, 0f, 1f); + Instance.threshold = Slider("Threshold", Instance.threshold, 0f, 1f); + Instance.opacity = Slider("Opacity", Instance.opacity, 0f, 1f); + Instance.darkness = Slider("Darkness", Instance.darkness, 0f, 1f); + + var m = flux._mat; + m.SetFloat("_NoiseScale", Instance.noiseScale); + m.SetFloat("_Speed", Instance.fluxSpeed); + m.SetFloat("_EdgeWidth", Instance.edgeWidth); + m.SetFloat("_Threshold", Instance.threshold); + m.SetFloat("_Opacity", Instance.opacity); + m.SetFloat("_Darkness", Instance.darkness); + } + + GUILayout.Space(8); + + if (cam.gameObject.TryGetComponent(out DistorationWaveEffect disto) && disto._mat) + { + GUILayout.Label("DistortionWave"); + Instance.amplitude = Slider("Amplitude", Instance.amplitude, 0f, 1f); + Instance.frequency = Slider("Frequency", Instance.frequency, 0f, 10f); + Instance.distoSpeed = Slider("Speed", Instance.distoSpeed, 0f, 5f); + Instance.radius = Slider("Radius", Instance.radius, 0f, 1f); + Instance.falloff = Slider("Falloff", Instance.falloff, 0f, 5f); + + var m = disto._mat; + m.SetFloat("_Amplitude", Instance.amplitude); + m.SetFloat("_Frequency", Instance.frequency); + m.SetFloat("_Speed", Instance.distoSpeed); + m.SetFloat("_Radius", Instance.radius); + m.SetFloat("_Falloff", Instance.falloff); + } + } + }); + + public static float Slider(string name, float value, float min, float max) + { + GUILayout.BeginHorizontal(); + GUILayout.Label(name, GUILayout.Width(100f)); + value = GUILayout.HorizontalSlider(value, min, max, GUILayout.Width(200f)); + GUILayout.EndHorizontal(); + return value; + } + + public void OnGUI() + { + if (EnableDebugger) DebuggingWindow.OnGUI(); + } + + public void Start() + { + Instance = this; + if (Camera.main) Zoom = Mathf.Clamp(Camera.main.orthographicSize, ZoomMin, ZoomMax); + } + + public void Update() + { + if (Input.GetKeyDown(KeyCode.F3)) EnableDebugger = !EnableDebugger; + if (EnableDebugger && ScrollZoomWhileOpen && AllowDebug()) { - var factor = wheel > 0 ? 1f / 1.25f : 1.25f; - Zoom = Mathf.Clamp(Zoom * factor, ZoomMin, ZoomMax); - ApplyZoom(Zoom); + float wheel = Input.GetAxis("Mouse ScrollWheel"); + if (Mathf.Abs(wheel) > 0.0001f) + { + var factor = wheel > 0 ? 1f / 1.25f : 1.25f; + Zoom = Mathf.Clamp(Zoom * factor, ZoomMin, ZoomMax); + ApplyZoom(Zoom); + } } - } - } - } + } + } } diff --git a/NewMod/DiscordStatus.cs b/NewMod/DiscordStatus.cs index c392733..68d7854 100644 --- a/NewMod/DiscordStatus.cs +++ b/NewMod/DiscordStatus.cs @@ -6,7 +6,7 @@ using UnityEngine; using UnityEngine.SceneManagement; -namespace NewMod.Patches +namespace NewMod { [HarmonyPatch] public static class NewModDiscordPatch @@ -18,9 +18,8 @@ public static class NewModDiscordPatch [HarmonyPatch(typeof(DiscordManager), nameof(DiscordManager.Start))] public static bool StartPrefix(DiscordManager __instance) { -#if ANDROID - return true; -#else + if (Application.platform == RuntimePlatform.Android) return true; + const long clientId = 1405946628115791933; discord = new Discord.Discord(clientId, (ulong)CreateFlags.Default); @@ -37,13 +36,13 @@ public static bool StartPrefix(DiscordManager __instance) __instance.SetInMenus(); return false; -#endif } [HarmonyPrefix] [HarmonyPatch(typeof(ActivityManager), nameof(ActivityManager.UpdateActivity))] public static void UpdateActivityPrefix([HarmonyArgument(0)] ref Activity activity) { + if (Application.platform == RuntimePlatform.Android) return; if (activity == null) return; var isBeta = false; diff --git a/NewMod/LocalSettings/NewModLocalSettings.cs b/NewMod/LocalSettings/NewModLocalSettings.cs index 387378d..3d52b8c 100644 --- a/NewMod/LocalSettings/NewModLocalSettings.cs +++ b/NewMod/LocalSettings/NewModLocalSettings.cs @@ -1,6 +1,8 @@ using BepInEx.Configuration; +using MiraAPI.Hud; using MiraAPI.LocalSettings; using MiraAPI.LocalSettings.Attributes; +using NewMod.Patches; using UnityEngine; namespace NewMod.LocalSettings @@ -23,6 +25,22 @@ public class NewModLocalSettings(ConfigFile config) : LocalSettingsTab(config) "Frames per second limit" ); + [LocalToggleSetting("Enable Custom Cursor", "Enable the custom cursor from the birthday update")] + public ConfigEntry EnableCustomCursor { get; } = config.Bind( + "Features", + "Cursor", + true, + "Enable the custom cursor from the birthday update" + ); + + [LocalToggleSetting("Always Buttons on Left (Only for Android)", "Places all custom buttons on the left side (recommended for Android devices)")] + public ConfigEntry AlwaysButtonsLeft { get; } = config.Bind( + "Features", + "AlwaysButtonsLeft", + true, + "Place all custom buttons on the left side" + ); + public override void OnOptionChanged(ConfigEntryBase configEntry) { base.OnOptionChanged(configEntry); @@ -31,6 +49,36 @@ public override void OnOptionChanged(ConfigEntryBase configEntry) { Application.targetFrameRate = (int)FrameRateLimit.Value; } + + if (configEntry == AlwaysButtonsLeft && Application.platform == RuntimePlatform.Android) + { + foreach (var btn in CustomButtonManager.Buttons) + { + btn.SetButtonLocation(ButtonLocation.BottomLeft); + } + } + if (configEntry == EnableCustomCursor) + { + if (EnableCustomCursor.Value) + { + var cur = NewModAsset.CustomCursor.LoadAsset(); + var tex = cur?.texture; + + if (tex != null) + { + Cursor.SetCursor(tex, Vector2.zero, CursorMode.Auto); + Cursor.visible = true; + Cursor.lockState = CursorLockMode.None; + MainMenuPatch._cachedCursor = tex; + } + } + else + { + Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto); + Cursor.visible = true; + Cursor.lockState = CursorLockMode.None; + } + } } } } \ No newline at end of file diff --git a/NewMod/Modifiers/AdrenalineModifier.cs b/NewMod/Modifiers/AdrenalineModifier.cs index 18a71ee..a994cc6 100644 --- a/NewMod/Modifiers/AdrenalineModifier.cs +++ b/NewMod/Modifiers/AdrenalineModifier.cs @@ -11,7 +11,7 @@ public class AdrenalineModifier : GameModifier public override bool HideOnUi => false; public override int GetAssignmentChance() => (int)OptionGroupSingleton.Instance.AdrenalineChance.Value; public override int GetAmountPerGame() => (int)OptionGroupSingleton.Instance.AdrenalineAmount; - + public override string GetDescription() => $"Move faster (x{OptionGroupSingleton.Instance.SpeedMultiplier:0.##})."; public override void OnActivate() { Player.MyPhysics.Speed *= OptionGroupSingleton.Instance.SpeedMultiplier; diff --git a/NewMod/Modifiers/DrowsyModifier.cs b/NewMod/Modifiers/DrowsyModifier.cs index c48b1b7..d6c98af 100644 --- a/NewMod/Modifiers/DrowsyModifier.cs +++ b/NewMod/Modifiers/DrowsyModifier.cs @@ -14,7 +14,7 @@ public sealed class DrowsyModifier : GameModifier public override bool ShowInFreeplay => true; public override Color FreeplayFileColor => new(0.6f, 0.7f, 1f); public override string GetDescription() => - $"Move slower (x{OptionGroupSingleton.Instance.DrowsyAmount:0.##})."; + $"Move slower (x{OptionGroupSingleton.Instance.SpeedMultiplier:0.##})."; public override int GetAssignmentChance() => (int)OptionGroupSingleton.Instance.DrowsyChance.Value; diff --git a/NewMod/Modifiers/FalseFormModifier.cs b/NewMod/Modifiers/FalseFormModifier.cs index 860d1bf..0a0582d 100644 --- a/NewMod/Modifiers/FalseFormModifier.cs +++ b/NewMod/Modifiers/FalseFormModifier.cs @@ -7,21 +7,14 @@ namespace NewMod.Modifiers { - public class FalseFormModifier : GameModifier + public class FalseFormModifier : TimedModifier { public override string ModifierName => "FalseForm"; public override bool ShowInFreeplay => true; public override bool HideOnUi => false; + public override float Duration => (int)OptionGroupSingleton.Instance.FalseFormDuration; private float timer; private AppearanceBackup oldAppearance; - public override int GetAssignmentChance() - { - return OptionGroupSingleton.Instance.FalseFormChance; - } - public override int GetAmountPerGame() - { - return (int)OptionGroupSingleton.Instance.FalseFormAmount; - } public override void OnActivate() { oldAppearance = new AppearanceBackup diff --git a/NewMod/Networking/BeaconPulseRpc.cs b/NewMod/Networking/BeaconPulseRpc.cs new file mode 100644 index 0000000..8503c1c --- /dev/null +++ b/NewMod/Networking/BeaconPulseRpc.cs @@ -0,0 +1,43 @@ +using Hazel; +using NewMod.Components.ScreenEffects; +using NewMod.Roles.CrewmateRoles; +using NewMod.Utilities; +using Reactor.Networking.Attributes; +using Reactor.Networking.Rpc; +using Reactor.Utilities; +using UnityEngine; + +namespace NewMod.Networking +{ + [RegisterCustomRpc((uint)CustomRPC.BeaconPulse)] + public class BeaconPulseRpc : PlayerCustomRpc + { + public BeaconPulseRpc(NewMod plugin, uint id) : base(plugin, id) { } + + public readonly record struct Data(float Duration); + + public override RpcLocalHandling LocalHandling => RpcLocalHandling.After; + + public override void Write(MessageWriter writer, Data data) + { + writer.Write(data.Duration); + } + + public override Data Read(MessageReader reader) + { + return new Data(reader.ReadSingle()); + } + + public override void Handle(PlayerControl sender, Data data) + { + var cam = Camera.main; + if (cam && !cam.GetComponent()) + { + cam.gameObject.AddComponent(); + } + Beacon.pulseUntil = Time.time + data.Duration; + + Logger.Instance.LogMessage($"Beacon pulse triggered by {sender.Data.PlayerName} for {data.Duration}s"); + } + } +} diff --git a/NewMod/NewMod.cs b/NewMod/NewMod.cs index 15c6060..681d7f9 100644 --- a/NewMod/NewMod.cs +++ b/NewMod/NewMod.cs @@ -37,11 +37,11 @@ namespace NewMod; public partial class NewMod : BasePlugin, IMiraPlugin { public const string Id = "com.callofcreator.newmod"; - public const string ModVersion = "1.2.7"; + public const string ModVersion = "1.2.8"; public Harmony Harmony { get; } = new Harmony(Id); public static BasePlugin Instance; public static Minigame minigame; - public const string SupportedAmongUsVersion = "2025.9.9"; + public const string SupportedAmongUsVersion = "2025.10.14"; public static ConfigEntry ShouldEnableBepInExConsole { get; set; } public ConfigFile GetConfigFile() => Config; public string OptionsTitleText => "NewMod"; @@ -62,6 +62,16 @@ public override void Load() ShouldEnableBepInExConsole = Config.Bind("NewMod", "Console", true, "Whether to enable BepInEx Console for debugging"); if (!ShouldEnableBepInExConsole.Value) ConsoleManager.DetachConsole(); + var bundle = NewModAsset.Bundle; + var assetNames = bundle.GetAllAssetNames(); + + Instance.Log.LogMessage($"AssetBundle '{bundle.name}' contains {assetNames.Length} assets"); + + foreach (var name in assetNames) + { + Instance.Log.LogMessage($"{name}"); + } + Instance.Log.LogMessage($"Loaded Successfully NewMod v{ModVersion} With MiraAPI Version : {MiraApiPlugin.Version}"); } public static void CheckVersionCompatibility() diff --git a/NewMod/NewMod.csproj b/NewMod/NewMod.csproj index fcfa021..16f74ed 100644 --- a/NewMod/NewMod.csproj +++ b/NewMod/NewMod.csproj @@ -1,6 +1,6 @@ - 1.2.7 + 1.2.8 dev NewMod is a mod for Among Us that introduces a variety of new roles, unique abilities CallofCreator @@ -15,25 +15,19 @@ - $(RestoreSources);$(MSBuildProjectDirectory)\..\libs\Android TRACE;ANDROID_BUILD - - + + + - - - ..\libs\MiraAPI.dll - - - diff --git a/NewMod/NewModAsset.cs b/NewMod/NewModAsset.cs index 2636ef6..0e92036 100644 --- a/NewMod/NewModAsset.cs +++ b/NewMod/NewModAsset.cs @@ -41,6 +41,7 @@ public static class NewModAsset public static LoadableResourceAsset CallWraith { get; } = new("NewMod.Resources.callwraith.png"); public static LoadableResourceAsset Shield { get; } = new("NewMod.Resources.Shield.png"); public static LoadableResourceAsset Slash { get; } = new("NewMod.Resources.Slash.png"); + public static LoadableResourceAsset DeployZone { get; } = new("NewMod.Resources.deployzone.png"); // SFX public static LoadableAudioResourceAsset ReviveSound { get; } = new("NewMod.Resources.Sounds.revive.wav"); @@ -61,6 +62,7 @@ public static class NewModAsset public static LoadableResourceAsset ShieldIcon { get; } = new("NewMod.Resources.RoleIcons.ShieldIcon.png"); public static LoadableResourceAsset RadarIcon { get; } = new("NewMod.Resources.RoleIcons.RadarIcon.png"); public static LoadableResourceAsset SlashIcon { get; } = new("NewMod.Resources.RoleIcons.SlashIcon.png"); + public static LoadableResourceAsset DeployZoneIcon { get; } = new("NewMod.Resources.RoleIcons.DeployzoneIcon.png"); // Notif Icons public static LoadableResourceAsset VisionDebuff { get; } = new("NewMod.Resources.NotifIcons.vision_debuff.png"); @@ -71,4 +73,9 @@ public static class NewModAsset public static LoadableAsset GlitchShader { get; } = new LoadableBundleAsset("GlitchFullScreen.shader", Bundle); public static LoadableAsset EarthquakeShader { get; } = new LoadableBundleAsset("EarthquakeFullScreen.shader", Bundle); public static LoadableAsset SlowPulseHueShader { get; } = new LoadableBundleAsset("SlowPulseHue.shader", Bundle); + public static LoadableAsset DistorationWaveShader { get; } = new LoadableBundleAsset("DistorationWave.shader", Bundle); + public static LoadableAsset ShadowFluxShader { get; } = new LoadableBundleAsset("ShadowFlux.shader", Bundle); + + // Textures + public static LoadableAsset NoiseTex { get; } = new LoadableBundleAsset("noise.png", Bundle); } \ No newline at end of file diff --git a/NewMod/NewModEndReasons.cs b/NewMod/NewModEndReasons.cs index 06443ea..fea1f8e 100644 --- a/NewMod/NewModEndReasons.cs +++ b/NewMod/NewModEndReasons.cs @@ -12,6 +12,7 @@ public enum NewModEndReasons InjectorWin = 117, PulseBladeWin = 118, TyrantWin = 119, - WraithCallerWin = 120 + WraithCallerWin = 120, + ShadeWin = 121 } } \ No newline at end of file diff --git a/NewMod/Options/ModifiersOptions.cs b/NewMod/Options/ModifiersOptions.cs index f4aae1f..6bccf5a 100644 --- a/NewMod/Options/ModifiersOptions.cs +++ b/NewMod/Options/ModifiersOptions.cs @@ -20,14 +20,6 @@ public class ModifiersOptions : AbstractOptionGroup Visible = () => OptionGroupSingleton.Instance.StickyAmount > 0 }; - [ModdedNumberOption("FalseForm Amount", min:0, max:6)] - public float FalseFormAmount { get; set; } = 10f; - - public ModdedNumberOption FalseFormChance { get; } = new("FalseForm Chance", 50f, 0, 100f, 10f, MiraNumberSuffixes.Percent) - { - Visible = () => OptionGroupSingleton.Instance.FalseFormAmount > 0 - }; - [ModdedNumberOption("Drowsy Amount", min:0f, max:6f)] public float DrowsyAmount { get; set; } = 10f; diff --git a/NewMod/Options/Roles/ShadeOptions.cs b/NewMod/Options/Roles/ShadeOptions.cs new file mode 100644 index 0000000..23d5856 --- /dev/null +++ b/NewMod/Options/Roles/ShadeOptions.cs @@ -0,0 +1,37 @@ +using MiraAPI.GameOptions; +using MiraAPI.GameOptions.Attributes; +using MiraAPI.Utilities; +using NewMod.Roles.NeutralRoles; + +namespace NewMod.Options.Roles.ShadeOptions +{ + public class ShadeOptions : AbstractOptionGroup + { + public override string GroupName => "Shade Options"; + + [ModdedNumberOption("Shadow Cooldown", min: 5f, max: 60f, suffixType: MiraNumberSuffixes.Seconds)] + public float Cooldown { get; set; } = 25f; + + [ModdedNumberOption("Max Shadow Uses", min: 1f, max: 5f, suffixType: MiraNumberSuffixes.None)] + public float MaxUses { get; set; } = 2f; + + [ModdedNumberOption("Shadow Duration", min: 5f, max: 40f, suffixType: MiraNumberSuffixes.Seconds)] + public float Duration { get; set; } = 20f; + + [ModdedNumberOption("Shadow Radius", min: 1f, max: 6f, suffixType: MiraNumberSuffixes.None)] + public float Radius { get; set; } = 3f; + + [ModdedNumberOption("Required Kills To Win", min: 1f, max: 5f, suffixType: MiraNumberSuffixes.None)] + public float RequiredKills { get; set; } = 3f; + + [ModdedEnumOption("Shadow Behavior", typeof(ShadowMode))] + public ShadowMode Behavior { get; set; } = ShadowMode.Invisible; + + public enum ShadowMode + { + Invisible, + KillEnabled, + Both + } + } +} diff --git a/NewMod/Patches/EndGamePatch.cs b/NewMod/Patches/EndGamePatch.cs index 3ca6b59..c42a5f4 100644 --- a/NewMod/Patches/EndGamePatch.cs +++ b/NewMod/Patches/EndGamePatch.cs @@ -19,6 +19,7 @@ using MiraAPI.Utilities; using NewMod.Options.Roles.EnergyThiefOptions; using NewMod.Options.Roles.WraithCallerOptions; +using NewMod.Options.Roles.ShadeOptions; namespace NewMod.Patches { @@ -131,6 +132,11 @@ public static void OnGameEnd(GameEndEvent evt) customWinColor = GetRoleColor(GetRoleType()); endGameManager.BackgroundBar.material.SetColor("_Color", customWinColor); break; + case (GameOverReason)NewModEndReasons.ShadeWin: + customWinText = "Darkness Consumes All"; + customWinColor = GetRoleColor(GetRoleType()); + endGameManager.BackgroundBar.material.SetColor("_Color", customWinColor); + break; default: customWinText = string.Empty; customWinColor = Color.white; @@ -218,6 +224,7 @@ public static bool Prefix(ShipStatus __instance) if (DestroyableSingleton.InstanceExists) return true; if (Time.timeSinceLevelLoad < 2f) return true; if (CheckForEndGameFaction(__instance, (GameOverReason)NewModEndReasons.WraithCallerWin)) return false; + if (CheckForEndGameFaction(__instance, (GameOverReason)NewModEndReasons.ShadeWin)) return false; if (CheckForEndGameFaction(__instance, (GameOverReason)NewModEndReasons.PulseBladeWin)) return false; if (CheckForEndGameFaction(__instance, (GameOverReason)NewModEndReasons.TyrantWin)) return false; if (CheckEndGameForRole(__instance, (GameOverReason)NewModEndReasons.DoubleAgentWin)) return false; @@ -277,6 +284,12 @@ public static bool CheckForEndGameFaction(ShipStatus __instance, GameO int current = WraithCallerUtilities.GetKillsNPC(player.PlayerId); shouldEndGame = current >= required; } + if (typeof(TFaction) == typeof(Shade)) + { + Shade.ShadeKills.TryGetValue(player.PlayerId, out var count); + int required = (int)OptionGroupSingleton.Instance.RequiredKills; + shouldEndGame = count >= required; + } if (shouldEndGame) { GameManager.Instance.RpcEndGame(winReason, false); diff --git a/NewMod/Patches/HudPatch.cs b/NewMod/Patches/HudPatch.cs new file mode 100644 index 0000000..4bc3769 --- /dev/null +++ b/NewMod/Patches/HudPatch.cs @@ -0,0 +1,15 @@ +using HarmonyLib; +using NewMod.Components.ScreenEffects; + +namespace NewMod.Patches +{ + [HarmonyPatch(typeof(HudManager), nameof(HudManager.Start))] + public static class HudPatch + { + public static void Postfix(HudManager __instance) + { + // For some reason the effect gets destroyed, so the best way to keep it persistent is to reassign it here + ShadowFluxEffect._shader = NewModAsset.ShadowFluxShader.LoadAsset(); + } + } +} \ No newline at end of file diff --git a/NewMod/Patches/MainMenuPatch.cs b/NewMod/Patches/MainMenuPatch.cs index 8684b7c..0e62cb4 100644 --- a/NewMod/Patches/MainMenuPatch.cs +++ b/NewMod/Patches/MainMenuPatch.cs @@ -9,6 +9,8 @@ using System.Collections.Generic; using System.Reflection; using System; +using NewMod.LocalSettings; +using MiraAPI.LocalSettings; namespace NewMod.Patches { @@ -31,7 +33,7 @@ public static void StartPostfix(MainMenuManager __instance) var cur = NewModAsset.CustomCursor.LoadAsset(); _cachedCursor = cur != null ? cur.texture : null; } - if (_cachedCursor != null) + if (_cachedCursor != null && LocalSettingsTabSingleton.Instance.EnableCustomCursor.Value) { Cursor.SetCursor(_cachedCursor, Vector2.zero, CursorMode.Auto); } diff --git a/NewMod/Patches/RolePatch.cs b/NewMod/Patches/RolePatch.cs new file mode 100644 index 0000000..64f4099 --- /dev/null +++ b/NewMod/Patches/RolePatch.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AmongUs.GameOptions; +using HarmonyLib; +using MiraAPI.GameOptions; +using MiraAPI.Roles; +using MiraAPI.Utilities; +using NewMod; +using NewMod.Options; +using NewMod.Roles; +using Reactor.Utilities; +using UnityEngine; +using Hazel; + +namespace NewMod.Patches +{ + [HarmonyPatch(typeof(RoleManager), nameof(RoleManager.SelectRoles))] + public static class SelectRolePatch + { + public static bool Prefix(RoleManager __instance) + { + if (!AmongUsClient.Instance.AmHost) + return true; + + Logger.Instance.LogMessage("-------------- SELECT ROLES: START --------------"); + Logger.Instance.LogMessage($"Running as host (clientId={AmongUsClient.Instance.ClientId})"); + + var opts = OptionGroupSingleton.Instance; + int target = Mathf.RoundToInt(opts.TotalNeutrals); + Logger.Instance.LogMessage($"Settings -> TotalNeutrals={opts.TotalNeutrals} → target={target}, KeepCrewMajority={opts.KeepCrewMajority}, PreferVariety={opts.PreferVariety}"); + + var allPlayers = GameData.Instance.AllPlayers.ToArray() + .Where(p => p?.Object && !p.IsDead && !p.Disconnected) + .ToList(); + + Logger.Instance.LogMessage($"Eligible players: {allPlayers.Count}"); + + var neutrals = allPlayers + .Where(p => p?.Object?.Data?.Role is ICustomRole cr && cr is INewModRole nm && + (nm.Faction == NewModFaction.Apex || nm.Faction == NewModFaction.Entropy)) + .Select(p => p.Object) + .ToList(); + + Logger.Instance.LogMessage($"Currently neutral (Apex/Entropy): {neutrals.Count}"); + + if (opts.KeepCrewMajority) + { + int crewCount = allPlayers.Count(p => + { + var rb = p?.Object?.Data?.Role; + if (rb == null) return false; + return rb is CrewmateRole || (!rb.IsImpostor && rb.TeamType == RoleTeamTypes.Crewmate); + }); + + int maxAllowed = Math.Max(0, (int)Math.Floor((crewCount - 1) / 2.0)); + int before = target; + target = Math.Min(target, maxAllowed); + Logger.Instance.LogMessage($"KeepCrewMajority applied -> crewCount={crewCount}, maxNeutrals={maxAllowed}, adjustedTarget={target} (was {before})"); + } + + int have = neutrals.Count; + if (have == target) + { + Logger.Instance.LogMessage("No change needed; exiting cleanly."); + Logger.Instance.LogMessage("-------------- SELECT ROLES: END (no-op) --------------"); + return false; + } + + if (have > target) + { + int remove = have - target; + Logger.Instance.LogMessage($"Too many neutrals; demoting {remove}"); + neutrals.Shuffle(); + + for (int i = 0; i < remove && i < neutrals.Count; i++) + { + var ply = neutrals[i]; + if (ply == null) continue; + + Logger.Instance.LogMessage($"→ Demoting {ply.Data.PlayerName} to Crewmate"); + ply.RpcSetRole(RoleTypes.Crewmate); + } + + Logger.Instance.LogMessage("Demotion complete."); + Logger.Instance.LogMessage("-------------- SELECT ROLES: END (demotions) --------------"); + return false; + } + + int need = target - have; + Logger.Instance.LogMessage($"Need to assign {need} more neutrals."); + + var crewElig = allPlayers + .Where(p => + { + var rb = p?.Object?.Data?.Role; + if (rb == null) return false; + bool isCrew = rb is CrewmateRole || (!rb.IsImpostor && rb.TeamType == RoleTeamTypes.Crewmate); + if (!isCrew) return false; + + if (rb is ICustomRole cr && cr is INewModRole nm2) + return nm2.Faction != NewModFaction.Apex && nm2.Faction != NewModFaction.Entropy; + + return true; + }) + .Select(p => p.Object) + .ToList(); + + Logger.Instance.LogMessage($"Crew eligible for neutral conversion: {crewElig.Count}"); + + if (crewElig.Count == 0) + { + Logger.Instance.LogMessage("No eligible crew found; aborting neutral assignment."); + Logger.Instance.LogMessage("-------------- SELECT ROLES: END (no candidates) --------------"); + return false; + } + + var active = CustomRoleUtils.GetActiveRoles().ToList(); + var candidates = new List(); + + foreach (var r in CustomRoleManager.CustomMiraRoles) + { + if (r is not INewModRole nm) continue; + if (nm.Faction != NewModFaction.Apex && nm.Faction != NewModFaction.Entropy) continue; + + var roleType = (RoleTypes)RoleId.Get(r.GetType()); + int already = active.Count(x => x && x.Role == roleType); + int left = r.Configuration.MaxRoleCount - already; + if (left <= 0) continue; + + int chance = r.GetChance().Value; + if (chance <= 0) continue; + + candidates.Add(new Candidate + { + Role = r, + Left = left, + Weight = chance, + RoleType = roleType + }); + } + + Logger.Instance.LogMessage($"Built neutral candidate list: {candidates.Count}"); + + if (candidates.Count == 0) + { + Logger.Instance.LogMessage("No candidates available; exiting."); + return false; + } + + var picks = new List(); + if (opts.PreferVariety) + { + var ordered = candidates.OrderByDescending(x => x.Weight).ToList(); + for (int i = 0; i < ordered.Count && picks.Count < need; i++) + { + var c = ordered[i]; + if (c.Left <= 0) continue; + picks.Add(c.Role); + c.Left--; + ordered[i] = c; + } + candidates = ordered; + } + + while (picks.Count < need) + { + var available = candidates.Where(c => c.Left > 0 && c.Weight > 0f).ToList(); + if (available.Count == 0) break; + + float total = available.Sum(c => c.Weight); + float rnum = UnityEngine.Random.Range(0f, total); + float acc = 0f; + var chosen = available.First(); + + foreach (var c in available) + { + acc += c.Weight; + if (rnum <= acc) + { + chosen = c; + break; + } + } + + picks.Add(chosen.Role); + + int idx = candidates.FindIndex(x => x.RoleType == chosen.RoleType); + if (idx >= 0) + { + var tmp = candidates[idx]; + tmp.Left--; + candidates[idx] = tmp; + } + } + + Logger.Instance.LogMessage($"Assigning {picks.Count} neutrals..."); + + foreach (var role in picks) + { + if (crewElig.Count == 0) break; + int idx = HashRandom.FastNext(crewElig.Count); + var pc = crewElig[idx]; + crewElig.RemoveAt(idx); + + var rt = (RoleTypes)RoleId.Get(role.GetType()); + Logger.Instance.LogMessage($"Assigning {role.GetType().Name} → {pc.Data.PlayerName}"); + pc.RpcSetRole(rt); + } + + Logger.Instance.LogMessage("Neutral assignment complete."); + Logger.Instance.LogMessage("-------------- SELECT ROLES: END --------------"); + return false; + } + public struct Candidate + { + public ICustomRole Role; + public int Left; + public float Weight; + public RoleTypes RoleType; + } + } + + // Inspired by https://github.com/AU-Avengers/TOU-Mira/blob/main/TownOfUs/Patches/RoleManagerPatches.cs#L747C3-L768C1 + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole))] + public static class RpcSetRolePatch + { + public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] RoleTypes roleType, [HarmonyArgument(1)] bool canOverrideRole = false) + { + if (AmongUsClient.Instance.AmClient) + __instance.StartCoroutine(__instance.CoSetRole(roleType, canOverrideRole)); + + var writer = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.SetRole, SendOption.Reliable); + writer.Write((ushort)roleType); + writer.Write(canOverrideRole); + AmongUsClient.Instance.FinishRpcImmediately(writer); + + Logger.Instance.LogMessage($"RpcSetRole: {__instance.Data.PlayerName} ({roleType})"); + return false; + } + } +} diff --git a/NewMod/Patches/Roles/Beacon/ShowMapPatch.cs b/NewMod/Patches/Roles/Beacon/ShowMapPatch.cs index 4fb2654..e63fb2b 100644 --- a/NewMod/Patches/Roles/Beacon/ShowMapPatch.cs +++ b/NewMod/Patches/Roles/Beacon/ShowMapPatch.cs @@ -3,7 +3,9 @@ using HarmonyLib; using MiraAPI.GameOptions; using MiraAPI.Utilities; +using NewMod.Components.ScreenEffects; using NewMod.Options.Roles.BeaconOptions; +using NewMod.Utilities; using Reactor.Utilities; using UnityEngine; using BC = NewMod.Roles.CrewmateRoles.Beacon; @@ -73,7 +75,11 @@ public static class BeaconOverlayTintKeeper static void Postfix(MapCountOverlay __instance) { if (PlayerControl.LocalPlayer.Data.Role is not BC) return; - if (Time.time >= BC.pulseUntil) return; + if (Time.time >= BC.pulseUntil) + { + var effect = Camera.main.GetComponent(); + Object.Destroy(effect); ; + } var map = MapBehaviour.Instance; if (!map || !map.IsOpen) return; diff --git a/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs b/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs index 7e989b8..08f5635 100644 --- a/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs +++ b/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs @@ -1,6 +1,7 @@ using HarmonyLib; using NewMod.Utilities; using NewMod.Roles.ImpostorRoles; +using NewMod.Roles.NeutralRoles; namespace NewMod.Patches.Roles.EnergyThief; @@ -17,6 +18,7 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] EndGam PranksterUtilities.ResetReportCount(); VisionaryUtilities.DeleteAllScreenshots(); WraithCallerUtilities.ClearAll(); + Shade.ShadeKills.Clear(); Revenant.HasUsedFeignDeath = false; Revenant.FeignDeathStates.Remove(PlayerControl.LocalPlayer.PlayerId); Revenant.StalkingStates[PlayerControl.LocalPlayer.PlayerId] = false; diff --git a/NewMod/Patches/SelectRolePatch.cs b/NewMod/Patches/SelectRolePatch.cs deleted file mode 100644 index b41fb8b..0000000 --- a/NewMod/Patches/SelectRolePatch.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AmongUs.GameOptions; -using HarmonyLib; -using MiraAPI.GameOptions; -using MiraAPI.Roles; -using MiraAPI.Utilities; -using NewMod; -using NewMod.Options; -using NewMod.Roles; -using Reactor.Utilities; -using UnityEngine; - -namespace NewMod.Patches -{ - [HarmonyPatch(typeof(RoleManager), nameof(RoleManager.SelectRoles))] - public static class SelectRolePatch - { - public static void Postfix(RoleManager __instance) - { - if (!AmongUsClient.Instance.AmHost) return; - - Logger.Instance.LogMessage("-------------- SELECT ROLES: START --------------"); - Logger.Instance.LogMessage( - $"SelectRoles Postfix entered on {(AmongUsClient.Instance.AmHost ? "HOST" : "CLIENT")} (clientId={AmongUsClient.Instance.ClientId})"); - - - var opts = OptionGroupSingleton.Instance; - int target = Mathf.RoundToInt(opts.TotalNeutrals); - Logger.Instance.LogMessage($"Config -> TotalNeutrals={opts.TotalNeutrals} (target={target}), KeepCrewMajority={opts.KeepCrewMajority}, PreferVariety={opts.PreferVariety}"); - - var all = GameData.Instance.AllPlayers.ToArray() - .Where(p => !p.IsDead && !p.Disconnected && p.Object) - .ToList(); - Logger.Instance.LogMessage($"Alive players eligible (all): {all.Count}"); - - var neutrals = all.Where(p => - { - var rb = p.Object.Data.Role; - return rb is ICustomRole cr && cr is INewModRole nm && - (nm.Faction == NewModFaction.Apex || nm.Faction == NewModFaction.Entropy); - }).Select(p => p.Object).ToList(); - Logger.Instance.LogMessage($"Current neutrals (Apex or Entropy): {neutrals.Count}"); - - if (opts.KeepCrewMajority) - { - int crewCount = all.Count(p => - { - var rb = p.Object.Data.Role; - if (!rb) return false; - return (rb is CrewmateRole) || (!rb.IsImpostor && rb.TeamType == RoleTeamTypes.Crewmate); - }); - - int maxAllowed = Math.Max(0, (int)Math.Floor((crewCount - 1) / 2.0)); - int before = target; - target = Math.Min(target, maxAllowed); - Logger.Instance.LogMessage($"KeepCrewMajority -> crewCount={crewCount}, maxAllowedNeutrals={maxAllowed}, target {before} => {target}"); - } - - int have = neutrals.Count; - Logger.Instance.LogMessage($"Neutral count check -> have={have}, target={target}"); - - if (have == target) - { - Logger.Instance.LogMessage("No changes needed, exiting early."); - Logger.Instance.LogMessage("-------------- SELECT ROLES: END (no-op) --------------"); - return; - } - - if (have > target) - { - int remove = have - target; - Logger.Instance.LogMessage($"Too many neutrals -> remove={remove}. Shuffling current neutrals..."); - neutrals.Shuffle(); - - for (int i = 0; i < remove && i < neutrals.Count; i++) - { - var ply = neutrals[i]; - Logger.Instance.LogMessage($"Demoting to Crewmate -> {ply.PlayerId}"); - ply.RpcSetRole(RoleTypes.Crewmate); - } - - Logger.Instance.LogMessage("Demotion phase complete."); - Logger.Instance.LogMessage("-------------- SELECT ROLES: END (demotions) --------------"); - return; - } - - int need = target - have; - Logger.Instance.LogMessage($"Need more neutrals -> need={need}"); - - var crewElig = all.Where(p => - { - var rb = p.Object.Data.Role; - if (!rb) return false; - bool isCrew = (rb is CrewmateRole) || (!rb.IsImpostor && rb.TeamType == RoleTeamTypes.Crewmate); - if (!isCrew) return false; - - if (rb is ICustomRole cr && cr is INewModRole nm2) - return nm2.Faction != NewModFaction.Apex && nm2.Faction != NewModFaction.Entropy; - - return true; - }).Select(p => p.Object).ToList(); - Logger.Instance.LogMessage($"Crew eligible for conversion -> count={crewElig.Count}"); - - if (crewElig.Count == 0) - { - Logger.Instance.LogMessage("No crew eligible to convert. Exiting."); - Logger.Instance.LogMessage("-------------- SELECT ROLES: END (no elig crew) --------------"); - return; - } - - var active = CustomRoleUtils.GetActiveRoles().ToList(); - Logger.Instance.LogMessage($"Active custom roles snapshot -> count={active.Count}"); - - var candidates = new List(); - - foreach (var r in CustomRoleManager.CustomMiraRoles) - { - if (r is not INewModRole nm) continue; - if (nm.Faction != NewModFaction.Apex && nm.Faction != NewModFaction.Entropy) continue; - - var roleType = (RoleTypes)RoleId.Get(r.GetType()); - int already = active.Count(x => x && x.Role == roleType); - int left = r.Configuration.MaxRoleCount - already; - if (left <= 0) continue; - - int chance = r.GetChance() ?? r.Configuration.DefaultChance; - float weight = chance; - if (weight <= 0f) continue; - - candidates.Add(new Candidate { Role = r, Left = left, Weight = weight, RoleType = roleType }); - Logger.Instance.LogMessage($"Candidate -> {r.GetType().Name} type={(ushort)roleType} left={left} weight={weight} already={already} max={r.Configuration.MaxRoleCount}"); - } - - Logger.Instance.LogMessage($"Candidate pool built -> count={candidates.Count}"); - if (candidates.Count == 0) - { - Logger.Instance.LogMessage("No candidates to assign. Exiting."); - Logger.Instance.LogMessage("-------------- SELECT ROLES: END (no candidates) --------------"); - return; - } - - var picks = new List(); - if (opts.PreferVariety) - { - Logger.Instance.LogMessage("PreferVariety enabled -> taking one of each highest weight until need is met."); - var ordered = candidates.OrderByDescending(x => x.Weight).ToList(); - for (int i = 0; i < ordered.Count && picks.Count < need; i++) - { - if (ordered[i].Left <= 0) continue; - picks.Add(ordered[i].Role); - Logger.Instance.LogMessage($"Variety pick -> {ordered[i].Role.GetType().Name}"); - var e = ordered[i]; e.Left -= 1; ordered[i] = e; - } - candidates = ordered; - } - - while (picks.Count < need) - { - var avail = candidates.Where(c => c.Left > 0 && c.Weight > 0f).ToList(); - if (avail.Count == 0) - { - Logger.Instance.LogMessage("No more available candidates with slots and weight. Breaking."); - break; - } - - float total = avail.Sum(c => c.Weight); - float rnum = UnityEngine.Random.Range(0f, total); - float acc = 0f; - var chosen = avail[0]; - - for (int i = 0; i < avail.Count; i++) - { - acc += avail[i].Weight; - if (rnum <= acc) { chosen = avail[i]; break; } - } - - picks.Add(chosen.Role); - Logger.Instance.LogMessage($"Weighted pick -> {chosen.Role.GetType().Name} (roll={rnum:F2} / total={total:F2})"); - - int gi = candidates.FindIndex(x => x.RoleType == chosen.RoleType); - if (gi >= 0) - { - candidates[gi] = new Candidate - { - Role = candidates[gi].Role, - Left = candidates[gi].Left - 1, - Weight = candidates[gi].Weight, - RoleType = candidates[gi].RoleType - }; - Logger.Instance.LogMessage($"Decrement slot -> {candidates[gi].Role.GetType().Name} now left={candidates[gi].Left}"); - } - } - - Logger.Instance.LogMessage($"Final picks -> count={picks.Count}. Starting assignment to crewElig={crewElig.Count}"); - - for (int i = 0; i < picks.Count && crewElig.Count > 0; i++) - { - int idx = HashRandom.FastNext(crewElig.Count); - var pc = crewElig[idx]; - crewElig.RemoveAt(idx); - - var rt = (RoleTypes)RoleId.Get(picks[i].GetType()); - Logger.Instance.LogMessage($"Assign -> playerId={pc.PlayerId} role={(ushort)rt} ({picks[i].GetType().Name})"); - pc.RpcSetRole(rt); - } - - Logger.Instance.LogMessage("Assignment phase complete."); - Logger.Instance.LogMessage("-------------- SELECT ROLES: END --------------"); - } - struct Candidate - { - public ICustomRole Role; - public int Left; - public float Weight; - public RoleTypes RoleType; - } - } -} diff --git a/NewMod/Resources/RoleIcons/DeployzoneIcon.png b/NewMod/Resources/RoleIcons/DeployzoneIcon.png new file mode 100644 index 0000000..f7e6f09 Binary files /dev/null and b/NewMod/Resources/RoleIcons/DeployzoneIcon.png differ diff --git a/NewMod/Resources/deployzone.png b/NewMod/Resources/deployzone.png new file mode 100644 index 0000000..6d74d7d Binary files /dev/null and b/NewMod/Resources/deployzone.png differ diff --git a/NewMod/Resources/newmod-android.bundle b/NewMod/Resources/newmod-android.bundle index 68cbb41..47bba7f 100644 Binary files a/NewMod/Resources/newmod-android.bundle and b/NewMod/Resources/newmod-android.bundle differ diff --git a/NewMod/Resources/newmod-win.bundle b/NewMod/Resources/newmod-win.bundle index a41b630..1d99211 100644 Binary files a/NewMod/Resources/newmod-win.bundle and b/NewMod/Resources/newmod-win.bundle differ diff --git a/NewMod/Resources/noise.png b/NewMod/Resources/noise.png new file mode 100644 index 0000000..bceb698 Binary files /dev/null and b/NewMod/Resources/noise.png differ diff --git a/NewMod/Roles/CrewmateRoles/Beacon.cs b/NewMod/Roles/CrewmateRoles/Beacon.cs index 15e06ab..7cb37ae 100644 --- a/NewMod/Roles/CrewmateRoles/Beacon.cs +++ b/NewMod/Roles/CrewmateRoles/Beacon.cs @@ -6,7 +6,12 @@ using MiraAPI.GameOptions; using MiraAPI.Roles; using MiraAPI.Utilities; +using NewMod.Components.ScreenEffects; +using NewMod.Networking; using NewMod.Options.Roles.BeaconOptions; +using NewMod.Utilities; +using Reactor.Networking.Rpc; +using Reactor.Utilities; using UnityEngine; namespace NewMod.Roles.CrewmateRoles @@ -90,7 +95,9 @@ public static void UpdateChargesFromTasks() grantedFromTasks = earned; Helpers.CreateAndShowNotification( $"+{delta} Beacon {(delta > 1 ? "charges" : "charge")} (tasks)", - new Color(0.75f, 0.65f, 1f), spr:NewModAsset.RadarIcon.LoadAsset()); + new Color(0.75f, 0.65f, 1f), spr: NewModAsset.RadarIcon.LoadAsset()); + + Rpc.Instance.Send(new BeaconPulseRpc.Data(settings.PulseDuration)); } } public static int GetCompletedTasks() diff --git a/NewMod/Roles/NeutralRoles/Shade.cs b/NewMod/Roles/NeutralRoles/Shade.cs new file mode 100644 index 0000000..0afc266 --- /dev/null +++ b/NewMod/Roles/NeutralRoles/Shade.cs @@ -0,0 +1,106 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Il2CppInterop.Runtime.Attributes; +using MiraAPI.Events; +using MiraAPI.Events.Vanilla.Gameplay; +using MiraAPI.GameOptions; +using MiraAPI.Roles; +using MiraAPI.Utilities; +using NewMod.Components; +using NewMod.Options.Roles.ShadeOptions; +using NewMod.Utilities; +using Reactor.Utilities; +using UnityEngine; + +namespace NewMod.Roles.NeutralRoles +{ + public class Shade : ImpostorRole, INewModRole + { + public static readonly Dictionary ShadeKills = new(); + public string RoleName => "Shade"; + public string RoleDescription => "Lurk. Fade. Kill unseen."; + public string RoleLongDescription => "Deploy a shadow field that grants invisibility and lethal power within its darkness."; + public Color RoleColor => new(0.45f, 0f, 0.8f); + public ModdedRoleTeams Team => ModdedRoleTeams.Custom; + public NewModFaction Faction => NewModFaction.Entropy; + public CustomRoleConfiguration Configuration => new(this) + { + AffectedByLightOnAirship = true, + CanUseSabotage = false, + CanUseVent = false, + UseVanillaKillButton = true, + TasksCountForProgress = false, + Icon = NewModAsset.DeployZoneIcon + }; + + [HideFromIl2Cpp] + public StringBuilder SetTabText() + { + var tabText = INewModRole.GetRoleTabText(this); + + int zonesActive = ShadowZone.zones.Count; + int playersInZones = Helpers.GetAlivePlayers().Count(p => ShadowZone.IsInsideAny(p.GetTruePosition())); + + var mode = OptionGroupSingleton.Instance.Behavior; + + tabText.AppendLine($"Within the dark you are unseen."); + tabText.AppendLine("\n"); + tabText.AppendLine($"Active Shadow Zones: {zonesActive}"); + tabText.AppendLine($"Players inside zones: {playersInZones}"); + + string effectText = mode switch + { + ShadeOptions.ShadowMode.Invisible => "Enter a shadow zone to become invisible.", + ShadeOptions.ShadowMode.KillEnabled => "Enter a shadow zone to gain the power to kill once.", + ShadeOptions.ShadowMode.Both => "Enter a shadow zone to become invisible and gain the power to kill once.", + _ => "Enter a shadow zone to embrace the darkness." + }; + + tabText.AppendLine($"\n{effectText}"); + + return tabText; + } + public override bool DidWin(GameOverReason gameOverReason) + { + return gameOverReason == (GameOverReason)NewModEndReasons.ShadeWin; + } + + [RegisterEvent] + public static void OnAfterMurder(AfterMurderEvent evt) + { + var killer = evt.Source; + var victim = evt.Target; + + Utils.RecordOnKill(killer, victim); + + if (killer.Data.Role is not Shade shadeRole) + return; + + if (!ShadowZone.IsInsideAny(victim.GetTruePosition())) + return; + + byte id = killer.PlayerId; + ShadeKills[id] = ShadeKills.GetValueOrDefault(id) + 1; + + if (killer.AmOwner) + { + int required = (int)OptionGroupSingleton.Instance.RequiredKills; + Coroutines.Start(CoroutinesHelper.CoNotify( + $"Shadow Harvest\nKills: {ShadeKills[id]}/{required}" + )); + } + } + [RegisterEvent] + public static void OnShadeRoleAssigned(SetRoleEvent evt) + { + if (evt.Player.AmOwner && evt.Player.Data.Role is Shade) + { + var hud = HudManager.Instance; + + hud.KillButton.currentTarget = null; + hud.KillButton.gameObject.SetActive(false); + } + } + } +} diff --git a/NewMod/Utilities/CoroutinesHelper.cs b/NewMod/Utilities/CoroutinesHelper.cs index 3a48032..23f1be4 100644 --- a/NewMod/Utilities/CoroutinesHelper.cs +++ b/NewMod/Utilities/CoroutinesHelper.cs @@ -436,7 +436,7 @@ public static IEnumerator DespawnCircle(GameObject go, float duration) /// /// Coroutine that waits for a given duration and then removes - /// specific visual effects (Earthquake, Glitch, SlowPulseHue) from a Camera. + /// specific visual effects from a Camera. /// /// The Camera to check for and remove effects from. /// The time in seconds to wait before removing the effects. @@ -451,6 +451,10 @@ public static IEnumerator RemoveCameraEffect(Camera cam, float duration) Object.Destroy(ge); if (cam.TryGetComponent(out var hue)) Object.Destroy(hue); + if (cam.TryGetComponent(out var dw)) + Object.Destroy(dw); + if (cam.TryGetComponent(out var sf)) + Object.Destroy(sf); } } } diff --git a/NewMod/Utilities/PranksterUtilities.cs b/NewMod/Utilities/PranksterUtilities.cs index e6787c6..5b5312c 100644 --- a/NewMod/Utilities/PranksterUtilities.cs +++ b/NewMod/Utilities/PranksterUtilities.cs @@ -23,7 +23,7 @@ public static void CreatePranksterDeadBody(PlayerControl player, byte parentId) { NewMod.Instance.Log.LogError("[PranksterUtilities] CreatePranksterDeadBody: Failed to create dead body, random player is null."); } - var deadBody = Object.Instantiate(GameManager.Instance.deadBodyPrefab[0]); + var deadBody = Object.Instantiate(GameManager.Instance.GetDeadBody(player.Data.Role)); deadBody.name = PranksterBodyName; deadBody.ParentId = parentId; diff --git a/NewMod/Utilities/VisionaryUtilities.cs b/NewMod/Utilities/VisionaryUtilities.cs index f088ed7..7759bc8 100644 --- a/NewMod/Utilities/VisionaryUtilities.cs +++ b/NewMod/Utilities/VisionaryUtilities.cs @@ -33,7 +33,7 @@ public static string ScreenshotDirectory { get { - string directory = Path.Combine(BepInEx.Paths.GameRootPath, "NewMod", "Screenshots"); + string directory = Path.Combine(Application.persistentDataPath, "NewMod", "Screenshots"); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); diff --git a/README.md b/README.md index 8b63c4d..1fb8be0 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ | v1.2.5 | v16.1.0 (2025.6.10) | [Download](https://github.com/CallOfCreator/NewMod/releases/download/V1.2.5/NewMod.zip) | | v1.2.6 | v16.1.0 (2025.6.10) | [Download](https://github.com/CallOfCreator/NewMod/releases/download/V1.2.6/NewMod.zip) | | v1.2.7 | v17.0.0 (2025.9.9) | [Download](https://github.com/CallOfCreator/NewMod/releases/download/V1.2.7/NewMod.zip) | +| v1.2.8 | v17.0.1 (2025.10.14) | [Download](https://github.com/CallOfCreator/NewMod/releases/download/V1.2.8/NewMod.zip) | --- @@ -96,9 +97,10 @@ NewMod is compatible with the following mods, enabling an enhanced experience wi | Mod ame | Mod Version | GitHub Link | Status | |-------------------|-------------|------------------------------------------------------|-------------| -| yanplaRoles | v0.1.6+ | [Download](https://github.com/yanpla/yanplaRoles) | ❌ Deprecated | +| yanplaRoles | v0.2.1+ | [Download](https://github.com/yanpla/yanplaRoles) | ✅ Supported | | LaunchpadReloaded | v0.3.4+ | [Download](https://github.com/All-Of-Us-Mods/LaunchpadReloaded) | ✅ Supported | | LevelImposter | v0.20.3+ | [Download](https://github.com/DigiWorm0/LevelImposter) | ✅ Supported | +| Submerged | v2025.10.22 | [Download](https://github.com/SubmergedAmongUs/Submerged) | ✅ Supported | --- diff --git a/libs/Android/AmongUs.GameLibs.Android.2025.9.9.nupkg b/libs/Android/AmongUs.GameLibs.Android.2025.9.9.nupkg deleted file mode 100644 index d09ec17..0000000 Binary files a/libs/Android/AmongUs.GameLibs.Android.2025.9.9.nupkg and /dev/null differ diff --git a/libs/MiraAPI.dll b/libs/MiraAPI.dll deleted file mode 100644 index d47ce6f..0000000 Binary files a/libs/MiraAPI.dll and /dev/null differ diff --git a/nuget.config b/nuget.config index 784b019..db78244 100644 --- a/nuget.config +++ b/nuget.config @@ -3,6 +3,5 @@ - \ No newline at end of file