diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0395685..3deace2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,29 +4,37 @@ on: [ "push", "pull_request" ] jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/cache@v4 + with: + path: | + ~/.nuget/packages + ~/.cache/bepinex + key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }} + restore-keys: | + ${{ runner.os }}-nuget- + - uses: actions/checkout@v4 with: submodules: true - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: dotnet-version: 6.x - - name: Run the Cake script - uses: cake-build/cake-action@v1 - with: - verbosity: Diagnostic + - name: Build + run: dotnet build PeasAPI/PeasAPI.csproj --configuration Release - - uses: actions/upload-artifact@v2 + - name: Upload PeasAPI + uses: actions/upload-artifact@v4 with: name: PeasAPI.dll - path: PeasAPI/bin/Release/netstandard2.1/PeasAPI.dll - - - uses: actions/upload-artifact@v2 + path: PeasAPI/bin/Release/net6.0/PeasAPI.dll + + - name: Upload PeasAPI.nupkg + uses: actions/upload-artifact@v4 with: name: PeasAPI.nupkg - path: PeasAPI/bin/Release/PeasAPI.*.nupkg + path: PeasAPI/bin/Release/PeasAPI-R.*.nupkg \ No newline at end of file diff --git a/.gitignore b/.gitignore index 95c2c8b..e54effa 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +.idea/ + # User-specific files *.rsuser *.suo @@ -13,9 +15,6 @@ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs -# Mono auto generated files -mono_crash.* - # Build results [Dd]ebug/ [Dd]ebugPublic/ @@ -29,7 +28,6 @@ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ -[Ll]ogs/ # Visual Studio 2015/2017 cache/options directory .vs/ @@ -43,10 +41,9 @@ Generated\ Files/ [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* -# NUnit +# NUNIT *.VisualState.xml TestResult.xml -nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ @@ -127,6 +124,9 @@ _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user +# JustCode is a .NET coding add-in +.JustCode + # TeamCity is a build add-in _TeamCity* @@ -184,8 +184,6 @@ PublishScripts/ # NuGet Packages *.nupkg -# NuGet Symbol Packages -*.snupkg # The packages folder can be ignored because of Package Restore **/[Pp]ackages/* # except build/, which is used as an MSBuild target. @@ -210,8 +208,6 @@ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx -*.appxbundle -*.appxupload # Visual Studio cache files # files ending in .cache can be ignored @@ -261,9 +257,7 @@ ServiceFabricBackup/ *.bim.layout *.bim_*.settings *.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl +*- Backup*.rdl # Microsoft Fakes FakesAssemblies/ @@ -299,6 +293,10 @@ paket-files/ # FAKE - F# Make .fake/ +# JetBrains Rider +.idea/ +*.sln.iml + # CodeRush personal settings .cr/personal @@ -341,58 +339,4 @@ ASALocalRun/ .localhistory/ # BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ - - -# Jetbrains Resharper/Rider - -# Common IntelliJ Platform excludes - -# User specific -**/.idea/ - -# Sensitive or high-churn files -**/.idea/**/dataSources/ -**/.idea/**/dataSources.ids -**/.idea/**/dataSources.xml -**/.idea/**/dataSources.local.xml -**/.idea/**/sqlDataSources.xml -**/.idea/**/dynamic.xml - -# Rider -# Rider auto-generates .iml files, and contentModel.xml -**/.idea/**/*.iml -**/.idea/**/contentModel.xml -**/.idea/**/modules.xml - -*.suo -*.user -.vs/ -[Bb]in/ -[Oo]bj/ -_UpgradeReport_Files/ -[Pp]ackages/ - -Thumbs.db -Desktop.ini -.DS_Store - - - -# Visual Studio Code - -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ \ No newline at end of file +healthchecksdb \ No newline at end of file diff --git a/PeasAPI/Components/RegisterCustomGameModeAttribute.cs b/PeasAPI/Components/RegisterCustomGameModeAttribute.cs index 6ab7acb..06a246e 100644 --- a/PeasAPI/Components/RegisterCustomGameModeAttribute.cs +++ b/PeasAPI/Components/RegisterCustomGameModeAttribute.cs @@ -1,9 +1,8 @@ using System; using System.Reflection; -using BepInEx.IL2CPP; +using BepInEx.Unity.IL2CPP; using HarmonyLib; using PeasAPI.GameModes; -using Reactor; namespace PeasAPI.Components { @@ -38,10 +37,7 @@ public static void Register(Assembly assembly, BasePlugin plugin) public static void Load() { - ChainloaderHooks.PluginLoad += plugin => - { - Register(plugin.GetType().Assembly, plugin); - }; + IL2CPPChainloader.Instance.PluginLoad += (pluginInfo, assembly, plugin) => Register(assembly, plugin); } } } \ No newline at end of file diff --git a/PeasAPI/Components/RegisterCustomRoleAttribute.cs b/PeasAPI/Components/RegisterCustomRoleAttribute.cs index 97e8cf7..1940d81 100644 --- a/PeasAPI/Components/RegisterCustomRoleAttribute.cs +++ b/PeasAPI/Components/RegisterCustomRoleAttribute.cs @@ -1,9 +1,8 @@ using System; using System.Reflection; -using BepInEx.IL2CPP; +using BepInEx.Unity.IL2CPP; using HarmonyLib; using PeasAPI.Roles; -using Reactor; namespace PeasAPI.Components { @@ -20,18 +19,19 @@ public static void Register(Assembly assembly, BasePlugin plugin) { foreach (var type in assembly.GetTypes()) { - var attribute = type.GetCustomAttribute(); + var attribute = type.GetCustomAttribute(); if (attribute != null) { if (!type.IsSubclassOf(typeof(BaseRole))) { - throw new InvalidOperationException($"Type {type.FullDescription()} must extend {nameof(BaseRole)}."); + throw new InvalidOperationException( + $"Type {type.FullDescription()} must extend {nameof(BaseRole)}."); } - + if (PeasAPI.Logging) PeasAPI.Logger.LogInfo($"Registered role {type.Name} from {type.Assembly.GetName().Name}"); - + Activator.CreateInstance(type, plugin); } } @@ -39,7 +39,7 @@ public static void Register(Assembly assembly, BasePlugin plugin) public static void Load() { - ChainloaderHooks.PluginLoad += plugin => Register(plugin.GetType().Assembly, plugin); + IL2CPPChainloader.Instance.PluginLoad += (pluginInfo, assembly, plugin) => Register(assembly, plugin); } } } \ No newline at end of file diff --git a/PeasAPI/CustomButtons/CustomButton.cs b/PeasAPI/CustomButtons/CustomButton.cs index 9c9c6e7..c5de091 100644 --- a/PeasAPI/CustomButtons/CustomButton.cs +++ b/PeasAPI/CustomButtons/CustomButton.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using AmongUs.GameOptions; using HarmonyLib; using UnityEngine; using Action = System.Action; @@ -13,10 +14,10 @@ public class CustomButton public static List Buttons = new List(); public static List VisibleButtons => Buttons.Where(button => button.Visible && button.CouldBeUsed()).ToList(); public static bool HudActive = true; - + private Color _startColorText = new Color(255, 255, 255); private Sprite _buttonSprite; - + public KillButton KillButtonManager; public Vector2 PositionOffset; public Vector2 TextOffset; @@ -105,19 +106,19 @@ private void Start() KillButtonManager.gameObject.SetActive(true); KillButtonManager.gameObject.name = "CustomButton"; KillButtonManager.transform.localScale = new Vector3(1, 1, 1); - + _startColorText = KillButtonManager.cooldownTimerText.color; - + KillButtonManager.graphic.enabled = true; KillButtonManager.graphic.sprite = _buttonSprite; - + KillButtonManager.buttonLabelText.enabled = UseText; KillButtonManager.buttonLabelText.text = Text; - KillButtonManager.buttonLabelText.transform.position += (Vector3) TextOffset + new Vector3(0f, 0.1f); - + KillButtonManager.buttonLabelText.transform.position += (Vector3)TextOffset + new Vector3(0f, 0.1f); + var button = KillButtonManager.GetComponent(); button.OnClick.RemoveAllListeners(); - button.OnClick.AddListener((UnityEngine.Events.UnityAction) listener); + button.OnClick.AddListener((UnityEngine.Events.UnityAction)listener); void listener() { @@ -137,7 +138,7 @@ void listener() } } } - + private void Update() { if (Target == TargetType.Player) @@ -167,7 +168,7 @@ private void Update() image = ObjectTarget.transform.FindChild("Sprite").GetComponent(); if (!image) image = ObjectTarget.GetComponentInChildren(); - + if (image) { image.material.SetFloat("_Outline", 0); @@ -181,7 +182,7 @@ private void Update() image = target.transform.FindChild("Sprite").GetComponent(); if (!image) image = target.GetComponentInChildren(); - + if (image) { image.material.SetFloat("_Outline", 1); @@ -202,7 +203,7 @@ private void Update() image = ObjectTarget.transform.FindChild("Sprite").GetComponent(); if (!image) image = ObjectTarget.GetComponentInChildren(); - + if (image) { image.material.SetFloat("_Outline", 0); @@ -216,7 +217,7 @@ private void Update() image = target.transform.FindChild("Sprite").GetComponent(); if (!image) image = target.GetComponentInChildren(); - + if (image) { image.material.SetFloat("_Outline", 1); @@ -248,7 +249,7 @@ private void Update() { KillButtonManager.cooldownTimerText.color = _startColorText; Cooldown = MaxCooldown; - + IsEffectActive = false; OnEffectEnd(); } @@ -257,16 +258,16 @@ private void Update() { if (CouldBeUsed() && Enabled) Cooldown -= Time.deltaTime; - + KillButtonManager.buttonLabelText.color = KillButtonManager.graphic.color = new Color(1f, 1f, 1f, 0.3f); } KillButtonManager.buttonLabelText.enabled = UseText; KillButtonManager.buttonLabelText.text = Text; - + KillButtonManager.gameObject.SetActive(CouldBeUsed()); KillButtonManager.graphic.enabled = CouldBeUsed(); - + if (CouldBeUsed()) { KillButtonManager.graphic.material.SetFloat("_Desat", 0f); @@ -277,13 +278,13 @@ private void Update() public bool CouldBeUsed() { - if (PlayerControl.LocalPlayer == null) + if (PlayerControl.LocalPlayer == null) return false; - - if (PlayerControl.LocalPlayer.Data == null) + + if (PlayerControl.LocalPlayer.Data == null) return false; - - if (MeetingHud.Instance != null) + + if (MeetingHud.Instance != null) return false; return _CouldBeUsed.Invoke(PlayerControl.LocalPlayer); @@ -296,7 +297,7 @@ public bool CanBeUsed() flag = PlayerTarget == null && ObjectTarget == null; return _CanBeUsed.Invoke(PlayerControl.LocalPlayer) && Usable && Cooldown < 0f && HudActive && !flag; } - + public void SetImage(Sprite image) { _buttonSprite = image; @@ -309,7 +310,7 @@ public void SetCoolDown(float cooldown, float? maxCooldown = null) MaxCooldown = maxCooldown.Value; KillButtonManager.SetCoolDown(Cooldown, MaxCooldown); } - + public bool IsCoolingDown() { return KillButtonManager.isCoolingDown; @@ -319,7 +320,7 @@ public PlayerControl FindClosestPlayer() { var from = PlayerControl.LocalPlayer; PlayerControl result = null; - float num = GameOptionsData.KillDistances[Mathf.Clamp(PlayerControl.GameOptions.KillDistance, 0, 2)]; + float num = LegacyGameOptions.KillDistances[Mathf.Clamp(GameOptionsManager.Instance.currentNormalGameOptions.KillDistance, 0, 2)]; if (!ShipStatus.Instance) { return null; @@ -355,8 +356,8 @@ public GameObject FindClosestObject() var from = PlayerControl.LocalPlayer; GameObject result1 = null; GameObject result2 = null; - float num1 = GameOptionsData.KillDistances[Mathf.Clamp(PlayerControl.GameOptions.KillDistance, 0, 2)]; - float num2 = GameOptionsData.KillDistances[Mathf.Clamp(PlayerControl.GameOptions.KillDistance, 0, 2)]; + float num1 = LegacyGameOptions.KillDistances[Mathf.Clamp(GameOptionsManager.Instance.currentNormalGameOptions.KillDistance, 0, 2)]; + float num2 = LegacyGameOptions.KillDistances[Mathf.Clamp(GameOptionsManager.Instance.currentNormalGameOptions.KillDistance, 0, 2)]; if (!ShipStatus.Instance) { return null; @@ -415,11 +416,11 @@ public static void Prefix(HudManager __instance) var button = Buttons[i]; var killButton = button.KillButtonManager; var canUse = button.CouldBeUsed(); - + Buttons[i].KillButtonManager.graphic.sprite = button._buttonSprite; - + killButton.gameObject.SetActive(button.Visible && canUse); - + killButton.buttonLabelText.enabled = canUse; killButton.buttonLabelText.alpha = killButton.isCoolingDown ? Palette.DisabledClear.a : Palette.EnabledColor.a; @@ -429,8 +430,8 @@ public static void Prefix(HudManager __instance) } } } - - [HarmonyPatch(typeof(HudManager), nameof(HudManager.SetHudActive))] + + [HarmonyPatch(typeof(HudManager), nameof(HudManager.SetHudActive), typeof(bool))] internal static class HudManagerSetHudActivePatch { public static void Prefix(HudManager __instance, [HarmonyArgument(0)] bool isActive) diff --git a/PeasAPI/CustomEndReason/CustomEndReason.cs b/PeasAPI/CustomEndReason/CustomEndReason.cs index a21edd1..0126373 100644 --- a/PeasAPI/CustomEndReason/CustomEndReason.cs +++ b/PeasAPI/CustomEndReason/CustomEndReason.cs @@ -1,8 +1,7 @@ using System.Collections.Generic; -using Hazel; using PeasAPI.CustomRpc; using PeasAPI.Roles; -using Reactor.Networking; +using Reactor.Networking.Rpc; using UnityEngine; namespace PeasAPI.CustomEndReason @@ -12,7 +11,7 @@ public class CustomEndReason /// /// Ends the game with the specified values /// - public CustomEndReason(Color color, string victoryText, string defeatText, string stinger, List winners) + public CustomEndReason(Color color, string victoryText, string defeatText, string stinger, List winners) { Rpc.Instance.Send(new RpcCustomEndReason.Data(color, victoryText, defeatText, stinger, winners)); } @@ -22,7 +21,7 @@ public CustomEndReason(Color color, string victoryText, string defeatText, strin /// public CustomEndReason(PlayerControl player) { - var role = player.GetRole(); + var role = player.GetCustomRole(); if (role == null) { @@ -37,7 +36,7 @@ public CustomEndReason(PlayerControl player) _winners.Add(_player.PlayerId); } - var winners = new List(); + var winners = new List(); foreach (var winner in _winners) { winners.Add(winner.GetPlayerInfo()); @@ -61,7 +60,7 @@ public CustomEndReason(PlayerControl player) _winners.Add(_player.PlayerId); } - var winners = new List(); + var winners = new List(); foreach (var winner in _winners) { winners.Add(winner.GetPlayerInfo()); @@ -108,12 +107,12 @@ public CustomEndReason(PlayerControl player) _winners.Add(player.PlayerId); foreach (var _player in GameData.Instance.AllPlayers) { - if (_player.PlayerId != player.PlayerId && _player.GetRole() == player.GetRole()) + if (_player.PlayerId != player.PlayerId && _player.GetCustomRole() == player.GetCustomRole()) _winners.Add(_player.PlayerId); } } - var winners = new List(); + var winners = new List(); foreach (var winner in _winners) { winners.Add(winner.GetPlayerInfo()); diff --git a/PeasAPI/CustomEndReason/EndReasonManager.cs b/PeasAPI/CustomEndReason/EndReasonManager.cs index 5a935f1..c671b3e 100644 --- a/PeasAPI/CustomEndReason/EndReasonManager.cs +++ b/PeasAPI/CustomEndReason/EndReasonManager.cs @@ -10,7 +10,7 @@ public class EndReasonManager public static Color Color; - public static List Winners; + public static List Winners; public static string VictoryText; @@ -37,13 +37,13 @@ private class SetEverythingUpPatch public static bool Prefix(EndGameManager __instance) { - if (TempData.EndReason != CustomGameOverReason) + if (EndGameResult.CachedGameOverReason != CustomGameOverReason) return true; - List _winners = new List(); + List _winners = new List(); foreach (var winner in Winners) { - _winners.Add(new WinningPlayerData(winner)); + _winners.Add(new CachedPlayerData(winner)); } __instance.DisconnectStinger = Stinger switch @@ -82,9 +82,9 @@ public static bool Prefix(EndGameManager __instance) transform.localScale = scaleVec; if (winner.IsDead) { - player.BodySprites.ToArray()[0].BodySprite.sprite = __instance.GhostSprite; + player.cosmetics.bodySprites.ToArray()[0].BodySprite.sprite = __instance.GhostSprite; player.SetDeadFlipX(i % 2 == 1); - player.HatSlot.color = GhostColor; + player.cosmetics.SetHatColor(GhostColor); } else { @@ -92,11 +92,11 @@ public static bool Prefix(EndGameManager __instance) player.SetSkin(winner.SkinId, winner.ColorId); } - PlayerControl.SetPlayerMaterialColors(winner.ColorId, player.CurrentBodySprite.BodySprite); - player.HatSlot.SetHat(winner.HatId, winner.ColorId); - PlayerControl.SetPetImage(winner.PetId, winner.ColorId, player.PetSlot); - player.NameText.text = winner.PlayerName; - player.NameText.transform.SetLocalZ(-15f); + PlayerMaterial.SetColors(winner.ColorId, player.cosmetics.currentBodySprite.BodySprite); + player.cosmetics.SetHat(winner.HatId, winner.ColorId); + //PlayerControl.SetPetImage(winner.PetId, winner.ColorId, player.PetSlot); + player.cosmetics.nameText.text = winner.PlayerName; + player.cosmetics.nameText.transform.SetLocalZ(-15f); } SoundManager.Instance.PlaySound(__instance.DisconnectStinger, false, 1f); @@ -110,7 +110,7 @@ private class AdjustEndScreenPatch { public static void Prefix(EndGameManager __instance) { - if (TempData.EndReason != CustomGameOverReason) + if (EndGameResult.CachedGameOverReason != CustomGameOverReason) return; __instance.DisconnectStinger = Stinger switch @@ -137,7 +137,7 @@ public static void Prefix(EndGameManager __instance) public static void Postfix(EndGameManager __instance) { - if (TempData.EndReason != CustomGameOverReason) + if (EndGameResult.CachedGameOverReason != CustomGameOverReason) return; Reset(); diff --git a/PeasAPI/CustomRpc/RpcCustomCheckColor.cs b/PeasAPI/CustomRpc/RpcCustomCheckColor.cs index 4cde6dc..ad5f34e 100644 --- a/PeasAPI/CustomRpc/RpcCustomCheckColor.cs +++ b/PeasAPI/CustomRpc/RpcCustomCheckColor.cs @@ -1,6 +1,6 @@ using Hazel; -using Reactor; -using Reactor.Networking; +using Reactor.Networking.Attributes; +using Reactor.Networking.Rpc; namespace PeasAPI.CustomRpc { diff --git a/PeasAPI/CustomRpc/RpcCustomEndReason.cs b/PeasAPI/CustomRpc/RpcCustomEndReason.cs index 98841f1..aa27497 100644 --- a/PeasAPI/CustomRpc/RpcCustomEndReason.cs +++ b/PeasAPI/CustomRpc/RpcCustomEndReason.cs @@ -2,8 +2,8 @@ using System.Linq; using Hazel; using PeasAPI.CustomEndReason; -using Reactor; -using Reactor.Networking; +using Reactor.Networking.Attributes; +using Reactor.Networking.Rpc; using UnityEngine; namespace PeasAPI.CustomRpc @@ -21,9 +21,9 @@ public readonly struct Data public readonly string VictoryText; public readonly string DefeatText; public readonly string Stinger; - public readonly List Winners; + public readonly List Winners; - public Data(Color color, string victoryText, string defeatText, string stinger, List winners) + public Data(Color color, string victoryText, string defeatText, string stinger, List winners) { Color = color; VictoryText = victoryText; @@ -66,7 +66,7 @@ public override Data Read(MessageReader reader) var winnerCount = reader.ReadInt32(); var _winners = reader.ReadBytes(winnerCount).ToList(); - var winners = new List(); + var winners = new List(); foreach (var winner in _winners) { winners.Add(winner.GetPlayerInfo()); @@ -88,7 +88,7 @@ public override void Handle(PlayerControl innerNetObject, Data data) EndReasonManager.Stinger = data.Stinger; if (AmongUsClient.Instance.AmHost) - ShipStatus.RpcEndGame(EndReasonManager.CustomGameOverReason, false); + GameManager.Instance.RpcEndGame(EndReasonManager.CustomGameOverReason, false); } } } \ No newline at end of file diff --git a/PeasAPI/CustomRpc/RpcInitializeRoles.cs b/PeasAPI/CustomRpc/RpcInitializeRoles.cs index a95001e..cd55720 100644 --- a/PeasAPI/CustomRpc/RpcInitializeRoles.cs +++ b/PeasAPI/CustomRpc/RpcInitializeRoles.cs @@ -4,9 +4,9 @@ using PeasAPI.CustomEndReason; using PeasAPI.GameModes; using PeasAPI.Roles; -using Reactor; -using Reactor.Extensions; -using Reactor.Networking; +using Reactor.Networking.Attributes; +using Reactor.Networking.Rpc; +using Reactor.Utilities.Extensions; namespace PeasAPI.CustomRpc { @@ -27,7 +27,7 @@ public override void Handle(PlayerControl innerNetObject) EndReasonManager.Reset(); - if (AmongUsClient.Instance.GameMode != global::GameModes.FreePlay) + if (AmongUsClient.Instance.NetworkMode != global::NetworkModes.FreePlay) { var rolesForPlayers = new List(); @@ -41,8 +41,8 @@ public override void Handle(PlayerControl innerNetObject) } var roles2 = (from role in Roles.RoleManager.Roles - where role.GetCount() > 0 && role.GetChance() > 0 && role.GetChance() < 100 - select role).ToList(); + where role.GetCount() > 0 && role.GetChance() > 0 && role.GetChance() < 100 + select role).ToList(); foreach (var role in roles2) { for (int i = 0; i < role.GetCount(); i++) @@ -50,7 +50,7 @@ where role.GetCount() > 0 && role.GetChance() > 0 && role.GetChance() < 100 rolesForPlayers.Add(role); } } - + for (int i = 0; i < roles2.Count;) { var role = roles2.Random(); @@ -59,7 +59,7 @@ where role.GetCount() > 0 && role.GetChance() > 0 && role.GetChance() < 100 temp.Remove(role); roles2 = temp; } - + rolesForPlayers.Do(AssignRole); } } @@ -86,14 +86,14 @@ private void AssignRole(BaseRole role) { var nonRoleImpostors = Roles.RoleManager.Impostors.Where(id => id.GetPlayer().Data.Role.IsSimpleRole && - !RoleManager.IsGhostRole(id.GetPlayerInfo().Role.Role) && id.GetPlayer().GetRole() == null) + !RoleManager.IsGhostRole(id.GetPlayerInfo().Role.Role) && id.GetPlayer().GetCustomRole() == null) .ToArray(); - + if (nonRoleImpostors.Length == 0) return; if (Roles.RoleManager.HostMod.IsRole.ContainsKey(role) && Roles.RoleManager.HostMod.IsRole[role] && - PlayerControl.LocalPlayer.GetRole() == null) + PlayerControl.LocalPlayer.GetCustomRole() == null) { PlayerControl.LocalPlayer.RpcSetRole(role); return; @@ -111,19 +111,19 @@ private void AssignRole(BaseRole role) { var nonRoleCrewmates = Roles.RoleManager.Crewmates.Where(id => id.GetPlayer().Data.Role.IsSimpleRole && - !RoleManager.IsGhostRole(id.GetPlayerInfo().Role.Role) && id.GetPlayer().GetRole() == null) + !RoleManager.IsGhostRole(id.GetPlayerInfo().Role.Role) && id.GetPlayer().GetCustomRole() == null) .ToArray(); - + if (nonRoleCrewmates.Length == 0) return; - + if (Roles.RoleManager.HostMod.IsRole.ContainsKey(role) && Roles.RoleManager.HostMod.IsRole[role] && - PlayerControl.LocalPlayer.GetRole() == null) + PlayerControl.LocalPlayer.GetCustomRole() == null) { PlayerControl.LocalPlayer.RpcSetRole(role); return; } - + var chance = HashRandom.Next(101); if (chance < role.GetChance()) { diff --git a/PeasAPI/CustomRpc/RpcResetRoles.cs b/PeasAPI/CustomRpc/RpcResetRoles.cs index 9df2f3f..b1261bd 100644 --- a/PeasAPI/CustomRpc/RpcResetRoles.cs +++ b/PeasAPI/CustomRpc/RpcResetRoles.cs @@ -1,6 +1,5 @@ -using PeasAPI.Roles; -using Reactor; -using Reactor.Networking; +using Reactor.Networking.Attributes; +using Reactor.Networking.Rpc; namespace PeasAPI.CustomRpc { diff --git a/PeasAPI/CustomRpc/RpcSetColor.cs b/PeasAPI/CustomRpc/RpcSetColor.cs index 47bc526..08d1f6f 100644 --- a/PeasAPI/CustomRpc/RpcSetColor.cs +++ b/PeasAPI/CustomRpc/RpcSetColor.cs @@ -1,6 +1,6 @@ using Hazel; -using Reactor; -using Reactor.Networking; +using Reactor.Networking.Attributes; +using Reactor.Networking.Rpc; namespace PeasAPI.CustomRpc { diff --git a/PeasAPI/CustomRpc/RpcSetRole.cs b/PeasAPI/CustomRpc/RpcSetRole.cs index fb46441..d54e14a 100644 --- a/PeasAPI/CustomRpc/RpcSetRole.cs +++ b/PeasAPI/CustomRpc/RpcSetRole.cs @@ -1,7 +1,7 @@ using Hazel; using PeasAPI.Roles; -using Reactor; -using Reactor.Networking; +using Reactor.Networking.Attributes; +using Reactor.Networking.Rpc; namespace PeasAPI.CustomRpc { @@ -47,7 +47,7 @@ public override Data Read(MessageReader reader) public override void Handle(PlayerControl innerNetObject, Data data) { - data.Player.SetRole(data.Role); + data.Player.SetCustomRole(data.Role); } } } \ No newline at end of file diff --git a/PeasAPI/CustomRpc/RpcSetVanillaRole.cs b/PeasAPI/CustomRpc/RpcSetVanillaRole.cs index 3abbd68..7dd15e8 100644 --- a/PeasAPI/CustomRpc/RpcSetVanillaRole.cs +++ b/PeasAPI/CustomRpc/RpcSetVanillaRole.cs @@ -1,6 +1,7 @@ -using Hazel; -using Reactor; -using Reactor.Networking; +using AmongUs.GameOptions; +using Hazel; +using Reactor.Networking.Attributes; +using Reactor.Networking.Rpc; namespace PeasAPI.CustomRpc { diff --git a/PeasAPI/CustomRpc/RpcShowMessage.cs b/PeasAPI/CustomRpc/RpcShowMessage.cs index 59b785b..9b4208c 100644 --- a/PeasAPI/CustomRpc/RpcShowMessage.cs +++ b/PeasAPI/CustomRpc/RpcShowMessage.cs @@ -2,8 +2,8 @@ using HarmonyLib; using Hazel; using PeasAPI.Managers; -using Reactor; -using Reactor.Networking; +using Reactor.Networking.Attributes; +using Reactor.Networking.Rpc; namespace PeasAPI.CustomRpc { diff --git a/PeasAPI/CustomRpc/RpcUpdateSetting.cs b/PeasAPI/CustomRpc/RpcUpdateSetting.cs index 513dc9b..2985b2d 100644 --- a/PeasAPI/CustomRpc/RpcUpdateSetting.cs +++ b/PeasAPI/CustomRpc/RpcUpdateSetting.cs @@ -1,72 +1,143 @@ -using Hazel; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using HarmonyLib; +using Hazel; using PeasAPI.Options; -using Reactor; -using Reactor.Networking; +using UnityEngine; namespace PeasAPI.CustomRpc { - [RegisterCustomRpc((uint) CustomRpcCalls.UpdateSetting)] - public class RpcUpdateSetting : PlayerCustomRpc + public static class RpcUpdateSetting { - public RpcUpdateSetting(PeasAPI plugin, uint id) : base(plugin, id) + public static IEnumerator SendRpc(CustomOption optionn = null, int RecipientId = -1) { - } + yield return new WaitForSecondsRealtime(0.5f); - public readonly struct Data - { - public readonly CustomOption Option; - public readonly object Value; + List options; + if (optionn != null) + options = new List { optionn }; + else + options = CustomOption.AllOptions; + + var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, + (byte)CustomRpcCalls.UpdateSetting, SendOption.Reliable, RecipientId); - public Data(CustomOption option, object value) + foreach (var option in options) { - Option = option; - Value = value; - } - } + if (option.Type == CustomOptionType.Header) continue; - public override RpcLocalHandling LocalHandling => RpcLocalHandling.None; + if (writer.Position > 1000) + { + AmongUsClient.Instance.FinishRpcImmediately(writer); + writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, + (byte)CustomRpcCalls.UpdateSetting, SendOption.Reliable, RecipientId); + } - public override void Write(MessageWriter writer, Data data) - { - writer.Write(data.Option.Id); - - if (data.Option.GetType() == typeof(CustomToggleOption)) - writer.Write((bool) data.Value); - else if (data.Option.GetType() == typeof(CustomNumberOption)) - writer.Write((float) data.Value); - else if (data.Option.GetType() == typeof(CustomStringOption)) - writer.Write((int) data.Value); - - //PeasApi.Logger.LogInfo("1: " + data.Option.Id + " " + data.Value); + writer.WritePacked(option.ID); + + switch (option.Type) + { + case CustomOptionType.Toggle: + writer.Write((bool)option.ValueObject); + break; + case CustomOptionType.Number: + { + switch ((option as CustomNumberOption).IntSafe) + { + case true: + writer.WritePacked((int)(float)option.ValueObject); + break; + case false: + writer.Write((float)option.ValueObject); + break; + } + + } + break; + case CustomOptionType.Role: + { + writer.WritePacked(Convert.ToInt32(option.ValueObject)); + option.BaseRole.Chance = Convert.ToInt32(option.ValueObject); + writer.WritePacked(Convert.ToInt32(option.ValueObject2)); + option.BaseRole.Count = option.BaseRole.MaxCount = Convert.ToInt32(option.ValueObject2); + } + break; + case CustomOptionType.String: + writer.WritePacked((int)option.ValueObject); + break; + } + } + + AmongUsClient.Instance.FinishRpcImmediately(writer); } - public override Data Read(MessageReader reader) + public static void ReceiveRpc(MessageReader reader, bool AllOptions) { - //PeasApi.Logger.LogInfo("2"); - var id = reader.ReadString(); - //PeasApi.Logger.LogInfo("2b: " + id); - var option = OptionManager.CustomOptions.Find(_option => _option.Id == id); - object value = null; + PeasAPI.Logger.LogInfo( + $"Options received - {reader.BytesRemaining} bytes"); + while (reader.BytesRemaining > 0) + { + var id = reader.ReadPackedInt32(); + var customOption = + CustomOption.AllOptions.FirstOrDefault(option => + option.ID == id); // Works but may need to change to gameObject.name check + var type = customOption?.Type; + object value = null; + object value2 = null; + + switch (type) + { + case CustomOptionType.Toggle: + value = reader.ReadBoolean(); + break; + case CustomOptionType.Number: + switch ((customOption as CustomNumberOption).IntSafe) + { + case true: + value = (float)reader.ReadPackedInt32(); + break; + case false: + value = reader.ReadSingle(); + break; + } + + break; + case CustomOptionType.Role: + value = reader.ReadPackedInt32(); + value2 = reader.ReadPackedInt32(); + break; + case CustomOptionType.String: + value = reader.ReadPackedInt32(); + break; + } - if (option.GetType() == typeof(CustomToggleOption)) - value = reader.ReadBoolean(); - else if (option.GetType() == typeof(CustomNumberOption)) - value = reader.ReadSingle(); - else if (option.GetType() == typeof(CustomStringOption)) - value = reader.ReadInt32(); - - return new Data(option, value); + customOption?.Set(value, value2, Notify: !AllOptions); + + if (LobbyInfoPane.Instance.LobbyViewSettingsPane.gameObject.activeSelf) + { + var panels = GameObject.FindObjectsOfType(); + foreach (var panel in panels) + if (panel.titleText.text == customOption.GetName() && + customOption.Type != CustomOptionType.Header) + panel.settingText.text = customOption.ToString(); + } + } } - public override void Handle(PlayerControl innerNetObject, Data data) + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.HandleRpc))] + public static class HandleRpc { - //PeasApi.Logger.LogInfo("4: " + data.Value.GetType()); - if (data.Option.GetType() == typeof(CustomToggleOption)) - ((CustomToggleOption) data.Option).SetValue((bool) data.Value); - else if (data.Option.GetType() == typeof(CustomNumberOption)) - ((CustomNumberOption) data.Option).SetValue((float) data.Value); - else if (data.Option.GetType() == typeof(CustomStringOption)) - ((CustomStringOption) data.Option).SetValue((int) data.Value); + private static void Postfix([HarmonyArgument(0)] byte callId, [HarmonyArgument(1)] MessageReader reader) + { + switch (callId) + { + case (byte)CustomRpcCalls.UpdateSetting: + ReceiveRpc(reader, reader.BytesRemaining > 8); + break; + } + } } } } \ No newline at end of file diff --git a/PeasAPI/Data.cs b/PeasAPI/Data.cs index 1331e56..6843ba5 100644 --- a/PeasAPI/Data.cs +++ b/PeasAPI/Data.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using Reactor.Extensions; +using System.Collections.Generic; using UnityEngine; namespace PeasAPI @@ -34,127 +30,5 @@ public CustomIntroScreen(bool overrideTeam = false, string team = null, string t RoleColor = roleColor.GetValueOrDefault(); } } - - public readonly struct Hat - { - public readonly string Name; - public readonly string ImagePath; - public readonly Assembly Assembly; - public readonly bool InFront; - public readonly bool NoBounce; - public readonly Vector2 ChipOffset; - public readonly Sprite BackImage; - public readonly Sprite FloorImage; - - public Hat(string name, string imagePath, Assembly assembly, bool inFront, bool noBounce, Vector2 chipOffset, Sprite backImage, Sprite floorImage) - { - Name = name; - ImagePath = imagePath; - Assembly = assembly; - InFront = inFront; - NoBounce = noBounce; - ChipOffset = chipOffset; - BackImage = backImage; - FloorImage = floorImage; - } - - public HatData CreateHat() - { - try - { - Texture2D tex = new Texture2D(128, 128, TextureFormat.ARGB32, false); - Stream myStream = Assembly.GetManifestResourceStream(ImagePath); - byte[] data = myStream.ReadFully(); - ImageConversion.LoadImage(tex, data, false); - - var newHat = ScriptableObject.CreateInstance(); - newHat.hatViewData.viewData = ScriptableObject.CreateInstance(); - newHat.hatViewData.viewData.MainImage = newHat.hatViewData.viewData.LeftMainImage = Sprite.Create( - tex, - new Rect(0, 0, tex.width, tex.height), - new Vector2(0.53f, 0.575f), - tex.width * 0.375f - ); - - newHat.ProductId = $"+{Name}"; - newHat.displayOrder += 100; - newHat.Free = true; - newHat.StoreName = Name; - newHat.name = Name; - - newHat.InFront = InFront; - newHat.NoBounce = NoBounce; - newHat.ChipOffset = ChipOffset; - newHat.hatViewData.viewData.BackImage = newHat.hatViewData.viewData.LeftBackImage = BackImage; - newHat.hatViewData.viewData.ClimbImage = newHat.hatViewData.viewData.LeftClimbImage = BackImage; - newHat.hatViewData.viewData.FloorImage = newHat.hatViewData.viewData.LeftFloorImage = FloorImage; - - return newHat; - } - catch (Exception e) - { - PeasAPI.Logger.LogError($"Error while creating a hat: {e}"); - } - - return null; - } - } - - public readonly struct Visor - { - public readonly string Name; - public readonly string ImagePath; - public readonly Assembly Assembly; - public readonly Vector2 ChipOffset; - public readonly Sprite ClimbImage; - public readonly Sprite FloorImage; - - public Visor(string name, string imagePath, Assembly assembly, Vector2 chipOffset, Sprite climbImage, Sprite floorImage) - { - Name = name; - ImagePath = imagePath; - Assembly = assembly; - ChipOffset = chipOffset; - ClimbImage = climbImage; - FloorImage = floorImage; - } - - public VisorData CreateVisor() - { - try - { - Texture2D tex = new Texture2D(128, 128, TextureFormat.ARGB32, false); - Stream myStream = Assembly.GetManifestResourceStream(ImagePath); - byte[] data = myStream.ReadFully(); - ImageConversion.LoadImage(tex, data, false); - - var newVisor = ScriptableObject.CreateInstance(); - newVisor.viewData.viewData = ScriptableObject.CreateInstance(); - newVisor.viewData.viewData.IdleFrame = newVisor.viewData.viewData.LeftIdleFrame = Sprite.Create( - tex, - new Rect(0, 0, tex.width, tex.height), - new Vector2(0.53f, 0.575f), - tex.width * 0.375f - ); - - newVisor.ProductId = $"+{Name}"; - newVisor.displayOrder += 100; - newVisor.Free = true; - newVisor.name = Name; - - newVisor.ChipOffset = ChipOffset; - newVisor.viewData.viewData.ClimbFrame = ClimbImage; - newVisor.viewData.viewData.FloorFrame = FloorImage; - - return newVisor; - } - catch (Exception e) - { - PeasAPI.Logger.LogError($"Error while creating a visor: {e.StackTrace}"); - } - - return null; - } - } } } \ No newline at end of file diff --git a/PeasAPI/Data.json b/PeasAPI/Data.json deleted file mode 100644 index 8539a3c..0000000 --- a/PeasAPI/Data.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "version": "1.7.0", - "downloadUrl": "https://github.com/Peasplayer/PeasAPI/releases/download/1.7.0/PeasAPI.dll" -} diff --git a/PeasAPI/Enums/FileType.cs b/PeasAPI/Enums/FileType.cs index 89b6fa3..a6ab859 100644 --- a/PeasAPI/Enums/FileType.cs +++ b/PeasAPI/Enums/FileType.cs @@ -1,6 +1,4 @@ -using System; - -namespace PeasAPI.Enums +namespace PeasAPI.Enums { public enum FileType { diff --git a/PeasAPI/Extensions.cs b/PeasAPI/Extensions.cs index 761240b..c16e03e 100644 --- a/PeasAPI/Extensions.cs +++ b/PeasAPI/Extensions.cs @@ -1,14 +1,11 @@ using System.Linq; -using HarmonyLib; +using AmongUs.GameOptions; using PeasAPI.CustomRpc; using PeasAPI.Options; using PeasAPI.Roles; -using Reactor.Extensions; -using Reactor.Networking; -using Reactor.Networking.MethodRpc; -using UnhollowerBaseLib; +using Reactor.Networking.Rpc; +using Reactor.Utilities.Extensions; using UnityEngine; -using Object = Il2CppSystem.Object; namespace PeasAPI { @@ -23,9 +20,9 @@ public static PlayerControl GetPlayer(this byte id) } /// - /// Gets a from it's id + /// Gets a from it's id /// - public static GameData.PlayerInfo GetPlayerInfo(this byte id) + public static NetworkedPlayerInfo GetPlayerInfo(this byte id) { return GameData.Instance.GetPlayerById(id); } @@ -39,12 +36,12 @@ public static void ToggleOutline(this PlayerControl player, bool active, Color c { if (active) { - player.MyRend.material.SetFloat("_Outline", 1f); - player.MyRend.material.SetColor("_OutlineColor", color); + player.cosmetics.currentBodySprite.BodySprite.material.SetFloat("_Outline", 1f); + player.cosmetics.currentBodySprite.BodySprite.material.SetColor("_OutlineColor", color); return; } - player.MyRend.material.SetFloat("_Outline", 0f); + player.cosmetics.currentBodySprite.BodySprite.material.SetFloat("_Outline", 0f); } public static Color SetAlpha(this Color original, float alpha) @@ -93,8 +90,10 @@ public static string GetTranslation(this StringNames stringName) /// /// Gets the role of a /// - public static BaseRole GetRole(this PlayerControl player) + public static BaseRole GetCustomRole(this PlayerControl player) { + if (Roles.RoleManager.Roles == null) return null; + foreach (var _role in Roles.RoleManager.Roles) { if (_role.Members.Contains(player.PlayerId)) @@ -103,11 +102,19 @@ public static BaseRole GetRole(this PlayerControl player) return null; } - + + public static bool IsCustomRole(this PlayerControl player) + { + if (player == null) + return false; + + return GetCustomRole(player) != null; + } + /// - /// Gets the role of a + /// Gets the role of a /// - public static BaseRole GetRole(this GameData.PlayerInfo player) + public static BaseRole GetCustomRole(this NetworkedPlayerInfo player) { foreach (var _role in Roles.RoleManager.Roles) { @@ -121,25 +128,25 @@ public static BaseRole GetRole(this GameData.PlayerInfo player) /// /// Checks if a is a certain role /// - public static bool IsRole(this PlayerControl player, BaseRole role) => player.GetRole() == role; + public static bool IsCustomRole(this PlayerControl player, BaseRole role) => player.GetCustomRole() == role; #nullable enable /// /// Gets the role of a /// - public static T? GetRole(this PlayerControl player) where T : BaseRole - => player.GetRole() as T; + public static T? GetCustomRole(this PlayerControl player) where T : BaseRole + => player.GetCustomRole() as T; /// /// Checks if a is a certain role /// - public static bool IsRole(this PlayerControl player) where T : BaseRole - => player.GetRole() != null; + public static bool IsCustomRole(this PlayerControl player) where T : BaseRole + => player.GetCustomRole() != null; /// /// Sets the role of a /// - public static void SetRole(this PlayerControl player, BaseRole? role) + public static void SetCustomRole(this PlayerControl player, BaseRole? role) { var oldRole = Roles.RoleManager.Roles.Where(r => r.Members.Contains(player.PlayerId)).ToList(); if (oldRole.Count != 0) @@ -160,8 +167,8 @@ public static void SetRole(this PlayerControl player, BaseRole? role) HudManager.Instance.KillButton.gameObject.SetActive(isImpostor && !isDead); HudManager.Instance.ImpostorVentButton.gameObject.SetActive(isImpostor && !isDead); - player.nameText.color = isImpostor ? Palette.ImpostorRed : Color.white; - player.nameText.text = player.name; + player.cosmetics.nameText.color = isImpostor ? Palette.ImpostorRed : Color.white; + player.cosmetics.nameText.text = player.name; } } @@ -177,7 +184,7 @@ public static void SetVanillaRole(this PlayerControl player, RoleTypes role) HudManager.Instance.MapButton.gameObject.SetActive(true); HudManager.Instance.ReportButton.gameObject.SetActive(true); HudManager.Instance.UseButton.gameObject.SetActive(true); - PlayerControl.LocalPlayer.RemainingEmergencies = PlayerControl.GameOptions.NumEmergencyMeetings; + PlayerControl.LocalPlayer.RemainingEmergencies = GameOptionsManager.Instance.currentNormalGameOptions.NumEmergencyMeetings; RoleManager.Instance.SetRole(player, role); player.Data.Role.SpawnTaskHeader(player); if (!DestroyableSingleton.InstanceExists) @@ -188,11 +195,11 @@ public static void SetVanillaRole(this PlayerControl player, RoleTypes role) { if (pc.Data.Role.TeamType == PlayerControl.LocalPlayer.Data.Role.TeamType) { - pc.nameText.color = pc.Data.Role.NameColor; + pc.cosmetics.nameText.color = pc.Data.Role.NameColor; } else { - pc.nameText.color = Palette.White; + pc.cosmetics.nameText.color = Palette.White; } }); } @@ -213,13 +220,13 @@ public static void RpcSetRole(this PlayerControl player, BaseRole? role) { Rpc.Instance.Send(new RpcSetRole.Data(player, role)); - player.SetRole(role); + player.SetCustomRole(role); } public static bool IsOnSameTeam(this PlayerControl player, PlayerControl otherPlayer) { - var role = player.GetRole(); - var otherRole = otherPlayer.GetRole(); + var role = player.GetCustomRole(); + var otherRole = otherPlayer.GetCustomRole(); if (role != null) { @@ -285,45 +292,7 @@ public static RoleTypes GetSimpleRoleType(this RoleTypes role) } #endregion Roles - - #region Options - - public static bool IsCustom(this OptionBehaviour option) - { - foreach (var customOption in OptionManager.CustomOptions) - { - if (customOption.Option == option) - return true; - } - - foreach (var customOption in OptionManager.CustomRoleOptions) - { - if (customOption.Option == option) - return true; - } - - return false; - } - - public static CustomOption? GetCustom(this OptionBehaviour option) - { - foreach (var customOption in OptionManager.CustomOptions) - { - if (customOption.Option == option) - return customOption; - } - - foreach (var customOption in OptionManager.CustomRoleOptions) - { - if (customOption.Option == option) - return customOption; - } - - return null; - } - - #endregion Options - + #region Position public static Vector3 SetX(this Transform transform, float x) { diff --git a/PeasAPI/GameModes/GameMode.cs b/PeasAPI/GameModes/GameMode.cs index ced0ca2..4887311 100644 --- a/PeasAPI/GameModes/GameMode.cs +++ b/PeasAPI/GameModes/GameMode.cs @@ -1,6 +1,6 @@ using System; -using System.Collections.Generic; -using BepInEx.IL2CPP; +using AmongUs.GameOptions; +using BepInEx.Unity.IL2CPP; namespace PeasAPI.GameModes { @@ -35,7 +35,7 @@ public virtual bool CanKill(PlayerControl killer, PlayerControl victim) return true; } - public virtual bool OnMeetingCall(PlayerControl caller, GameData.PlayerInfo target) + public virtual bool OnMeetingCall(PlayerControl caller, NetworkedPlayerInfo target) { return true; } diff --git a/PeasAPI/GameModes/Patches.cs b/PeasAPI/GameModes/Patches.cs index 3f34ec8..54dbd61 100644 --- a/PeasAPI/GameModes/Patches.cs +++ b/PeasAPI/GameModes/Patches.cs @@ -1,8 +1,9 @@ using System; using System.Linq; +using AmongUs.GameOptions; using HarmonyLib; using Il2CppSystem.Collections.Generic; -using Reactor; +using Reactor.Localization.Utilities; using UnityEngine; namespace PeasAPI.GameModes @@ -23,10 +24,10 @@ public static void Prefix(ShipStatus __instance) } } - [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.RpcEndGame))] - class ShipStatusRpcEndGamePatch + [HarmonyPatch(typeof(GameManager), nameof(GameManager.RpcEndGame))] + class GameManagerRpcEndGamePatch { - public static bool Prefix(ShipStatus __instance, [HarmonyArgument(0)] GameOverReason reason) + public static bool Prefix(GameManager __instance, [HarmonyArgument(0)] GameOverReason reason) { foreach (var mode in GameModeManager.Modes) { @@ -101,7 +102,7 @@ public static void AllowVanillaRolesPatch(PlayerControl __instance, [HarmonyArgu } } - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetRole))] + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CoSetRole))] [HarmonyPrefix] public static void AssignLocalRolePatch(PlayerControl __instance, [HarmonyArgument(0)] ref RoleTypes roleType) { @@ -125,16 +126,18 @@ public static bool Prefix(SabotageButton __instance) { if (mode.Enabled) { - HudManager.Instance.ShowMap((Action) (map => + var mapOptions = new MapOptions { - foreach (MapRoom mapRoom in map.infectedOverlay.rooms.ToArray()) - { - mapRoom.gameObject.SetActive(mode.AllowSabotage(mapRoom.room)); - } - - map.ShowSabotageMap(); - })); + Mode = MapOptions.Modes.Sabotage + }; + MapBehaviour.Instance.Show(mapOptions); + + foreach (MapRoom mapRoom in MapBehaviour.Instance.infectedOverlay.rooms.ToArray()) + { + mapRoom.gameObject.SetActive(mode.AllowSabotage(mapRoom.room)); + } + return false; } } @@ -155,13 +158,10 @@ public static void Prefix(MapBehaviour __instance) { if (mode.Enabled) { - HudManager.Instance.ShowMap((Action) (map => + foreach (MapRoom mapRoom in __instance.infectedOverlay.rooms.ToArray()) { - foreach (MapRoom mapRoom in map.infectedOverlay.rooms.ToArray()) - { - mapRoom.gameObject.SetActive(mode.AllowSabotage(mapRoom.room)); - } - })); + mapRoom.gameObject.SetActive(mode.AllowSabotage(mapRoom.room)); + } } } } @@ -171,7 +171,7 @@ public static void Prefix(MapBehaviour __instance) [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdReportDeadBody))] class PlayerControlCmdReportDeadBodyPatch { - public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] GameData.PlayerInfo target) + public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] NetworkedPlayerInfo target) { foreach (var mode in GameModeManager.Modes) { @@ -183,10 +183,10 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] GameDat } } - [HarmonyPatch(typeof(PlayerControl._CoSetTasks_d__112), nameof(PlayerControl._CoSetTasks_d__112.MoveNext))] + [HarmonyPatch(typeof(PlayerControl._CoSetTasks_d__103), nameof(PlayerControl._CoSetTasks_d__103.MoveNext))] public static class PlayerControlSetTasks { - public static void Postfix(PlayerControl._CoSetTasks_d__112 __instance) + public static void Postfix(PlayerControl._CoSetTasks_d__103 __instance) { if (__instance == null) return; @@ -292,7 +292,11 @@ public static void RoleTeamPatch(IntroCutscene __instance, [HarmonyArgument(0)] [HarmonyPostfix] static void SetupGameModeSetting(AmongUsClient __instance) { - GameModeManager.GameModeOption.Values = GameModeManager.Modes.ConvertAll(mode => mode.Name).Prepend("None").ToList().ConvertAll(mode => (StringNames) CustomStringName.Register(mode)); + var modeNames = GameModeManager.Modes.ConvertAll(mode => mode.Name); + + modeNames.Insert(0, "None"); + + GameModeManager.GameModeOption.Values = modeNames.ToArray(); } } } \ No newline at end of file diff --git a/PeasAPI/Managers/CustomColorManager.cs b/PeasAPI/Managers/CustomColorManager.cs index cdbcd36..582329c 100644 --- a/PeasAPI/Managers/CustomColorManager.cs +++ b/PeasAPI/Managers/CustomColorManager.cs @@ -1,14 +1,10 @@ -using System; using System.Collections.Generic; using System.Linq; +using AmongUs.Data.Legacy; using BepInEx.Configuration; using HarmonyLib; -using InnerNet; -using PeasAPI.CustomRpc; -using Reactor; -using Reactor.Extensions; -using Reactor.Networking; -using UnhollowerBaseLib; +using Reactor.Localization.Utilities; +using Reactor.Utilities.Extensions; using UnityEngine; using Object = UnityEngine.Object; @@ -61,7 +57,7 @@ public AUColor(Color body, Color shadow, string name) { Body = body; Shadow = shadow; - Name = CustomStringName.Register(name); + Name = CustomStringName.CreateAndRegister(name); } } @@ -138,7 +134,7 @@ private static void OnEnablePostfix(PlayerTab __instance) [HarmonyPatch(nameof(PlayerTab.SelectColor))] private static void SelectColor(PlayerTab __instance, int colorId) { - __instance.PlayerPreview.HatSlot.SetHat(SaveManager.LastHat, colorId); + __instance.PlayerPreview.cosmetics.SetHat(LegacySaveManager.LastHat, colorId); } } @@ -188,7 +184,7 @@ private static bool CheckColor(byte bodyColor, PlayerControl __instance) __instance.RpcSetColor(bodyColor); return false; - bool ColorIsOccupied(GameData.PlayerInfo p) + bool ColorIsOccupied(NetworkedPlayerInfo p) { return !p.Disconnected && p.PlayerId != __instance.PlayerId && p.DefaultOutfit.ColorId == bodyColor; @@ -196,24 +192,23 @@ bool ColorIsOccupied(GameData.PlayerInfo p) } } - // Prevent custom color from being saved inside SaveManager - [HarmonyPatch(typeof(SaveManager), nameof(SaveManager.BodyColor))] - private static class SaveManagerPatch + // Prevent custom color from being saved inside LegacySaveManager + private static class LegacySaveManagerPatch { private const byte MAXColor = 17; private static ConfigEntry Data => PeasAPI.ConfigFile - .Bind("CustomSaveManager", "Player Color ID", (byte) SaveManager.colorConfig); + .Bind("CustomLegacySaveManager", "Player Color ID", (byte) LegacySaveManager.colorConfig); + [HarmonyPatch(typeof(LegacySaveManager), nameof(LegacySaveManager.BodyColor), MethodType.Getter)] [HarmonyPrefix] - [HarmonyPatch(MethodType.Getter)] private static bool GetterPatch(ref byte __result) { __result = Data.Value; return false; } + [HarmonyPatch(typeof(LegacySaveManager), nameof(LegacySaveManager.BodyColor), MethodType.Setter)] [HarmonyPrefix] - [HarmonyPatch(MethodType.Setter)] private static bool SetterPatch(byte value) { Data.Value = value; diff --git a/PeasAPI/Managers/CustomHatManager.cs b/PeasAPI/Managers/CustomHatManager.cs index e6a46d4..a8171c6 100644 --- a/PeasAPI/Managers/CustomHatManager.cs +++ b/PeasAPI/Managers/CustomHatManager.cs @@ -1,59 +1,316 @@ -using System; -using HarmonyLib; +using System; +using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Reflection; +using AmongUs.Data; +using BepInEx.Logging; +using HarmonyLib; +using PowerTools; +using Reactor.Utilities; +using Reactor.Utilities.Extensions; +using TMPro; using UnityEngine; -using static PeasAPI.Data; +using UnityEngine.AddressableAssets; +using Object = UnityEngine.Object; + +namespace PeasAPI.Managers; -namespace PeasAPI.Managers +public static class CustomHatManager { - public class CustomHatManager + private static bool LoadedHats; + internal static readonly Dictionary ViewDataCache = new(); + internal static readonly List RegisteredHats = new(); + + private static ManualLogSource Log => PluginSingleton.Instance.Log; + + internal static void LoadHatsRoutine() { - public static List CustomHats = new List(); - public static List CustomVisors = new List(); + if (LoadedHats || !DestroyableSingleton.InstanceExists || + DestroyableSingleton.Instance.allHats.Count == 0) + return; + LoadedHats = true; + Coroutines.Start(LoadHats()); + } - public static void RegisterNewHat(string name, string imagePath, Vector2 chipOffset = new Vector2(), bool inFront = true, bool noBounce = true, Sprite backImage = null, Sprite floorImage = null) + internal static IEnumerator LoadHats() + { + try { - var hat = new Hat(name, imagePath, Assembly.GetCallingAssembly(), inFront, noBounce, chipOffset, backImage, floorImage); - - CustomHats.Add(hat); - - if (PeasAPI.Logging) - PeasAPI.Logger.LogInfo($"Registered hat {name} from {Assembly.GetCallingAssembly().GetName().Name}"); + var hatData = new List(); + hatData.AddRange(DestroyableSingleton.Instance.allHats); + hatData.ForEach(x => x.StoreName = "Vanilla"); + + var originalCount = DestroyableSingleton.Instance.allHats.ToList().Count; + + // Process registered hats + for (var i = 0; i < RegisteredHats.Count; i++) + { + var customHat = RegisteredHats[i]; + var hatBehaviour = GenerateHatBehaviour(customHat); + hatBehaviour.StoreName = customHat.ModName; + hatBehaviour.ProductId = customHat.Name; + hatBehaviour.name = customHat.Name + $"({customHat.Author})"; + hatBehaviour.Free = true; + hatBehaviour.displayOrder = originalCount + i; + hatData.Add(hatBehaviour); + } + + DestroyableSingleton.Instance.allHats = hatData.ToArray(); } - - public static void RegisterNewVisor(string name, string imagePath, Vector2 chipOffset = new Vector2(), Sprite climbImage = null, Sprite floorImage = null) + catch (Exception e) { - var visor = new Visor(name, imagePath, Assembly.GetCallingAssembly(), chipOffset, climbImage, floorImage); - - CustomVisors.Add(visor); - - if (PeasAPI.Logging) - PeasAPI.Logger.LogInfo($"Registered visor {name} from {Assembly.GetCallingAssembly().GetName().Name}"); + Log.LogError($"Error while loading hats: {e.Message}\nStack: {e.StackTrace}"); } + + yield return null; } - [HarmonyPatch(typeof(HatManager), nameof(HatManager.GetHatById))] - public static class HatManagerPatch + public static void RegisterNewHat(string name, Sprite image, Vector2 chipOffset = default, + bool inFront = true, bool noBounce = true, string author = "Unknown", string modName = "Custom", Sprite climbImage = null, + Sprite backImage = null, Sprite floorImage = null) { - private static bool modded = false; + if (chipOffset == default) + chipOffset = new Vector2(-0.1f, 0.35f); - public static void Prefix(HatManager __instance) + RegisteredHats.Add(new CustomHat + { + Name = name, + Image = image, + ChipOffset = chipOffset, + InFront = inFront, + NoBounce = noBounce, + Author = author, + ModName = modName, + ClimbImage = climbImage, + BackImage = backImage, + FloorImage = floorImage + }); + } + + private static HatData GenerateHatBehaviour(CustomHat customHat) + { + Sprite sprite; + if (HatCache.hatViewDatas.ContainsKey(customHat.Name)) { - if (modded) - return; - - modded = true; + sprite = HatCache.hatViewDatas[customHat.Name]; + } + else + { + sprite = customHat.Image; + if (sprite != null) + { + HatCache.hatViewDatas.Add(customHat.Name, sprite); + } + else + { + Log.LogError($"Failed to load hat image: {customHat.Image}"); + return null; + } + } + + var hat = ScriptableObject.CreateInstance(); + var viewData = ViewDataCache[hat.name] = ScriptableObject.CreateInstance(); + + hat.ChipOffset = customHat.ChipOffset; + viewData.MainImage = sprite; + viewData.ClimbImage = customHat.ClimbImage ?? sprite; + viewData.BackImage = customHat.BackImage ?? sprite; + viewData.FloorImage = customHat.FloorImage ?? sprite; + hat.ViewDataRef = new AssetReference(ViewDataCache[hat.name].Pointer); + hat.InFront = customHat.InFront; + hat.NoBounce = customHat.NoBounce; + + return hat; + } + + [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.Awake))] + public class AmongUsClient_Patches + { + private static bool _executed; + + public static void Prefix() + { + if (!_executed) + { + LoadHats(); + _executed = true; + } + } + } + + [HarmonyPatch(typeof(HatParent), nameof(HatParent.SetHat), typeof(int))] + public static class HP_patch + { + public static bool Prefix(HatParent __instance, int color) + { + if (!HatCache.hatViewDatas.ContainsKey(__instance.Hat.ProductId)) return true; + __instance.UnloadAsset(); + __instance.PopulateFromViewData(); + __instance.SetMaterialColor(color); + return false; + } + } + + [HarmonyPatch(typeof(HatParent), nameof(HatParent.PopulateFromViewData))] + public static class PF_patch + { + public static bool Prefix(HatParent __instance) + { + if (!HatCache.hatViewDatas.ContainsKey(__instance.Hat.ProductId)) return true; + __instance.UpdateMaterial(); + var hat = HatCache.hatViewDatas[__instance.Hat.ProductId]; + + var spriteAnimNodeSync = __instance.SpriteSyncNode ?? __instance.GetComponent(); + if ((bool)spriteAnimNodeSync) + spriteAnimNodeSync.NodeId = __instance.Hat.NoBounce ? 1 : 0; + if (__instance.Hat.InFront) + { + __instance.BackLayer.enabled = false; + __instance.FrontLayer.enabled = true; + __instance.FrontLayer.sprite = hat; + } + else + { + __instance.BackLayer.enabled = true; + __instance.FrontLayer.enabled = false; + __instance.FrontLayer.sprite = null; + __instance.BackLayer.sprite = hat; + } - foreach (var hat in CustomHatManager.CustomHats) - __instance.allHats.Add(hat.CreateHat()); - foreach (var visor in CustomHatManager.CustomVisors) - __instance.allVisors.Add(visor.CreateVisor()); - - __instance.allHats.ToArray().ToList().Sort((h1, h2) => String.Compare(h2.ProductId, h1.ProductId, StringComparison.Ordinal)); - - __instance.allVisors.ToArray().ToList().Sort((h1, h2) => String.Compare(h2.ProductId, h1.ProductId, StringComparison.Ordinal)); + if (!__instance.options.Initialized || !__instance.HideHat()) + return false; + __instance.FrontLayer.enabled = false; + __instance.BackLayer.enabled = false; + return false; } } -} \ No newline at end of file + + [HarmonyPatch(typeof(HatParent), nameof(HatParent.SetClimbAnim))] + public static class PF_climb_patch + { + public static bool Prefix(HatParent __instance) + { + if (!HatCache.hatViewDatas.ContainsKey(__instance.Hat.ProductId)) return true; + __instance.FrontLayer.sprite = null; + return false; + } + } + + [HarmonyPatch(typeof(InventoryManager), nameof(InventoryManager.CheckUnlockedItems))] + public class InventoryManager_Patches + { + public static void Prefix() + { + LoadHatsRoutine(); + } + } + + [HarmonyPatch(typeof(HatsTab), nameof(HatsTab.OnEnable))] + public static class HatsTab_OnEnable + { + public static bool Prefix(HatsTab __instance) + { + __instance.currentHat = + DestroyableSingleton.Instance.GetHatById(DataManager.Player.Customization.Hat); + var allHats = DestroyableSingleton.Instance.GetUnlockedHats(); + var hatGroups = new SortedList>( + new PaddedComparer("Vanilla", "") + ); + foreach (var hat in allHats) + { + if (!hatGroups.ContainsKey(hat.StoreName)) + hatGroups[hat.StoreName] = new List(); + hatGroups[hat.StoreName].Add(hat); + } + + foreach (var instanceColorChip in __instance.ColorChips) + instanceColorChip.gameObject.Destroy(); + __instance.ColorChips.Clear(); + var groupNameText = __instance.GetComponentInChildren(false); + var hatIdx = 0; + foreach (var (groupName, hats) in hatGroups) + { + var text = Object.Instantiate(groupNameText, __instance.scroller.Inner); + text.gameObject.transform.localScale = Vector3.one; + text.GetComponent().Destroy(); + text.text = groupName; + text.alignment = TextAlignmentOptions.Center; + text.fontSize = 3f; + text.fontSizeMax = 3f; + text.fontSizeMin = 0f; + + hatIdx = (hatIdx + 4) / 5 * 5; + + var xLerp = __instance.XRange.Lerp(0.5f); + var yLerp = __instance.YStart - hatIdx / __instance.NumPerRow * __instance.YOffset; + text.transform.localPosition = new Vector3(xLerp, yLerp, -1f); + hatIdx += 5; + foreach (var hat in hats.OrderBy(HatManager.Instance.allHats.IndexOf)) + { + var num = __instance.XRange.Lerp(hatIdx % __instance.NumPerRow / (__instance.NumPerRow - 1f)); + var num2 = __instance.YStart - hatIdx / __instance.NumPerRow * __instance.YOffset; + var colorChip = Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner); + colorChip.transform.localPosition = new Vector3(num, num2, -1f); + colorChip.Button.OnClick.AddListener((Action)(() => __instance.SelectHat(hat))); + colorChip.Inner.SetHat(hat, + __instance.HasLocalPlayer() + ? PlayerControl.LocalPlayer.Data.DefaultOutfit.ColorId + : DataManager.Player.Customization.Color); + colorChip.Inner.transform.localPosition = hat.ChipOffset + new Vector2(0f, -0.3f); + colorChip.Tag = hat; + __instance.ColorChips.Add(colorChip); + hatIdx += 1; + } + } + + __instance.scroller.ContentYBounds.max = + -(__instance.YStart - (hatIdx + 1) / __instance.NumPerRow * __instance.YOffset) - 3f; + __instance.currentHatIsEquipped = true; + + return false; + } + } + + public static class HatCache + { + public static Dictionary hatViewDatas = new(); + } + + public class PaddedComparer : IComparer where T : IComparable + { + private readonly T[] _forcedToBottom; + + public PaddedComparer(params T[] forcedToBottom) + { + _forcedToBottom = forcedToBottom; + } + + public int Compare(T x, T y) + { + if (_forcedToBottom.Contains(x) && _forcedToBottom.Contains(y)) + return StringComparer.InvariantCulture.Compare(x, y); + + if (_forcedToBottom.Contains(x)) + return 1; + if (_forcedToBottom.Contains(y)) + return -1; + + return StringComparer.InvariantCulture.Compare(x, y); + } + } +} + +// New class to store custom hat data +public class CustomHat +{ + public string Name { get; set; } + public Sprite Image { get; set; } + public Vector2 ChipOffset { get; set; } + public bool InFront { get; set; } + public bool NoBounce { get; set; } + public string Author { get; set; } + public string ModName { get; set; } + public Sprite ClimbImage { get; set; } + public Sprite BackImage { get; set; } + public Sprite FloorImage { get; set; } +} diff --git a/PeasAPI/Managers/CustomNamePlateManager.cs b/PeasAPI/Managers/CustomNamePlateManager.cs new file mode 100644 index 0000000..70eba0c --- /dev/null +++ b/PeasAPI/Managers/CustomNamePlateManager.cs @@ -0,0 +1,246 @@ +using System.Collections.Generic; +using HarmonyLib; +using UnityEngine; +using System.Linq; +using Innersloth.Assets; +using System; +using UnityEngine.AddressableAssets; +using AmongUs.Data; +using Reactor.Utilities.Extensions; +using Reactor.Utilities; +using TMPro; + +namespace PeasAPI.Managers +{ + public static class CustomNamePlateManager + { + public struct CustomNamePlateData + { + public string Name; + public string Author; + public Sprite Image; + public string Group; + } + + public static bool _customNameplatesLoaded = false; + static readonly List namePlateData = new(); + private static readonly List customPlateData = new(); + public static readonly Dictionary CustomNameplateViewDatas = []; + public static readonly Dictionary> RegisteredNamePlates = new(); + + public static void RegisterNewNamePlate(string name, Sprite image, string author = "Unknown", string group = "Custom") + { + if (!RegisteredNamePlates.ContainsKey(group)) + { + RegisteredNamePlates[group] = new List(); + } + + RegisteredNamePlates[group].Add(new CustomNamePlateData + { + Name = name, + Author = author, + Image = image, + Group = group + }); + } + + [HarmonyPatch(typeof(HatManager), nameof(HatManager.GetNamePlateById))] + class UnlockedNamePlatesPatch + { + public static void Postfix(HatManager __instance) + { + if (_customNameplatesLoaded) return; + _customNameplatesLoaded = true; + var AllPlates = __instance.allNamePlates.ToList(); + + foreach (var group in RegisteredNamePlates) + { + foreach (var data in group.Value) + { + NamePlateViewData nvd = new NamePlateViewData(); + nvd.Image = data.Image; + + var nameplate = new CustomNamePlates(nvd); + nameplate.name = $"{data.Name} (by {data.Author})"; + nameplate.ProductId = "lmj_" + nameplate.name.Replace(' ', '_'); + nameplate.BundleId = "lmj_" + nameplate.name.Replace(' ', '_'); + nameplate.displayOrder = 99; + nameplate.ChipOffset = new Vector2(0f, 0.2f); + nameplate.Free = true; + namePlateData.Add(nameplate); + customPlateData.Add(nameplate); + var assetRef = new AssetReference(nvd.Pointer); + nameplate.ViewDataRef = assetRef; + nameplate.CreateAddressableAsset(); + CustomNameplateViewDatas.TryAdd(nameplate.ProductId, nvd); + } + } + + AllPlates.AddRange(namePlateData); + __instance.allNamePlates = AllPlates.ToArray(); + } + } + + [HarmonyPatch(typeof(NameplatesTab), nameof(NameplatesTab.OnEnable))] + public static class NameplatesTabOnEnablePatch + { + private static TMP_Text Template; + + private static float CreateNameplatePackage(List nameplates, string packageName, float YStart, NameplatesTab __instance) + { + + var offset = YStart; + + if (Template) + { + var title = UnityEngine.Object.Instantiate(Template, __instance.scroller.Inner); + var material = title.GetComponent().material; + material.SetFloat("_StencilComp", 4f); + material.SetFloat("_Stencil", 1f); + title.transform.localPosition = new(2.25f, YStart, -1f); + title.transform.localScale = Vector3.one * 1.5f; + title.fontSize *= 0.5f; + title.enableAutoSizing = false; + Coroutines.Start(Utility.PerformTimedAction(0.1f, _ => title.SetText(packageName, true))); + offset -= 0.8f * __instance.YOffset; + } + + for (var i = 0; i < nameplates.Count; i++) + { + var nameplate = nameplates[i]; + var xpos = __instance.XRange.Lerp(i % __instance.NumPerRow / (__instance.NumPerRow - 1f)); + var ypos = offset - (i / __instance.NumPerRow * __instance.YOffset); + var colorChip = UnityEngine.Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner); + + if (ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard) + { + colorChip.Button.OverrideOnMouseOverListeners(() => __instance.SelectNameplate(nameplate)); + colorChip.Button.OverrideOnMouseOutListeners(() => __instance.SelectNameplate(HatManager.Instance.GetNamePlateById(DataManager.Player.Customization.NamePlate))); + colorChip.Button.OverrideOnClickListeners(__instance.ClickEquip); + } + else + colorChip.Button.OverrideOnClickListeners(() => __instance.SelectNameplate(nameplate)); + + colorChip.Button.ClickMask = __instance.scroller.Hitbox; + colorChip.transform.localPosition = new(xpos, ypos, -1f); + colorChip.ProductId = nameplate.ProductId; + colorChip.Tag = nameplate; + colorChip.SelectionHighlight.gameObject.SetActive(false); + + if (CustomNameplateViewDatas.TryGetValue(colorChip.ProductId, out var viewData)) + colorChip.gameObject.GetComponent().image.sprite = viewData.Image; + else + DefaultNameplateCoro(__instance, colorChip.gameObject.GetComponent()); + + __instance.ColorChips.Add(colorChip); + } + + return offset - ((nameplates.Count - 1) / __instance.NumPerRow * __instance.YOffset) - 1.5f; + } + + private static void DefaultNameplateCoro(NameplatesTab __instance, NameplateChip chip) => __instance.StartCoroutine(__instance.CoLoadAssetAsync(HatManager.Instance + .GetNamePlateById(chip.ProductId).ViewDataRef, (Action)(viewData => chip.image.sprite = viewData?.Image))); + + public static bool Prefix(NameplatesTab __instance) + { + for (var i = 0; i < __instance.scroller.Inner.childCount; i++) + __instance.scroller.Inner.GetChild(i).gameObject.Destroy(); + + __instance.ColorChips = new(); + var array = HatManager.Instance.GetUnlockedNamePlates(); + var packages = new Dictionary>(); + + foreach (var data in array) + { + + var package = "Innersloth"; + + if (data.ProductId.StartsWith("lmj_")) + { + // 查找这个名牌属于哪个组 + package = "Custom"; + foreach (var group in RegisteredNamePlates) + { + if (customPlateData.Any(v => v.ProductId == data.ProductId && + group.Value.Any(vd => $"{vd.Name} (by {vd.Author})" == v.name))) + { + package = group.Key; + break; + } + } + } + + if (!packages.ContainsKey(package)) + packages[package] = []; + + packages[package].Add(data); + } + + var YOffset = __instance.YStart; + Template = __instance.transform.FindChild("Text").gameObject.GetComponent(); + var keys = packages.Keys.OrderBy(x => x switch + { + "Innersloth" => 999, // 确保官方内容在最后 + _ => Array.IndexOf(RegisteredNamePlates.Keys.ToArray(), x) // 自定义组按注册顺序 + }); + + keys.ForEach(key => YOffset = CreateNameplatePackage(packages[key], key, YOffset, __instance)); + + if (array.Length != 0) + __instance.GetDefaultSelectable().PlayerEquippedForeground.SetActive(true); + + __instance.plateId = DataManager.Player.Customization.NamePlate; + __instance.currentNameplateIsEquipped = true; + __instance.SetScrollerBounds(); + __instance.scroller.ContentYBounds.max = -(YOffset + 3.8f); + return false; + } + } + + static Dictionary cache = new(); + static NamePlateViewData GetByCache(string id) + { + if (!cache.ContainsKey(id)) + { + cache[id] = customPlateData.FirstOrDefault(x => x.ProductId == id)?.nameplateViewData; + } + return cache[id]; + } + + [HarmonyPatch(typeof(CosmeticsCache), nameof(CosmeticsCache.GetNameplate))] + class CosmeticsCacheGetPlatePatch + { + public static bool Prefix(CosmeticsCache __instance, string id, ref NamePlateViewData __result) + { + if (!id.StartsWith("lmj_")) return true; + __result = GetByCache(id); + if (__result == null) + __result = __instance.nameplates["nameplate_NoPlate"].GetAsset(); + return false; + } + } + + [HarmonyPatch(typeof(PlayerVoteArea), nameof(PlayerVoteArea.PreviewNameplate))] + class PreviewNameplatesPatch + { + public static void Postfix(PlayerVoteArea __instance, string plateID) + { + if (!plateID.StartsWith("lmj_")) return; + NamePlateViewData npvd = GetByCache(plateID); + if (npvd != null) + { + __instance.Background.sprite = npvd.Image; + } + } + } + } + + class CustomNamePlates : NamePlateData + { + public NamePlateViewData nameplateViewData; + public CustomNamePlates(NamePlateViewData hvd) + { + nameplateViewData = hvd; + } + } +} \ No newline at end of file diff --git a/PeasAPI/Managers/CustomServerManager.cs b/PeasAPI/Managers/CustomServerManager.cs index 245a8be..9ada637 100644 --- a/PeasAPI/Managers/CustomServerManager.cs +++ b/PeasAPI/Managers/CustomServerManager.cs @@ -2,41 +2,26 @@ using System.Collections.Generic; using System.Net; using System.Net.Sockets; +using AmongUs.Data.Player; using HarmonyLib; +using UnityEngine; +using UnityEngine.Events; namespace PeasAPI.Managers { public class CustomServerManager { - public static List CustomServer = new List(); + public static List CustomServer = new(); /// /// Adds a custom region to the game /// public static void RegisterServer(string name, string ip, ushort port) { - if (Uri.CheckHostName(ip).ToString() == "Dns") - { - try - { - foreach (IPAddress address in Dns.GetHostAddresses(ip)) - if (address.AddressFamily == AddressFamily.InterNetwork) - { - ip = address.ToString(); - break; - } - } - catch - { - } - } - - CustomServer.Add(new DnsRegionInfo(ip, name, StringNames.NoTranslation, ip, port, false) - .Cast()); - ServerManager.Instance.AddOrUpdateRegion(new DnsRegionInfo(ip, name, StringNames.NoTranslation, ip, port, false) - .Cast()); + CustomServer.Add(new StaticHttpRegionInfo(name, StringNames.NoTranslation, ip, + new[] { new ServerInfo(name + "-1", ip, port, false) })); } - + //Skidded from https://github.com/edqx/Edward.SkipAuth [HarmonyPatch(typeof(AuthManager._CoConnect_d__4), nameof(AuthManager._CoConnect_d__4.MoveNext))] public static class DoNothingInConnect @@ -64,7 +49,7 @@ public static void Postfix(ServerManager __instance) var defaultRegions = new List(); foreach (var server in CustomServer) { - defaultRegions.Add(server); + defaultRegions.Add(server.Cast()); } ServerManager.DefaultRegions = defaultRegions.ToArray(); __instance.AvailableRegions = defaultRegions.ToArray(); @@ -80,19 +65,66 @@ public static void Postfix(MainMenuManager __instance) { if (!_initialized && CustomServer.Count != 0 && ServerManager.Instance.CurrentRegion.Name != CustomServer[0].Name) { - ServerManager.Instance.SetRegion(CustomServer[0]); + ServerManager.Instance.SetRegion(CustomServer[0].Cast()); } _initialized = true; } } - [HarmonyPatch(typeof(StatsManager), nameof(StatsManager.AmBanned), MethodType.Getter)] - public static class AmBannedPatch + [HarmonyPatch(typeof(PlayerBanData), nameof(PlayerBanData.IsBanned), MethodType.Getter)] + public static class IsBannedPatch { public static void Postfix(out bool __result) { __result = false; } } + + [HarmonyPatch(typeof(ServerDropdown), nameof(ServerDropdown.FillServerOptions))] + public static class ServerDropdownPatch + { + public static bool Prefix(ServerDropdown __instance) + { + var num = 0; + __instance.background.size = new Vector2(8.4f, 4.8f); + + foreach (var regionInfo in DestroyableSingleton.Instance.AvailableRegions) + { + if (DestroyableSingleton.Instance.CurrentRegion.Equals(regionInfo)) + { + __instance.defaultButtonSelected = __instance.firstOption; + __instance.firstOption.ChangeButtonText( + DestroyableSingleton.Instance.GetStringWithDefault( + regionInfo.TranslateName, + regionInfo.Name)); + } + else + { + var region = regionInfo; + var serverListButton = __instance.ButtonPool.Get(); + var x = num % 2 == 0 ? -2 : 2; + var y = -0.55f * (num / 2); + serverListButton.transform.localPosition = new Vector3(x, __instance.y_posButton + y, -1f); + serverListButton.transform.localScale = Vector3.one; + serverListButton.Text.text = + DestroyableSingleton.Instance.GetStringWithDefault( + regionInfo.TranslateName, + regionInfo.Name); + serverListButton.Text.ForceMeshUpdate(); + serverListButton.Button.OnClick.RemoveAllListeners(); + serverListButton.Button.OnClick.AddListener((UnityAction)(() => { __instance.ChooseOption(region); })); + __instance.controllerSelectable.Add(serverListButton.Button); + __instance.background.transform.localPosition = new Vector3( + 0f, + __instance.initialYPos + (-0.3f * (num / 2)), + 0f); + __instance.background.size = new Vector2(__instance.background.size.x, 1.2f + (0.6f * (num / 2))); + num++; + } + } + + return false; + } + } } } \ No newline at end of file diff --git a/PeasAPI/Managers/CustomVisorManager.cs b/PeasAPI/Managers/CustomVisorManager.cs new file mode 100644 index 0000000..9798450 --- /dev/null +++ b/PeasAPI/Managers/CustomVisorManager.cs @@ -0,0 +1,376 @@ +using System.Collections.Generic; +using HarmonyLib; +using UnityEngine; +using System.Linq; +using System; +using UnityEngine.AddressableAssets; +using AmongUs.Data; +using Innersloth.Assets; +using Reactor.Utilities.Extensions; +using Reactor.Utilities; +using TMPro; + +namespace PeasAPI.Managers +{ + public static class CustomVisorManager + { + public static Material MagicShader = new Material(Shader.Find("Unlit/PlayerShader")); + + public struct CustomVisorData + { + public string Name; + public string Author; + public Sprite Image; + public Sprite ClimbImage; + public Sprite FloorImage; + public Vector2 ChipOffset; + public bool MatchPlayerColor; + public string Group; + } + + public static bool _customVisorLoaded = false; + static readonly List visorData = new(); + private static readonly List customVisorData = new(); + public static readonly Dictionary CustomVisorViewDatas = []; + public static readonly Dictionary> RegisteredVisors = new(); + + public static void RegisterNewVisor(string name, Sprite image, Vector2 chipOffset = new Vector2(), + Sprite climbImage = null, Sprite floorImage = null, + bool matchPlayerColor = false, string author = "Unknown", string group = "Custom") + { + if (!RegisteredVisors.ContainsKey(group)) + { + RegisteredVisors[group] = new List(); + } + + RegisteredVisors[group].Add(new CustomVisorData + { + Name = name, + Author = author, + Image = image, + ClimbImage = climbImage, + FloorImage = floorImage, + ChipOffset = chipOffset, + MatchPlayerColor = matchPlayerColor, + Group = group + }); + } + + [HarmonyPatch(typeof(HatManager), nameof(HatManager.GetVisorById))] + class AddCustomVisorsPatch + { + public static void Postfix(HatManager __instance) + { + if (_customVisorLoaded) return; + _customVisorLoaded = true; + var AllVisors = __instance.allVisors.ToList(); + + foreach (var group in RegisteredVisors) + { + foreach (var data in group.Value) + { + VisorViewData vvd = new VisorViewData(); + vvd.IdleFrame = data.Image; + vvd.LeftIdleFrame = data.ClimbImage; + vvd.FloorFrame = data.FloorImage; + vvd.MatchPlayerColor = data.MatchPlayerColor; + + var visor = new CustomVisors(vvd); + visor.name = $"{data.Name} (by {data.Author})"; + visor.ProductId = "lmj_" + visor.name.Replace(' ', '_'); + visor.BundleId = "lmj_" + visor.name.Replace(' ', '_'); + visor.displayOrder = 99; + visor.ChipOffset = data.ChipOffset; + visor.Free = true; + visorData.Add(visor); + customVisorData.Add(visor); + var assetRef = new AssetReference(vvd.Pointer); + visor.ViewDataRef = assetRef; + visor.CreateAddressableAsset(); + CustomVisorViewDatas.TryAdd(visor.ProductId, vvd); + } + } + + AllVisors.AddRange(visorData); + __instance.allVisors = AllVisors.ToArray(); + } + } + + [HarmonyPatch(typeof(VisorsTab), nameof(VisorsTab.OnEnable))] + public static class VisorsTabOnEnablePatch + { + private static TMP_Text Template; + + private static float CreateVisorPackage(List visors, string packageName, float YStart, VisorsTab __instance) + { + + var offset = YStart; + + if (Template) + { + var title = UnityEngine.Object.Instantiate(Template, __instance.scroller.Inner); + var material = title.GetComponent().material; + material.SetFloat("_StencilComp", 4f); + material.SetFloat("_Stencil", 1f); + title.transform.localPosition = new(2.25f, YStart, -1f); + title.transform.localScale = Vector3.one * 1.5f; + title.fontSize *= 0.5f; + title.enableAutoSizing = false; + Coroutines.Start(Utility.PerformTimedAction(0.1f, _ => title.SetText(packageName, true))); + offset -= 0.8f * __instance.YOffset; + } + + for (var i = 0; i < visors.Count; i++) + { + var visor = visors[i]; + var xpos = __instance.XRange.Lerp(i % __instance.NumPerRow / (__instance.NumPerRow - 1f)); + var ypos = offset - (i / __instance.NumPerRow * __instance.YOffset); + var colorChip = UnityEngine.Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner); + + if (ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard) + { + colorChip.Button.OverrideOnMouseOverListeners(() => __instance.SelectVisor(visor)); + colorChip.Button.OverrideOnMouseOutListeners(() => __instance.SelectVisor(HatManager.Instance.GetVisorById(DataManager.Player.Customization.Visor))); + colorChip.Button.OverrideOnClickListeners(__instance.ClickEquip); + } + else + colorChip.Button.OverrideOnClickListeners(() => __instance.SelectVisor(visor)); + + colorChip.Button.ClickMask = __instance.scroller.Hitbox; + colorChip.transform.localPosition = new(xpos, ypos, -1f); + colorChip.Inner.SetMaskType(PlayerMaterial.MaskType.SimpleUI); + colorChip.Inner.transform.localPosition = visor.ChipOffset; + colorChip.ProductId = visor.ProductId; + colorChip.Tag = visor; + __instance.UpdateMaterials(colorChip.Inner.FrontLayer, visor); + var colorId = __instance.HasLocalPlayer() ? PlayerControl.LocalPlayer.Data.DefaultOutfit.ColorId : DataManager.Player.Customization.Color; + + if (CustomVisorViewDatas.TryGetValue(visor.ProductId, out var data)) + ColorChipFix(colorChip, data.IdleFrame, colorId); + else + visor.SetPreview(colorChip.Inner.FrontLayer, colorId); + + colorChip.SelectionHighlight.gameObject.SetActive(false); + __instance.ColorChips.Add(colorChip); + } + + return offset - ((visors.Count - 1) / __instance.NumPerRow * __instance.YOffset) - 1.5f; + } + + private static void ColorChipFix(ColorChip chip, Sprite sprite, int colorId) + { + chip.Inner.FrontLayer.sprite = sprite; + AddressableAssetHandler.AddToGameObject(chip.Inner.FrontLayer.gameObject); + + if (Application.isPlaying) + PlayerMaterial.SetColors(colorId, chip.Inner.FrontLayer); + } + + public static bool Prefix(VisorsTab __instance) + { + for (var i = 0; i < __instance.scroller.Inner.childCount; i++) + __instance.scroller.Inner.GetChild(i).gameObject.Destroy(); + + __instance.ColorChips = new(); + var array = HatManager.Instance.GetUnlockedVisors(); + var packages = new Dictionary>(); + + foreach (var data in array) + { + var package = "Innersloth"; + + if (data.ProductId.StartsWith("lmj_")) + { + package = "Custom"; + foreach (var group in RegisteredVisors) + { + if (customVisorData.Any(v => v.ProductId == data.ProductId && + group.Value.Any(vd => $"{vd.Name} (by {vd.Author})" == v.name))) + { + package = group.Key; + break; + } + } + } + + if (!packages.ContainsKey(package)) + packages[package] = []; + + packages[package].Add(data); + } + + var yOffset = __instance.YStart; + Template = __instance.transform.FindChild("Text").gameObject.GetComponent(); + var keys = packages.Keys.OrderBy(x => x switch { + "Innersloth" => 999, + _ => Array.IndexOf(RegisteredVisors.Keys.ToArray(), x) + }); + + keys.ForEach(key => yOffset = CreateVisorPackage(packages[key], key, yOffset, __instance)); + + if (array.Length != 0) + __instance.GetDefaultSelectable().PlayerEquippedForeground.SetActive(true); + + __instance.visorId = DataManager.Player.Customization.Visor; + __instance.currentVisorIsEquipped = true; + __instance.SetScrollerBounds(); + __instance.scroller.ContentYBounds.max = -(yOffset + 4.1f); + return false; + } + } + + [HarmonyPatch(typeof(CosmeticsCache), nameof(CosmeticsCache.GetVisor))] + class CosmeticsCacheGetVisorPatch + { + public static bool Prefix(CosmeticsCache __instance, string id, ref VisorViewData __result) + { + if (!id.StartsWith("lmj_")) return true; + __result = GetByCache(id); + if (__result == null) + __result = __instance.visors["visor_EmptyVisor"].GetAsset(); + return false; + } + } + + static Dictionary cache = new(); + static VisorViewData GetByCache(string id) + { + if (!cache.ContainsKey(id)) + { + cache[id] = customVisorData.FirstOrDefault(x => x.ProductId == id)?.visorViewData; + } + return cache[id]; + } + + [HarmonyPatch(typeof(VisorLayer), nameof(VisorLayer.UpdateMaterial))] + class VisorLayerUpdateMaterialPatch + { + public static bool Prefix(VisorLayer __instance) + { + if (__instance.visorData == null || !__instance.visorData.ProductId.StartsWith("lmj_")) return true; + VisorViewData asset = GetByCache(__instance.visorData.ProductId); + if (asset == null) return true; + + PlayerMaterial.MaskType maskType = __instance.matProperties.MaskType; + if (asset.MatchPlayerColor) + { + if (maskType == PlayerMaterial.MaskType.ComplexUI || maskType == PlayerMaterial.MaskType.ScrollingUI) + { + __instance.Image.sharedMaterial = DestroyableSingleton.Instance.MaskedPlayerMaterial; + } + else + { + __instance.Image.sharedMaterial = DestroyableSingleton.Instance.PlayerMaterial; + } + } + else if (maskType == PlayerMaterial.MaskType.ComplexUI || maskType == PlayerMaterial.MaskType.ScrollingUI) + { + __instance.Image.sharedMaterial = DestroyableSingleton.Instance.MaskedMaterial; + } + else + { + __instance.Image.sharedMaterial = DestroyableSingleton.Instance.DefaultShader; + } + switch (maskType) + { + case PlayerMaterial.MaskType.SimpleUI: + __instance.Image.maskInteraction = (SpriteMaskInteraction)1; + break; + case PlayerMaterial.MaskType.Exile: + __instance.Image.maskInteraction = (SpriteMaskInteraction)2; + break; + default: + __instance.Image.maskInteraction = (SpriteMaskInteraction)0; + break; + } + __instance.Image.material.SetInt(PlayerMaterial.MaskLayer, __instance.matProperties.MaskLayer); + if (asset.MatchPlayerColor) + { + PlayerMaterial.SetColors(__instance.matProperties.ColorId, __instance.Image); + } + if (__instance.matProperties.MaskLayer <= 0) + { + PlayerMaterial.SetMaskLayerBasedOnLocalPlayer(__instance.Image, __instance.matProperties.IsLocalPlayer); + return false; + } + __instance.Image.material.SetInt(PlayerMaterial.MaskLayer, __instance.matProperties.MaskLayer); + return false; + } + } + + [HarmonyPatch(typeof(VisorLayer), nameof(VisorLayer.SetFlipX))] + class VisorLayerSetFlipXPatch + { + public static bool Prefix(VisorLayer __instance, bool flipX) + { + if (__instance.visorData == null || !__instance.visorData.ProductId.StartsWith("lmj_")) return true; + VisorViewData asset = GetByCache(__instance.visorData.ProductId); + if (asset == null) return true; + + __instance.Image.flipX = flipX; + if (flipX && asset.LeftIdleFrame) + { + __instance.Image.sprite = asset.LeftIdleFrame; + } + else + { + __instance.Image.sprite = asset.IdleFrame; + } + return false; + } + } + + [HarmonyPatch(typeof(VisorLayer), nameof(VisorLayer.SetFloorAnim))] + class VisorLayerSetVisorFloorPositionPatch + { + public static bool Prefix(VisorLayer __instance) + { + if (__instance.visorData == null || !__instance.visorData.ProductId.StartsWith("lmj_")) return true; + VisorViewData asset = GetByCache(__instance.visorData.ProductId); + if (asset == null) return true; + + __instance.Image.sprite = asset.FloorFrame ? asset.FloorFrame : asset.IdleFrame; + return false; + } + } + + [HarmonyPatch(typeof(VisorLayer), nameof(VisorLayer.PopulateFromViewData))] + class VisorLayerPopulateFromViewDataPatch + { + public static bool Prefix(VisorLayer __instance) + { + if (__instance.visorData == null || !__instance.visorData.ProductId.StartsWith("lmj_")) + return true; + __instance.UpdateMaterial(); + if (!__instance.IsDestroyedOrNull()) + { + __instance.transform.SetLocalZ(__instance.DesiredLocalZPosition); + __instance.SetFlipX(__instance.Image.flipX); + } + return false; + } + } + + [HarmonyPatch(typeof(VisorLayer), nameof(VisorLayer.SetVisor), new Type[] { typeof(VisorData), typeof(int) })] + class VisorLayerSetVisorPatch + { + public static bool Prefix(VisorLayer __instance, VisorData data, int color) + { + if (!data.ProductId.StartsWith("lmj_")) return true; + __instance.visorData = data; + __instance.SetMaterialColor(color); + __instance.PopulateFromViewData(); + return false; + } + } + } + + class CustomVisors : VisorData + { + public VisorViewData visorViewData; + public CustomVisors(VisorViewData hvd) + { + visorViewData = hvd; + } + } +} \ No newline at end of file diff --git a/PeasAPI/Managers/PlayerMenuManager.cs b/PeasAPI/Managers/PlayerMenuManager.cs index eaf3623..b23e736 100644 --- a/PeasAPI/Managers/PlayerMenuManager.cs +++ b/PeasAPI/Managers/PlayerMenuManager.cs @@ -3,8 +3,7 @@ using System.Collections.Generic; using System.Linq; using HarmonyLib; -using Newtonsoft.Json.Utilities; -using Reactor; +using Reactor.Utilities; using TMPro; using UnityEngine; using UnityEngine.Events; @@ -41,7 +40,7 @@ private static IEnumerator CoCreatePlayerMenu(List players, Action().Locked = false; @@ -121,7 +120,7 @@ private static void CloseMenu() [HarmonyPatch] internal static class Patches { - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CoStartMeeting))] + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.StartMeeting))] [HarmonyPrefix] public static void OnMeetingStartPatch(PlayerControl __instance) { @@ -137,7 +136,7 @@ public static void MeetingHudOnStartPatch(MeetingHud __instance) { if (IsMenuOpen) { - HudManager.Instance.Chat.SetPosition(null); + HudManager.Instance.Chat.chatButtonAspectPosition = null; HudManager.Instance.Chat.SetVisible(false); __instance.discussionTimer = 20; } @@ -185,7 +184,7 @@ public static bool DisableDeadOverlayPatch(MeetingHud __instance) [HarmonyPrefix] public static bool DummyDontVotePatch(DummyBehaviour __instance) { - GameData.PlayerInfo data = __instance.myPlayer.Data; + NetworkedPlayerInfo data = __instance.myPlayer.Data; if (data == null || data.IsDead) { return false; diff --git a/PeasAPI/Managers/TextMessageManager.cs b/PeasAPI/Managers/TextMessageManager.cs index fe3992d..6094743 100644 --- a/PeasAPI/Managers/TextMessageManager.cs +++ b/PeasAPI/Managers/TextMessageManager.cs @@ -1,10 +1,11 @@ using System.Collections; using System.Collections.Generic; -using BepInEx.IL2CPP.Utils.Collections; +using BepInEx.Unity.IL2CPP.Utils.Collections; using HarmonyLib; using PeasAPI.CustomRpc; -using Reactor.Extensions; -using Reactor.Networking; +using Reactor.Networking.Rpc; +using Reactor.Utilities; +using Reactor.Utilities.Extensions; using TMPro; using UnityEngine; @@ -16,7 +17,7 @@ public static class TextMessageManager public static void ShowMessage(string message, float duration) { - Reactor.Coroutines.Start(CoShowText(message, duration)); + Coroutines.Start(CoShowText(message, duration)); } public static void RpcShowMessage(string message, float duration, List targets) diff --git a/PeasAPI/Managers/UpdateManager.cs b/PeasAPI/Managers/UpdateManager.cs index ef01d58..1ef75f4 100644 --- a/PeasAPI/Managers/UpdateManager.cs +++ b/PeasAPI/Managers/UpdateManager.cs @@ -5,8 +5,7 @@ using System.Text; using PeasAPI.Enums; using PeasAPI.Managers.UpdateTools; -using Reactor; -using Reactor.Extensions; +using Reactor.Utilities.Extensions; using TMPro; using UnityEngine; using Object = UnityEngine.Object; @@ -18,6 +17,13 @@ public static class UpdateManager public static bool DoUpdateChecks = true; private static readonly List UpdateListeners = new(); + /// + /// Note: PeasAPI's update detection determines whether there is an update by checking the `tag_name` of the latest release on GitHub and the Assembly version of the mod. The `tag_name` should be written as "v1.0.0" or "1.0.0". + /// + /// + /// + /// + /// public static void RegisterGitHubUpdateListener(string owner, string repoName, UpdateType updateType = UpdateType.Every, FileType type = FileType.Dll) { var callingAssembly = Assembly.GetCallingAssembly(); diff --git a/PeasAPI/Managers/UpdateTools/GitHubUpdater.cs b/PeasAPI/Managers/UpdateTools/GitHubUpdater.cs index 82f42e4..a375e52 100644 --- a/PeasAPI/Managers/UpdateTools/GitHubUpdater.cs +++ b/PeasAPI/Managers/UpdateTools/GitHubUpdater.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; -using System.Net.Http.Headers; using System.Reflection; using System.Text.Json; using PeasAPI.Enums; @@ -53,8 +52,10 @@ private string GetLinkByPriority(IReadOnlyCollection array) var first = array.Cast().FirstOrDefault(x => x?.GetProperty("content_type").GetString() ?.Equals(priority) ?? true) ?? array.FirstOrDefault(); - - return first.GetProperty("browser_download_url").GetString(); + + var dlURLPre = Utility.isChinese() ? "https://ghproxy.fangkuai.fun/" : ""; + + return dlURLPre + first.GetProperty("browser_download_url").GetString(); } } } \ No newline at end of file diff --git a/PeasAPI/Managers/UpdateTools/UpdateListener.cs b/PeasAPI/Managers/UpdateTools/UpdateListener.cs index 636d2bf..8b24874 100644 --- a/PeasAPI/Managers/UpdateTools/UpdateListener.cs +++ b/PeasAPI/Managers/UpdateTools/UpdateListener.cs @@ -2,13 +2,11 @@ using System.IO; using System.IO.Compression; using System.Linq; -using System.Net; using System.Net.Http; using System.Reflection; using System.Text.Json; -using BepInEx.IL2CPP; +using BepInEx.Unity.IL2CPP; using PeasAPI.Enums; -using Reactor; using UnityEngine; namespace PeasAPI.Managers.UpdateTools @@ -103,29 +101,32 @@ private string GetAssemblyPath() public virtual void UpdateMod() { - using var webClient = new WebClient(); - var directoryName = Path.GetDirectoryName(Application.dataPath); var assemblyPath = GetAssemblyPath(); var text = $"OutdatedMods\\{Name}.dll"; - + Directory.CreateDirectory($"{directoryName}\\OutdatedMods"); if (File.Exists(text)) File.Delete(text); File.Move(assemblyPath, text); - + + using var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.Add("User-Agent", "PeasAPI Updater"); + switch (Type) { case FileType.Dll: - webClient.DownloadFile(AssetLink, $"{Name}.dll"); + var dllBytes = httpClient.GetByteArrayAsync(AssetLink).Result; + File.WriteAllBytes($"{Name}.dll", dllBytes); File.Move($"{Name}.dll", assemblyPath); break; - + case FileType.Zip: - webClient.DownloadFile(AssetLink, $"{Name}.zip"); + var zipBytes = httpClient.GetByteArrayAsync(AssetLink).Result; + File.WriteAllBytes($"{Name}.zip", zipBytes); ZipFile.ExtractToDirectory($"{Name}.zip", "BepInEx\\plugins", true); File.Delete($"{Name}.zip"); break; - + case FileType.First: throw new ArgumentOutOfRangeException(); default: throw new ArgumentOutOfRangeException(); } diff --git a/PeasAPI/Managers/WatermarkManager.cs b/PeasAPI/Managers/WatermarkManager.cs index db3e7bc..4d5ef62 100644 --- a/PeasAPI/Managers/WatermarkManager.cs +++ b/PeasAPI/Managers/WatermarkManager.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; -using BepInEx; using HarmonyLib; -using Reactor; -using UnhollowerRuntimeLib; +using InnerNet; +using Reactor.Patches; +using TMPro; using UnityEngine; namespace PeasAPI.Managers @@ -16,76 +16,62 @@ public class Watermark /// public string VersionText { get; set; } - /// - /// How much the version text should be lowered - /// - public Vector3 VersionTextOffset { get; set; } - /// /// Text that gets added to the ping text /// public string PingText { get; set; } - /// - /// How much the ping text should be lowered - /// - public Vector3 PingTextOffset { get; set; } - - public Watermark(string versionText, string pingText, - Vector3 versionTextOffset, Vector3 pingTextOffset) + public Watermark(string versionText, string pingText) { VersionText = versionText; PingText = pingText; - VersionTextOffset = versionTextOffset; - PingTextOffset = pingTextOffset; } } private static List Watermarks = new List(); - public static readonly Vector2 defaultVersionTextOffset = new (0f, -0.2f); - public static readonly Vector2 defaultPingTextOffset = new Vector2(0f, 0f); - - public static Watermark PeasApiWatermark = new Watermark($"\nPeasAPI {PeasAPI.Version} by Peasplayer\nReactor v{ReactorPlugin.Version}\nBepInEx v{Paths.BepInExVersion}", - "\nPeasAPI", new Vector2(), new Vector2()); + public static Watermark PeasApiWatermark = new Watermark($"PeasAPI {PeasAPI.Version} by Peasplayer", + "\nPeasAPI"); - public static void AddWatermark(string versionText, string pingText, - Vector2 versionTextOffset = new Vector2(), Vector2 pingTextOffset = new Vector2()) + public static void AddWatermark(string versionText, string pingText) { - var watermark = new Watermark(versionText, pingText, versionTextOffset, pingTextOffset); + var watermark = new Watermark(versionText, pingText); Watermarks.Add(watermark); } - [HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))] - public static class VersionShowerStartPatch + static bool haveStart = false; + + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] + public static class MainMenuManagerStartPatch { - static void Postfix(VersionShower __instance) + static void Postfix(MainMenuManager __instance) { - foreach (var watermark in Watermarks) + if (!haveStart) { - __instance.transform.position += watermark.VersionTextOffset; - - if (watermark.VersionText != null) - __instance.text.text += watermark.VersionText; - - foreach (var gameObject in Object.FindObjectsOfTypeAll(Il2CppType.Of())) - if (gameObject.name.Contains("ReactorVersion")) - Object.Destroy(gameObject); - } - - if (PeasAPI.ShamelessPlug) - { - __instance.transform.position += PeasApiWatermark.VersionTextOffset; + haveStart = true; - if (PeasApiWatermark.VersionText != null) - __instance.text.text += PeasApiWatermark.VersionText; - - foreach (var gameObject in Object.FindObjectsOfTypeAll(Il2CppType.Of())) - if (gameObject.name.Contains("ReactorVersion")) - Object.Destroy(gameObject); + foreach (var watermark in Watermarks) + { + if (watermark.VersionText != null) + { + ReactorVersionShower.TextUpdated += text => + { + text.text += "\n" + watermark.VersionText; + }; + } + } + + if (PeasAPI.ShamelessPlug) + { + if (PeasApiWatermark.VersionText != null) + { + ReactorVersionShower.TextUpdated += text => + { + text.text += "\n" + PeasApiWatermark.VersionText; + }; + } + } } - - __instance.transform.position = new Vector3(-5.2333f, 2.85f, 0f) - new Vector3(0f, 0.2875f / 2 * (__instance.text.text.Split('\n').Length - 1)); } } @@ -94,19 +80,27 @@ public static class PingTrackerUpdatePatch { public static void Postfix(PingTracker __instance) { - __instance.transform.localPosition = new Vector3(2.5833f, 2.9f, 0f); - foreach (var watermark in Watermarks) + var position = __instance.GetComponent(); + if (AmongUsClient.Instance.GameState == InnerNetClient.GameStates.Started) { - __instance.transform.localPosition += watermark.PingTextOffset; - + __instance.text.alignment = TextAlignmentOptions.Top; + position.Alignment = AspectPosition.EdgeAlignments.Top; + position.DistanceFromEdge = new Vector3(1.5f, 0.11f, 0); + } + else + { + position.Alignment = AspectPosition.EdgeAlignments.LeftTop; + __instance.text.alignment = TextAlignmentOptions.TopLeft; + position.DistanceFromEdge = new Vector3(0.5f, 0.11f); + } + foreach (var watermark in Watermarks) + { if (watermark.PingText != null) __instance.text.text += watermark.PingText; } if (PeasAPI.ShamelessPlug) { - __instance.transform.localPosition += PeasApiWatermark.PingTextOffset; - if (PeasApiWatermark.PingText != null) __instance.text.text += PeasApiWatermark.PingText; } diff --git a/PeasAPI/Options/CustomHeaderOption.cs b/PeasAPI/Options/CustomHeaderOption.cs new file mode 100644 index 0000000..f367c3f --- /dev/null +++ b/PeasAPI/Options/CustomHeaderOption.cs @@ -0,0 +1,15 @@ +namespace PeasAPI.Options; + +public class CustomHeaderOption : CustomOption +{ + public CustomHeaderOption(MultiMenu menu, string name) : base(num++, menu, name, + CustomOptionType.Header, 0) + { + } + + public override void OptionCreated() + { + base.OptionCreated(); + Setting.Cast().TitleText.text = GetName(); + } +} \ No newline at end of file diff --git a/PeasAPI/Options/CustomNumberOption.cs b/PeasAPI/Options/CustomNumberOption.cs index ff3be6f..8b30d79 100644 --- a/PeasAPI/Options/CustomNumberOption.cs +++ b/PeasAPI/Options/CustomNumberOption.cs @@ -1,113 +1,50 @@ -using System; -using System.Reflection; -using BepInEx.Configuration; -using PeasAPI.CustomRpc; -using Reactor; -using Reactor.Networking; -using Object = UnityEngine.Object; +using System; -namespace PeasAPI.Options +namespace PeasAPI.Options; + +public class CustomNumberOption : CustomOption { - public class CustomNumberOption : CustomOption + public CustomNumberOption(MultiMenu multiMenu, string optionName, float min, float max, + float increment, float value, Func format = null) + : base(num++, multiMenu, optionName, CustomOptionType.Number, value, null, format) { - public float Value { get; private set; } - - public float OldValue { get; private set; } - - public float MinValue { get; set; } - - public float MaxValue { get; set; } - - public float Increment { get; set; } - - public NumberSuffixes SuffixType { get; set; } - - public delegate void OnValueChangedHandler(CustomNumberOptionValueChangedArgs args); - - public event OnValueChangedHandler OnValueChanged; - - private ConfigEntry _configEntry; - - public class CustomNumberOptionValueChangedArgs - { - public CustomNumberOption Option; - - public float OldValue; - - public float NewValue; - - public CustomNumberOptionValueChangedArgs(CustomNumberOption option, float oldValue, float newValue) - { - Option = option; - OldValue = oldValue; - NewValue = newValue; - } - } - - public void SetValue(float value) - { - var oldValue = Value; - - if (AmongUsClient.Instance.AmHost && _configEntry != null) - _configEntry.Value = value; - - Value = value; - OldValue = oldValue; - - ValueChanged(value, oldValue); - - if (AmongUsClient.Instance.AmHost) - Rpc.Instance.Send(new RpcUpdateSetting.Data(this, value)); - } - - public void ValueChanged(float newValue, float oldValue) - { - var args = new CustomNumberOptionValueChangedArgs(this, oldValue, newValue); - OnValueChanged?.Invoke(args); - } + Min = min; + Max = max; + Increment = increment; + IntSafe = Min % 1 == 0 && Max % 1 == 0 && Increment % 1 == 0; + } - internal OptionBehaviour CreateOption(NumberOption numberOptionPrefab) - { - NumberOption numberOption = - Object.Instantiate(numberOptionPrefab, numberOptionPrefab.transform.parent); - - numberOption.TitleText.text = Title; - numberOption.Title = CustomStringName.Register(Title); - numberOption.Value = Value; - numberOption.ValidRange = new FloatRange(MinValue, MaxValue); - numberOption.Increment = Increment; - numberOption.SuffixType = SuffixType; + protected float Min { get; set; } + protected float Max { get; set; } + protected float Increment { get; set; } + public bool IntSafe { get; private set; } - Option = numberOption; + public float Value => (float)ValueObject; - numberOption.OnValueChanged = new Action(behaviour => - { - SetValue(numberOption.Value); - }); + public void Increase() + { + if (Value + Increment > + Max + 0.001f) // the slight increase is because of the stupid float rounding errors in the Giant speed + Set(Min); + else + Set(Value + Increment); + } - return numberOption; - } - - public CustomNumberOption(string id, string title, float minValue, float maxValue, float increment, float defaultValue, NumberSuffixes suffixType) : base(title) - { - Id = $"{Assembly.GetCallingAssembly().GetName().Name}.NumberOption.{id}"; - try - { - _configEntry = PeasAPI.ConfigFile.Bind("Options", Id, defaultValue); - } - catch (Exception e) - { - PeasAPI.Logger.LogError($"Error while loading the option \"{title}\": {e.Source}"); - } + public void Decrease() + { + if (Value - Increment < Min - 0.001f) // added it here to in case I missed something else + Set(Max); + else + Set(Value - Increment); + } - Value = _configEntry?.Value ?? defaultValue; - MinValue = minValue; - MaxValue = maxValue; - Increment = increment; - SuffixType = suffixType; - HudFormat = "{0}: {1}{2}"; - - OptionManager.CustomOptions.Add(this); - } + public override void OptionCreated() + { + base.OptionCreated(); + var number = Setting.Cast(); + number.ValidRange = new FloatRange(Min, Max); + number.Increment = Increment; + number.Value = number.oldValue = Value; + number.ValueText.text = ToString(); } } \ No newline at end of file diff --git a/PeasAPI/Options/CustomOption.cs b/PeasAPI/Options/CustomOption.cs index af0fd33..917c572 100644 --- a/PeasAPI/Options/CustomOption.cs +++ b/PeasAPI/Options/CustomOption.cs @@ -1,26 +1,292 @@ -namespace PeasAPI.Options +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using PeasAPI.CustomRpc; +using PeasAPI.Roles; +using Reactor.Localization.Utilities; +using Reactor.Utilities; + +namespace PeasAPI.Options; + +public class CustomOption { - public abstract class CustomOption + public static List AllOptions = new(); + + private static bool _isLoading = false; + public static int num = 1; + public readonly int ID; + public readonly MultiMenu Menu; + + public BaseRole BaseRole; + public Func Format; + public string Name; + public bool IsRoleOption; + + public StringNames StringName; + + public CustomOption(int id, MultiMenu menu, string name, CustomOptionType type, + object defaultValue, object defaultValue2 = null, + Func format = null, + BaseRole baseRole = null, bool isRoleOption = false) + { + ID = id; + Menu = menu; + Name = name; + Type = type; + DefaultValue = ValueObject = defaultValue; + DefaultValue2 = ValueObject2 = defaultValue2; + Format = format ?? (obj => $"{obj}"); + BaseRole = baseRole; + + AllOptions.Add(this); + Set(ValueObject, ValueObject2); + + StringName = CustomStringName.CreateAndRegister(GetName()); + + IsRoleOption = isRoleOption; + } + + public object ValueObject { get; set; } + public object ValueObject2 { get; set; } + public OptionBehaviour Setting { get; set; } + public CustomOptionType Type { get; set; } + public object DefaultValue { get; set; } + public object DefaultValue2 { get; set; } + public static Func Seconds { get; } = value => $"{value:0.0#}s"; + public static Func Multiplier { get; } = value => $"{value:0.0#}x"; + public string GetName() + { + return Name; + } + + public override string ToString() + { + return Format(ValueObject); + } + + public string ToString2() + { + return Format(ValueObject2); + } + + public virtual void OptionCreated() + { + Setting.name = Setting.gameObject.name = GetName(); + } + + private static string CreateSafeKey(string menu, string name) + { + string safeKey = $"{menu}_{name}"; + safeKey = Regex.Replace(safeKey, @"[=\n\t\\""'\[\]]", "_"); + + if (string.IsNullOrEmpty(safeKey)) + safeKey = "default_key"; + + return safeKey; + } + + /// + /// Saves all current settings to the BepInEx configuration file + /// + public static void SaveSettings() + { + if (_isLoading) return; + + try + { + bool configChanged = false; + + foreach (var option in AllOptions) + { + string safeKey = CreateSafeKey(option.Menu.ToString(), option.Name); + + if (option.Type == CustomOptionType.Role) + { + var valueEntry = PeasAPI.ConfigFile.Bind("Settings", $"{safeKey}_Value", + option.DefaultValue?.ToString(), + $"Custom option: {option.Name} (Value)"); + + var value2Entry = PeasAPI.ConfigFile.Bind("Settings", $"{safeKey}_Value2", + option.DefaultValue2?.ToString(), + $"Custom option: {option.Name} (Value2)"); + + if (valueEntry.Value != option.ValueObject?.ToString()) + { + valueEntry.Value = option.ValueObject?.ToString(); + configChanged = true; + } + + if (value2Entry.Value != option.ValueObject2?.ToString()) + { + value2Entry.Value = option.ValueObject2?.ToString(); + configChanged = true; + } + } + else + { + var valueEntry = PeasAPI.ConfigFile.Bind("Settings", safeKey, + option.DefaultValue?.ToString(), + $"Custom option: {option.Name}"); + + if (valueEntry.Value != option.ValueObject?.ToString()) + { + valueEntry.Value = option.ValueObject?.ToString(); + configChanged = true; + } + } + } + + if (configChanged) + { + PeasAPI.ConfigFile.Save(); + PeasAPI.Logger.LogInfo("Settings have been saved to the BepInEx configuration file"); + } + } + catch (Exception ex) + { + PeasAPI.Logger.LogError($"Error occurred while saving settings: {ex.Message}"); + } + } + + /// + /// Loads settings from the BepInEx configuration file + /// + public static void LoadSettings() { - public string Title { get; set; } - - public string Id { get; internal set; } + try + { + _isLoading = true; + + foreach (var option in AllOptions) + { + string safeKey = CreateSafeKey(option.Menu.ToString(), option.Name); + + try + { + if (option.Type == CustomOptionType.Role) + { + // Role options have two values + var valueEntry = PeasAPI.ConfigFile.Bind("Settings", $"{safeKey}_Value", + option.DefaultValue?.ToString(), + $"Custom option: {option.Name} (Value)"); - public bool HudVisible { get; set; } = true; - - public bool MenuVisible { get; set; } = true; - - public bool AdvancedRoleOption { get; set; } + var value2Entry = PeasAPI.ConfigFile.Bind("Settings", $"{safeKey}_Value2", + option.DefaultValue2?.ToString(), + $"Custom option: {option.Name} (Value2)"); - public string HudFormat { get; set; } = "{0}"; + object value = ConvertValue(valueEntry.Value, option.Type); + object value2 = ConvertValue(value2Entry.Value, option.Type); - internal bool IsFromPeasAPI { get; set; } = false; - - public OptionBehaviour Option { get; internal set; } + option.Set(value, value2, SendRpc: false, Notify: false); + } + else + { + // Other options have only one value + var valueEntry = PeasAPI.ConfigFile.Bind("Settings", safeKey, + option.DefaultValue?.ToString(), + $"Custom option: {option.Name}"); - public CustomOption(string title) + object value = ConvertValue(valueEntry.Value, option.Type); + option.Set(value, null, SendRpc: false, Notify: false); + } + } + catch (Exception ex) + { + PeasAPI.Logger.LogWarning($"Error occurred while loading option {option.Name}: {ex.Message}"); + } + } + + PeasAPI.Logger.LogInfo("Settings have been loaded from the BepInEx configuration file"); + } + catch (Exception ex) { - Title = title; + PeasAPI.Logger.LogError($"Error occurred while loading settings: {ex.Message}"); } + finally + { + _isLoading = false; + } + } + + private static object ConvertValue(string value, CustomOptionType type) + { + if (string.IsNullOrEmpty(value)) return null; + + switch (type) + { + case CustomOptionType.Toggle: + return bool.Parse(value); + case CustomOptionType.Number: + return float.Parse(value); + case CustomOptionType.String: + return int.Parse(value); + case CustomOptionType.Role: + return int.Parse(value); + default: + return value; + } + } + + public void Set(object value, object value2 = null, bool SendRpc = true, bool Notify = false) + { + ValueObject = value; + ValueObject2 = value2; + + if (!_isLoading && AmongUsClient.Instance?.AmHost == true) + { + SaveSettings(); + } + + if (Setting != null && AmongUsClient.Instance.AmHost && SendRpc) + Coroutines.Start(RpcUpdateSetting.SendRpc(this)); + + try + { + if (Setting is ToggleOption toggle) + { + var newValue = (bool)ValueObject; + toggle.oldValue = newValue; + if (toggle.CheckMark != null) toggle.CheckMark.enabled = newValue; + } + else if (Setting is NumberOption number) + { + var newValue = (float)ValueObject; + + number.Value = number.oldValue = newValue; + number.ValueText.text = ToString(); + } + else if (Setting is RoleOptionSetting roleOption) + { + var newValue = (int)ValueObject; + var newValue2 = (int)ValueObject2; + + roleOption.roleChance = newValue; + roleOption.chanceText.text = ToString(); + + roleOption.roleMaxCount = newValue2; + roleOption.countText.text = ToString2(); + } + else if (Setting is StringOption str) + { + var newValue = (int)ValueObject; + + str.Value = str.oldValue = newValue; + str.ValueText.text = ToString(); + } + } + catch + { + } + + if (HudManager.InstanceExists && Type != CustomOptionType.Header && Notify) + if (IsRoleOption) + { + HudManager.Instance.Notifier.AddRoleSettingsChangeMessage(StringName, (int)ValueObject2, (int)ValueObject, RoleTeamTypes.Crewmate); + } + else + { + HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, ToString(), + HudManager.Instance.Notifier.lastMessageKey != (int)StringName); + } } } \ No newline at end of file diff --git a/PeasAPI/Options/CustomOptionButton.cs b/PeasAPI/Options/CustomOptionButton.cs deleted file mode 100644 index 7dbec0a..0000000 --- a/PeasAPI/Options/CustomOptionButton.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using Reactor; -using Object = UnityEngine.Object; - -namespace PeasAPI.Options -{ - public class CustomOptionButton : CustomOption - { - public bool Value { get; private set; } - - public bool OldValue { get; private set; } - - public delegate void OnValueChangedHandler(CustomOptionButtonValueChangedArgs args); - - public event OnValueChangedHandler OnValueChanged; - - public class CustomOptionButtonValueChangedArgs - { - public CustomOptionButton Option; - - public bool OldValue; - - public bool NewValue; - - public CustomOptionButtonValueChangedArgs(CustomOptionButton option, bool oldValue, bool newValue) - { - Option = option; - OldValue = oldValue; - NewValue = newValue; - } - } - - public void SetValue(bool value) - { - var oldValue = !value; - - if (AmongUsClient.Instance.AmHost) - { - if (Option) - ((ToggleOption) Option).CheckMark.enabled = value; - - Value = value; - OldValue = oldValue; - - ValueChanged(value, oldValue); - } - else - { - if (Option) - ((StringOption) Option).Value = value ? 0 : 1; - - Value = value; - OldValue = oldValue; - - ValueChanged(value, oldValue); - } - } - - public void ValueChanged(bool newValue, bool oldValue) - { - var args = new CustomOptionButtonValueChangedArgs(this, oldValue, newValue); - OnValueChanged?.Invoke(args); - } - - internal OptionBehaviour CreateOption(ToggleOption toggleOptionPrefab, StringOption stringOptionPrefab) - { - if (AmongUsClient.Instance.AmHost) - { - ToggleOption toggleOption = - Object.Instantiate(toggleOptionPrefab, toggleOptionPrefab.transform.parent); - - Option = toggleOption; - - toggleOption.TitleText.text = Title; - toggleOption.Title = CustomStringName.Register(Title); - toggleOption.CheckMark.enabled = false; - toggleOption.transform.FindChild("CheckBox").gameObject.SetActive(false); - - toggleOption.OnValueChanged = new Action(behaviour => - { - SetValue(!toggleOption.oldValue); - }); - - return toggleOption; - } - else - { - StringOption toggleOption = - Object.Instantiate(stringOptionPrefab, stringOptionPrefab.transform.parent); - - Option = toggleOption; - - toggleOption.TitleText.text = Title; - toggleOption.Title = CustomStringName.Register(Title); - toggleOption.Value = 0; - toggleOption.transform.FindChild("Value_TMP").gameObject.SetActive(false); - - toggleOption.OnValueChanged = new Action(behaviour => - { - SetValue(Value); - }); - toggleOption.OnValueChanged.Invoke(toggleOption); - - return toggleOption; - } - } - - public CustomOptionButton(string id, string title, bool defaultValue) : base(title) - { - OptionManager.CustomOptions.Add(this); - } - } -} \ No newline at end of file diff --git a/PeasAPI/Options/CustomOptionHeader.cs b/PeasAPI/Options/CustomOptionHeader.cs deleted file mode 100644 index 4870aed..0000000 --- a/PeasAPI/Options/CustomOptionHeader.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Reactor; -using Reactor.Extensions; -using UnityEngine; - -namespace PeasAPI.Options -{ - public class CustomOptionHeader : CustomOption - { - public CustomOptionHeader(string title) : base(title) - { - OptionManager.CustomOptions.Add(this); - } - - internal OptionBehaviour CreateOption(ToggleOption toggleOptionPrefab) - { - ToggleOption header = - Object.Instantiate(toggleOptionPrefab, toggleOptionPrefab.transform.parent); - - header.TitleText.text = Title; - header.Title = CustomStringName.Register(Title); - - var checkBox = header.transform.FindChild("CheckBox")?.gameObject; - if (checkBox) checkBox.Destroy(); - - var background = header.transform.FindChild("Background")?.gameObject; - if (background) background.Destroy(); - - Option = header; - HudFormat = "{0}"; - - return header; - } - } -} \ No newline at end of file diff --git a/PeasAPI/Options/CustomOptionType.cs b/PeasAPI/Options/CustomOptionType.cs new file mode 100644 index 0000000..91eb86a --- /dev/null +++ b/PeasAPI/Options/CustomOptionType.cs @@ -0,0 +1,19 @@ +namespace PeasAPI.Options; + +public enum CustomOptionType +{ + Header, + Toggle, + Number, + String, + Role +} + +public enum MultiMenu +{ + Main, + Crewmate, + Neutral, + Impostor, + NULL +} \ No newline at end of file diff --git a/PeasAPI/Options/CustomRoleOption.cs b/PeasAPI/Options/CustomRoleOption.cs index 4d16721..3f52656 100644 --- a/PeasAPI/Options/CustomRoleOption.cs +++ b/PeasAPI/Options/CustomRoleOption.cs @@ -1,157 +1,139 @@ -using System; +using System; +using System.Collections.Generic; using System.Linq; -using HarmonyLib; using PeasAPI.Roles; -using Reactor; -using Reactor.Extensions; +using Reactor.Utilities.Extensions; using TMPro; using UnityEngine; +using UnityEngine.Events; +using UnityEngine.UI; +using UnityEngine.UIElements.UIR; +using static UnityEngine.UI.Button; using Object = UnityEngine.Object; -namespace PeasAPI.Options +namespace PeasAPI.Options; + +public class CustomRoleOption : CustomOption { - public class CustomRoleOption : CustomOption + internal CustomOption[] AdvancedOptions; + + public CustomRoleOption(BaseRole baseRole, string prefix, CustomOption[] advancedOptions, MultiMenu menu = MultiMenu.NULL) : base(num++, + menu == MultiMenu.NULL ? GetMultiMenu(baseRole) : menu, + Utility.ColorString(baseRole.Color, baseRole.Name), CustomOptionType.Role, baseRole.Chance, baseRole.Count, baseRole: baseRole) { - public BaseRole Role; - - public int Count; - - public int Chance; - - public CustomOption[] AdvancedOptions; - - public string AdvancedOptionPrefix;public delegate void OnValueChangedHandler(CustomRoleOptionValueChangedArgs args); - - public event OnValueChangedHandler OnValueChanged; - - public class CustomRoleOptionValueChangedArgs + List removedOptions = new List(); + if (advancedOptions != null) { - public CustomRoleOption Option; - - public int Count; - - public int OldCount; - - public int Chance; - - public int OldChance; - - public CustomRoleOptionValueChangedArgs(CustomRoleOption option, int count, int oldCount, int chance, int oldChance) + foreach (var option in advancedOptions) { - Option = option; - Count = count; - OldCount = oldCount; - Chance = chance; - OldChance = oldChance; + option.IsRoleOption = true; + if (option != null && CustomOption.AllOptions.Contains(option)) + { + removedOptions.Add(option); + CustomOption.AllOptions.Remove(option); + } } } - public void SetValue(int count, int chance) + foreach (var option in removedOptions) { - var oldCount = Count; - var oldChance = Chance; - - Count = count; - Chance = chance; - - ValueChanged(count, oldCount, chance, oldChance); - } - - internal void ValueChanged(int count, int oldCount, int chance, int oldChance) - { - var args = new CustomRoleOptionValueChangedArgs(this, count, oldCount, chance, oldChance); - OnValueChanged?.Invoke(args); + CustomOption.AllOptions.Add(option); } - internal OptionBehaviour CreateOption(RoleOptionSetting roleOptionPrefab) + AdvancedOptions = advancedOptions; + if (advancedOptions != null) { - if (Option != null) + foreach (var option in advancedOptions.Where(o => o != null)) { - return Option; + option.Name = $"{prefix}{option.Name}"; } - var newSetting = Object.Instantiate(roleOptionPrefab, roleOptionPrefab.transform.parent); - newSetting.name = Role.Name; - newSetting.Role = Role.RoleBehaviour; - newSetting.SetRole(PlayerControl.GameOptions.RoleOptions); - - if (!PlayerControl.GameOptions.RoleOptions.roleRates.ContainsKey(Role.RoleBehaviour.Role)) - PlayerControl.GameOptions.RoleOptions.roleRates[Role.RoleBehaviour.Role] = - new RoleOptionsData.RoleRate(); - var rates = PlayerControl.GameOptions.RoleOptions.roleRates[Role.RoleBehaviour.Role]; - Count = rates.MaxCount; - Chance = rates.Chance; - - Option = newSetting; - return newSetting; } - - internal AdvancedRoleSettingsButton CreateOptionObjects(GameObject roleTabTemplate) + } + + public static Func PercentFormat { get; } = value => $"{value:0}%"; + + private static MultiMenu GetMultiMenu(BaseRole baseRole) + { + switch (baseRole.Team) { - if (OptionManager.NumberOptionPrefab == null || OptionManager.ToggleOptionPrefab == null || - OptionManager.StringOptionPrefab == null) - return null; - - var tab = Object.Instantiate(roleTabTemplate, roleTabTemplate.transform.parent); - tab.name = Role.Name + " Settings"; - - if (AdvancedOptions.Length == 0 && Option != null) - { - Option.transform.FindChild("More Options").gameObject.SetActive(false); - } - - foreach (var option in tab.GetComponentsInChildren()) - { - option.gameObject.DestroyImmediate(); - } + case Team.Role: + return MultiMenu.Neutral; + case Team.Alone: + return MultiMenu.Neutral; + case Team.Crewmate: + return MultiMenu.Crewmate; + case Team.Impostor: + return MultiMenu.Impostor; + default: + return MultiMenu.Main; + } + } - foreach (var advancedOption in AdvancedOptions) - { - OptionBehaviour optionBehaviour = null; - switch (advancedOption) - { - case CustomNumberOption option: - optionBehaviour = option.CreateOption(OptionManager.NumberOptionPrefab); - break; - case CustomToggleOption option: - optionBehaviour = option.CreateOption(OptionManager.ToggleOptionPrefab, OptionManager.StringOptionPrefab); - break; - case CustomStringOption option: - optionBehaviour = option.CreateOption(OptionManager.StringOptionPrefab); - break; - } + public int ChanceValue => (int)ValueObject; + public int CountValue => (int)ValueObject2; - optionBehaviour.Title = CustomStringName.Register(advancedOption.Title); - optionBehaviour.name = advancedOption.Title; + public void IncreaseChance() + { + int newChance; + if (ChanceValue + 10 > 100 + 0.001f) + newChance = 0; + else + newChance = ChanceValue + 10; - var optionTransform = optionBehaviour.transform; - optionTransform.parent = tab.transform; - optionTransform.localScale = Vector3.one; - optionTransform.localPosition = - new Vector3(-1.25f, 0.06f - AdvancedOptions.ToList().IndexOf(advancedOption) * 0.56f, 0f); - } - - var roleName = tab.transform.FindChild("Role Name"); - roleName.GetComponent().Destroy(); - roleName.GetComponent().text = Role.Name; - - var advancedOptions = new AdvancedRoleSettingsButton - { - Tab = tab, - Type = Role.RoleBehaviour.Role - }; - - return advancedOptions; - } + Set(newChance, CountValue); + } - public CustomRoleOption(BaseRole role, string advancedOptionPrefix, params CustomOption[] advancedOptions) : base(role.Name) - { - Role = role; - AdvancedOptions = advancedOptions; - AdvancedOptions.Do(option => option.AdvancedRoleOption = true ); - AdvancedOptionPrefix = advancedOptionPrefix; - HudFormat = "{0}: {1} with {2}% Chance"; - - OptionManager.CustomRoleOptions.Add(this); - } + public void DecreaseChance() + { + int newChance; + if (ChanceValue - 10 < 0 - 0.001f) + newChance = 100; + else + newChance = ChanceValue - 10; + + Set(newChance, CountValue); + } + + public void IncreaseCount() + { + int newCount; + if (CountValue + 1 > 15 + 0.001f) + newCount = 0; + else + newCount = CountValue + 1; + + Set(ChanceValue, newCount); + } + + public void DecreaseCount() + { + int newCount; + if (CountValue - 1 < 0 - 0.001f) + newCount = 15; + else + newCount = CountValue - 1; + + Set(ChanceValue, newCount); + } + + public int GetChance() + { + return ChanceValue; + } + + public int GetCount() + { + return CountValue; + } + + public override void OptionCreated() + { + base.OptionCreated(); + var roleOption = Setting.Cast(); + roleOption.roleChance = BaseRole.Chance = (int)ValueObject; + roleOption.roleMaxCount = (int)ValueObject2; + roleOption.chanceText.text = ToString(); + roleOption.countText.text = ToString2(); + roleOption.transform.GetChild(0).GetComponent().alignment = TextAlignmentOptions.Left; } } \ No newline at end of file diff --git a/PeasAPI/Options/CustomStringOption.cs b/PeasAPI/Options/CustomStringOption.cs index 286a0e4..c662d5f 100644 --- a/PeasAPI/Options/CustomStringOption.cs +++ b/PeasAPI/Options/CustomStringOption.cs @@ -1,118 +1,40 @@ -using System; -using BepInEx.Configuration; -using System.Collections.Generic; -using System.Reflection; -using BepInEx.IL2CPP; -using PeasAPI.CustomRpc; -using Reactor; -using Reactor.Networking; -using UnityEngine; -using Object = UnityEngine.Object; +namespace PeasAPI.Options; -namespace PeasAPI.Options +public class CustomStringOption : CustomOption { - public class CustomStringOption : CustomOption + public CustomStringOption(MultiMenu menu, string optionName, string[] values, + int startingId = 0) : + base(num++, menu, optionName, CustomOptionType.String, startingId) { - public int Value { get; private set; } - - public string StringValue - { - get - { - if (Values.Count >= Value + 1) - return Values[Value].GetTranslation(); - return "Error"; - } - } - - public int OldValue { get; private set; } - - public List Values { get; set; } - - public delegate void OnValueChangedHandler(CustomStringOptionValueChangedArgs args); - - public event OnValueChangedHandler OnValueChanged; - - private ConfigEntry _configEntry; - - public class CustomStringOptionValueChangedArgs - { - public CustomStringOption Option; - - public int OldValue; - - public int NewValue; - - public CustomStringOptionValueChangedArgs(CustomStringOption option, int oldValue, int newValue) - { - Option = option; - OldValue = oldValue; - NewValue = newValue; - } - } - - public void SetValue(int value) - { - var oldValue = Value; - - if (AmongUsClient.Instance.AmHost && _configEntry != null) - _configEntry.Value = value; - - Value = value; - if (Option != null) - ((StringOption) Option).Value = value; - OldValue = oldValue; - - ValueChanged(value, oldValue); + Values = values; + Format = value => Values[(int)value]; + } - if (AmongUsClient.Instance.AmHost) - Rpc.Instance.Send(new RpcUpdateSetting.Data(this, value)); - } - - public void ValueChanged(int newValue, int oldValue) - { - var args = new CustomStringOptionValueChangedArgs(this, oldValue, newValue); - OnValueChanged?.Invoke(args); - } + public string[] Values { get; set; } + public int Value => (int)ValueObject; + public string StringValue => Values[Value]; - internal OptionBehaviour CreateOption(StringOption stringOptionPrefab) - { - StringOption stringOption = - Object.Instantiate(stringOptionPrefab, stringOptionPrefab.transform.parent); - - stringOption.TitleText.text = Title; - stringOption.Title = CustomStringName.Register(Title); - stringOption.Value = Value; - stringOption.ValueText.text = StringValue; - stringOption.Values = Values.ToArray(); - - Option = stringOption; + public void Increase() + { + if (Value >= Values.Length - 1) + Set(0); + else + Set(Value + 1); + } - stringOption.OnValueChanged = new Action(behaviour => - { - SetValue(stringOption.Value); - }); - - return stringOption; - } - - public CustomStringOption(string id, string title, params string[] values) : base(title) - { - Id = $"{Assembly.GetCallingAssembly().GetName().Name}.StringOption.{id}"; - try - { - _configEntry = PeasAPI.ConfigFile.Bind("Options", Id, 0); - } catch (Exception e) { - PeasAPI.Logger.LogError($"Error while loading the option \"{title}\": {e.Source}"); - } - - Value = _configEntry?.Value ?? 0; - Values = new List(); - foreach (var value in values) - Values.Add((StringNames)CustomStringName.Register(value)); - HudFormat = "{0}: {1}"; + public void Decrease() + { + if (Value <= 0) + Set(Values.Length - 1); + else + Set(Value - 1); + } - OptionManager.CustomOptions.Add(this); - } + public override void OptionCreated() + { + base.OptionCreated(); + var str = Setting.Cast(); + str.Value = str.oldValue = Value; + str.ValueText.text = ToString(); } } \ No newline at end of file diff --git a/PeasAPI/Options/CustomToggleOption.cs b/PeasAPI/Options/CustomToggleOption.cs index 43f93e0..882400c 100644 --- a/PeasAPI/Options/CustomToggleOption.cs +++ b/PeasAPI/Options/CustomToggleOption.cs @@ -1,141 +1,24 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using BepInEx.Configuration; -using PeasAPI.CustomRpc; -using Reactor; -using Reactor.Networking; -using UnityEngine; -using Object = System.Object; +namespace PeasAPI.Options; -namespace PeasAPI.Options +public class CustomToggleOption : CustomOption { - public class CustomToggleOption : CustomOption + public CustomToggleOption(MultiMenu menu, string optionName, bool value = true) : base( + num++, menu, optionName, CustomOptionType.Toggle, value) { - public bool Value { get; private set; } - - public bool OldValue { get; private set; } - - public delegate void OnValueChangedHandler(CustomToggleOptionValueChangedArgs args); - - public event OnValueChangedHandler OnValueChanged; - - private ConfigEntry _configEntry; - - public class CustomToggleOptionValueChangedArgs - { - public CustomToggleOption Option; - - public bool OldValue; - - public bool NewValue; - - public CustomToggleOptionValueChangedArgs(CustomToggleOption option, bool oldValue, bool newValue) - { - Option = option; - OldValue = oldValue; - NewValue = newValue; - } - } - - public void SetValue(bool value) - { - var oldValue = !value; - - if (AmongUsClient.Instance.AmHost) - { - if (_configEntry != null) - _configEntry.Value = value; - - if (Option) - ((ToggleOption) Option).CheckMark.enabled = value; - - Value = value; - OldValue = oldValue; - - ValueChanged(value, oldValue); - - Rpc.Instance.Send(new RpcUpdateSetting.Data(this, value)); - } - else - { - if (Option) - ((StringOption) Option).Value = value ? 0 : 1; - - Value = value; - OldValue = oldValue; - - ValueChanged(value, oldValue); - } - } - - internal void ValueChanged(bool newValue, bool oldValue) - { - var args = new CustomToggleOptionValueChangedArgs(this, oldValue, newValue); - OnValueChanged?.Invoke(args); - } - - internal OptionBehaviour CreateOption(ToggleOption toggleOptionPrefab, StringOption stringOptionPrefab) - { - if (AmongUsClient.Instance.AmHost) - { - ToggleOption toggleOption = - UnityEngine.Object.Instantiate(toggleOptionPrefab, toggleOptionPrefab.transform.parent); - - Option = toggleOption; - - toggleOption.TitleText.text = Title; - toggleOption.Title = CustomStringName.Register(Title); - toggleOption.CheckMark.enabled = Value; - - toggleOption.OnValueChanged = new Action(behaviour => - { - SetValue(!toggleOption.oldValue); - }); - - return toggleOption; - } - else - { - StringOption toggleOption = - UnityEngine.Object.Instantiate(stringOptionPrefab, stringOptionPrefab.transform.parent); - - Option = toggleOption; - - toggleOption.TitleText.text = Title; - toggleOption.Title = CustomStringName.Register(Title); - toggleOption.Value = Value ? 0 : 1; + Format = val => (bool)val ? "On" : "Off"; + } - var values = new List(); - values.Add(CustomStringName.Register("On")); - values.Add(CustomStringName.Register("Off")); - toggleOption.Values = values.ToArray(); + public bool Value => (bool)ValueObject; - toggleOption.OnValueChanged = new Action(behaviour => - { - SetValue(toggleOption.Value == 0); - }); + public void Toggle() + { + Set(!Value); + } - return toggleOption; - } - } - - public CustomToggleOption(string id, string title, bool defaultValue) : base(title) - { - Id = $"{Assembly.GetCallingAssembly().GetName().Name}.ToggleOption.{id}"; - try - { - _configEntry = PeasAPI.ConfigFile.Bind("Options", Id, defaultValue); - } - catch (Exception e) - { - PeasAPI.Logger.LogError($"Error while loading the option \"{title}\": {e.Source}"); - } - - Value = _configEntry?.Value ?? defaultValue; - HudFormat = "{0}: {1}"; - - OptionManager.CustomOptions.Add(this); - } + public override void OptionCreated() + { + base.OptionCreated(); + var tgl = Setting.Cast(); + tgl.CheckMark.enabled = Value; } } \ No newline at end of file diff --git a/PeasAPI/Options/OptionManager.cs b/PeasAPI/Options/OptionManager.cs deleted file mode 100644 index 92ac2bf..0000000 --- a/PeasAPI/Options/OptionManager.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; - -namespace PeasAPI.Options -{ - public class OptionManager - { - public static List CustomOptions = new List(); - - public static List CustomRoleOptions = new List(); - - public static List HudVisibleOptions => CustomOptions.FindAll(option => option.HudVisible); - - public static List MenuVisibleOptions => CustomOptions.FindAll(option => option.MenuVisible && !option.AdvancedRoleOption); - - public static ToggleOption ToggleOptionPrefab; - public static NumberOption NumberOptionPrefab; - public static StringOption StringOptionPrefab; - } -} \ No newline at end of file diff --git a/PeasAPI/Options/Patches.cs b/PeasAPI/Options/Patches.cs index deaa875..881f42d 100644 --- a/PeasAPI/Options/Patches.cs +++ b/PeasAPI/Options/Patches.cs @@ -1,358 +1,847 @@ using System; +using System.Collections.Generic; using System.Linq; +using AmongUs.GameOptions; using HarmonyLib; -using Il2CppSystem.Text; -using InnerNet; using PeasAPI.CustomRpc; -using Reactor.Extensions; -using Reactor.Networking; +using PeasAPI.Roles; +using Reactor.Utilities; +using Reactor.Utilities.Extensions; +using TMPro; using UnityEngine; +using UnityEngine.Events; +using static UnityEngine.UI.Button; using Object = UnityEngine.Object; -namespace PeasAPI.Options +namespace PeasAPI.Options; + +public static class Patches { - [HarmonyPatch] - public static class Patches + [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.CreateSettings))] + private class MoreTasks { - private static float AllOptionSize = 6.73f; - private static float LowestOption = -7.85f; - private static float OptionSize = 0.5f; - private static float HudTextSize = 1.4f; + public static void Postfix(GameOptionsMenu __instance) + { + if (__instance.gameObject.name == "GAME SETTINGS TAB") + try + { + var commonTasks = __instance.Children.ToArray().FirstOrDefault(x => + x.TryCast()?.intOptionName == Int32OptionNames.NumCommonTasks) + .Cast(); + if (commonTasks != null) commonTasks.ValidRange = new FloatRange(0f, 4f); - private static Scroller OptionsScroller; + var shortTasks = __instance.Children.ToArray() + .FirstOrDefault(x => x.TryCast()?.intOptionName == Int32OptionNames.NumShortTasks) + .Cast(); + if (shortTasks != null) shortTasks.ValidRange = new FloatRange(0f, 26f); - [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Start))] - [HarmonyPostfix] - private static void GameOptionsMenuStartPatch(GameOptionsMenu __instance) + var longTasks = __instance.Children.ToArray() + .FirstOrDefault(x => x.TryCast()?.intOptionName == Int32OptionNames.NumLongTasks) + .Cast(); + if (longTasks != null) longTasks.ValidRange = new FloatRange(0f, 15f); + } + catch + { + } + } + } + + [HarmonyPatch(typeof(GameSettingMenu), nameof(GameSettingMenu.ChangeTab))] + private class ChangeTab + { + public static void Postfix(GameSettingMenu __instance, int tabNum, bool previewOnly) { - var numberOptionPrefab = OptionManager.NumberOptionPrefab = Object.FindObjectsOfType().FirstOrDefault(); - - var toggleOptionPrefab = OptionManager.ToggleOptionPrefab = Object.FindObjectOfType(); - - var stringOptionPrefab = OptionManager.StringOptionPrefab = Object.FindObjectsOfType().FirstOrDefault(); + if (SettingsUpdate.customRolesSettings != null) + { + SettingsUpdate.customRolesSettings.gameObject.Destroy(); + SettingsUpdate.customRolesSettings = null; + } - LowestOption = 1.15f - __instance.Children.Length * 0.5f; + if (previewOnly) return; + foreach (var tab in SettingsUpdate.Tabs) + if (tab != null) + tab.SetActive(false); + foreach (var button in SettingsUpdate.Buttons) button.SelectButton(false); + if (tabNum > 2) + { + tabNum -= 3; + if (tabNum < SettingsUpdate.Tabs.Count && SettingsUpdate.Tabs[tabNum] != null) // Added null check + SettingsUpdate.Tabs[tabNum].SetActive(true); + + if (tabNum > 4) return; + if (tabNum < SettingsUpdate.Buttons.Count && SettingsUpdate.Buttons[tabNum] != null) // Added null check + SettingsUpdate.Buttons[tabNum].SelectButton(true); - foreach (var customOption in OptionManager.CustomOptions.Where(option => !option.AdvancedRoleOption)) + __instance.StartCoroutine(Effects.Lerp(1f, new Action(p => + { + __instance.RoleSettingsTab.gameObject.SetActive(false); + + foreach (var option in CustomOption.AllOptions) + { + if (option?.Setting == null) continue; // Added null check + + if (option.Type == CustomOptionType.Number) + { + var number = option.Setting.TryCast(); + if (number?.TitleText == null) continue; // Added null check + number.TitleText.text = option.GetName(); + if (number.TitleText.text.StartsWith(" 20) + number.TitleText.fontSize = 2.25f; + else if (number.TitleText.text.Length > 40) + number.TitleText.fontSize = 2f; + else number.TitleText.fontSize = 2.75f; + } + else if (option.Type == CustomOptionType.Toggle) + { + var tgl = option.Setting.TryCast(); + if (tgl?.TitleText == null) continue; // Added null check + tgl.TitleText.text = option.GetName(); + if (tgl.TitleText.text.Length > 20) + tgl.TitleText.fontSize = 2.25f; + else if (tgl.TitleText.text.Length > 40) + tgl.TitleText.fontSize = 2f; + else tgl.TitleText.fontSize = 2.75f; + } + else if (option.Type == CustomOptionType.String) + { + var str = option.Setting.TryCast(); + if (str?.TitleText == null) continue; // Added null check + str.TitleText.text = option.GetName(); + if (str.TitleText.text.Length > 20) + str.TitleText.fontSize = 2.25f; + else if (str.TitleText.text.Length > 40) + str.TitleText.fontSize = 2f; + else str.TitleText.fontSize = 2.75f; + } + } + }))); + } + } + } + + [HarmonyPatch(typeof(GameSettingMenu), nameof(GameSettingMenu.Close))] + private class CloseSettings + { + public static void Prefix(GameSettingMenu __instance) + { + LobbyInfoPane.Instance.EditButton.gameObject.SetActive(true); + } + } + + [HarmonyPatch(typeof(GameSettingMenu), nameof(GameSettingMenu.Start))] + internal class SettingsUpdate + { + public static List Buttons = new(); + public static List Tabs = new(); + public static bool firstStart = true; + + public static void Postfix(GameSettingMenu __instance) + { + if (firstStart) + { + CustomOption.LoadSettings(); + firstStart = false; + } + + __instance.GameSettingsButton.OnMouseOver.RemoveAllListeners(); + LobbyInfoPane.Instance.EditButton.gameObject.SetActive(false); + Buttons.ForEach(x => x?.Destroy()); + Tabs.ForEach(x => x?.Destroy()); + Buttons = new List(); + Tabs = new List(); + + if (GameOptionsManager.Instance.currentGameOptions.GameMode == AmongUs.GameOptions.GameModes.HideNSeek) return; + + GameObject.Find("What Is This?")?.Destroy(); + GameObject.Find("RoleSettingsButton")?.Destroy(); + GameObject.Find("GamePresetButton")?.Destroy(); + __instance.ChangeTab(1, false); + + var settingsButton = GameObject.Find("GameSettingsButton"); + settingsButton.transform.localPosition += new Vector3(0f, 2f, 0f); + settingsButton.transform.localScale *= 0.9f; + + CreateSettings(__instance, 3, "ModSettings", "Mod Settings", settingsButton, MultiMenu.Main); + CreateSettings(__instance, 4, "CrewSettings", "Crewmate Settings", settingsButton, MultiMenu.Crewmate); + CreateSettings(__instance, 5, "NeutralSettings", "Neutral Settings", settingsButton, MultiMenu.Neutral); + CreateSettings(__instance, 6, "ImpSettings", "Impostor Settings", settingsButton, MultiMenu.Impostor); + } + + internal static RolesSettingsMenu customRolesSettings = null; + + public static void CreateSettings(GameSettingMenu __instance, int target, string name, string text, GameObject settingsButton, MultiMenu menu) + { + var panel = GameObject.Find("LeftPanel"); + var button = GameObject.Find(name); + if (button == null) + { + button = GameObject.Instantiate(settingsButton, panel.transform); + button.transform.localPosition += new Vector3(0f, -0.55f * target + 1.1f, 0f); + button.name = name; + __instance.StartCoroutine(Effects.Lerp(1f, + new Action(p => + { + button.transform.FindChild("FontPlacer").GetComponentInChildren().text = + text; + }))); + var passiveButton = button.GetComponent(); + passiveButton.OnClick.RemoveAllListeners(); + passiveButton.OnClick.AddListener((Action)(() => { __instance.ChangeTab(target, false); })); + passiveButton.SelectButton(false); + Buttons.Add(passiveButton); + } + + var settingsTab = GameObject.Find("GAME SETTINGS TAB"); + Tabs.RemoveAll(x => x == null); + var tab = GameObject.Instantiate(settingsTab, settingsTab.transform.parent); + tab.name = name; + var tabOptions = tab.GetComponent(); + foreach (var child in tabOptions.Children) child.Destroy(); + tabOptions.scrollBar.transform.FindChild("SliderInner").DestroyChildren(); + tabOptions.Children.Clear(); + var options = CustomOption.AllOptions.Where(x => x.Menu == menu).ToList(); + + var num = 1.5f; + + if (target > 3) + { + var header = Object.Instantiate(tabOptions.categoryHeaderOrigin, Vector3.zero, Quaternion.identity, tabOptions.settingsContainer); + header.SetHeader(StringNames.ImpostorsCategory, 20); + header.Title.text = DestroyableSingleton.Instance.GetString(StringNames.RoleQuotaLabel); + header.transform.localScale = Vector3.one * 0.65f; + header.transform.localPosition = new Vector3(-0.9f, num, -2f); + num -= 0.65f; + + var roleHeader = Object.Instantiate(tabOptions.RolesMenu.categoryHeaderEditRoleOrigin, Vector3.zero, Quaternion.identity, tabOptions.settingsContainer); + roleHeader.SetHeader(target == 6 ? StringNames.ImpostorRolesHeader : StringNames.CrewmateRolesHeader, 20); + roleHeader.Title.text = target == 4 ? "Crewmate Roles" : target == 5 ? "Neutral Roles" : "Impostor Roles"; + roleHeader.Background.color = target == 4 ? Palette.CrewmateBlue : target == 5 ? Color.gray : Palette.ImpostorRed; + roleHeader.transform.localPosition = new Vector3(4.75f, num + 0.2f, -2f); + num -= 0.61f; + } + + foreach (var option in options) { - OptionBehaviour option = null; + if (option.IsRoleOption) + continue; - if (customOption.GetType() == typeof(CustomToggleOption)) + if (option.Type == CustomOptionType.Header) { - option = ((CustomToggleOption)customOption).CreateOption(toggleOptionPrefab, stringOptionPrefab); + var header = Object.Instantiate(tabOptions.categoryHeaderOrigin, Vector3.zero, + Quaternion.identity, tabOptions.settingsContainer); + header.SetHeader(StringNames.ImpostorsCategory, 20); + header.Title.text = option.GetName(); + header.transform.localScale = Vector3.one * 0.65f; + header.transform.localPosition = new Vector3(-0.9f, num, -2f); + num -= 0.625f; + continue; } - else if (customOption.GetType() == typeof(CustomNumberOption)) + + if (option.Type == CustomOptionType.Number) { - option = ((CustomNumberOption) customOption).CreateOption(numberOptionPrefab); + OptionBehaviour optionBehaviour = Object.Instantiate(tabOptions.numberOptionOrigin, + Vector3.zero, Quaternion.identity, tabOptions.settingsContainer); + optionBehaviour.transform.localPosition = new Vector3(0.95f, num, -2f); + optionBehaviour.SetClickMask(tabOptions.ButtonClickMask); + SpriteRenderer[] components = optionBehaviour.GetComponentsInChildren(true); + for (var i = 0; i < components.Length; i++) + components[i].material.SetInt(PlayerMaterial.MaskLayer, 20); + + var numberOption = optionBehaviour as NumberOption; + option.Setting = numberOption; + + tabOptions.Children.Add(optionBehaviour); } - else if (customOption.GetType() == typeof(CustomStringOption)) + + else if (option.Type == CustomOptionType.Role) { - option = ((CustomStringOption) customOption).CreateOption(stringOptionPrefab); + OptionBehaviour optionBehaviour = Object.Instantiate(tabOptions.RolesMenu.roleOptionSettingOrigin, + Vector3.zero, Quaternion.identity, tabOptions.settingsContainer); + optionBehaviour.transform.localPosition = new Vector3(-0.39f, num + 0.26f, -2f); + optionBehaviour.SetClickMask(tabOptions.ButtonClickMask); + SpriteRenderer[] components = optionBehaviour.GetComponentsInChildren(true); + for (var i = 0; i < components.Length; i++) + components[i].material.SetInt(PlayerMaterial.MaskLayer, 20); + + var roleOptionSettingOption = optionBehaviour as RoleOptionSetting; + roleOptionSettingOption.SetRole(GameOptionsManager.Instance.CurrentGameOptions.RoleOptions, option.BaseRole.RoleBehaviour, 20); + roleOptionSettingOption.ChanceMinusBtn.isInteractable = true; + roleOptionSettingOption.ChancePlusBtn.isInteractable = true; + roleOptionSettingOption.CountMinusBtn.isInteractable = true; + roleOptionSettingOption.CountPlusBtn.isInteractable = true; + roleOptionSettingOption.transform.GetChild(3).GetComponent().color = option.BaseRole.Color; + option.Setting = roleOptionSettingOption; + + tabOptions.Children.Add(optionBehaviour); + + // Role Setting Button + var newButton = Object.Instantiate(roleOptionSettingOption.buttons[0], roleOptionSettingOption.transform); + newButton.name = "ConfigButton"; + newButton.transform.localPosition = new Vector3(0.4473f, -0.3f, -2f); + newButton.transform.FindChild("Text_TMP").gameObject.DestroyImmediate(); + newButton.activeSprites.Destroy(); + + // Read Sprite from Mod Resources + var btnRend = newButton.transform.FindChild("ButtonSprite").GetComponent(); + btnRend.sprite = Utility.CreateSprite("PeasAPI.Resources.Cog.png", 100f); + + var passiveButton = newButton.GetComponent(); + passiveButton.OnClick = new ButtonClickedEvent(); + passiveButton.interactableColor = btnRend.color = Color.white; + passiveButton.interactableHoveredColor = Palette.CrewmateBlue; + passiveButton.OnClick.AddListener((UnityAction)(() => + { + __instance.ToggleLeftSideDarkener(on: true); + __instance.ToggleRightSideDarkener(on: false); + var roleTab = Object.Instantiate(__instance.RoleSettingsTab, __instance.transform); + roleTab.gameObject.SetActive(true); + roleTab.transform.GetChild(1).gameObject.SetActive(false); + roleTab.transform.localPosition = new Vector3(1.4873f, -0.653f, -4f); + customRolesSettings = roleTab; + tabOptions.gameObject.SetActive(false); + // Create custom role tab and change to custom role tab + ChangeTab(roleTab, new ModRoleRulesCategory { Role = option.BaseRole, AllGameSettings = option.BaseRole.AdvancedOptions.Values.ToList() }, tabOptions); + })); } - else if (customOption.GetType() == typeof(CustomOptionHeader)) + + else if (option.Type == CustomOptionType.Toggle) { - option = ((CustomOptionHeader) customOption).CreateOption(toggleOptionPrefab); + OptionBehaviour optionBehaviour = Object.Instantiate(tabOptions.checkboxOrigin, Vector3.zero, + Quaternion.identity, tabOptions.settingsContainer); + optionBehaviour.transform.localPosition = new Vector3(0.95f, num, -2f); + optionBehaviour.SetClickMask(tabOptions.ButtonClickMask); + SpriteRenderer[] components = optionBehaviour.GetComponentsInChildren(true); + for (var i = 0; i < components.Length; i++) + components[i].material.SetInt(PlayerMaterial.MaskLayer, 20); + + var toggleOption = optionBehaviour as ToggleOption; + option.Setting = toggleOption; + + tabOptions.Children.Add(optionBehaviour); } - else if (customOption.GetType() == typeof(CustomOptionButton)) + + else if (option.Type == CustomOptionType.String) { - option = ((CustomOptionButton) customOption).CreateOption(toggleOptionPrefab, stringOptionPrefab); + var playerCount = GameOptionsManager.Instance.currentNormalGameOptions.MaxPlayers; + + OptionBehaviour optionBehaviour = Object.Instantiate(tabOptions.stringOptionOrigin, + Vector3.zero, Quaternion.identity, tabOptions.settingsContainer); + optionBehaviour.transform.localPosition = new Vector3(0.95f, num, -2f); + optionBehaviour.SetClickMask(tabOptions.ButtonClickMask); + SpriteRenderer[] components = optionBehaviour.GetComponentsInChildren(true); + for (var i = 0; i < components.Length; i++) + components[i].material.SetInt(PlayerMaterial.MaskLayer, 20); + + var stringOption = optionBehaviour as StringOption; + option.Setting = stringOption; + + tabOptions.Children.Add(optionBehaviour); } - option.transform.localPosition = new Vector3(option.transform.localPosition.x, - LowestOption + 2 * OptionSize - (OptionManager.CustomOptions.IndexOf(customOption) + 1) * OptionSize, -1); + num -= 0.45f; + tabOptions.scrollBar.SetYBoundsMax(-num - 1.65f); + option.OptionCreated(); + } - var options = __instance.Children.ToList(); - options.Add(option); - __instance.Children = options.ToArray(); + for (var i = 0; i < tabOptions.Children.Count; i++) + { + var optionBehaviour = tabOptions.Children[i]; + if (AmongUsClient.Instance && !AmongUsClient.Instance.AmHost) optionBehaviour.SetAsPlayer(); } - __instance.GetComponentInParent().ContentYBounds.max = - AllOptionSize + OptionManager.MenuVisibleOptions.Count * 0.5f - 2 * OptionSize; + Tabs.Add(tab); + tab.SetActive(false); } - [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Update))] - [HarmonyPostfix] - private static void GameOptionsMenuUpdatePatch(GameOptionsMenu __instance) + private static void ChangeTab(RolesSettingsMenu __instance, ModRoleRulesCategory cat, GameOptionsMenu tabOptions) { - __instance.GetComponentInParent().ContentYBounds.max = - AllOptionSize + OptionManager.MenuVisibleOptions.Count * 0.5f - 2 * OptionSize; + CreateAdvancedSettings(__instance, cat, tabOptions); + __instance.roleDescriptionText.text = cat.Role.LongDescription; + __instance.roleTitleText.text = cat.Role.Name; + __instance.roleScreenshot.sprite = cat.Role.Icon; + __instance.roleHeaderSprite.color = cat.Role.Color; + __instance.roleHeaderText.color = Color.white; + __instance.RoleChancesSettings.SetActive(false); + __instance.AdvancedRolesSettings.SetActive(true); + __instance.RefreshChildren(); + ControllerManager.Instance.CurrentUiState.SelectableUiElements = __instance.ControllerSelectable; + } - foreach (var option in __instance.Children.ToList().FindAll(option => option.IsCustom())) + private static void CreateAdvancedSettings(RolesSettingsMenu __instance, ModRoleRulesCategory cat, GameOptionsMenu tabOptions) + { + float num = -0.872f; + foreach (CustomOption allGameSetting in cat.AllGameSettings) { - var customOption = option.GetCustom(); - if (customOption == null) - continue; - - option.gameObject.SetActive(customOption.MenuVisible); - - if (customOption.MenuVisible) - option.transform.localPosition = new Vector3(option.transform.localPosition.x, - LowestOption + 2 * OptionSize - (OptionManager.MenuVisibleOptions.IndexOf(customOption) + 1) * OptionSize, -1); - - if (option.gameObject.GetComponent() != null) - option.gameObject.GetComponent().TitleText.text = customOption.Title; - else if (option.gameObject.GetComponent() != null) - option.gameObject.GetComponent().TitleText.text = customOption.Title; - else if (option.gameObject.GetComponent() != null) - option.gameObject.GetComponent().TitleText.text = customOption.Title; + switch (allGameSetting.Type) + { + case CustomOptionType.Number: + { + OptionBehaviour optionBehaviour = Object.Instantiate(__instance.numberOptionOrigin, Vector3.zero, Quaternion.identity, __instance.AdvancedRolesSettings.transform); + optionBehaviour.transform.localPosition = new Vector3(2.17f, num, -2f); + optionBehaviour.SetClickMask(__instance.ButtonClickMask); + optionBehaviour.LabelBackground.enabled = false; + SpriteRenderer[] components = optionBehaviour.GetComponentsInChildren(true); + for (var i = 0; i < components.Length; i++) + components[i].material.SetInt(PlayerMaterial.MaskLayer, 20); + optionBehaviour.AssociatedRole = cat.Role.RoleBehaviour.Role; + + var numberOption = optionBehaviour as NumberOption; + numberOption.MinusBtn.enabled = true; + numberOption.PlusBtn.enabled = true; + allGameSetting.Setting = numberOption; + + tabOptions.Children.Add(optionBehaviour); + break; + } + case CustomOptionType.Toggle: + { + OptionBehaviour optionBehaviour = Object.Instantiate(tabOptions.checkboxOrigin, Vector3.zero, Quaternion.identity, __instance.AdvancedRolesSettings.transform); + optionBehaviour.transform.localPosition = new Vector3(2.17f, num, -2f); + optionBehaviour.SetClickMask(__instance.ButtonClickMask); + optionBehaviour.LabelBackground.enabled = false; + SpriteRenderer[] components = optionBehaviour.GetComponentsInChildren(true); + for (var i = 0; i < components.Length; i++) + components[i].material.SetInt(PlayerMaterial.MaskLayer, 20); + optionBehaviour.AssociatedRole = cat.Role.RoleBehaviour.Role; + + var toggleOption = optionBehaviour as ToggleOption; + allGameSetting.Setting = toggleOption; + + tabOptions.Children.Add(optionBehaviour); + break; + } + case CustomOptionType.String: + { + var playerCount = GameOptionsManager.Instance.currentNormalGameOptions.MaxPlayers; + + OptionBehaviour optionBehaviour = Object.Instantiate(tabOptions.stringOptionOrigin, Vector3.zero, Quaternion.identity, __instance.AdvancedRolesSettings.transform); + optionBehaviour.transform.localPosition = new Vector3(2.17f, num, -2f); + optionBehaviour.SetClickMask(__instance.ButtonClickMask); + optionBehaviour.LabelBackground.enabled = false; + SpriteRenderer[] components = optionBehaviour.GetComponentsInChildren(true); + for (var i = 0; i < components.Length; i++) + components[i].material.SetInt(PlayerMaterial.MaskLayer, 20); + optionBehaviour.AssociatedRole = cat.Role.RoleBehaviour.Role; + + var stringOption = optionBehaviour as StringOption; + allGameSetting.Setting = stringOption; + + tabOptions.Children.Add(optionBehaviour); + break; + } + } + num -= 0.45f; + __instance.scrollBar.SetYBoundsMax(-num - 1.65f); + allGameSetting.OptionCreated(); } + __instance.scrollBar.CalculateAndSetYBounds(cat.AllGameSettings.Count + 3, 1f, 6f, 0.45f); + __instance.scrollBar.ScrollToTop(); } - [HarmonyPatch(typeof(RolesSettingsMenu), nameof(RolesSettingsMenu.OnEnable))] - [HarmonyPostfix] - public static void RoleOptionCreatePatch(RolesSettingsMenu __instance) + public struct ModRoleRulesCategory { - var roleSettingPrefab = __instance.AllRoleSettings.ToArray()[0]; - var roleTabPrefab = __instance.AllAdvancedSettingTabs.ToArray()[0].Tab; - foreach (var option in OptionManager.CustomRoleOptions) + public BaseRole Role; + + public List AllGameSettings; + } + } + + [HarmonyPatch(typeof(LobbyViewSettingsPane), nameof(LobbyViewSettingsPane.SetTab))] + private class SetTabPane + { + public static bool Prefix(LobbyViewSettingsPane __instance) + { + if ((int)__instance.currentTab < 6) { - if (option.GetType() == typeof(CustomRoleOption)) - { - var newSetting = option.CreateOption(roleSettingPrefab); - newSetting.transform.localPosition = roleSettingPrefab.transform.localPosition - new Vector3(0f , (__instance.AllRoleSettings.ToArray().Count + OptionManager.CustomRoleOptions.IndexOf(option) + 1) * 0.5f); - - var tab = option.CreateOptionObjects(roleTabPrefab); - if (tab != null) - __instance.AllAdvancedSettingTabs.Add(tab); - } + ChangeTabPane.Postfix(__instance, __instance.currentTab); + return false; } - var scroller = roleSettingPrefab.gameObject.transform.parent.parent.GetComponent(); - scroller.ContentYBounds.max = (OptionManager.CustomRoleOptions.Count - 3) * 0.5f; - scroller.transform.FindChild("UI_Scrollbar").gameObject.SetActive(true); + return true; } + } - [HarmonyPatch(typeof(RolesSettingsMenu), nameof(RolesSettingsMenu.ValueChanged))] - [HarmonyPostfix] - public static void RoleOptionValueChangedPatch(RolesSettingsMenu __instance, [HarmonyArgument(0)] OptionBehaviour obj) + [HarmonyPatch(typeof(LobbyViewSettingsPane), nameof(LobbyViewSettingsPane.ChangeTab))] + private class ChangeTabPane + { + public static void Postfix(LobbyViewSettingsPane __instance, StringNames category) { - var custom = obj.GetCustom(); - if (custom != null) + var tab = (int)category; + + foreach (var button in SettingsAwake.Buttons) button.SelectButton(false); + if (tab > 5) return; + __instance.taskTabButton.SelectButton(false); + + if (tab > 0) { - switch (custom) - { - case CustomRoleOption option: - var rates = PlayerControl.GameOptions.RoleOptions.roleRates[option.Role.RoleBehaviour.Role]; - option.SetValue(rates.MaxCount, rates.Chance); - break; - case CustomNumberOption option: - option.SetValue(obj.GetFloat()); - break; - case CustomToggleOption option: - option.SetValue(obj.GetBool()); - break; - case CustomStringOption option: - option.SetValue(obj.GetInt()); - break; - } + tab -= 1; + SettingsAwake.Buttons[tab].SelectButton(true); + SettingsAwake.AddSettings(__instance, SettingsAwake.ButtonTypes[tab]); } } - - [HarmonyPatch(typeof(OptionBehaviour), nameof(OptionBehaviour.SetAsPlayer))] - public static class OptionBehaviourSetAsPlayerPatch + } + + [HarmonyPatch(typeof(LobbyViewSettingsPane), nameof(LobbyViewSettingsPane.Update))] + private class UpdatePane + { + public static void Postfix(LobbyViewSettingsPane __instance) + { + if (SettingsAwake.Buttons.Count == 0) SettingsAwake.Postfix(__instance); + } + } + + [HarmonyPatch(typeof(LobbyViewSettingsPane), nameof(LobbyViewSettingsPane.Awake))] + private class SettingsAwake + { + public static readonly List Buttons = new(); + public static readonly List ButtonTypes = new(); + + public static void Postfix(LobbyViewSettingsPane __instance) { - public static bool Prefix(OptionBehaviour __instance) + Buttons.ForEach(x => x?.Destroy()); + Buttons.Clear(); + ButtonTypes.Clear(); + + if (GameOptionsManager.Instance.currentGameOptions.GameMode == AmongUs.GameOptions.GameModes.HideNSeek) return; + + GameObject.Find("RolesTabs")?.Destroy(); + var overview = GameObject.Find("OverviewTab"); + overview.transform.localScale = new Vector3(0.73f, 1f, 1f); + overview.transform.localPosition += new Vector3(-0.8f, 0f, 0f); + overview.transform.GetChild(0).GetChild(0).transform.localScale += new Vector3(0.35f, 0f, 0f); + overview.transform.GetChild(0).GetChild(0).transform.localPosition += new Vector3(-1f, 0f, 0f); + + CreateButton(__instance, 1, "ModTab", "Mod Settings", MultiMenu.Main, overview); + CreateButton(__instance, 2, "CrewmateTab", "Crewmate Settings", MultiMenu.Crewmate, overview); + CreateButton(__instance, 3, "NeutralTab", "Neutral Settings", MultiMenu.Neutral, overview); + CreateButton(__instance, 4, "ImpostorTab", "Impostor Settings", MultiMenu.Impostor, overview); + } + + public static void CreateButton(LobbyViewSettingsPane __instance, int target, string name, string text, + MultiMenu menu, GameObject overview) + { + var tab = GameObject.Find(name); + if (tab == null) { - foreach (var button in __instance.GetComponentsInChildren()) - { - button.Destroy(); - button.gameObject.SetActive(button.gameObject.name == __instance.gameObject.name && - __instance.Title != StringNames.GameRecommendedSettings); - } - - return false; + tab = GameObject.Instantiate(overview, overview.transform.parent); + tab.transform.localPosition += new Vector3(2.5f, 0f, 0f) * target; + tab.transform.GetChild(0).GetChild(0).transform.localPosition += new Vector3(-0.5f, 0f, 0f); + tab.name = name; + __instance.StartCoroutine(Effects.Lerp(1f, + new Action(p => + { + tab.transform.FindChild("FontPlacer").GetComponentInChildren().text = + text; + }))); + var pTab = tab.GetComponent(); + pTab.OnClick.RemoveAllListeners(); + pTab.OnClick.AddListener((Action)(() => { __instance.ChangeTab((StringNames)target); })); + pTab.SelectButton(false); + Buttons.Add(pTab); + ButtonTypes.Add(menu); } } - - [HarmonyPatch(typeof(NumberOption), nameof(NumberOption.FixedUpdate))] - [HarmonyPostfix] - private static void NumberOptionFixedUpdatePatch(NumberOption __instance) + + public static void AddSettings(LobbyViewSettingsPane __instance, MultiMenu menu) { - var customOption = (CustomNumberOption) __instance.GetCustom(); - if (customOption != null) + var options = CustomOption.AllOptions.Where(x => x.Menu == menu).ToList(); + + var num = 1.3f; + var headingCount = 0; + var settingsThisHeader = 0; + var settingRowCount = 0; + + for (int j = 0; j < __instance.settingsInfo.Count; j++) { - if (__instance.SuffixType == NumberSuffixes.None) - { - __instance.ValueText.text = customOption.Value.ToString(); - return; - } + __instance.settingsInfo[j].gameObject.Destroy(); + } + + __instance.settingsInfo.Clear(); - if (__instance.SuffixType == NumberSuffixes.Multiplier) + foreach (var option in options) + if (option.Type == CustomOptionType.Header) { - __instance.ValueText.text = customOption.Value + "x"; - return; + if (settingsThisHeader % 2 != 0) num -= 0.85f; + var header = Object.Instantiate(__instance.categoryHeaderOrigin); + header.SetHeader(StringNames.ImpostorsCategory, 61); + header.Title.text = option.GetName(); + header.transform.SetParent(__instance.settingsContainer); + header.transform.localScale = Vector3.one; + header.transform.localPosition = new Vector3(-9.8f, num, -2f); + __instance.settingsInfo.Add(header.gameObject); + num -= 1f; + headingCount += 1; + settingsThisHeader = 0; } - - if (__instance.SuffixType == NumberSuffixes.Seconds) + else { - __instance.ValueText.text = customOption.Value + "s"; - return; + var playerCount = GameOptionsManager.Instance.currentNormalGameOptions.MaxPlayers; + + if (option.Type == CustomOptionType.Role) + { + if (settingsThisHeader % 2 != 0) num -= 0.85f; + var panel = Object.Instantiate(__instance.infoPanelRoleOrigin); + panel.transform.SetParent(__instance.settingsContainer); + panel.transform.localScale = Vector3.one; + panel.transform.localPosition = new Vector3(-6.76f, num, -2f); + panel.SetInfo(option.BaseRole.Name, (int)option.ValueObject2, (int)option.ValueObject, 61, option.BaseRole.Color, RoleManager.Instance.AllRoles[8].RoleIconSolid, option.BaseRole.Team == Roles.Team.Crewmate); + __instance.settingsInfo.Add(panel.gameObject); + num -= 0.75f; + headingCount += 1; + settingsThisHeader = 0; + } + else + { + var panel = Object.Instantiate(__instance.infoPanelOrigin); + panel.transform.SetParent(__instance.settingsContainer); + panel.transform.localScale = Vector3.one; + if (settingsThisHeader % 2 != 0) + { + panel.transform.localPosition = new Vector3(-3f, num, -2f); + num -= 0.85f; + } + else + { + settingRowCount += 1; + panel.transform.localPosition = new Vector3(-8.9f, num, -2f); + } + + settingsThisHeader += 1; + panel.SetInfo(StringNames.ImpostorsCategory, option.ToString(), 61); + panel.titleText.text = option.GetName(); + __instance.settingsInfo.Add(panel.gameObject); + } } - __instance.ValueText.text = customOption.Value.ToString(); - + float actual_spacing = (headingCount * 1.05f + settingRowCount * 0.85f) / (headingCount + settingRowCount) * 1.01f; + __instance.scrollBar.CalculateAndSetYBounds(__instance.settingsInfo.Count + (headingCount + settingRowCount) * 2 + headingCount, 2f, 6f, actual_spacing); + } + } + + [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.CoSpawnPlayer))] + private class PlayerJoinPatch + { + public static void Postfix(PlayerPhysics __instance) + { + if (PlayerControl.AllPlayerControls.Count < 2 || !AmongUsClient.Instance || + !PlayerControl.LocalPlayer || !AmongUsClient.Instance.AmHost) return; + + Coroutines.Start(RpcUpdateSetting.SendRpc(RecipientId: __instance.myPlayer.OwnerId)); + } + } + + + [HarmonyPatch(typeof(ToggleOption), nameof(ToggleOption.Toggle))] + private class ToggleButtonPatch + { + public static bool Prefix(ToggleOption __instance) + { + var option = + CustomOption.AllOptions.FirstOrDefault(option => + option.Setting == __instance); // Works but may need to change to gameObject.name check + if (option is CustomToggleOption toggle) + { + toggle.Toggle(); + return false; } + + if (GameOptionsManager.Instance.currentGameOptions.GameMode == AmongUs.GameOptions.GameModes.HideNSeek || + __instance.boolOptionName == BoolOptionNames.VisualTasks || + __instance.boolOptionName == BoolOptionNames.AnonymousVotes || + __instance.boolOptionName == BoolOptionNames.ConfirmImpostor) return true; + return false; } + } - private static bool OnModdedPage; - - [HarmonyPatch(typeof(KeyboardJoystick), nameof(KeyboardJoystick.Update))] - [HarmonyPostfix] - private static void SwitchSettingsPagesPatch(KeyboardJoystick __instance) + [HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.IncreaseChance))] + private class RoleOptionSettingPatchIncreaseChance + { + public static bool Prefix(RoleOptionSetting __instance) { - if (Input.GetKeyDown(KeyCode.RightShift)) - OnModdedPage = !OnModdedPage; + var option = + CustomOption.AllOptions.FirstOrDefault(option => + option.Setting == __instance); // Works but may need to change to gameObject.name check + if (option is CustomRoleOption roleOption) + { + roleOption.IncreaseChance(); + return false; + } + + return true; } - - [HarmonyPatch(typeof(GameOptionsData), nameof(GameOptionsData.ToHudString))] - [HarmonyPrefix] - private static bool AddInformationPatch(GameOptionsData __instance) + } + + [HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.DecreaseChance))] + private class RoleOptionSettingPatchDecreaseChance + { + public static bool Prefix(RoleOptionSetting __instance) { - if (OnModdedPage) + var option = + CustomOption.AllOptions.FirstOrDefault(option => + option.Setting == __instance); // Works but may need to change to gameObject.name check + if (option is CustomRoleOption roleOption) { - __instance.settings.Length = 0; - __instance.settings.AppendLine("Press RightShift to switch to the vanilla settings"); - __instance.settings.AppendLine(); - - __instance.settings.AppendLine("Roles:"); - foreach (var option in OptionManager.CustomRoleOptions) - { - __instance.settings.AppendLine(String.Format(option.HudFormat, $"{option.Role.Color.ToTextColor()}{option.Role.Name}{Utility.StringColor.Reset}", - __instance.RoleOptions.GetNumPerGame(option.Role.RoleBehaviour.Role), - __instance.RoleOptions.GetChancePerGame(option.Role.RoleBehaviour.Role))); - option.AdvancedOptions.Where(_option => _option.HudVisible).Do(_option => RenderOption(_option, __instance.settings, option.AdvancedOptionPrefix) ); - } - - OptionManager.HudVisibleOptions.Where(option => !option.IsFromPeasAPI && !option.AdvancedRoleOption).Do(option => RenderOption(option, __instance.settings) ); - + roleOption.DecreaseChance(); + return false; + } + + return true; + } + } + + [HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.IncreaseCount))] + private class RoleOptionSettingPatchIncreaseCount + { + public static bool Prefix(RoleOptionSetting __instance) + { + var option = + CustomOption.AllOptions.FirstOrDefault(option => + option.Setting == __instance); // Works but may need to change to gameObject.name check + if (option is CustomRoleOption roleOption) + { + roleOption.IncreaseCount(); return false; } + return true; } - - [HarmonyPatch(typeof(GameOptionsData), nameof(GameOptionsData.ToHudString))] - [HarmonyPostfix] - private static void GameOptionsDataToHudStringPatch(GameOptionsData __instance, ref string __result) + } + + [HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.DecreaseCount))] + private class RoleOptionSettingPatchDecreaseCount + { + public static bool Prefix(RoleOptionSetting __instance) { - if (!OnModdedPage) + var option = + CustomOption.AllOptions.FirstOrDefault(option => + option.Setting == __instance); // Works but may need to change to gameObject.name check + if (option is CustomRoleOption roleOption) { - var text = __instance.settings.ToString(); - __instance.settings.Clear(); - __instance.settings.AppendLine("Press RightShift to switch to the modded settings"); - __instance.settings.AppendLine(); - __instance.settings.AppendLine(text); - - OptionManager.HudVisibleOptions.Where(option => option.IsFromPeasAPI).Do(option => RenderOption(option, __instance.settings) ); + roleOption.DecreaseCount(); + return false; } - __result = __instance.settings.ToString(); + return true; } + } - internal static void RenderOption(CustomOption option, StringBuilder builder, string prefix = "") + [HarmonyPatch(typeof(NumberOption), nameof(NumberOption.Initialize))] + private class NumberOptionInitialise + { + public static bool Prefix(NumberOption __instance) { - switch (option) + var option = + CustomOption.AllOptions.FirstOrDefault(option => + option.Setting == __instance); + if (option is CustomNumberOption number) { - case CustomToggleOption _option: - builder.AppendLine(prefix + String.Format(_option.HudFormat, _option.Title, _option.Value ? "On" : "Off") + Utility.StringColor.Reset); - break; - case CustomNumberOption _option: - builder.AppendLine(prefix + String.Format(_option.HudFormat, _option.Title, _option.Value, _option.SuffixType switch - { - NumberSuffixes.None => "", - NumberSuffixes.Multiplier => "x", - NumberSuffixes.Seconds => "s", - _ => "" - }) + Utility.StringColor.Reset); - break; - case CustomStringOption _option: - builder.AppendLine(prefix + String.Format(_option.HudFormat, _option.Title, _option.StringValue) + Utility.StringColor.Reset); - break; - case CustomOptionHeader _option: - builder.AppendLine(prefix + String.Format(_option.HudFormat, _option.Title) + Utility.StringColor.Reset); - break; + __instance.MinusBtn.isInteractable = true; + __instance.PlusBtn.isInteractable = true; + return false; } + + return true; } + } - [HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))] - [HarmonyPostfix] - private static void HudManagerUpdatePatch(HudManager __instance) + [HarmonyPatch(typeof(NumberOption), nameof(NumberOption.Increase))] + private class NumberOptionPatchIncrease + { + public static bool Prefix(NumberOption __instance) { - if (__instance.GameSettings == null) - return; - - __instance.GameSettings.fontSizeMin = - __instance.GameSettings.fontSizeMax = - __instance.GameSettings.fontSize = HudTextSize; - - CreateScroller(__instance); - - var bottomLeft = Camera.main.ScreenToWorldPoint(new Vector3(0, 0, 0)) - Camera.main.transform.localPosition; - - OptionsScroller.ContentYBounds = new FloatRange(-bottomLeft.y, Mathf.Max(-bottomLeft.y, __instance.GameSettings.renderedHeight - -bottomLeft.y + 0.02F)); + var option = + CustomOption.AllOptions.FirstOrDefault(option => + option.Setting == __instance); // Works but may need to change to gameObject.name check + if (option is CustomNumberOption number) + { + number.Increase(); + return false; + } + + return true; } + } - //THIS BIT IS SKIDDED FROM ESSENTIALS: https://github.com/DorCoMaNdO/Reactor-Essentials - private static void CreateScroller(HudManager hudManager) + [HarmonyPatch(typeof(NumberOption), nameof(NumberOption.Decrease))] + private class NumberOptionPatchDecrease + { + public static bool Prefix(NumberOption __instance) { - if (OptionsScroller != null) return; - - OptionsScroller = new GameObject("OptionsScroller").AddComponent(); - OptionsScroller.transform.SetParent(hudManager.GameSettings.transform.parent); - OptionsScroller.gameObject.layer = 5; - - OptionsScroller.transform.localScale = Vector3.one; - OptionsScroller.allowX = false; - OptionsScroller.allowY = true; - OptionsScroller.active = true; - OptionsScroller.velocity = new Vector2(0, 0); - OptionsScroller.ContentYBounds = new FloatRange(0, 0); - OptionsScroller.enabled = true; - - OptionsScroller.Inner = hudManager.GameSettings.transform; - hudManager.GameSettings.transform.SetParent(OptionsScroller.transform); + var option = + CustomOption.AllOptions.FirstOrDefault(option => + option.Setting == __instance); // Works but may need to change to gameObject.name check + if (option is CustomNumberOption number) + { + number.Decrease(); + return false; + } + + return true; } + } - [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameJoined))] - [HarmonyPostfix] - private static void RoleOptionInitialisePatch(AmongUsClient __instance) + [HarmonyPatch(typeof(StringOption), nameof(StringOption.Increase))] + private class StringOptionPatchIncrease + { + public static bool Prefix(StringOption __instance) { - if (!__instance.AmHost) - return; - - foreach (var option in OptionManager.CustomRoleOptions) + var option = CustomOption.AllOptions.FirstOrDefault(option => option.Setting == __instance); + if (option is CustomStringOption str) { - if (!PlayerControl.GameOptions.RoleOptions.roleRates.ContainsKey(option.Role.RoleBehaviour.Role)) - PlayerControl.GameOptions.RoleOptions.roleRates[option.Role.RoleBehaviour.Role] = - new RoleOptionsData.RoleRate(); - var rates = PlayerControl.GameOptions.RoleOptions.roleRates[option.Role.RoleBehaviour.Role]; - option.Count = rates.MaxCount; - option.Chance = rates.Chance; + str.Increase(); + + return false; } + + return true; } + } - [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerJoined))] - [HarmonyPostfix] - private static void AmongUsClientOnPlayerJoinedPatch(AmongUsClient __instance, - [HarmonyArgument(0)] ClientData client) + [HarmonyPatch(typeof(StringOption), nameof(StringOption.Decrease))] + private class StringOptionPatchDecrease + { + public static bool Prefix(StringOption __instance) { - if (__instance.AmHost) + var option = CustomOption.AllOptions.FirstOrDefault(option => option.Setting == __instance); + if (option is CustomStringOption str) { - foreach (var option in OptionManager.CustomOptions) - { - if (option.GetType() == typeof(CustomToggleOption)) - { - Rpc.Instance.SendTo(client.Id, new RpcUpdateSetting.Data(option, ((CustomToggleOption) option).Value)); - } - else if (option.GetType() == typeof(CustomNumberOption)) - { - Rpc.Instance.SendTo(client.Id, new RpcUpdateSetting.Data(option, ((CustomNumberOption) option).Value)); - } - else if (option.GetType() == typeof(CustomStringOption)) - { - Rpc.Instance.SendTo(client.Id, new RpcUpdateSetting.Data(option, ((CustomStringOption) option).Value)); - } - } + str.Decrease(); + + return false; } + + return true; + } + } + + [HarmonyPatch(typeof(NotificationPopper), nameof(NotificationPopper.AddRoleSettingsChangeMessage))] + public static class NotificationPopperPatch + { + [HarmonyPrefix] + public static bool RoleChangeMsgPatch( + NotificationPopper __instance, + [HarmonyArgument(0)] StringNames key, + [HarmonyArgument(1)] int roleCount, + [HarmonyArgument(2)] int roleChance, + [HarmonyArgument(3)] RoleTeamTypes teamType, + [HarmonyArgument(4)] bool playSound) + { + var item = TranslationController.Instance.GetString( + StringNames.LobbyChangeSettingNotificationRole, + string.Concat( + "", + Palette.CrewmateSettingChangeText.ToTextColor(), + TranslationController.Instance.GetString(key), + "" + ), + "" + roleCount + "", + "" + roleChance + "%" + ); + + __instance.SettingsChangeMessageLogic(key, item, playSound); + return false; } } } \ No newline at end of file diff --git a/PeasAPI/Patches.cs b/PeasAPI/Patches.cs index 30042d3..f46b3e3 100644 --- a/PeasAPI/Patches.cs +++ b/PeasAPI/Patches.cs @@ -1,19 +1,18 @@ using HarmonyLib; -using UnityEngine; namespace PeasAPI { [HarmonyPatch] public static class Patches { - [HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))] + /*[HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))] [HarmonyPostfix] public static void ChangeZOfAccountTabPatch() { var tab = AccountManager.Instance.accountTab; tab.transform.SetZ(1f); tab.GetComponent().computedClosedPosition = tab.GetComponent().computedClosedPosition.SetZ(1); - } + }*/ [HarmonyPatch(typeof(AccountManager), nameof(AccountManager.RandomizeName))] [HarmonyPrefix] diff --git a/PeasAPI/PeasAPI-Icon.png b/PeasAPI/PeasAPI-Icon.png index 9f98727..8ba40c9 100644 Binary files a/PeasAPI/PeasAPI-Icon.png and b/PeasAPI/PeasAPI-Icon.png differ diff --git a/PeasAPI/PeasAPI.cs b/PeasAPI/PeasAPI.cs index f1882e1..45a02e9 100644 --- a/PeasAPI/PeasAPI.cs +++ b/PeasAPI/PeasAPI.cs @@ -1,7 +1,8 @@ -using BepInEx; +using System; +using BepInEx; using BepInEx.Configuration; -using BepInEx.IL2CPP; using BepInEx.Logging; +using BepInEx.Unity.IL2CPP; using HarmonyLib; using InnerNet; using PeasAPI.Components; @@ -15,16 +16,18 @@ namespace PeasAPI { [HarmonyPatch] - [BepInPlugin(Id, "PeasAPI", Version)] + [BepInPlugin(Id, "PeasAPI", VersionString)] [BepInProcess("Among Us.exe")] [BepInDependency(ReactorPlugin.Id)] public class PeasAPI : BasePlugin { public const string Id = "tk.peasplayer.amongus.api"; - public const string Version = "1.8.3"; + public const string VersionString = "1.9.2"; public Harmony Harmony { get; } = new Harmony(Id); + public static Version Version = Version.Parse(VersionString); + public static readonly Random Random = new Random(); public static ConfigFile ConfigFile { get; private set; } @@ -47,7 +50,7 @@ public static bool GameStarted { return GameData.Instance && ShipStatus.Instance && AmongUsClient.Instance && (AmongUsClient.Instance.GameState == InnerNetClient.GameStates.Started || - AmongUsClient.Instance.GameMode == global::GameModes.FreePlay); + AmongUsClient.Instance.NetworkMode == global::NetworkModes.FreePlay); } } @@ -71,15 +74,16 @@ public override void Load() ConfigFile.Bind("CustomServer", "Port", (ushort)22023).Value); } - UpdateManager.RegisterGitHubUpdateListener("Peasplayer", "PeasAPI"); + UpdateManager.RegisterGitHubUpdateListener("fangkuaiclub", "PeasAPI-R"); RegisterCustomRoleAttribute.Load(); RegisterCustomGameModeAttribute.Load(); - + + new CustomHeaderOption(MultiMenu.Main, "General Settings"); ShowRolesOfDead = - new CustomToggleOption("ShowRolesOfDead", "Show the roles of dead player", false) {IsFromPeasAPI = true}; - GameModeManager.GameModeOption = new CustomStringOption("gamemode", "GameMode", "None") {IsFromPeasAPI = true}; - + new CustomToggleOption(MultiMenu.Main, "Show the roles of dead player", false); + GameModeManager.GameModeOption = new CustomStringOption(MultiMenu.Main, "GameMode", new string[] { "None" }); + Harmony.PatchAll(); } diff --git a/PeasAPI/PeasAPI.csproj b/PeasAPI/PeasAPI.csproj index 92acbe8..5c1db95 100644 --- a/PeasAPI/PeasAPI.csproj +++ b/PeasAPI/PeasAPI.csproj @@ -1,19 +1,18 @@  - netstandard2.1 - 1.8.3 + net6.0 + 1.9.2 release API for making Among Us mods - Peasplayer + FangkuaiYa latest - - Steam - 2022.3.29 - 2022.3.29 - + embedded + + PeasAPI-R PeasAPI-Icon.png + README.md git - https://github.com/Peasplayer/PeasAPI + https://github.com/fangkuaiclub/PeasAPI-R AGPL-3.0-only true true @@ -22,10 +21,13 @@ - - - - + + + + + + + @@ -33,7 +35,7 @@ - + diff --git a/PeasAPI/Resources/Cog.png b/PeasAPI/Resources/Cog.png new file mode 100644 index 0000000..8551db4 Binary files /dev/null and b/PeasAPI/Resources/Cog.png differ diff --git a/PeasAPI/Placeholder.png b/PeasAPI/Resources/Placeholder.png similarity index 100% rename from PeasAPI/Placeholder.png rename to PeasAPI/Resources/Placeholder.png diff --git a/PeasAPI/Roles/BaseRole.cs b/PeasAPI/Roles/BaseRole.cs index 3e9212e..08813d8 100644 --- a/PeasAPI/Roles/BaseRole.cs +++ b/PeasAPI/Roles/BaseRole.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using BepInEx.IL2CPP; +using BepInEx.Unity.IL2CPP; using PeasAPI.Managers; using PeasAPI.Options; using UnityEngine; @@ -25,10 +25,16 @@ public abstract class BaseRole /// The description of the Role. Will displayed at the intro /// public abstract string Description { get; } - + + /// + /// More detailed role description, show in role settings + /// public abstract string LongDescription { get; } - public virtual Sprite Icon { get; } = Utility.CreateSprite("PeasAPI.Placeholder.png"); + /// + /// Used to display images in role settings + /// + public virtual Sprite Icon { get; } = Utility.CreateSprite("PeasAPI.Resources.Placeholder.png"); /// /// The description of the Role at the task list @@ -60,12 +66,11 @@ public abstract class BaseRole /// /// How many player should get the Role /// + public virtual int Chance { get; set; } = 0; + public virtual int Count { get; set; } = 0; - public virtual int MaxCount { get; set; } = 15; - public virtual int Chance { get; set; } = 100; - public virtual bool CreateRoleOption { get; set; } = true; public CustomRoleOption Option; @@ -76,7 +81,7 @@ public abstract class BaseRole public virtual Type[] GameModeWhitelist { get; } = Array.Empty(); - public virtual float KillDistance { get; set; } = GameOptionsData.KillDistances[Mathf.Clamp(PlayerControl.GameOptions.KillDistance, 0, 2)]; + public virtual float KillDistance { get; set; } = Mathf.Clamp(GameManager.Instance?.LogicOptions?.GetKillDistance() ?? 1.8f, 0, 2); /// /// If a member of the role should be able to kill that player / in general @@ -118,7 +123,7 @@ public virtual bool _IsRoleVisible(PlayerControl playerWithRole, PlayerControl p switch (this.Visibility) { - case Visibility.Role: return perspective.IsRole(this); + case Visibility.Role: return perspective.IsCustomRole(this); case Visibility.Impostor: return perspective.Data.Role.IsImpostor; case Visibility.Crewmate: return true; case Visibility.NoOne: return false; @@ -127,38 +132,6 @@ public virtual bool _IsRoleVisible(PlayerControl playerWithRole, PlayerControl p } } - /// - /// This method calculates the nearest player to kill for a member of this role - /// - public virtual PlayerControl FindClosestTarget(PlayerControl from, bool protecting) - { - PlayerControl result = null; - float num = KillDistance; - if (!ShipStatus.Instance) - { - return null; - } - Vector2 truePosition = from.GetTruePosition(); - foreach (var playerInfo in GameData.Instance.AllPlayers) - { - if (!playerInfo.Disconnected && playerInfo.PlayerId != from.PlayerId && !playerInfo.IsDead && (from.GetRole().CanKill(playerInfo.Object) || protecting) && !playerInfo.Object.inVent) - { - PlayerControl @object = playerInfo.Object; - if (@object && @object.Collider.enabled) - { - Vector2 vector = @object.GetTruePosition() - truePosition; - float magnitude = vector.magnitude; - if (magnitude <= num && !PhysicsHelpers.AnyNonTriggersBetween(truePosition, vector.normalized, magnitude, Constants.ShipAndObjectsMask)) - { - result = @object; - num = magnitude; - } - } - } - } - return result; - } - public virtual bool ShouldGameEnd(GameOverReason reason) => true; /// @@ -184,10 +157,10 @@ internal void _OnUpdate() continue; if (PlayerControl.LocalPlayer == null) continue; - if (playerControl.IsRole(this) && _IsRoleVisible(playerControl, PlayerControl.LocalPlayer)) + if (playerControl.IsCustomRole(this) && _IsRoleVisible(playerControl, PlayerControl.LocalPlayer)) { - playerControl.nameText.color = this.Color; - playerControl.nameText.text = $"{player.GetPlayer().name}\n{Name}"; + playerControl.cosmetics.nameText.color = this.Color; + playerControl.cosmetics.nameText.text = $"{player.GetPlayer().name}\n{Name}"; } } @@ -213,10 +186,10 @@ internal void _OnMeetingUpdate(MeetingHud __instance) continue; if (PlayerControl.LocalPlayer == null) continue; - if (playerControl.IsRole(this) && _IsRoleVisible(playerControl, PlayerControl.LocalPlayer)) + if (playerControl.IsCustomRole(this) && _IsRoleVisible(playerControl, PlayerControl.LocalPlayer)) { - playerControl.nameText.color = this.Color; - playerControl.nameText.text = $"{player.GetPlayer().name}\n{Name}"; + playerControl.cosmetics.nameText.color = this.Color; + playerControl.cosmetics.nameText.text = $"{player.GetPlayer().name}\n{Name}"; } } @@ -227,7 +200,7 @@ internal void _OnMeetingUpdate(MeetingHud __instance) continue; if (PlayerControl.LocalPlayer == null) continue; - if (player.IsRole(this) && _IsRoleVisible(player, PlayerControl.LocalPlayer)) + if (player.IsCustomRole(this) && _IsRoleVisible(player, PlayerControl.LocalPlayer)) { pstate.NameText.color = Color; pstate.NameText.text = $"{player.name}\n{Name}"; @@ -276,14 +249,14 @@ public virtual void OnTaskComplete(PlayerControl player, PlayerTask task) public int GetCount() { - return Option?.Count ?? Count; + return Option?.GetCount() ?? Count; } - + public int GetChance() { - return Option?.Chance ?? Chance; + return Option?.GetChance() ?? Chance; } - + public BaseRole(BasePlugin plugin) { Id = RoleManager.GetRoleId(); diff --git a/PeasAPI/Roles/ModRole.cs b/PeasAPI/Roles/ModRole.cs new file mode 100644 index 0000000..7765b4d --- /dev/null +++ b/PeasAPI/Roles/ModRole.cs @@ -0,0 +1,86 @@ +using Il2CppSystem.Text; +using PeasAPI; +using Reactor.Utilities.Attributes; + +namespace PeasAPI.Roles; + +[RegisterInIl2Cpp] +public class ModRole : RoleBehaviour +{ + public override bool IsDead => false; + + public void Update() + { + if (!PlayerControl.LocalPlayer) + return; + + if (!PlayerControl.LocalPlayer.IsCustomRole()) + return; + + if (CanUseKillButton != PlayerControl.LocalPlayer.GetCustomRole().CanKill()) + { + CanUseKillButton = !CanUseKillButton; + HudManager.Instance.SetHudActive(true); + } + } + + public override bool CanUse(IUsable usable) + { + var role = PlayerControl.LocalPlayer.GetCustomRole(); + if (role != null && role.CanVent) + { + CanVent = role.CanVent; + return usable.TryCast() != null; + } + + var console = usable.TryCast(); + + if (!role.HasToDoTasks) + return !(console != null) || console.AllowImpostor; + + return console != null; + } + + public override bool DidWin(GameOverReason gameOverReason) + { + /*var customRole = PlayerControl.LocalPlayer.GetCustomRole(); + if (customRole != null) + return customRole.DidWin(gameOverReason);*/ + PeasAPI.Logger.LogInfo(gameOverReason); + return false; + } + + public override void AppendTaskHint(StringBuilder taskStringBuilder) + { + if (BlurbMed.IsNullOrWhiteSpace()) + return; + + taskStringBuilder.AppendFormat("\n{0}{1} {2}\n{3}", NameColor.ToTextColor(), NiceName, + DestroyableSingleton.Instance.GetString(StringNames.RoleHint), BlurbMed); + } + + public override void SpawnTaskHeader(PlayerControl playerControl) + { + if (!playerControl.IsLocal()) + return; + + var importantTask = PlayerTask.GetOrCreateTask(playerControl); + importantTask.Text = string.Concat(NameColor.ToTextColor(), Blurb, "\r", + TasksCountTowardProgress + ? "" + : "\n" + DestroyableSingleton.Instance.GetString(StringNames.FakeTasks), + ""); + } + + public override PlayerControl FindClosestTarget() + { + if (PlayerControl.LocalPlayer.IsCustomRole()) + { + var playersInAbilityRangeSorted = GetPlayersInAbilityRangeSorted(GetTempPlayerList()); + if (playersInAbilityRangeSorted.Count <= 0) return null; + return playersInAbilityRangeSorted.ToArray()[0]; + } + + return null; + } +} \ No newline at end of file diff --git a/PeasAPI/Roles/Patches.cs b/PeasAPI/Roles/Patches.cs index 67f7062..22e90d9 100644 --- a/PeasAPI/Roles/Patches.cs +++ b/PeasAPI/Roles/Patches.cs @@ -1,13 +1,11 @@ -using System; -using System.Linq; +using System.Linq; +using AmongUs.GameOptions; using HarmonyLib; using Il2CppSystem.Collections.Generic; using PeasAPI.CustomButtons; using PeasAPI.CustomRpc; -using Reactor.Networking; -using UnhollowerBaseLib; +using Reactor.Networking.Rpc; using UnityEngine; -using Object = Il2CppSystem.Object; namespace PeasAPI.Roles { @@ -27,7 +25,7 @@ public static void OnGameEndPatch(AmongUsClient __instance) [HarmonyPrefix] public static void ResetRolePatch(AmongUsClient __instance) { - PlayerControl.AllPlayerControls.ToArray().Where(player => player != null).Do(player => player.SetRole(null)); + PlayerControl.AllPlayerControls.ToArray().Where(player => player != null).Do(player => player.SetCustomRole(null)); } [HarmonyPatch(typeof(global::RoleManager), nameof(global::RoleManager.SelectRoles))] @@ -37,10 +35,10 @@ public static void InitializeRolesPatch() Rpc.Instance.Send(); } - [HarmonyPatch(typeof(global::RoleManager), nameof(global::RoleManager.AssignRolesFromList))] + [HarmonyPatch(typeof(global::LogicRoleSelectionNormal), nameof(global::LogicRoleSelectionNormal.AssignRolesFromList))] [HarmonyPrefix] - public static bool ChangeImpostors(global::RoleManager __instance, - [HarmonyArgument(0)] List players, [HarmonyArgument(1)] int teamMax, + public static bool ChangeImpostors(global::LogicRoleSelectionNormal __instance, + [HarmonyArgument(0)] List players, [HarmonyArgument(1)] int teamMax, [HarmonyArgument(2)] List roleList, [HarmonyArgument(3)] ref int rolesAssigned) { while (roleList.Count > 0 && players.Count > 0 && rolesAssigned < teamMax) @@ -59,13 +57,13 @@ public static bool ChangeImpostors(global::RoleManager __instance, return false; } - [HarmonyPatch(typeof(IntroCutscene._ShowRole_d__24), nameof(IntroCutscene._ShowRole_d__24.MoveNext))] + [HarmonyPatch(typeof(IntroCutscene._ShowRole_d__41), nameof(IntroCutscene._ShowRole_d__41.MoveNext))] [HarmonyPostfix] - public static void RoleTextPatch(IntroCutscene._ShowRole_d__24 __instance) + public static void RoleTextPatch(IntroCutscene._ShowRole_d__41 __instance) { - if (PlayerControl.LocalPlayer.GetRole() != null) + if (PlayerControl.LocalPlayer.GetCustomRole() != null) { - var role = PlayerControl.LocalPlayer.GetRole(); + var role = PlayerControl.LocalPlayer.GetCustomRole(); var scene = __instance.__4__this; scene.RoleText.text = role.Name; @@ -80,9 +78,9 @@ public static void RoleTextPatch(IntroCutscene._ShowRole_d__24 __instance) [HarmonyPostfix] public static void TeamTextPatch(IntroCutscene __instance) { - if (PlayerControl.LocalPlayer.GetRole() != null) + if (PlayerControl.LocalPlayer.GetCustomRole() != null) { - var role = PlayerControl.LocalPlayer.GetRole(); + var role = PlayerControl.LocalPlayer.GetCustomRole(); var scene = __instance; scene.TeamTitle.text = role.Name; @@ -99,9 +97,9 @@ public static void TeamTextPatch(IntroCutscene __instance) public static void RoleTeamPatch(IntroCutscene __instance, [HarmonyArgument(0)] ref List yourTeam) { - if (PlayerControl.LocalPlayer.GetRole() != null) + if (PlayerControl.LocalPlayer.GetCustomRole() != null) { - var role = PlayerControl.LocalPlayer.GetRole(); + var role = PlayerControl.LocalPlayer.GetCustomRole(); if (role.Team == Team.Alone) { yourTeam = new List(); @@ -151,18 +149,18 @@ public static bool Prefix(ref string __result, [HarmonyArgument(0)] StringNames return true; } }*/ - [HarmonyPatch(typeof(ExileController), nameof(ExileController.Begin))] + [HarmonyPatch(typeof(ExileController), nameof(ExileController.BeginForGameplay))] [HarmonyPostfix] - public static void ChangeExileTextPatch(ExileController __instance, [HarmonyArgument(0)] GameData.PlayerInfo exiled, [HarmonyArgument(1)] bool tie) + public static void ChangeExileTextPatch(ExileController __instance, [HarmonyArgument(0)] NetworkedPlayerInfo player, [HarmonyArgument(1)] bool voteTie) { - if (tie || exiled == null) + if (voteTie || player == null) return; - var role = exiled.Object.GetRole(); + var role = player.Object.GetCustomRole(); if (role != null) { var article = role.Members.Count > 1 ? "a" : "the"; - __instance.completeString = $"{ExileController.Instance.exiled.PlayerName} was {article} {role.Name}."; + __instance.completeString = $"{ExileController.Instance.initData.networkedPlayer.PlayerName} was {article} {role.Name}."; } } @@ -194,7 +192,7 @@ public static void Postfix(PlayerControl __instance) { if (PeasAPI.GameStarted) { - var localRole = PlayerControl.LocalPlayer.GetRole(); + var localRole = PlayerControl.LocalPlayer.GetCustomRole(); if (localRole != null && __instance.PlayerId == PlayerControl.LocalPlayer.PlayerId) { @@ -205,7 +203,7 @@ public static void Postfix(PlayerControl __instance) { if (!__instance.Data.Role.IsImpostor) __instance.SetKillTimer(__instance.killTimer - Time.fixedDeltaTime); - PlayerControl target = __instance.FindClosestTarget(false); + PlayerControl target = __instance.Data.Role.FindClosestTarget(); HudManager.Instance.KillButton.SetTarget(target); } else @@ -260,13 +258,13 @@ public static bool RemoveCheckMurder(KillButton __instance) Debug.LogWarning(string.Format("Bad kill from {0} to {1}", killer.PlayerId, num)); return false; } - GameData.PlayerInfo data = target.Data; + NetworkedPlayerInfo data = target.Data; if (data == null || data.IsDead || target.inVent) { Debug.LogWarning("Invalid target data for kill"); return false; } - PlayerControl.LocalPlayer.RpcMurderPlayer(__instance.currentTarget); + PlayerControl.LocalPlayer.RpcMurderPlayer(__instance.currentTarget, true); __instance.SetTarget(null); } return false; @@ -279,7 +277,7 @@ public static bool Prefix(KillButton __instance, [HarmonyArgument(0)] PlayerCont { if (!PlayerControl.LocalPlayer || PlayerControl.LocalPlayer.Data == null || !PlayerControl.LocalPlayer.Data.Role) return false; - RoleTeamTypes teamType = PlayerControl.LocalPlayer.GetRole() == null ? PlayerControl.LocalPlayer.Data.Role.TeamType : PlayerControl.LocalPlayer.GetRole().CanKill() ? RoleTeamTypes.Impostor : RoleTeamTypes.Crewmate; + RoleTeamTypes teamType = PlayerControl.LocalPlayer.GetCustomRole() == null ? PlayerControl.LocalPlayer.Data.Role.TeamType : PlayerControl.LocalPlayer.GetCustomRole().CanKill() ? RoleTeamTypes.Impostor : RoleTeamTypes.Crewmate; if (__instance.currentTarget && __instance.currentTarget != target) { __instance.currentTarget.ToggleHighlight(false, teamType); @@ -301,12 +299,12 @@ public static class PlayerControlSetKillTimerPatch { public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] float time) { - if (__instance.GetRole() != null && __instance.GetRole().CanKill() || __instance.Data.Role.CanUseKillButton) + if (__instance.GetCustomRole() != null && __instance.GetCustomRole().CanKill() || __instance.Data.Role.CanUseKillButton) { - if (PlayerControl.GameOptions.KillCooldown <= 0f) + if (GameOptionsManager.Instance.currentNormalGameOptions.KillCooldown <= 0f) return false; - __instance.killTimer = Mathf.Clamp(time, 0f, PlayerControl.GameOptions.KillCooldown); - DestroyableSingleton.Instance.KillButton.SetCoolDown(__instance.killTimer, PlayerControl.GameOptions.KillCooldown); + __instance.killTimer = Mathf.Clamp(time, 0f, GameOptionsManager.Instance.currentNormalGameOptions.KillCooldown); + DestroyableSingleton.Instance.KillButton.SetCoolDown(__instance.killTimer, GameOptionsManager.Instance.currentNormalGameOptions.KillCooldown); } return false; } @@ -328,40 +326,23 @@ public static void OnPlayerExiledPatch(PlayerControl __instance) RoleManager.Roles.Do(r => r.OnExiled(__instance)); } - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CoStartMeeting))] + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.StartMeeting))] [HarmonyPrefix] public static void OnMeetingStart(MeetingHud __instance) { RoleManager.Roles.Do(r => r.OnMeetingStart(__instance)); } - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FindClosestTarget))] - public static class PlayerControlFindClosestTargetPatch - { - public static bool Prefix(PlayerControl __instance, out PlayerControl __result, - [HarmonyArgument(0)] bool protecting) - { - if (__instance.GetRole() != null) - { - __result = __instance.GetRole().FindClosestTarget(__instance, protecting); - return false; - } - - __result = null; - return true; - } - } - - [HarmonyPatch(typeof(PlayerControl._CoSetTasks_d__112), nameof(PlayerControl._CoSetTasks_d__112.MoveNext))] + [HarmonyPatch(typeof(PlayerControl._CoSetTasks_d__103), nameof(PlayerControl._CoSetTasks_d__103.MoveNext))] public static class PlayerControlSetTasks { - public static void Postfix(PlayerControl._CoSetTasks_d__112 __instance) + public static void Postfix(PlayerControl._CoSetTasks_d__103 __instance) { if (__instance == null) return; var player = __instance.__4__this; - var role = player.GetRole(); + var role = player.GetCustomRole(); if (role == null) return; @@ -397,25 +378,25 @@ public static bool Prefix(SabotageButton __instance) { if (__instance.isActiveAndEnabled && PeasAPI.GameStarted) { - var role = PlayerControl.LocalPlayer.GetRole(); - + var role = PlayerControl.LocalPlayer.GetCustomRole(); if (role == null) return true; - - HudManager.Instance.ShowMap((Action)(map => + + var mapOptions = new MapOptions { - foreach (MapRoom mapRoom in map.infectedOverlay.rooms.ToArray() - .Where(room => !role.CanSabotage(room.room))) - { - mapRoom.gameObject.SetActive(false); - } - - map.ShowSabotageMap(); - })); - + Mode = MapOptions.Modes.Sabotage + }; + + MapBehaviour.Instance.Show(mapOptions); + + foreach (MapRoom mapRoom in MapBehaviour.Instance.infectedOverlay.rooms.ToArray()) + { + mapRoom.gameObject.SetActive(role.CanSabotage(mapRoom.room)); + } + return false; } - + return true; } } @@ -427,18 +408,15 @@ public static bool Prefix(MapBehaviour __instance) { if (PeasAPI.GameStarted) { - var role = PlayerControl.LocalPlayer.GetRole(); + var role = PlayerControl.LocalPlayer.GetCustomRole(); if (role == null) return true; - HudManager.Instance.ShowMap((Action) (map => + foreach (MapRoom mapRoom in __instance.infectedOverlay.rooms.ToArray()) { - foreach (MapRoom mapRoom in map.infectedOverlay.rooms.ToArray()) - { - mapRoom.gameObject.SetActive(role.CanSabotage(mapRoom.room)); - } - })); + mapRoom.gameObject.SetActive(role.CanSabotage(mapRoom.room)); + } //return false; } @@ -454,7 +432,7 @@ public static class VentCanUsePatch public static void Postfix(Vent __instance, [HarmonyArgument(1)] ref bool canUse, [HarmonyArgument(2)] ref bool couldUse, ref float __result) { - BaseRole role = PlayerControl.LocalPlayer.GetRole(); + BaseRole role = PlayerControl.LocalPlayer.GetCustomRole(); if (role == null) return; @@ -475,9 +453,9 @@ public static void Postfix(Vent __instance, [HarmonyArgument(1)] ref bool canUse } } - [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.RpcEndGame))] + [HarmonyPatch(typeof(GameManager), nameof(GameManager.RpcEndGame))] [HarmonyPrefix] - private static bool ShouldGameEndPatch(ShipStatus __instance, [HarmonyArgument(0)] GameOverReason endReason) + private static bool ShouldGameEndPatch(GameManager __instance, [HarmonyArgument(0)] GameOverReason endReason) { return RoleManager.Roles.Count(r => r.Members.Count != 0 && !r.ShouldGameEnd(endReason)) == 0; } @@ -520,7 +498,7 @@ private static bool DoTasksCountPatch(GameData __instance) __instance.CompletedTasks = 0; foreach (var playerInfo in __instance.AllPlayers) { - if (!playerInfo.Disconnected && playerInfo.Tasks != null && playerInfo.Object && (PlayerControl.GameOptions.GhostsDoTasks || !playerInfo.IsDead) && playerInfo.Role && playerInfo.Role.TasksCountTowardProgress && (playerInfo.GetRole() == null || playerInfo.GetRole().HasToDoTasks)) + if (!playerInfo.Disconnected && playerInfo.Tasks != null && playerInfo.Object && (GameOptionsManager.Instance.currentNormalGameOptions.GhostsDoTasks || !playerInfo.IsDead) && playerInfo.Role && playerInfo.Role.TasksCountTowardProgress && (playerInfo.GetCustomRole() == null || playerInfo.GetCustomRole().HasToDoTasks)) { foreach (var task in playerInfo.Tasks) { @@ -534,8 +512,8 @@ private static bool DoTasksCountPatch(GameData __instance) } /*for (int i = 0; i < __instance.AllPlayers.Count; i++) { - GameData.PlayerInfo playerInfo = __instance.AllPlayers[i]; - if (!playerInfo.Disconnected && playerInfo.Tasks != null && playerInfo.Object && (PlayerControl.GameOptions.GhostsDoTasks || !playerInfo.IsDead) && playerInfo.Role && playerInfo.Role.TasksCountTowardProgress) + NetworkedPlayerInfo playerInfo = __instance.AllPlayers[i]; + if (!playerInfo.Disconnected && playerInfo.Tasks != null && playerInfo.Object && (GameOptionsManager.Instance.currentNormalGameOptions.GhostsDoTasks || !playerInfo.IsDead) && playerInfo.Role && playerInfo.Role.TasksCountTowardProgress) { for (int j = 0; j < playerInfo.Tasks.Count; j++) { diff --git a/PeasAPI/Roles/RoleManager.cs b/PeasAPI/Roles/RoleManager.cs index 63cee14..e45a3a2 100644 --- a/PeasAPI/Roles/RoleManager.cs +++ b/PeasAPI/Roles/RoleManager.cs @@ -1,10 +1,11 @@ using System.Collections.Generic; using System.Linq; +using AmongUs.GameOptions; using HarmonyLib; using PeasAPI.CustomRpc; -using Reactor; -using Reactor.Extensions; -using Reactor.Networking; +using Reactor.Localization.Utilities; +using Reactor.Networking.Rpc; +using Reactor.Utilities.Extensions; using UnityEngine; namespace PeasAPI.Roles @@ -25,23 +26,25 @@ internal static RoleBehaviour ToRoleBehaviour(BaseRole baseRole) { if (GameObject.Find($"{baseRole.Name}-Role")) { - return GameObject.Find($"{baseRole.Name}-Role").GetComponent(); + return GameObject.Find($"{baseRole.Name}-Role").GetComponent(); } var roleObject = new GameObject($"{baseRole.Name}-Role"); roleObject.DontDestroy(); - var role = roleObject.AddComponent(); - role.StringName = CustomStringName.Register(baseRole.Name); - role.BlurbName = CustomStringName.Register(baseRole.Description); - role.BlurbNameLong = CustomStringName.Register(baseRole.LongDescription); - role.BlurbNameMed = CustomStringName.Register(baseRole.Name); - role.Role = (RoleTypes) (6 + baseRole.Id); + var role = roleObject.AddComponent(); + role.StringName = CustomStringName.CreateAndRegister(baseRole.Name); + role.BlurbName = CustomStringName.CreateAndRegister(baseRole.Description); + role.BlurbNameLong = CustomStringName.CreateAndRegister(baseRole.LongDescription); + role.BlurbNameMed = CustomStringName.CreateAndRegister(baseRole.Name); + role.Role = (RoleTypes) (20 + baseRole.Id); + role.NameColor = baseRole.Color; var abilityButtonSettings = ScriptableObject.CreateInstance(); abilityButtonSettings.Image = baseRole.Icon; - abilityButtonSettings.Text = CustomStringName.Register(baseRole.Name); + abilityButtonSettings.Text = CustomStringName.CreateAndRegister(baseRole.Name); + abilityButtonSettings.FontMaterial = Material.GetDefaultMaterial(); role.Ability = abilityButtonSettings; role.TeamType = baseRole.Team switch @@ -57,8 +60,6 @@ internal static RoleBehaviour ToRoleBehaviour(BaseRole baseRole) role.CanVent = baseRole.CanVent; role.CanUseKillButton = baseRole.CanKill(); - PlayerControl.GameOptions.RoleOptions.SetRoleRate(role.Role, 0, 0); - global::RoleManager.Instance.AllRoles.AddItem(role); return role; diff --git a/PeasAPI/Utility.cs b/PeasAPI/Utility.cs index ee9a8af..34803bc 100644 --- a/PeasAPI/Utility.cs +++ b/PeasAPI/Utility.cs @@ -1,8 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; -using Reactor.Extensions; +using Reactor.Utilities; +using Reactor.Utilities.Extensions; using UnityEngine; namespace PeasAPI @@ -11,7 +15,7 @@ public static class Utility { public static Sprite CreateSprite(string image, float pixelsPerUnit = 128f) { - Texture2D tex = GUIExtensions.CreateEmptyTexture(); + Texture2D tex = CanvasUtilities.CreateEmptyTexture(); Stream myStream = Assembly.GetCallingAssembly().GetManifestResourceStream(image); byte[] data = myStream.ReadFully(); ImageConversion.LoadImage(tex, data, false); @@ -21,6 +25,18 @@ public static Sprite CreateSprite(string image, float pixelsPerUnit = 128f) return sprite; } + public static string ColorString(Color c, string s) + { + return string.Format("{4}", ToByte(c.r), ToByte(c.g), ToByte(c.b), + ToByte(c.a), s); + } + + private static byte ToByte(float f) + { + f = Mathf.Clamp01(f); + return (byte)(f * 255); + } + public static List GetAllPlayers() { if (PlayerControl.AllPlayerControls != null && PlayerControl.AllPlayerControls.Count > 0) @@ -28,6 +44,70 @@ public static List GetAllPlayers() return GameData.Instance.AllPlayers.ToArray().ToList().ConvertAll(p => p.Object); } + public static void OverrideOnClickListeners(this PassiveButton passive, Action action, bool enabled = true) + { + passive.OnClick?.RemoveAllListeners(); + passive.OnClick = new(); + passive.OnClick.AddListener(action); + passive.enabled = enabled; + } + + public static void OverrideOnMouseOverListeners(this PassiveButton passive, Action action, bool enabled = true) + { + passive.OnMouseOver?.RemoveAllListeners(); + passive.OnMouseOver = new(); + passive.OnMouseOver.AddListener(action); + passive.enabled = enabled; + } + + public static void OverrideOnMouseOutListeners(this PassiveButton passive, Action action, bool enabled = true) + { + passive.OnMouseOut?.RemoveAllListeners(); + passive.OnMouseOut = new(); + passive.OnMouseOut.AddListener(action); + passive.enabled = enabled; + } + public static void ForEach(this IEnumerable source, Action action) + { + foreach (var item in source) + action(item); + } + + public static IEnumerator PerformTimedAction(float duration, Action action) + { + for (var t = 0f; t < duration; t += Time.deltaTime) + { + action(t / duration); + yield return EndFrame(); + } + + action(1f); + yield break; + } + + public static IEnumerator EndFrame() + { + yield return new WaitForEndOfFrame(); + } + + /// + /// Used to detect whether the region where the player is located is China. + /// + /// + public static bool isChinese() + { + try + { + var name = CultureInfo.CurrentUICulture.Name; + if (name.StartsWith("zh")) return true; + return false; + } + catch + { + return false; + } + } + public class StringColor { public const string Reset = ""; diff --git a/README.md b/README.md index 855dbab..9296130 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,37 @@ # PeasAPI -API for making Among Us mods +PeasAPI is an API for developing *Among Us* mods, designed to provide convenient functional support for mod developers and simplify the mod development process. -*This mod is not affiliated with Among Us or Innersloth LLC, and the content contained therein is not endorsed or otherwise sponsored by Innersloth LLC. Portions of the materials contained herein are property of Innersloth LLC. © Innersloth LLC.* +## Features + +1. **Role Management**: Offers rich role-related functionalities, supporting the creation, assignment, and management of custom roles. It enables easy implementation of team judgment between roles, role attribute settings, and more. + +2. **Game Mode Expansion**: Supports custom game modes, allowing switching and configuration of different game modes within the game. + +3. **Network Communication**: Integrates network RPC communication capabilities to facilitate synchronization of game states, role information, and other data among players. + +4. **Update Management**: Equipped with automatic update checking. It can detect updates for related mods, prompt users, and support fetching updates from GitHub repositories. + +5. **Configuration Options**: Allows developers to configure the plugin through configuration files, including log switches, custom server configurations, etc. + +6. **Extended Tools**: Provides various practical extension methods such as player object acquisition, color processing, vector operations, etc., to simplify the development process. + +7. **Watermark Management**: Supports adding custom watermark information to the game interface, with the ability to adjust the watermark position and content according to the game state. + +## Installation Instructions + +1. Ensure the BepInEx framework is installed. +2. Place `PeasAPI.dll` into the `BepInEx\plugins` folder under the *Among Us* game directory. +3. Launch the game to load the plugin. + +## Usage Method + +Search for PeasAPI-R in the Nuget package manager of your programming software to add the dependency to your project for use. + +## License + +This project is open-source under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3. For details, please refer to the [LICENSE](LICENSE) file. + +## Disclaimer + +This mod is not affiliated with *Among Us* or Innersloth LLC, and the content contained herein is not endorsed or sponsored by Innersloth LLC. Portions of the materials are the property of Innersloth LLC. © Innersloth LLC. \ No newline at end of file diff --git a/build.cake b/build.cake deleted file mode 100644 index 56e3b47..0000000 --- a/build.cake +++ /dev/null @@ -1,32 +0,0 @@ -var target = Argument("target", "Build"); - -var buildId = EnvironmentVariable("GITHUB_RUN_NUMBER"); - -var @ref = EnvironmentVariable("GITHUB_REF"); -const string prefix = "refs/tags/"; -var tag = !string.IsNullOrEmpty(@ref) && @ref.StartsWith(prefix) ? @ref.Substring(prefix.Length) : null; - -Task("Build") - .Does(() => -{ - var settings = new DotNetCoreBuildSettings - { - Configuration = "Release", - MSBuildSettings = new DotNetCoreMSBuildSettings() - }; - - if (buildId != null) - { - settings.VersionSuffix = "ci." + buildId.Split("+")[0]; - } - - //settings.VersionSuffix = buildId.Split("+")[0] + "--helo--" + buildId.Split("+")[1]; - - foreach (var gamePlatform in new[] { "Steam", "Itch" }) - { - settings.MSBuildSettings.Properties["GamePlatform"] = new[] { gamePlatform }; - DotNetCoreBuild(".", settings); - } -}); - -RunTarget(target); diff --git a/PeasAPI/nuget.config b/nuget.config similarity index 56% rename from PeasAPI/nuget.config rename to nuget.config index 9a4ac66..b4401ac 100644 --- a/PeasAPI/nuget.config +++ b/nuget.config @@ -3,5 +3,7 @@ + + \ No newline at end of file