From 7b072c91a263c97be02ad55322d7b41284f930af Mon Sep 17 00:00:00 2001 From: gendelo3 Date: Fri, 8 Mar 2024 13:39:20 +0100 Subject: [PATCH] Update v4.5.2 --- README.md | 21 ++- TheOtherRoles/CustomOptionHolder.cs | 14 +- TheOtherRoles/Main.cs | 2 +- TheOtherRoles/Modules/BepInExUpdater.cs | 4 +- .../Modules/CustomHats/CustomHatManager.cs | 56 ++++++-- .../Modules/CustomHats/HatExtension.cs | 1 + .../Modules/CustomHats/HatsLoader.cs | 2 + .../CustomHats/Patches/HatParentPatches.cs | 20 +-- TheOtherRoles/Modules/CustomOptions.cs | 2 +- TheOtherRoles/Modules/DynamicLobbies.cs | 2 +- TheOtherRoles/Objects/Footprint.cs | 112 +++++++++------- .../Patches/GameStartManagerPatch.cs | 72 +++++++++-- TheOtherRoles/Patches/IntroPatch.cs | 12 +- TheOtherRoles/Patches/MeetingPatch.cs | 7 - TheOtherRoles/Patches/PlayerControlPatch.cs | 12 +- TheOtherRoles/Patches/UpdatePatch.cs | 10 ++ TheOtherRoles/RPC.cs | 27 +++- TheOtherRoles/Resources/StopClean.png | Bin 0 -> 1480 bytes TheOtherRoles/TheOtherRoles.cs | 19 ++- TheOtherRoles/TheOtherRoles.csproj | 2 +- TheOtherRoles/Utilities/EventUtility.cs | 122 ++---------------- TheOtherRoles/packages.lock.json | 6 +- 22 files changed, 299 insertions(+), 226 deletions(-) create mode 100644 TheOtherRoles/Resources/StopClean.png diff --git a/README.md b/README.md index da7ba9abd..4c577fea5 100644 --- a/README.md +++ b/README.md @@ -42,15 +42,16 @@ The [Role Assignment](#role-assignment) section explains how the roles are being # Releases | Among Us - Version| Mod Version | Link | |----------|-------------|-----------------| +| 2024.3.5s| v4.5.2| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.5.2/TheOtherRoles.zip) | 2023.11.28s| v4.5.1| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.5.1/TheOtherRoles.zip) -| 2023.11.28s| v4.5.0| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.5.0/TheOtherRoles.zip) -| 2023.07.12s| v4.4.2| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.4.2/TheOtherRoles.zip)
Click to show older versions | Among Us - Version| Mod Version | Link | |----------|-------------|-----------------| +| 2023.11.28s| v4.5.0| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.5.0/TheOtherRoles.zip) +| 2023.07.12s| v4.4.2| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.4.2/TheOtherRoles.zip) | 2023.07.12s| v4.4.1| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.4.1/TheOtherRoles.zip) | 2023.07.12s| v4.4.0| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.4.0/TheOtherRoles.zip) | 2023.07.12s| v4.3.4| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.3.4/TheOtherRoles.zip) @@ -131,6 +132,16 @@ The [Role Assignment](#role-assignment) section explains how the roles are being
Click to show the Changelog + **Version 4.5.2** +- Updated to Among Us version 2024.3.5s (various small / "long" features and bugfixes) +- Updated BepInEx dependency to 688 +- Added a new feature: Stop the game start (With an option to allow any player to stop it) +- Added a new option (guesser mode): Sidekick is always guesser +- Fixed the Arsonist Win - death reasons for already dead players are no longer replaced +- Changed the implementation of the Detective footprints - Improved performance can be expected +- Changed the medic shield: Is now also displayed in meetings to players who can see it with brackets around the name + + **Version 4.5.1** - Fix a bug that lead to Props not being able to use the disguise button @@ -853,6 +864,7 @@ Thanks to miniduikboot & GD for hosting modded servers (and so much more) # Settings The mod adds a few new settings to Among Us (in addition to the role settings): +- **Any Player Can Stop The Start:** If turned off, only the host can stop the game start. If on, all players can do it. Non-hosts stopping the start will send a chat message indicating who stopped it. - **Number of Crewmates:** The number of Crewmate roles can be set inside a lobby. - **Fill Crewmate Roles (Ignores Min/Max):** Everyone will get a role, even if the settings say there would be plain Crewmates (needs enough roles over 0%). - **Number of Neutrals:** The number of Neutral roles can be set inside a lobby. @@ -1584,7 +1596,7 @@ The Time Master won't be affected by the rewind. ## Medic ### **Team: Crewmates** -The Medic can shield (highlighted by an outline around the player) one player per game, which makes the player unkillable.\ +The Medic can shield (highlighted by an outline around the player) one player per game, which makes the player unkillable.\ The shield is also shown in the meeting as brackets around the shielded player's name. The shielded player can still be voted out and might also be an Impostor.\ If set in the options, the shielded player and/or the Medic will get a red flash on their screen if someone (Impostor, Sheriff, ...) tried to murder them.\ If the Medic dies, the shield disappears with them.\ @@ -2086,7 +2098,8 @@ Players can additionally have a modifier, if enabled (e.g. Medic Guesser Mini). | Number of Neutral Guessers | - | Number of Impostor Guessers | - | Force Jackal Guesser | If set to "On", the first neutral role who will be Guesser is the Jackal. -| Force Thief Guesser | If set to "On", the first (or second if Force Jackal Guesser) neutral role who will be Guesser is the Thief. +| Sidekick Is Always Guesser | The converted sidekick will become a guesser | - +| Force Thief Guesser | If set to "On", the first (or second if Force Jackal Guesser) neutral role who will be Guesser is the Thief. | Guessers Can Have A Modifier | - | Guesser Number Of Shots | - | Guesser Can Shoot Multiple Times Per Meeting | - diff --git a/TheOtherRoles/CustomOptionHolder.cs b/TheOtherRoles/CustomOptionHolder.cs index 876f718c1..0578dcf30 100644 --- a/TheOtherRoles/CustomOptionHolder.cs +++ b/TheOtherRoles/CustomOptionHolder.cs @@ -21,8 +21,8 @@ public class CustomOptionHolder { public static CustomOption modifiersCountMin; public static CustomOption modifiersCountMax; - public static CustomOption enableCodenameHorsemode; - public static CustomOption enableCodenameDisableHorses; + public static CustomOption anyPlayerCanStopStart; + public static CustomOption enableEventMode; public static CustomOption mafiaSpawnRate; public static CustomOption janitorCooldown; @@ -331,6 +331,7 @@ public class CustomOptionHolder { public static CustomOption guesserGamemodeKillsThroughShield; public static CustomOption guesserGamemodeEvilCanKillSpy; public static CustomOption guesserGamemodeCantGuessSnitchIfTaksDone; + public static CustomOption guesserGamemodeSidekickIsAlwaysGuesser; // Hide N Seek Gamemode public static CustomOption hideNSeekHunterCount; @@ -412,9 +413,9 @@ public static void Load() { // Role Options presetSelection = CustomOption.Create(0, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Preset"), presets, null, true); activateRoles = CustomOption.Create(1, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Enable Mod Roles And Block Vanilla Roles"), true, null, true); + anyPlayerCanStopStart = CustomOption.Create(2, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Any Player Can Stop The Start"), false, null, false); - if (Utilities.EventUtility.canBeEnabled) enableCodenameHorsemode = CustomOption.Create(10423, Types.General, cs(Color.green, "Enable Codename Horsemode"), true, null, true); - if (Utilities.EventUtility.canBeEnabled) enableCodenameDisableHorses = CustomOption.Create(10424, Types.General, cs(Color.green, "Disable Horses"), false, enableCodenameHorsemode, false); + if (Utilities.EventUtility.canBeEnabled) enableEventMode = CustomOption.Create(10423, Types.General, cs(Color.green, "Enable Special Mode"), true, null, true); // Using new id's for the options to not break compatibilty with older versions crewmateRolesCountMin = CustomOption.Create(300, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Minimum Crewmate Roles"), 15f, 0f, 15f, 1f, null, true); @@ -426,7 +427,7 @@ public static void Load() { impostorRolesCountMax = CustomOption.Create(305, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Impostor Roles"), 15f, 0f, 15f, 1f); modifiersCountMin = CustomOption.Create(306, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Minimum Modifiers"), 15f, 0f, 15f, 1f); modifiersCountMax = CustomOption.Create(307, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Modifiers"), 15f, 0f, 15f, 1f); - + mafiaSpawnRate = CustomOption.Create(18, Types.Impostor, cs(Janitor.color, "Mafia"), rates, null, true); janitorCooldown = CustomOption.Create(19, Types.Impostor, "Janitor Cooldown", 30f, 10f, 60f, 2.5f, mafiaSpawnRate); @@ -710,6 +711,7 @@ public static void Load() { guesserGamemodeNeutralNumber = CustomOption.Create(2002, Types.Guesser, cs(Guesser.color, "Number of Neutral Guessers"), 15f, 1f, 15f, 1f, null, true); guesserGamemodeImpNumber = CustomOption.Create(2003, Types.Guesser, cs(Guesser.color, "Number of Impostor Guessers"), 15f, 1f, 15f, 1f, null, true); guesserForceJackalGuesser = CustomOption.Create(2007, Types.Guesser, "Force Jackal Guesser", false, null, true); + guesserGamemodeSidekickIsAlwaysGuesser = CustomOption.Create(2012, Types.Guesser, "Sidekick Is Always Guesser", false, null); guesserForceThiefGuesser = CustomOption.Create(2011, Types.Guesser, "Force Thief Guesser", false, null, true); guesserGamemodeHaveModifier = CustomOption.Create(2004, Types.Guesser, "Guessers Can Have A Modifier", true, null); guesserGamemodeNumberOfShots = CustomOption.Create(2005, Types.Guesser, "Guesser Number Of Shots", 3f, 1f, 15f, 1f, null); @@ -717,6 +719,8 @@ public static void Load() { guesserGamemodeKillsThroughShield = CustomOption.Create(2008, Types.Guesser, "Guesses Ignore The Medic Shield", true, null); guesserGamemodeEvilCanKillSpy = CustomOption.Create(2009, Types.Guesser, "Evil Guesser Can Guess The Spy", true, null); guesserGamemodeCantGuessSnitchIfTaksDone = CustomOption.Create(2010, Types.Guesser, "Guesser Can't Guess Snitch When Tasks Completed", true, null); + // Care: 2012 already taken! + // Hide N Seek Gamemode (3000 - 3999) hideNSeekMap = CustomOption.Create(3020, Types.HideNSeekMain, cs(Color.yellow, "Map"), new string[] { "The Skeld", "Mira", "Polus", "Airship", "Fungle", "Submerged", "LI Map"}, null, true, onChange: () => { int map = hideNSeekMap.selection; if (map >= 3) map++; GameOptionsManager.Instance.currentNormalGameOptions.MapId = (byte)map; }); diff --git a/TheOtherRoles/Main.cs b/TheOtherRoles/Main.cs index 66c8ce99b..5ccb3c555 100644 --- a/TheOtherRoles/Main.cs +++ b/TheOtherRoles/Main.cs @@ -33,7 +33,7 @@ namespace TheOtherRoles public class TheOtherRolesPlugin : BasePlugin { public const string Id = "me.eisbison.theotherroles"; - public const string VersionString = "4.5.1"; + public const string VersionString = "4.5.2"; public static uint betaDays = 0; // amount of days for the build to be usable (0 for infinite!) public static Version Version = Version.Parse(VersionString); diff --git a/TheOtherRoles/Modules/BepInExUpdater.cs b/TheOtherRoles/Modules/BepInExUpdater.cs index 9702678dc..42b3591e4 100644 --- a/TheOtherRoles/Modules/BepInExUpdater.cs +++ b/TheOtherRoles/Modules/BepInExUpdater.cs @@ -19,8 +19,8 @@ namespace TheOtherRoles.Modules; public class BepInExUpdater : MonoBehaviour { - public const string RequiredBepInExVersion = "6.0.0-be.671+9caf61dca07043beae57b0771f6a5283aa02436b"; - public const string BepInExDownloadURL = "https://builds.bepinex.dev/projects/bepinex_be/671/BepInEx-Unity.IL2CPP-win-x86-6.0.0-be.671%2B9caf61d.zip"; + public const string RequiredBepInExVersion = "6.0.0-be.688+49015217f3becf052d33fa4658ac19229f5daa3a"; + public const string BepInExDownloadURL = "https://builds.bepinex.dev/projects/bepinex_be/688/BepInEx-Unity.IL2CPP-win-x86-6.0.0-be.688%2B4901521.zip"; public static bool UpdateRequired => Paths.BepInExVersion.ToString() != RequiredBepInExVersion; public void Awake() diff --git a/TheOtherRoles/Modules/CustomHats/CustomHatManager.cs b/TheOtherRoles/Modules/CustomHats/CustomHatManager.cs index 4eb518577..27e5fc151 100644 --- a/TheOtherRoles/Modules/CustomHats/CustomHatManager.cs +++ b/TheOtherRoles/Modules/CustomHats/CustomHatManager.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Security.Cryptography; using UnityEngine; using UnityEngine.AddressableAssets; +using System.Reflection; namespace TheOtherRoles.Modules.CustomHats; @@ -28,12 +30,11 @@ internal static string RepositoryUrl internal static string CustomSkinsDirectory => Path.Combine(Path.GetDirectoryName(Application.dataPath)!, ResourcesDirectory); internal static string HatsDirectory => CustomSkinsDirectory; - internal static readonly List UnregisteredHats = new(); + internal static List UnregisteredHats = new(); internal static readonly Dictionary ViewDataCache = new(); internal static readonly Dictionary ExtensionCache = new(); private static readonly HatsLoader Loader; - private static Material cachedShader; internal static HatExtension TestExtension { get; private set; } @@ -71,8 +72,6 @@ internal static bool IsCached(this HatParent hatParent) internal static HatData CreateHatBehaviour(CustomHat ch, bool testOnly = false) { - if (cachedShader == null) cachedShader = DestroyableSingleton.Instance.PlayerMaterial; - var viewData = ViewDataCache[ch.Name] = ScriptableObject.CreateInstance(); var hat = ScriptableObject.CreateInstance(); @@ -101,16 +100,11 @@ internal static HatData CreateHatBehaviour(CustomHat ch, bool testOnly = false) hat.ChipOffset = new Vector2(0f, 0.2f); hat.Free = true; - if (ch.Adaptive && cachedShader != null) - { - viewData.AltShader = cachedShader; - } - - var extend = new HatExtension - { + var extend = new HatExtension { Author = ch.Author ?? "Unknown", Package = ch.Package ?? "Misc.", - Condition = ch.Condition ?? "none" + Condition = ch.Condition ?? "none", + Adaptive = ch.Adaptive, }; if (ch.FlipResource != null) @@ -141,6 +135,8 @@ internal static HatData CreateHatBehaviour(CustomHat ch, bool testOnly = false) private static Sprite CreateHatSprite(string path) { var texture = Helpers.loadTextureFromDisk(Path.Combine(HatsDirectory, path)); + if (texture == null) + texture = Helpers.loadTextureFromResources(path); if (texture == null) return null; var sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), @@ -280,4 +276,40 @@ internal static List GenerateDownloadList(List hats) return toDownload; } + + public static List loadHorseHats() { + List hatdatas = new(); + Assembly assembly = Assembly.GetExecutingAssembly(); + string[] resourceNames = assembly.GetManifestResourceNames(); + List hatFiles = new(); + Dictionary> hatFilesSorted = new Dictionary>(); + foreach (string resourceName in resourceNames) { + if (resourceName.Contains("TheOtherRoles.Resources.HorseHats.") && resourceName.Contains(".png")) { + hatFiles.Add(resourceName); + } + } + + foreach (string s in hatFiles) { + string value = s.Substring(0, s.LastIndexOf("HorseSpecialHat") + 17); + if (value.Contains(".")) value.Remove(value.LastIndexOf(".")); + if (!hatFilesSorted.ContainsKey(value)) hatFilesSorted.Add(value, new List()); + hatFilesSorted[value].Add(s); + } + int i = 0; + foreach (var item in hatFilesSorted) { + CustomHat info = new CustomHat(); + info.Name = $"April Hat {i++:D2}"; + info.Author = "A Fool"; + info.Resource = item.Value.FirstOrDefault(x => !x.Contains("back")); + info.BackResource = item.Value.FirstOrDefault(x => x.Contains("back")); + info.Adaptive = info.Resource != null && info.Resource.Contains("adaptive"); + info.FlipResource = item.Value.FirstOrDefault(x => x.Contains("flip")); + info.ClimbResource = item.Value.FirstOrDefault(x => x.Contains("climb")); + info.Package = "April Fools Hats"; + if (info.Resource == null || info.Name == null) // required + continue; + hatdatas.Add(info); + } + return hatdatas; + } } \ No newline at end of file diff --git a/TheOtherRoles/Modules/CustomHats/HatExtension.cs b/TheOtherRoles/Modules/CustomHats/HatExtension.cs index a50f5b74b..4b4c28a37 100644 --- a/TheOtherRoles/Modules/CustomHats/HatExtension.cs +++ b/TheOtherRoles/Modules/CustomHats/HatExtension.cs @@ -9,4 +9,5 @@ public class HatExtension public string Condition { get; set; } public Sprite FlipImage { get; set; } public Sprite BackFlipImage { get; set; } + public bool Adaptive { get; set; } } \ No newline at end of file diff --git a/TheOtherRoles/Modules/CustomHats/HatsLoader.cs b/TheOtherRoles/Modules/CustomHats/HatsLoader.cs index d8b0584f4..b0b3c1e80 100644 --- a/TheOtherRoles/Modules/CustomHats/HatsLoader.cs +++ b/TheOtherRoles/Modules/CustomHats/HatsLoader.cs @@ -2,6 +2,7 @@ using System.IO; using System.Text.Json; using BepInEx.Unity.IL2CPP.Utils; +using TheOtherRoles.Utilities; using UnityEngine; using UnityEngine.Networking; using static TheOtherRoles.Modules.CustomHats.CustomHatManager; @@ -51,6 +52,7 @@ private IEnumerator CoFetchHats() UnregisteredHats.AddRange(SanitizeHats(response)); var toDownload = GenerateDownloadList(UnregisteredHats); + if (EventUtility.isEnabled) UnregisteredHats.AddRange(CustomHatManager.loadHorseHats()); TheOtherRolesPlugin.Logger.LogMessage($"I'll download {toDownload.Count} hat files"); diff --git a/TheOtherRoles/Modules/CustomHats/Patches/HatParentPatches.cs b/TheOtherRoles/Modules/CustomHats/Patches/HatParentPatches.cs index 6112de08f..ecfaa72b6 100644 --- a/TheOtherRoles/Modules/CustomHats/Patches/HatParentPatches.cs +++ b/TheOtherRoles/Modules/CustomHats/Patches/HatParentPatches.cs @@ -4,6 +4,7 @@ using HarmonyLib; using PowerTools; using TheOtherRoles; +using TheOtherRoles.Modules.CustomHats.Extensions; using UnityEngine; namespace TheOtherRoles.Modules.CustomHats.Patches; @@ -24,7 +25,7 @@ private static void SetHatPrefix(HatParent __instance) private static bool SetHatPrefix(HatParent __instance, HatData hat, int color) { if (SetCustomHat(__instance)) return true; - __instance.PopulateFromHatViewData(); + __instance.PopulateFromViewData(); __instance.SetMaterialColor(color); return false; } @@ -34,8 +35,8 @@ private static bool SetHatPrefix(HatParent __instance, HatData hat, int color) private static bool SetHatPrefix(HatParent __instance, int color) { if (!__instance.IsCached()) return true; - __instance.hatDataAsset = null; - __instance.PopulateFromHatViewData(); + __instance.viewAsset = null; + __instance.PopulateFromViewData(); __instance.SetMaterialColor(color); return false; } @@ -45,12 +46,13 @@ private static bool SetHatPrefix(HatParent __instance, int color) private static bool UpdateMaterialPrefix(HatParent __instance) { if (!__instance.TryGetCached(out var asset)) return true; - if (asset && asset.AltShader) + var extend = HatDataExtensions.GetHatExtension(__instance.Hat); + if (asset && extend != null && extend.Adaptive) { - __instance.FrontLayer.sharedMaterial = asset.AltShader; + __instance.FrontLayer.sharedMaterial = DestroyableSingleton.Instance.PlayerMaterial; if (__instance.BackLayer) { - __instance.BackLayer.sharedMaterial = asset.AltShader; + __instance.BackLayer.sharedMaterial = DestroyableSingleton.Instance.PlayerMaterial; } } else @@ -185,8 +187,8 @@ private static bool SetIdleAnimPrefix(HatParent __instance, int colorId) { if (!__instance.Hat) return false; if (!__instance.IsCached()) return true; - __instance.hatDataAsset = null; - __instance.PopulateFromHatViewData(); + __instance.viewAsset = null; + __instance.PopulateFromViewData(); __instance.SetMaterialColor(colorId); return false; } @@ -203,7 +205,7 @@ private static bool SetClimbAnimPrefix(HatParent __instance) return false; } - [HarmonyPatch(nameof(HatParent.PopulateFromHatViewData))] + [HarmonyPatch(nameof(HatParent.PopulateFromViewData))] [HarmonyPrefix] private static bool PopulateFromHatViewDataPrefix(HatParent __instance) { diff --git a/TheOtherRoles/Modules/CustomOptions.cs b/TheOtherRoles/Modules/CustomOptions.cs index b072bf5d6..61bf0f3e9 100644 --- a/TheOtherRoles/Modules/CustomOptions.cs +++ b/TheOtherRoles/Modules/CustomOptions.cs @@ -104,7 +104,7 @@ public static void switchPreset(int newPreset) { } public static void saveVanillaOptions() { - vanillaSettings.Value = Convert.ToBase64String(GameOptionsManager.Instance.gameOptionsFactory.ToBytes(GameManager.Instance.LogicOptions.currentGameOptions)); + vanillaSettings.Value = Convert.ToBase64String(GameOptionsManager.Instance.gameOptionsFactory.ToBytes(GameManager.Instance.LogicOptions.currentGameOptions, false)); } public static void loadVanillaOptions() { diff --git a/TheOtherRoles/Modules/DynamicLobbies.cs b/TheOtherRoles/Modules/DynamicLobbies.cs index 3719bfeb8..cabb5eeae 100644 --- a/TheOtherRoles/Modules/DynamicLobbies.cs +++ b/TheOtherRoles/Modules/DynamicLobbies.cs @@ -27,7 +27,7 @@ static bool Prefix(ChatController __instance) { if (LobbyLimit != GameOptionsManager.Instance.currentNormalGameOptions.MaxPlayers) { GameOptionsManager.Instance.currentNormalGameOptions.MaxPlayers = LobbyLimit; FastDestroyableSingleton.Instance.LastPlayerCount = LobbyLimit; - CachedPlayer.LocalPlayer.PlayerControl.RpcSyncSettings(GameOptionsManager.Instance.gameOptionsFactory.ToBytes(GameOptionsManager.Instance.currentGameOptions)); // TODO Maybe simpler?? + CachedPlayer.LocalPlayer.PlayerControl.RpcSyncSettings(GameOptionsManager.Instance.gameOptionsFactory.ToBytes(GameOptionsManager.Instance.currentGameOptions, false)); // TODO Maybe simpler?? __instance.AddChat(CachedPlayer.LocalPlayer.PlayerControl, $"Lobby Size changed to {LobbyLimit} players"); } else { __instance.AddChat(CachedPlayer.LocalPlayer.PlayerControl, $"Lobby Size is already {LobbyLimit}"); diff --git a/TheOtherRoles/Objects/Footprint.cs b/TheOtherRoles/Objects/Footprint.cs index 8d8138e61..f4c6d4f9f 100644 --- a/TheOtherRoles/Objects/Footprint.cs +++ b/TheOtherRoles/Objects/Footprint.cs @@ -1,30 +1,58 @@ +using InnerNet; +using Reactor.Utilities.Extensions; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; +using TheOtherRoles.Players; using TheOtherRoles.Utilities; using UnityEngine; +using static TheOtherRoles.TheOtherRoles; namespace TheOtherRoles.Objects { public class FootprintHolder : MonoBehaviour - { + { static FootprintHolder() => ClassInjector.RegisterTypeInIl2Cpp(); public FootprintHolder(IntPtr ptr) : base(ptr) { } private static FootprintHolder _instance; public static FootprintHolder Instance - { + { get => _instance ? _instance : _instance = new GameObject("FootprintHolder").AddComponent(); set => _instance = value; } - + private static Sprite _footprintSprite; private static Sprite FootprintSprite => _footprintSprite ??= Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Footprint.png", 600f); private static bool AnonymousFootprints => TheOtherRoles.Detective.anonymousFootprints; private static float FootprintDuration => TheOtherRoles.Detective.footprintDuration; + + private static int FootPrintsPerPlayer => (int)(1 / TheOtherRoles.Detective.footprintIntervall * TheOtherRoles.Detective.footprintDuration); + + private static int nextFootStep = 0; + + private static List> footPrintObjectList2D = new(); + + public static void clearAndReload() { + footPrintObjectList2D.ForEach(x => x.ForEach(y => y.GameObject.Destroy())); + footPrintObjectList2D.Clear(); + foreach (var player in CachedPlayer.AllPlayers) { + List fpList = new(); + for (int i=0; i _pool = new(); - private readonly List _activeFootprints = new(); - private readonly List _toRemove = new(); [HideFromIl2Cpp] public void MakeFootprint(PlayerControl player) { - if (!_pool.TryTake(out var print)) - { - print = new(); - } + int playerN = CachedPlayer.AllPlayers.IndexOf(CachedPlayer.AllPlayers.First(x => x.PlayerId == player.PlayerId)); + Footprint print = footPrintObjectList2D[player.PlayerId][nextFootStep]; print.Lifetime = FootprintDuration; @@ -68,48 +90,40 @@ public void MakeFootprint(PlayerControl player) print.GameObject.SetActive(true); print.Owner = player; print.Data = player.Data; - _activeFootprints.Add(print); } - private void Update() + + private static float updateDt = 0.10f; + + private void Start() { + InvokeRepeating(nameof(FootprintUpdate), updateDt, updateDt); + } + private void FootprintUpdate() { - var dt = Time.deltaTime; - _toRemove.Clear(); - foreach (var activeFootprint in _activeFootprints) - { - var p = activeFootprint.Lifetime / FootprintDuration; - - if (activeFootprint.Lifetime <= 0) - { - _toRemove.Add(activeFootprint); - continue; - } - - Color color; - if (AnonymousFootprints || Camouflager.camouflageTimer > 0 || Helpers.MushroomSabotageActive()) - { - color = Palette.PlayerColors[6]; - } - else if (activeFootprint.Owner == Morphling.morphling && Morphling.morphTimer > 0 && Morphling.morphTarget && Morphling.morphTarget.Data != null) - { - color = Palette.PlayerColors[Morphling.morphTarget.Data.DefaultOutfit.ColorId]; - } - else - { - color = Palette.PlayerColors[activeFootprint.Data.DefaultOutfit.ColorId]; - } + if (Detective.detective == null || Detective.detective != CachedPlayer.LocalPlayer.PlayerControl) + return; + for (int playerN = 0; playerN < CachedPlayer.AllPlayers.Count; playerN++) { + foreach (var activeFootprint in footPrintObjectList2D[playerN]) { + var p = activeFootprint.Lifetime / FootprintDuration; - color.a = Math.Clamp(p, 0f, 1f); - activeFootprint.Renderer.color = color; + if (activeFootprint.Lifetime <= 0) { + activeFootprint.GameObject.SetActive(false); + continue; + } - activeFootprint.Lifetime -= dt; - } - - foreach (var footprint in _toRemove) - { - footprint.GameObject.SetActive(false); - _activeFootprints.Remove(footprint); - _pool.Add(footprint); + Color color; + if (AnonymousFootprints || Camouflager.camouflageTimer > 0 || Helpers.MushroomSabotageActive()) { + color = Palette.PlayerColors[6]; + } else if (activeFootprint.Owner == Morphling.morphling && Morphling.morphTimer > 0 && Morphling.morphTarget && Morphling.morphTarget.Data != null) { + color = Palette.PlayerColors[Morphling.morphTarget.Data.DefaultOutfit.ColorId]; + } else { + color = Palette.PlayerColors[activeFootprint.Data.DefaultOutfit.ColorId]; + } + color.a = Math.Clamp(p, 0f, 1f); + activeFootprint.Renderer.color = color; + + activeFootprint.Lifetime -= updateDt; + } } } diff --git a/TheOtherRoles/Patches/GameStartManagerPatch.cs b/TheOtherRoles/Patches/GameStartManagerPatch.cs index c6b257626..c5eb05338 100644 --- a/TheOtherRoles/Patches/GameStartManagerPatch.cs +++ b/TheOtherRoles/Patches/GameStartManagerPatch.cs @@ -8,6 +8,7 @@ using TheOtherRoles.Players; using TheOtherRoles.Utilities; using System.Linq; +using Reactor.Utilities.Extensions; namespace TheOtherRoles.Patches { public class GameStartManagerPatch { @@ -47,7 +48,8 @@ public class GameStartManagerUpdatePatch { public static float startingTimer = 0; private static bool update = false; private static string currentText = ""; - + private static GameObject copiedStartButton; + public static void Prefix(GameStartManager __instance) { if (!GameData.Instance ) return; // No instance __instance.MinPlayers = 1; @@ -100,12 +102,40 @@ public static void Postfix(GameStartManager __instance) { __instance.GameStartText.transform.localPosition = __instance.StartButton.transform.localPosition; } + if (__instance.startState != GameStartManager.StartingStates.Countdown) + copiedStartButton?.Destroy(); + // Make starting info available to clients: if (startingTimer <= 0 && __instance.startState == GameStartManager.StartingStates.Countdown) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.SetGameStarting, Hazel.SendOption.Reliable, -1); AmongUsClient.Instance.FinishRpcImmediately(writer); RPCProcedure.setGameStarting(); + + // Activate Stop-Button + copiedStartButton = GameObject.Instantiate(__instance.StartButton.gameObject, __instance.StartButton.gameObject.transform.parent); + copiedStartButton.transform.localPosition = __instance.StartButton.transform.localPosition; + copiedStartButton.GetComponent().sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.StopClean.png", 180f); + copiedStartButton.SetActive(true); + var startButtonText = copiedStartButton.GetComponentInChildren(); + startButtonText.text = "STOP"; + startButtonText.fontSize *= 0.8f; + startButtonText.fontSizeMax = startButtonText.fontSize; + startButtonText.gameObject.transform.localPosition = Vector3.zero; + PassiveButton startButtonPassiveButton = copiedStartButton.GetComponent(); + + void StopStartFunc() { + __instance.ResetStartState(); + copiedStartButton.Destroy(); + startingTimer = 0; + } + startButtonPassiveButton.OnClick.AddListener((Action)(() => StopStartFunc())); + __instance.StartCoroutine(Effects.Lerp(.1f, new System.Action((p) => { + startButtonText.text = "STOP"; + }))); + } + if (__instance.startState == GameStartManager.StartingStates.Countdown) + __instance.GameStartText.transform.localPosition = __instance.StartButton.transform.localPosition + Vector3.up * 0.6f; } // Client update with handshake infos @@ -125,16 +155,42 @@ public static void Postfix(GameStartManager __instance) { __instance.GameStartText.transform.localPosition = __instance.StartButton.transform.localPosition + Vector3.up * 2; } else { __instance.GameStartText.transform.localPosition = __instance.StartButton.transform.localPosition; - if (__instance.startState != GameStartManager.StartingStates.Countdown && startingTimer <= 0) { + if (!__instance.GameStartText.text.StartsWith("Starting")) __instance.GameStartText.text = String.Empty; + } + + if (!__instance.GameStartText.text.StartsWith("Starting") || !CustomOptionHolder.anyPlayerCanStopStart.getBool()) + copiedStartButton?.Destroy(); + if (CustomOptionHolder.anyPlayerCanStopStart.getBool() && copiedStartButton == null && __instance.GameStartText.text.StartsWith("Starting")) { + + // Activate Stop-Button + copiedStartButton = GameObject.Instantiate(__instance.StartButton.gameObject, __instance.StartButton.gameObject.transform.parent); + copiedStartButton.transform.localPosition = __instance.StartButton.transform.localPosition; + copiedStartButton.GetComponent().sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.StopClean.png", 180f); + copiedStartButton.SetActive(true); + var startButtonText = copiedStartButton.GetComponentInChildren(); + startButtonText.text = "STOP"; + startButtonText.fontSize *= 0.8f; + startButtonText.fontSizeMax = startButtonText.fontSize; + startButtonText.gameObject.transform.localPosition = Vector3.zero; + PassiveButton startButtonPassiveButton = copiedStartButton.GetComponent(); + + void StopStartFunc() { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.StopStart, Hazel.SendOption.Reliable, AmongUsClient.Instance.HostId); + writer.Write(PlayerControl.LocalPlayer.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + copiedStartButton.Destroy(); + __instance.GameStartText.text = String.Empty; + startingTimer = 0; } - else { - __instance.GameStartText.text = $"Starting in {(int)startingTimer + 1}"; - if (startingTimer <= 0) { - __instance.GameStartText.text = String.Empty; - } - } + startButtonPassiveButton.OnClick.AddListener((Action)(() => StopStartFunc())); + __instance.StartCoroutine(Effects.Lerp(.1f, new System.Action((p) => { + startButtonText.text = "STOP"; + }))); + } + if (__instance.GameStartText.text.StartsWith("Starting") && CustomOptionHolder.anyPlayerCanStopStart.getBool()) + __instance.GameStartText.transform.localPosition = __instance.StartButton.transform.localPosition + Vector3.up * 0.6f; } // Start Timer diff --git a/TheOtherRoles/Patches/IntroPatch.cs b/TheOtherRoles/Patches/IntroPatch.cs index 71b46094c..101b97a88 100644 --- a/TheOtherRoles/Patches/IntroPatch.cs +++ b/TheOtherRoles/Patches/IntroPatch.cs @@ -273,12 +273,20 @@ public static void Postfix(IntroCutscene __instance, ref Il2CppSystem.Collectio } } - [HarmonyPatch(typeof(Constants), nameof(Constants.ShouldHorseAround))] + /* Horses are broken since 2024.3.5 - keeping this code in case they return. + * [HarmonyPatch(typeof(AprilFoolsMode), nameof(AprilFoolsMode.ShouldHorseAround))] public static class ShouldAlwaysHorseAround { public static bool Prefix(ref bool __result) { - __result = EventUtility.isEnabled && !EventUtility.disableHorses; + __result = EventUtility.isEnabled && !EventUtility.disableEventMode; return false; } + }*/ + + [HarmonyPatch(typeof(AprilFoolsMode), nameof(AprilFoolsMode.ShouldShowAprilFoolsToggle))] + public static class ShouldShowAprilFoolsToggle { + public static void Postfix(ref bool __result) { + __result = __result || EventUtility.isEventDate || EventUtility.canBeEnabled; // Extend it to a 7 day window instead of just 1st day of the Month + } } } diff --git a/TheOtherRoles/Patches/MeetingPatch.cs b/TheOtherRoles/Patches/MeetingPatch.cs index 30cc11da8..39b26a854 100644 --- a/TheOtherRoles/Patches/MeetingPatch.cs +++ b/TheOtherRoles/Patches/MeetingPatch.cs @@ -592,13 +592,6 @@ static void populateButtonsPostfix(MeetingHud __instance) { }))); } - //Fix visor in Meetings - /** - foreach (PlayerVoteArea pva in __instance.playerStates) { - if(pva.PlayerIcon != null && pva.PlayerIcon.VisorSlot != null){ - pva.PlayerIcon.VisorSlot.transform.position += new Vector3(0, 0, -1f); - } - } */ bool isGuesser = HandleGuesser.isGuesser(CachedPlayer.LocalPlayer.PlayerId); diff --git a/TheOtherRoles/Patches/PlayerControlPatch.cs b/TheOtherRoles/Patches/PlayerControlPatch.cs index 456732f37..21fa92468 100644 --- a/TheOtherRoles/Patches/PlayerControlPatch.cs +++ b/TheOtherRoles/Patches/PlayerControlPatch.cs @@ -69,13 +69,8 @@ static void setBasePlayerOutlines() { bool isMorphedMorphling = target == Morphling.morphling && Morphling.morphTarget != null && Morphling.morphTimer > 0f; bool hasVisibleShield = false; Color color = Medic.shieldedColor; - if (Camouflager.camouflageTimer <= 0f && !Helpers.MushroomSabotageActive() && Medic.shielded != null && ((target == Medic.shielded && !isMorphedMorphling) || (isMorphedMorphling && Morphling.morphTarget == Medic.shielded))) { - hasVisibleShield = Medic.showShielded == 0 || Helpers.shouldShowGhostInfo() // Everyone or Ghost info - || (Medic.showShielded == 1 && (CachedPlayer.LocalPlayer.PlayerControl == Medic.shielded || CachedPlayer.LocalPlayer.PlayerControl == Medic.medic)) // Shielded + Medic - || (Medic.showShielded == 2 && CachedPlayer.LocalPlayer.PlayerControl == Medic.medic); // Medic only - // Make shield invisible till after the next meeting if the option is set (the medic can already see the shield) - hasVisibleShield = hasVisibleShield && (Medic.meetingAfterShielding || !Medic.showShieldAfterMeeting || CachedPlayer.LocalPlayer.PlayerControl == Medic.medic || Helpers.shouldShowGhostInfo()); - } + if (Camouflager.camouflageTimer <= 0f && !Helpers.MushroomSabotageActive() && Medic.shieldVisible(target)) + hasVisibleShield = true; if (Camouflager.camouflageTimer <= 0f && !Helpers.MushroomSabotageActive() && TORMapOptions.firstKillPlayer != null && TORMapOptions.shieldFirstKill && ((target == TORMapOptions.firstKillPlayer && !isMorphedMorphling) || (isMorphedMorphling && Morphling.morphTarget == TORMapOptions.firstKillPlayer))) { hasVisibleShield = true; @@ -207,6 +202,7 @@ static void detectiveUpdateFootPrints() { FootprintHolder.Instance.MakeFootprint(player); } } + FootprintHolder.updateNextFootstep(); } } @@ -1438,7 +1434,7 @@ public static void Postfix(PlayerControl __instance) public static class PlayerPhysicsFixedUpdate { public static void Postfix(PlayerPhysics __instance) { - bool shouldInvert = (Invert.invert.FindAll(x => x.PlayerId == CachedPlayer.LocalPlayer.PlayerId).Count > 0 && Invert.meetings > 0) ^ EventUtility.eventInvert; // xor. if already invert, eventInvert will turn it off for 10s + bool shouldInvert = Invert.invert.FindAll(x => x.PlayerId == CachedPlayer.LocalPlayer.PlayerId).Count > 0 && Invert.meetings > 0; if (__instance.AmOwner && AmongUsClient.Instance && AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Started && diff --git a/TheOtherRoles/Patches/UpdatePatch.cs b/TheOtherRoles/Patches/UpdatePatch.cs index 1b2bfa1dd..7d2522549 100644 --- a/TheOtherRoles/Patches/UpdatePatch.cs +++ b/TheOtherRoles/Patches/UpdatePatch.cs @@ -231,6 +231,16 @@ static void setNameTags() { if (target != null) player.NameText.text += $" ({(Helpers.isLighterColor(target) ? "L" : "D")})"; } } + + // Add medic shield info: + if (MeetingHud.Instance != null && Medic.medic != null && Medic.shielded != null && Medic.shieldVisible(Medic.shielded)) { + foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates) + if (player.TargetPlayerId == Medic.shielded.PlayerId) { + player.NameText.text = Helpers.cs(Medic.color, "[") + player.NameText.text + Helpers.cs(Medic.color, "]"); + // player.HighlightedFX.color = Medic.color; + // player.HighlightedFX.enabled = true; + } + } } static void updateShielded() { diff --git a/TheOtherRoles/RPC.cs b/TheOtherRoles/RPC.cs index 4edcbdf1a..6153efdff 100644 --- a/TheOtherRoles/RPC.cs +++ b/TheOtherRoles/RPC.cs @@ -16,7 +16,6 @@ using AmongUs.Data; using AmongUs.GameOptions; using Assets.CoreScripts; - namespace TheOtherRoles { public enum RoleId { @@ -97,6 +96,7 @@ enum CustomRPC DynamicMapOption, SetGameStarting, ShareGamemode, + StopStart, // Role functionality @@ -220,6 +220,13 @@ public static void shareGamemode(byte gm) { TORMapOptions.gameMode = (CustomGamemodes) gm; } + public static void stopStart(byte playerId) { + if (AmongUsClient.Instance.AmHost && CustomOptionHolder.anyPlayerCanStopStart.getBool()) { + GameStartManager.Instance.ResetStartState(); + PlayerControl.LocalPlayer.RpcSendChat($"{Helpers.playerById(playerId).Data.PlayerName} stopped the game start!"); + } + } + public static void workaroundSetRoles(byte numberOfRoles, MessageReader reader) { for (int i = 0; i < numberOfRoles; i++) @@ -236,7 +243,7 @@ public static void workaroundSetRoles(byte numberOfRoles, MessageReader reader) } public static void setRole(byte roleId, byte playerId) { - foreach (PlayerControl player in CachedPlayer.AllPlayers) + foreach (PlayerControl player in CachedPlayer.AllPlayers) { if (player.PlayerId == playerId) { switch((RoleId)roleId) { case RoleId.Jester: @@ -373,7 +380,12 @@ public static void setRole(byte roleId, byte playerId) { Bomber.bomber = player; break; } - } + if (AmongUsClient.Instance.AmHost && Helpers.roleCanUseVents(player) && !player.Data.Role.IsImpostor) { + player.RpcSetRole(RoleTypes.Engineer); + player.SetRole(RoleTypes.Engineer); + } + } + } } public static void setModifier(byte modifierId, byte playerId, byte flag) { @@ -676,6 +688,8 @@ public static void jackalCreatesSidekick(byte targetId) { Sidekick.wasSpy = wasSpy; Sidekick.wasImpostor = wasImpostor; if (player == CachedPlayer.LocalPlayer.PlayerControl) SoundEffectsManager.play("jackalSidekick"); + if (HandleGuesser.isGuesserGm && CustomOptionHolder.guesserGamemodeSidekickIsAlwaysGuesser.getBool() && !HandleGuesser.isGuesser(targetId)) + setGuesserGm(targetId); } Jackal.canCreateSidekick = false; } @@ -919,7 +933,7 @@ public static void sealVent(int ventId) { public static void arsonistWin() { Arsonist.triggerArsonistWin = true; foreach (PlayerControl p in CachedPlayer.AllPlayers) { - if (p != Arsonist.arsonist) { + if (p != Arsonist.arsonist && !p.Data.IsDead) { p.Exiled(); overrideDeathReasonAndKiller(p, DeadPlayer.CustomDeathReason.Arson, Arsonist.arsonist); } @@ -1063,6 +1077,8 @@ public static void thiefStealsRole(byte playerId) { if (target == Sidekick.sidekick) { Sidekick.sidekick = thief; Jackal.formerJackals.Add(target); + if (HandleGuesser.isGuesserGm && CustomOptionHolder.guesserGamemodeSidekickIsAlwaysGuesser.getBool() && !HandleGuesser.isGuesser(thief.PlayerId)) + setGuesserGm(thief.PlayerId); } if (target == Guesser.evilGuesser) Guesser.evilGuesser = thief; if (target == Godfather.godfather) Godfather.godfather = thief; @@ -1519,6 +1535,9 @@ static void Postfix([HarmonyArgument(0)]byte callId, [HarmonyArgument(1)]Message byte gm = reader.ReadByte(); RPCProcedure.shareGamemode(gm); break; + case (byte)CustomRPC.StopStart: + RPCProcedure.stopStart(reader.ReadByte()); + break; // Game mode case (byte)CustomRPC.SetGuesserGm: diff --git a/TheOtherRoles/Resources/StopClean.png b/TheOtherRoles/Resources/StopClean.png new file mode 100644 index 0000000000000000000000000000000000000000..d284137bfa7e5a26db44f4613a814bea0f616aae GIT binary patch literal 1480 zcmeAS@N?(olHy`uVBq!ia0y~yU`zwDuX3;f$rS#mN}xbGhdT=VVJIvs2r8K<^nbcY)RBtgcmGzDzdwK1+94xkMp~&a!>qj7D|e`* z6|YWuA-}8Stf3xl-IXI}yOK!^)&KEeJbBpbH z?IRBDlUg4wKko1pSE+ll>(NK)=Zj7=t(P=3URn8wV`Iddgr&`qC$DrY?_9iUS#Yi5 z>y4)>yElie+<49QC->pXTGg|qQpz34oSZ6(PL8~(AqmQEtNprKMc#Vb{Y$()UuC+} zIwJ{7v4B>#n_rv%AAFy)_Ry6j;hH*(clG-|8*;UjHKyGy+}AI=ypoA!exvr@x9iOs zZu(z(ovT~L!jr=+em&RsCbM37!$Nguv(#{|O9EW15B~P-(`4tl$5fjgm$$Zdjva&K zsjQ!D78WeWxbHUgGKN%Kn0hiOt%w(O|P0P)M@GHKN2hKQ}iuuY|$5C^fMp zHASI3vm`^o-P1Q9ypd0wfq{9Ur;B4q#jUqD4YPv-WsW`kKQ+Qw!$RW+?;N4)(iaT< ze)8!xt($pk;Z2!aCmXt(y6-Fc^>ID#(`$O?<|>;Llf@+0zUacjprDMR{CVu>q+ZOP z6R+>d0<@hRFefgt$*n?KaQm49oN=y4VvVmHXB9R%bk=XVu;$Vemezhx<|r=}xtnLM zwCH_TMbfiBPp1S;NzLT!Te(WBYVW+=dS})sow0o0(J{kE?PEs%eI@C<@^ovq=7Sei z^|{JpXGEsXIDNZ(&B2S8Kh3^=C?;4}*yQr&=lYLMf6s5bVK9IB<%(ZVHY#SF>#yf6 zdwi=@IVWO@VA>JMK(`r!=`N8VLNd{9j$kU7Xpt;*14&0B)Ib!1)VM+v0@aA6Dsvcj zY}Qad(_y@*$3XcUnCLJ%)ML>NB9u>Y7(dbh5)d^Yg$m~uor%hr*^nBm^CzC&{|v(+ zo(2PE4r3%*;S2+ZF;EalgC$`!SP_yWiXwz0P!p0OIvKzEf)V5CUmrg|IJ16YabNuB zRzt@bmaEIPkOb6}j~9LE`gsfEB`Pdx$*$SFrbgkXA$WS|&` z07gNYOC*pGAVQ4`Fq%N-0HY6L7|W_32slUCS!m|(jtdzopr0H5?D#sB~S literal 0 HcmV?d00001 diff --git a/TheOtherRoles/TheOtherRoles.cs b/TheOtherRoles/TheOtherRoles.cs index 6143779be..54f65a835 100644 --- a/TheOtherRoles/TheOtherRoles.cs +++ b/TheOtherRoles/TheOtherRoles.cs @@ -78,6 +78,9 @@ public static void clearAndReloadRoles() { HideNSeek.clearAndReload(); PropHunt.clearAndReload(); + // Objects: + FootprintHolder.clearAndReload(); + } public static class Jester { @@ -447,6 +450,20 @@ public static Sprite getButtonSprite() { return buttonSprite; } + public static bool shieldVisible(PlayerControl target) { + bool hasVisibleShield = false; + + bool isMorphedMorphling = target == Morphling.morphling && Morphling.morphTarget != null && Morphling.morphTimer > 0f; + if (Medic.shielded != null && ((target == Medic.shielded && !isMorphedMorphling) || (isMorphedMorphling && Morphling.morphTarget == Medic.shielded))) { + hasVisibleShield = Medic.showShielded == 0 || Helpers.shouldShowGhostInfo() // Everyone or Ghost info + || (Medic.showShielded == 1 && (CachedPlayer.LocalPlayer.PlayerControl == Medic.shielded || CachedPlayer.LocalPlayer.PlayerControl == Medic.medic)) // Shielded + Medic + || (Medic.showShielded == 2 && CachedPlayer.LocalPlayer.PlayerControl == Medic.medic); // Medic only + // Make shield invisible till after the next meeting if the option is set (the medic can already see the shield) + hasVisibleShield = hasVisibleShield && (Medic.meetingAfterShielding || !Medic.showShieldAfterMeeting || CachedPlayer.LocalPlayer.PlayerControl == Medic.medic || Helpers.shouldShowGhostInfo()); + } + return hasVisibleShield; + } + public static void clearAndReload() { medic = null; shielded = null; @@ -1466,7 +1483,7 @@ public static string getInfo(PlayerControl target, PlayerControl killer) { if (!roleString.Contains("Impostor") && !roleString.Contains("Crewmate")) msg = "If my role hasn't been saved, there's no " + roleString + " in the game anymore."; else - msg = "I am a " + roleString + " without an other role."; + msg = "I was a " + roleString + " without another role."; } else if (randomNumber == 1) msg = "I'm not sure, but I guess a " + typeOfColor + " color killed me."; else if (randomNumber == 2) msg = "If I counted correctly, I died " + Math.Round(timeSinceDeath / 1000) + "s before the next meeting started."; else msg = "It seems like my killer is the " + RoleInfo.GetRolesString(Medium.target.killerIfExisting, false, false, true) + "."; diff --git a/TheOtherRoles/TheOtherRoles.csproj b/TheOtherRoles/TheOtherRoles.csproj index f5915799c..162180684 100644 --- a/TheOtherRoles/TheOtherRoles.csproj +++ b/TheOtherRoles/TheOtherRoles.csproj @@ -16,7 +16,7 @@ - + diff --git a/TheOtherRoles/Utilities/EventUtility.cs b/TheOtherRoles/Utilities/EventUtility.cs index caacedcd6..a7c7a1ccb 100644 --- a/TheOtherRoles/Utilities/EventUtility.cs +++ b/TheOtherRoles/Utilities/EventUtility.cs @@ -9,73 +9,30 @@ using System.Linq; using InnerNet; using TheOtherRoles.Modules; +using HarmonyLib; namespace TheOtherRoles.Utilities; +[HarmonyPatch] public static class EventUtility { - public enum EventTypes { - Communication, - Animation, - Invert, - KnockKnock - } - - public static readonly float[] eventFrequencies = {15f, 60f, 60f, 300f}; - public static readonly float[] eventDurations = {0f, 1f, 5f, 0f}; - public static double[] eventProbabilities; - private static bool knocked = false; - public static bool disableHorses = false; - public static void Load() { if (!isEnabled) return; - eventProbabilities = new double[6]; - foreach (EventTypes curEvent in Enum.GetValues(typeof(EventTypes))) { - float desired_trials = 60 * eventFrequencies[(int)curEvent]; - eventProbabilities[(int)curEvent] = desired_trials != 0 ? 1f / desired_trials : 1f; - } - - } - - private static List eventQueue = null; - public static bool eventInvert = false; + } public static void clearAndReload() { - eventQueue = new List(); - eventInvert = false; - if (canBeEnabled && CustomOptionHolder.enableCodenameDisableHorses != null) - disableHorses = CustomOptionHolder.enableCodenameDisableHorses.getBool(); } public static void Update() { - if (!isEnabled || eventQueue == null || AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started || TheOtherRoles.rnd == null || IntroCutscene.Instance) return; - foreach (EventTypes curEvent in eventQueue.ToArray()) { - if (TheOtherRoles.rnd.NextSingle() < eventProbabilities[(int)curEvent]) { - eventQueue.Remove(curEvent); - StartEvent(curEvent); - } - } - - AddToQueue(EventTypes.Animation); - AddToQueue(EventTypes.Invert); - if (!knocked) { - AddToQueue(EventTypes.KnockKnock); - } + if (!isEnabled || AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started || TheOtherRoles.rnd == null || IntroCutscene.Instance) return; } - private static DateTime enabled = DateTime.FromBinary(-8585213068854775808); + public static DateTime enabled = DateTime.FromBinary(638475264000000000); public static bool isEventDate => DateTime.Today.Date == enabled; - public static bool canBeEnabled => DateTime.Today.Date > enabled && DateTime.Today.Date <= enabled.AddDays(7); // One Week after the EVENT - public static bool isEnabled => isEventDate || canBeEnabled && CustomOptionHolder.enableCodenameHorsemode != null && CustomOptionHolder.enableCodenameHorsemode.getBool(); - - public static void AddToQueue(EventTypes newEvent) { - if (!isEnabled || eventQueue == null || eventQueue.Contains(newEvent)) return; - eventQueue.Add(newEvent); - } + public static bool canBeEnabled => DateTime.Today.Date >= enabled && DateTime.Today.Date <= enabled.AddDays(7); // One Week after the EVENT + public static bool isEnabled => isEventDate || canBeEnabled && CustomOptionHolder.enableEventMode != null && CustomOptionHolder.enableEventMode.getBool(); - - private static string defaultHat = "default"; public static void meetingEndsUpdate() { if (!isEnabled) return; // TODO - Implement Horse hats @@ -85,75 +42,24 @@ public static void meetingEndsUpdate() { public static void meetingStartsUpdate() { if (!isEnabled) return; - if (rnd.NextDouble() <= 0.3f) { AddToQueue(EventTypes.Communication); } - PlayerControl.LocalPlayer.RpcSetHat(defaultHat); - HudManager.Instance.StartCoroutine(Effects.Lerp(1f, new Action((p) => { - if (MeetingHud.Instance && MeetingHud.Instance.playerStates != null) { - foreach (PlayerVoteArea pva in MeetingHud.Instance.playerStates) { - GameData.PlayerInfo pInfo = GameData.Instance.AllPlayers.ToArray().First(x => x.PlayerId == pva.TargetPlayerId); - pva.SetCosmetics(pInfo); // Needed cause cosmetics are set async'd. - } - } - }))); } public static void gameStartsUpdate() { if (!isEnabled) return; - defaultHat = PlayerControl.LocalPlayer.Data.DefaultOutfit.HatId; - meetingEndsUpdate(); - List relevantPlayers = CachedPlayer.AllPlayers.Where(x => !x.Data.IsDead && x != CachedPlayer.LocalPlayer).ToList(); - foreach (CachedPlayer pc in relevantPlayers) - pc.PlayerControl.MyPhysics.SetBodyType(disableHorses ? PlayerBodyTypes.Normal : PlayerBodyTypes.Horse); - } public static void gameEndsUpdate() { if (!isEnabled) return; - if (defaultHat != "default") - PlayerControl.LocalPlayer.RpcSetHat(defaultHat); } - public static void StartEvent(EventTypes eventToStart) { - List relevantPlayers = CachedPlayer.AllPlayers.Where(x => !x.Data.IsDead && x != CachedPlayer.LocalPlayer).ToList(); - switch (eventToStart) { - case EventTypes.Animation: - CachedPlayer animationPlayer = relevantPlayers[rnd.Next(relevantPlayers.Count)]; - animationPlayer.PlayerPhysics.SetBodyType(rnd.Next(2) > 0 ? (disableHorses ? PlayerBodyTypes.Horse : PlayerBodyTypes.Normal) : PlayerBodyTypes.Seeker); - FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(eventDurations[(int)EventTypes.Animation], new Action((p) => { - if (p==1) - animationPlayer.PlayerControl.MyPhysics.SetBodyType(disableHorses ? PlayerBodyTypes.Normal : PlayerBodyTypes.Horse); - }))); - break; - case EventTypes.Communication: - int index = TheOtherRoles.rnd.Next(relevantPlayers.Count); - CachedPlayer firstPlayer = relevantPlayers[index]; - relevantPlayers.RemoveAt(index); - string msg = firstPlayer.Data.PlayerName + " "; - foreach (CachedPlayer pc in relevantPlayers.ToArray()) { - if (TheOtherRoles.rnd.NextSingle() < 1f / relevantPlayers.Count) { - relevantPlayers.Remove(pc); - msg += pc.Data.PlayerName + " "; - } - } - RoomTracker tracker = FastDestroyableSingleton.Instance.roomTracker; - string lastRoom = ""; - try { lastRoom = FastDestroyableSingleton.Instance.GetString(tracker.LastRoom.RoomId); } catch { } - msg += lastRoom != ""? lastRoom : (TheOtherRoles.rnd.Next(2) > 0 ? "sus" : "safe"); - - FastDestroyableSingleton.Instance.Chat.AddChat(relevantPlayers[rnd.Next(relevantPlayers.Count)], $"{msg}"); - break; - case EventTypes.Invert: - eventInvert = true; - FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(eventDurations[(int)EventTypes.Invert], new Action((p) => { - if (p == 1) - eventInvert = false; - }))); - break; - case EventTypes.KnockKnock: - SoundEffectsManager.play("knockKnock"); - knocked = true; - break; + [HarmonyPatch(typeof(ChatController), nameof(ChatController.AddChat))] + public static class AddChatPatch { + public static void Prefix(ChatController __instance, PlayerControl sourcePlayer, ref string chatText, bool censor) { + if (!isEnabled) return; + var charArray = chatText.ToCharArray(); + Array.Reverse(charArray); + chatText = new string(charArray); } } } \ No newline at end of file diff --git a/TheOtherRoles/packages.lock.json b/TheOtherRoles/packages.lock.json index 3a2a5c34f..e3543ec28 100644 --- a/TheOtherRoles/packages.lock.json +++ b/TheOtherRoles/packages.lock.json @@ -4,9 +4,9 @@ "net6.0": { "AmongUs.GameLibs.Steam": { "type": "Direct", - "requested": "[2023.11.28, )", - "resolved": "2023.11.28", - "contentHash": "EsKrZS4tjTRrY7mtZls3tMy54zije4vleEA9s0UGUr4SF3qm2axR6wMncyIJCk4ACXlN5Bxpsd3/upDe9Gnx6A==" + "requested": "[2024.3.5, )", + "resolved": "2024.3.5", + "contentHash": "5KOcBSFnql0DmrL82GikWb5I+KLvD8h8RsS28BKTCws2fPKwWauUm/L9Zh73pIHcnOMCpvnAxWeZh/AqxAEI0Q==" }, "BepInEx.IL2CPP.MSBuild": { "type": "Direct",