From: Mallöris <|||||@||.||> Date: Sun, 1 May 2022 18:00:21 +0000 (+0200) Subject: version 4.1.0 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=32f2e080fd6885bb2723b8db3d9b6ecf1c3844ab;p=rhonda%2Ftheotherroles.git version 4.1.0 --- diff --git a/TheOtherRoles/Buttons.cs b/TheOtherRoles/Buttons.cs index f17189c..4ad84e9 100644 --- a/TheOtherRoles/Buttons.cs +++ b/TheOtherRoles/Buttons.cs @@ -47,6 +47,7 @@ namespace TheOtherRoles public static CustomButton pursuerButton; public static CustomButton witchSpellButton; public static CustomButton ninjaButton; + public static CustomButton mayorMeetingButton; public static Dictionary> deputyHandcuffedButtons = null; @@ -93,6 +94,7 @@ namespace TheOtherRoles trackerTrackCorpsesButton.MaxTimer = Tracker.corpsesTrackingCooldown; witchSpellButton.MaxTimer = Witch.cooldown; ninjaButton.MaxTimer = Ninja.cooldown; + mayorMeetingButton.MaxTimer = PlayerControl.GameOptions.EmergencyCooldown; timeMasterShieldButton.EffectDuration = TimeMaster.shieldDuration; hackerButton.EffectDuration = Hacker.duration; @@ -213,14 +215,20 @@ namespace TheOtherRoles } else if (task.TaskType == TaskTypes.StopCharles) { ShipStatus.Instance.RpcRepairSystem(SystemTypes.Reactor, 0 | 16); ShipStatus.Instance.RpcRepairSystem(SystemTypes.Reactor, 1 | 16); + } else if (SubmergedCompatibility.isSubmerged() && task.TaskType == SubmergedCompatibility.RetrieveOxygenMask) { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.EngineerFixSubmergedOxygen, Hazel.SendOption.Reliable, -1); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.engineerFixSubmergedOxygen(); } + } }, () => { return Engineer.engineer != null && Engineer.engineer == PlayerControl.LocalPlayer && Engineer.remainingFixes > 0 && !PlayerControl.LocalPlayer.Data.IsDead; }, () => { bool sabotageActive = false; foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks) - if (task.TaskType == TaskTypes.FixLights || task.TaskType == TaskTypes.RestoreOxy || task.TaskType == TaskTypes.ResetReactor || task.TaskType == TaskTypes.ResetSeismic || task.TaskType == TaskTypes.FixComms || task.TaskType == TaskTypes.StopCharles) + if (task.TaskType == TaskTypes.FixLights || task.TaskType == TaskTypes.RestoreOxy || task.TaskType == TaskTypes.ResetReactor || task.TaskType == TaskTypes.ResetSeismic || task.TaskType == TaskTypes.FixComms || task.TaskType == TaskTypes.StopCharles + || SubmergedCompatibility.isSubmerged() && task.TaskType == SubmergedCompatibility.RetrieveOxygenMask) sabotageActive = true; return sabotageActive && Engineer.remainingFixes > 0 && PlayerControl.LocalPlayer.CanMove; }, @@ -746,9 +754,9 @@ namespace TheOtherRoles usePortalButton = new CustomButton( () => { bool didTeleport = false; - Vector2 exit = Portal.findExit(PlayerControl.LocalPlayer.transform.position); - Vector2 entry = Portal.findEntry(PlayerControl.LocalPlayer.transform.position); - PlayerControl.LocalPlayer.NetTransform.RpcSnapTo(entry); // TODO: check for bans on servers + Vector3 exit = Portal.findExit(PlayerControl.LocalPlayer.transform.position); + Vector3 entry = Portal.findEntry(PlayerControl.LocalPlayer.transform.position); + PlayerControl.LocalPlayer.NetTransform.RpcSnapTo(entry); if (!PlayerControl.LocalPlayer.Data.IsDead) { // Ghosts can portal too, but non-blocking and only with a local animation MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UsePortal, Hazel.SendOption.Reliable, -1); @@ -761,6 +769,9 @@ namespace TheOtherRoles PlayerControl.LocalPlayer.moveable = false; PlayerControl.LocalPlayer.NetTransform.Halt(); if (p >= 0.5f && p <= 0.53f && !didTeleport) { + if (SubmergedCompatibility.isSubmerged()) { + SubmergedCompatibility.ChangeFloor(exit.y > -7); + } PlayerControl.LocalPlayer.NetTransform.RpcSnapTo(exit); didTeleport = true; } @@ -1005,7 +1016,7 @@ namespace TheOtherRoles RPCProcedure.sealVent(SecurityGuard.ventTarget.Id); SecurityGuard.ventTarget = null; - } else if (PlayerControl.GameOptions.MapId != 1) { // Place camera if there's no vent and it's not MiraHQ + } else if (PlayerControl.GameOptions.MapId != 1 && !SubmergedCompatibility.isSubmerged()) { // Place camera if there's no vent and it's not MiraHQ or Submerged var pos = PlayerControl.LocalPlayer.transform.position; byte[] buff = new byte[sizeof(float) * 2]; Buffer.BlockCopy(BitConverter.GetBytes(pos.x), 0, buff, 0*sizeof(float), sizeof(float)); @@ -1020,12 +1031,12 @@ namespace TheOtherRoles }, () => { return SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead && SecurityGuard.remainingScrews >= Mathf.Min(SecurityGuard.ventPrice, SecurityGuard.camPrice); }, () => { - securityGuardButton.actionButton.graphic.sprite = (SecurityGuard.ventTarget == null && PlayerControl.GameOptions.MapId != 1) ? SecurityGuard.getPlaceCameraButtonSprite() : SecurityGuard.getCloseVentButtonSprite(); + securityGuardButton.actionButton.graphic.sprite = (SecurityGuard.ventTarget == null && PlayerControl.GameOptions.MapId != 1 && !SubmergedCompatibility.isSubmerged()) ? SecurityGuard.getPlaceCameraButtonSprite() : SecurityGuard.getCloseVentButtonSprite(); if (securityGuardButtonScrewsText != null) securityGuardButtonScrewsText.text = $"{SecurityGuard.remainingScrews}/{SecurityGuard.totalScrews}"; if (SecurityGuard.ventTarget != null) return SecurityGuard.remainingScrews >= SecurityGuard.ventPrice && PlayerControl.LocalPlayer.CanMove; - return PlayerControl.GameOptions.MapId != 1 && SecurityGuard.remainingScrews >= SecurityGuard.camPrice && PlayerControl.LocalPlayer.CanMove; + return PlayerControl.GameOptions.MapId != 1 && !SubmergedCompatibility.isSubmerged() && SecurityGuard.remainingScrews >= SecurityGuard.camPrice && PlayerControl.LocalPlayer.CanMove; }, () => { securityGuardButton.Timer = securityGuardButton.MaxTimer; }, SecurityGuard.getPlaceCameraButtonSprite(), @@ -1070,7 +1081,8 @@ namespace TheOtherRoles if (SecurityGuard.cantMove) PlayerControl.LocalPlayer.moveable = false; PlayerControl.LocalPlayer.NetTransform.Halt(); // Stop current movement }, - () => { return SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead && SecurityGuard.remainingScrews < Mathf.Min(SecurityGuard.ventPrice, SecurityGuard.camPrice); }, + () => { return SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead && SecurityGuard.remainingScrews < Mathf.Min(SecurityGuard.ventPrice, SecurityGuard.camPrice) + && !SubmergedCompatibility.isSubmerged(); }, () => { if (securityGuardChargesText != null) securityGuardChargesText.text = $"{SecurityGuard.charges} / {SecurityGuard.maxCharges}"; securityGuardCamButton.actionButton.graphic.sprite = PlayerControl.GameOptions.MapId == 1 ? SecurityGuard.getLogSprite() : SecurityGuard.getCamSprite(); @@ -1230,7 +1242,7 @@ namespace TheOtherRoles if (Medium.target == null || Medium.target.player == null) return; string msg = ""; - int randomNumber = Medium.target.killerIfExisting?.PlayerId == Mini.mini?.PlayerId ? TheOtherRoles.rnd.Next(3) : TheOtherRoles.rnd.Next(4); + int randomNumber = TheOtherRoles.rnd.Next(4); string typeOfColor = Helpers.isLighterColor(Medium.target.killerIfExisting.Data.DefaultOutfit.ColorId) ? "lighter" : "darker"; float timeSinceDeath = ((float)(Medium.meetingStartTime - Medium.target.timeOfDeath).TotalMilliseconds); string name = " (" + Medium.target.player.Data.PlayerName + ")"; @@ -1239,7 +1251,7 @@ namespace TheOtherRoles if (randomNumber == 0) msg = "What is your role? My role is " + RoleInfo.GetRolesString(Medium.target.player, false) + name; else if (randomNumber == 1) msg = "What is your killer`s color type? My killer is a " + typeOfColor + " color" + name; else if (randomNumber == 2) msg = "When did you die? I have died " + Math.Round(timeSinceDeath / 1000) + "s before meeting started" + name; - else msg = "What is your killer`s role? My killer is " + RoleInfo.GetRolesString(Medium.target.killerIfExisting, false) + name; //exlude mini + else msg = "What is your killer`s role? My killer is " + RoleInfo.GetRolesString(Medium.target.killerIfExisting, false) + name; DestroyableSingleton.Instance.Chat.AddChat(PlayerControl.LocalPlayer, $"{msg}"); @@ -1392,6 +1404,9 @@ namespace TheOtherRoles writer2.Write(Ninja.ninjaMarked.PlayerId); writer2.Write(byte.MaxValue); AmongUsClient.Instance.FinishRpcImmediately(writer2); + if (SubmergedCompatibility.isSubmerged()) { + SubmergedCompatibility.ChangeFloor(Ninja.ninjaMarked.transform.localPosition.y > -7); + } RPCProcedure.uncheckedMurderPlayer(PlayerControl.LocalPlayer.PlayerId, Ninja.ninjaMarked.PlayerId, byte.MaxValue); // Create Second trace after killing @@ -1435,6 +1450,39 @@ namespace TheOtherRoles KeyCode.F ); + mayorMeetingButton = new CustomButton( + () => { + PlayerControl.LocalPlayer.NetTransform.Halt(); // Stop current movement + Helpers.handleVampireBiteOnBodyReport(); // Manually call Vampire handling, since the CmdReportDeadBody Prefix won't be called + RPCProcedure.uncheckedCmdReportDeadBody(PlayerControl.LocalPlayer.PlayerId, Byte.MinValue); + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedCmdReportDeadBody, Hazel.SendOption.Reliable, -1); + writer.Write(PlayerControl.LocalPlayer.PlayerId); + writer.Write(Byte.MaxValue); + AmongUsClient.Instance.FinishRpcImmediately(writer); + }, + () => { return Mayor.mayor != null && Mayor.mayor == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead ;}, + () => { + mayorMeetingButton.actionButton.OverrideText("Emergency"); + bool sabotageActive = false; + foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks) + if (task.TaskType == TaskTypes.FixLights || task.TaskType == TaskTypes.RestoreOxy || task.TaskType == TaskTypes.ResetReactor || task.TaskType == TaskTypes.ResetSeismic || task.TaskType == TaskTypes.FixComms || task.TaskType == TaskTypes.StopCharles + || SubmergedCompatibility.isSubmerged() && task.TaskType == SubmergedCompatibility.RetrieveOxygenMask) + sabotageActive = true; + return !sabotageActive && PlayerControl.LocalPlayer.CanMove; + }, + () => { mayorMeetingButton.Timer = mayorMeetingButton.MaxTimer; }, + Mayor.getMeetingSprite(), + new Vector3(-1.8f, -0.06f, 0), + __instance, + KeyCode.F, + true, + 0f, + () => {}, + false, + "Meeting" + ); + // Set the default (or settings from the previous game) timers / durations when spawning the buttons setCustomButtonCooldowns(); deputyHandcuffedButtons = null; diff --git a/TheOtherRoles/CustomOptionHolder.cs b/TheOtherRoles/CustomOptionHolder.cs index 9bbf8ac..833b498 100644 --- a/TheOtherRoles/CustomOptionHolder.cs +++ b/TheOtherRoles/CustomOptionHolder.cs @@ -103,6 +103,7 @@ namespace TheOtherRoles { public static CustomOption mayorSpawnRate; public static CustomOption mayorCanSeeVoteColors; public static CustomOption mayorTasksNeededToSeeVoteColors; + public static CustomOption mayorMeetingButton; public static CustomOption portalmakerSpawnRate; public static CustomOption portalmakerCooldown; @@ -224,8 +225,6 @@ namespace TheOtherRoles { public static CustomOption lawyerSpawnRate; public static CustomOption lawyerTargetCanBeJester; - public static CustomOption lawyerWinsAfterMeetings; - public static CustomOption lawyerNeededMeetings; public static CustomOption lawyerVision; public static CustomOption lawyerKnowsRole; public static CustomOption pursuerCooldown; @@ -278,6 +277,7 @@ namespace TheOtherRoles { public static CustomOption dynamicMapEnableMira; public static CustomOption dynamicMapEnablePolus; public static CustomOption dynamicMapEnableAirShip; + public static CustomOption dynamicMapEnableSubmerged; internal static Dictionary blockedRolePairings = new Dictionary(); @@ -398,8 +398,6 @@ namespace TheOtherRoles { lawyerSpawnRate = CustomOption.Create(350, Types.Neutral, cs(Lawyer.color, "Lawyer"), rates, null, true); lawyerTargetCanBeJester = CustomOption.Create(351, Types.Neutral, "Lawyer Target Can Be The Jester", false, lawyerSpawnRate); - lawyerWinsAfterMeetings = CustomOption.Create(352, Types.Neutral, "Lawyer Wins After Meetings", false, lawyerSpawnRate); - lawyerNeededMeetings = CustomOption.Create(353, Types.Neutral, "Lawyer Needed Meetings To Win", 5f, 1f, 15f, 1f, lawyerWinsAfterMeetings); lawyerVision = CustomOption.Create(354, Types.Neutral, "Lawyer Vision", 1f, 0.25f, 3f, 0.25f, lawyerSpawnRate); lawyerKnowsRole = CustomOption.Create(355, Types.Neutral, "Lawyer Knows Target Role", false, lawyerSpawnRate); pursuerCooldown = CustomOption.Create(356, Types.Neutral, "Pursuer Blank Cooldown", 30f, 5f, 60f, 2.5f, lawyerSpawnRate); @@ -409,9 +407,9 @@ namespace TheOtherRoles { shifterShiftsModifiers = CustomOption.Create(71, Types.Crewmate, "Shifter Shifts Modifiers", false, shifterSpawnRate); mayorSpawnRate = CustomOption.Create(80, Types.Crewmate, cs(Mayor.color, "Mayor"), rates, null, true); - mayorCanSeeVoteColors = CustomOption.Create(81, Types.Crewmate, "Mayor Can See Vote Colors", false, mayorSpawnRate); mayorTasksNeededToSeeVoteColors = CustomOption.Create(82, Types.Crewmate, "Completed Tasks Needed To See Vote Colors", 5f, 0f, 20f, 1f, mayorCanSeeVoteColors); + mayorMeetingButton = CustomOption.Create(83, Types.Crewmate, "Mobile Emergency Button", true, mayorSpawnRate); engineerSpawnRate = CustomOption.Create(90, Types.Crewmate, cs(Engineer.color, "Engineer"), rates, null, true); engineerNumberOfFixes = CustomOption.Create(91, Types.Crewmate, "Number Of Sabotage Fixes", 1f, 1f, 3f, 1f, engineerSpawnRate); @@ -563,6 +561,8 @@ namespace TheOtherRoles { dynamicMapEnablePolus = CustomOption.Create(503, Types.General, "Enable Polus Rotation", true, dynamicMap, false); dynamicMapEnableAirShip = CustomOption.Create(504, Types.General, "Enable Airship Rotation", true, dynamicMap, false); + dynamicMapEnableSubmerged = CustomOption.Create(505, Types.General, "Enable Submerged Rotation", true, dynamicMap, false); + blockedRolePairings.Add((byte)RoleId.Vampire, new [] { (byte)RoleId.Warlock}); blockedRolePairings.Add((byte)RoleId.Warlock, new [] { (byte)RoleId.Vampire}); blockedRolePairings.Add((byte)RoleId.Spy, new [] { (byte)RoleId.Mini}); diff --git a/TheOtherRoles/Helpers.cs b/TheOtherRoles/Helpers.cs index f1541ff..ab8a144 100644 --- a/TheOtherRoles/Helpers.cs +++ b/TheOtherRoles/Helpers.cs @@ -30,14 +30,15 @@ namespace TheOtherRoles { return null; } - public static Texture2D loadTextureFromResources(string path) { + public static unsafe Texture2D loadTextureFromResources(string path) { try { Texture2D texture = new Texture2D(2, 2, TextureFormat.ARGB32, true); Assembly assembly = Assembly.GetExecutingAssembly(); Stream stream = assembly.GetManifestResourceStream(path); - var byteTexture = new byte[stream.Length]; - var read = stream.Read(byteTexture, 0, (int) stream.Length); - LoadImage(texture, byteTexture, false); + var length = stream.Length; + var byteTexture = new Il2CppStructArray(length); + stream.Read(new Span(IntPtr.Add(byteTexture.Pointer, IntPtr.Size * 4).ToPointer(), (int) length)); + ImageConversion.LoadImage(texture, byteTexture, false); return texture; } catch { System.Console.WriteLine("Error loading texture from resources: " + path); @@ -49,8 +50,8 @@ namespace TheOtherRoles { try { if (File.Exists(path)) { Texture2D texture = new Texture2D(2, 2, TextureFormat.ARGB32, true); - byte[] byteTexture = File.ReadAllBytes(path); - LoadImage(texture, byteTexture, false); + var byteTexture = Il2CppSystem.IO.File.ReadAllBytes(path); + ImageConversion.LoadImage(texture, byteTexture, false); return texture; } } catch { @@ -59,15 +60,6 @@ namespace TheOtherRoles { return null; } - internal delegate bool d_LoadImage(IntPtr tex, IntPtr data, bool markNonReadable); - internal static d_LoadImage iCall_LoadImage; - private static bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable) { - if (iCall_LoadImage == null) - iCall_LoadImage = IL2CPP.ResolveICall("UnityEngine.ImageConversion::LoadImage"); - var il2cppArray = (Il2CppStructArray) data; - return iCall_LoadImage.Invoke(tex.Pointer, il2cppArray.Pointer, markNonReadable); - } - public static PlayerControl playerById(byte id) { foreach (PlayerControl player in PlayerControl.AllPlayerControls) @@ -155,8 +147,8 @@ namespace TheOtherRoles { public static void clearAllTasks(this PlayerControl player) { if (player == null) return; - for (int i = 0; i < player.myTasks.Count; i++) { - PlayerTask playerTask = player.myTasks[i]; + foreach (var playerTask in player.myTasks) + { playerTask.OnRemove(); UnityEngine.Object.Destroy(playerTask.gameObject); } @@ -377,5 +369,10 @@ namespace TheOtherRoles { return team; } + + public static object TryCast(this Il2CppObjectBase self, Type type) + { + return AccessTools.Method(self.GetType(), nameof(Il2CppObjectBase.TryCast)).MakeGenericMethod(type).Invoke(self, Array.Empty()); + } } } diff --git a/TheOtherRoles/Main.cs b/TheOtherRoles/Main.cs index 2976ea8..9aebb39 100644 --- a/TheOtherRoles/Main.cs +++ b/TheOtherRoles/Main.cs @@ -17,13 +17,14 @@ using TheOtherRoles.Modules; namespace TheOtherRoles { [BepInPlugin(Id, "The Other Roles", VersionString)] + [BepInDependency(SubmergedCompatibility.SUBMERGED_GUID, BepInDependency.DependencyFlags.SoftDependency)] [BepInProcess("Among Us.exe")] public class TheOtherRolesPlugin : BasePlugin { public const string Id = "me.eisbison.theotherroles"; - public const string VersionString = "4.0.0"; + public const string VersionString = "4.1.0"; - public static System.Version Version = System.Version.Parse(VersionString); + public static Version Version = Version.Parse(VersionString); internal static BepInEx.Logging.ManualLogSource Logger; public Harmony Harmony { get; } = new Harmony(Id); @@ -90,6 +91,8 @@ namespace TheOtherRoles CustomColors.Load(); Patches.FreeNamePatch.Initialize(); Harmony.PatchAll(); + SubmergedCompatibility.Initialize(); + AddComponent(); } public static Sprite GetModStamp() { if (ModStamp) return ModStamp; diff --git a/TheOtherRoles/Modules/CustomHats.cs b/TheOtherRoles/Modules/CustomHats.cs index 5cd1a03..ee49664 100644 --- a/TheOtherRoles/Modules/CustomHats.cs +++ b/TheOtherRoles/Modules/CustomHats.cs @@ -281,11 +281,11 @@ namespace TheOtherRoles.Modules { float ypos = offset - (i / __instance.NumPerRow) * (isDefaultPackage ? 1f : 1.5f) * __instance.YOffset; ColorChip colorChip = UnityEngine.Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner); if (ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard) { - colorChip.Button.OnMouseOver.AddListener((UnityEngine.Events.UnityAction)(() => __instance.SelectHat(hat))); - colorChip.Button.OnMouseOut.AddListener((UnityEngine.Events.UnityAction)(() => __instance.SelectHat(DestroyableSingleton.Instance.GetHatById(SaveManager.LastHat)))); - colorChip.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => __instance.ClickEquip())); + colorChip.Button.OnMouseOver.AddListener((System.Action)(() => __instance.SelectHat(hat))); + colorChip.Button.OnMouseOut.AddListener((System.Action)(() => __instance.SelectHat(DestroyableSingleton.Instance.GetHatById(SaveManager.LastHat)))); + colorChip.Button.OnClick.AddListener((System.Action)(() => __instance.ClickEquip())); } else { - colorChip.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => __instance.SelectHat(hat))); + colorChip.Button.OnClick.AddListener((System.Action)(() => __instance.SelectHat(hat))); } colorChip.Button.ClickMask = __instance.scroller.Hitbox; Transform background = colorChip.transform.FindChild("Background"); @@ -442,6 +442,7 @@ namespace TheOtherRoles.Modules { List markedfordownload = new List(); string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\"; + if (!Directory.Exists(filePath)) Directory.CreateDirectory(filePath); MD5 md5 = MD5.Create(); foreach (CustomHatOnline data in hatdatas) { if (doesResourceRequireDownload(filePath + data.resource, data.reshasha, md5)) diff --git a/TheOtherRoles/Modules/CustomOptions.cs b/TheOtherRoles/Modules/CustomOptions.cs index 4cc2e36..17b5c3f 100644 --- a/TheOtherRoles/Modules/CustomOptions.cs +++ b/TheOtherRoles/Modules/CustomOptions.cs @@ -224,7 +224,7 @@ namespace TheOtherRoles { if (button == null) continue; int copiedIndex = i; button.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent(); - button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { + button.OnClick.AddListener((System.Action)(() => { gameSettingMenu.RegularGameSettings.SetActive(false); gameSettingMenu.RolesSettings.gameObject.SetActive(false); torSettings.gameObject.SetActive(false); diff --git a/TheOtherRoles/Modules/ModUpdater.cs b/TheOtherRoles/Modules/ModUpdater.cs index aab8679..1d68ac8 100644 --- a/TheOtherRoles/Modules/ModUpdater.cs +++ b/TheOtherRoles/Modules/ModUpdater.cs @@ -1,223 +1,212 @@ using System; -using BepInEx; -using BepInEx.Configuration; -using BepInEx.IL2CPP; -using Il2CppSystem; -using Hazel; -using HarmonyLib; -using UnityEngine; -using UnityEngine.UI; -using UnityEngine.Events; -using UnhollowerBaseLib; -using System.IO; -using System.Reflection; using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Net; +using System.IO; using System.Net.Http; -using System.Net.Http.Headers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using System.Reflection; using System.Threading.Tasks; -using System.Security.Cryptography; +using BepInEx; +using BepInEx.IL2CPP.Utils; using Newtonsoft.Json.Linq; -using Newtonsoft.Json; +using TMPro; using Twitch; +using UnhollowerBaseLib.Attributes; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.UI; +using Version = SemanticVersioning.Version; + +namespace TheOtherRoles.Modules +{ + public class ModUpdateBehaviour : MonoBehaviour + { + public static readonly bool CheckForSubmergedUpdates = true; + public static bool showPopUp = true; + + public static ModUpdateBehaviour Instance { get; private set; } + public ModUpdateBehaviour(IntPtr ptr) : base(ptr) { } + + public class UpdateData + { + public string Content; + public string Tag; + public JObject Request; + + public UpdateData(JObject data) + { + Tag = data["tag_name"]?.ToString().TrimStart('v'); + Content = data["body"]?.ToString(); + Request = data; + } -namespace TheOtherRoles.Modules { - [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] - public class ModUpdaterButton { - private static void Prefix(MainMenuManager __instance) { - CustomHatLoader.LaunchHatFetcher(); - ModUpdater.LaunchUpdater(); - if (!ModUpdater.hasUpdate) return; - var template = GameObject.Find("ExitGameButton"); - if (template == null) return; + public bool IsNewer(Version version) + { + if (!Version.TryParse(Tag, out var myVersion)) return false; + return myVersion > version; + } + } + + public UpdateData TORUpdate; + public UpdateData SubmergedUpdate; - var button = UnityEngine.Object.Instantiate(template, null); - button.transform.localPosition = new Vector3(button.transform.localPosition.x, button.transform.localPosition.y + 1.2f, button.transform.localPosition.z); + [HideFromIl2Cpp] + public UpdateData RequiredUpdateData => TORUpdate ?? SubmergedUpdate; + + public void Awake() + { + if (Instance) Destroy(this); + Instance = this; + + SceneManager.add_sceneLoaded((Action) (OnSceneLoaded)); + this.StartCoroutine(CoCheckUpdates()); + + foreach (var file in Directory.GetFiles(Paths.PluginPath, "*.old")) + { + File.Delete(file); + } + } + + private void OnSceneLoaded(Scene scene, LoadSceneMode mode) + { + if (scene.name != "MainMenu") return; + if (RequiredUpdateData is null) return; + + var template = GameObject.Find("ExitGameButton"); + if (!template) return; + + var button = Instantiate(template, null); + var buttonTransform = button.transform; + var pos = buttonTransform.localPosition; + pos.y += 1.2f; + buttonTransform.localPosition = pos; PassiveButton passiveButton = button.GetComponent(); SpriteRenderer buttonSprite = button.GetComponent(); passiveButton.OnClick = new Button.ButtonClickedEvent(); - passiveButton.OnClick.AddListener((UnityEngine.Events.UnityAction)onClick); + passiveButton.OnClick.AddListener((Action) (() => + { + this.StartCoroutine(CoUpdate()); + button.SetActive(false); + })); - var text = button.transform.GetChild(0).GetComponent(); - __instance.StartCoroutine(Effects.Lerp(0.1f, new System.Action((p) => { - text.SetText("Update"); - }))); + var text = button.transform.GetChild(0).GetComponent(); + string t = "Update"; + if (TORUpdate is null) t = SubmergedCompatibility.Loaded ? $"Update\nSubmerged" : $"Download\nSubmerged"; + + StartCoroutine(Effects.Lerp(0.1f, (Action)(p => text.SetText(t)))); buttonSprite.color = text.color = Color.red; - passiveButton.OnMouseOut.AddListener((UnityEngine.Events.UnityAction)delegate { - buttonSprite.color = text.color = Color.red; - }); + passiveButton.OnMouseOut.AddListener((Action)(() => buttonSprite.color = text.color = Color.red)); - TwitchManager man = DestroyableSingleton.Instance; - ModUpdater.InfoPopup = UnityEngine.Object.Instantiate(man.TwitchPopup); - ModUpdater.InfoPopup.TextAreaTMP.fontSize *= 0.7f; - ModUpdater.InfoPopup.TextAreaTMP.enableAutoSizing = false; + var isSubmerged = TORUpdate == null; + var announcement = $"A new {(isSubmerged ? "Submerged" : "THE OTHER ROLES")} update to {(isSubmerged ? SubmergedUpdate.Tag : TORUpdate.Tag)} is available\n{(isSubmerged ? SubmergedUpdate.Content : TORUpdate.Content)}"; + var mgr = FindObjectOfType(true); + if (showPopUp) mgr.StartCoroutine(CoShowAnnouncement(announcement)); + showPopUp = false; + } + + [HideFromIl2Cpp] + public IEnumerator CoUpdate() + { + var isSubmerged = TORUpdate is null; + var updateName = (isSubmerged ? "Submerged" : "The Other Roles"); + + var popup = Instantiate(TwitchManager.Instance.TwitchPopup); + popup.TextAreaTMP.fontSize *= 0.7f; + popup.TextAreaTMP.enableAutoSizing = false; + popup.Show(); + popup.TextAreaTMP.text = $"Updating {updateName}\nPlease wait..."; + + var download = Task.Run(DownloadUpdate); + while (!download.IsCompleted) yield return null; + popup.TextAreaTMP.text = download.Result ? $"{updateName}\nupdated successfully\nPlease restart the game." : "Update wasn't successful\nTry again later,\nor update manually."; - void onClick() { - ModUpdater.ExecuteUpdate(); - button.SetActive(false); - } } - } - [HarmonyPatch(typeof(AnnouncementPopUp), nameof(AnnouncementPopUp.UpdateAnnounceText))] - public static class Announcement { - public static bool Prefix(AnnouncementPopUp __instance) { - var text = __instance.AnnounceTextMeshPro; - text.text = ModUpdater.announcement; - return false; + [HideFromIl2Cpp] + public IEnumerator CoShowAnnouncement(string announcement) + { + var popUp = Instantiate(FindObjectOfType(true)); + popUp.gameObject.SetActive(true); + yield return popUp.Init(); + var last = SaveManager.LastAnnouncement; + last.Id = 1; + last.Text = announcement; + SelectableHyperLinkHelper.DestroyGOs(popUp.selectableHyperLinks, name); + popUp.AnnounceTextMeshPro.text = announcement; } - } - public class ModUpdater { - public static bool running = false; - public static bool hasUpdate = false; - public static string updateURI = null; - private static Task updateTask = null; - public static string announcement = ""; - public static GenericPopup InfoPopup; - - public static void LaunchUpdater() { - if (running) return; - running = true; - checkForUpdate().GetAwaiter().GetResult(); - clearOldVersions(); - if (hasUpdate || TheOtherRolesPlugin.ShowPopUpVersion.Value != TheOtherRolesPlugin.VersionString) { - DestroyableSingleton.Instance.Announcement.gameObject.SetActive(true); - TheOtherRolesPlugin.ShowPopUpVersion.Value = TheOtherRolesPlugin.VersionString; + [HideFromIl2Cpp] + public static IEnumerator CoCheckUpdates() + { + var torUpdateCheck = Task.Run(() => Instance.GetGithubUpdate("Eisbison", "TheOtherRoles")); + while (!torUpdateCheck.IsCompleted) yield return null; + if (torUpdateCheck.Result != null && torUpdateCheck.Result.IsNewer(Version.Parse(TheOtherRolesPlugin.VersionString))) + { + Instance.TORUpdate = torUpdateCheck.Result; } - MapOptions.reloadPluginOptions(); - } - public static void ExecuteUpdate() { - string info = "Updating The Other Roles\nPlease wait..."; - ModUpdater.InfoPopup.Show(info); // Show originally - if (updateTask == null) { - if (updateURI != null) { - updateTask = downloadUpdate(); - } else { - info = "Unable to auto-update\nPlease update manually"; + if (CheckForSubmergedUpdates) + { + var submergedUpdateCheck = Task.Run(() => Instance.GetGithubUpdate("SubmergedAmongUs", "Submerged")); + while (!submergedUpdateCheck.IsCompleted) yield return null; + if (submergedUpdateCheck.Result != null && (!SubmergedCompatibility.Loaded || submergedUpdateCheck.Result.IsNewer(SubmergedCompatibility.Version))) + { + Instance.SubmergedUpdate = submergedUpdateCheck.Result; } - } else { - info = "Update might already\nbe in progress"; - } - ModUpdater.InfoPopup.StartCoroutine(Effects.Lerp(0.01f, new System.Action((p) => { ModUpdater.setPopupText(info); }))); - } - - public static void clearOldVersions() { - try { - DirectoryInfo d = new DirectoryInfo(Path.GetDirectoryName(Application.dataPath) + @"\BepInEx\plugins"); - string[] files = d.GetFiles("*.old").Select(x => x.FullName).ToArray(); // Getting old versions - foreach (string f in files) - File.Delete(f); - } catch (System.Exception e) { - System.Console.WriteLine("Exception occured when clearing old versions:\n" + e); } } - public static async Task checkForUpdate() { - try { - HttpClient http = new HttpClient(); - http.DefaultRequestHeaders.Add("User-Agent", "TheOtherRoles Updater"); - var response = await http.GetAsync(new System.Uri("https://api.github.com/repos/Eisbison/TheOtherRoles/releases/latest"), HttpCompletionOption.ResponseContentRead); - // var response = await http.GetAsync(new System.Uri("https://api.github.com/repos/EoF-1141/TheOtherRoles/releases/latest"), HttpCompletionOption.ResponseContentRead); - if (response.StatusCode != HttpStatusCode.OK || response.Content == null) { - System.Console.WriteLine("Server returned no data: " + response.StatusCode.ToString()); - return false; - } - string json = await response.Content.ReadAsStringAsync(); - JObject data = JObject.Parse(json); - - string tagname = data["tag_name"]?.ToString(); - if (tagname == null) { - return false; // Something went wrong - } + [HideFromIl2Cpp] + public async Task GetGithubUpdate(string owner, string repo) + { + var client = new HttpClient(); + client.DefaultRequestHeaders.Add("User-Agent", "TheOtherRoles Updater"); - string changeLog = data["body"]?.ToString(); - if (changeLog != null) announcement = changeLog; - // check version - System.Version ver = System.Version.Parse(tagname.Replace("v", "")); - int diff = TheOtherRolesPlugin.Version.CompareTo(ver); - if (diff < 0) { // Update required - hasUpdate = true; - announcement = $@"A new THE OTHER ROLES -update to v{ver} is available - -{announcement}"; - - JToken assets = data["assets"]; - if (!assets.HasValues) - return false; - - for (JToken current = assets.First; current != null; current = current.Next) { - string browser_download_url = current["browser_download_url"]?.ToString(); - if (browser_download_url != null && current["content_type"] != null) { - if (current["content_type"].ToString().Equals("application/x-msdownload") && - browser_download_url.EndsWith(".dll")) { - updateURI = browser_download_url; - return true; - } - } - } - } else { - announcement = $@"THE OTHER ROLES Version {ver}: + var req = await client.GetAsync($"https://api.github.com/repos/{owner}/{repo}/releases/latest", HttpCompletionOption.ResponseContentRead); + if (!req.IsSuccessStatusCode) return null; -{announcement}"; - } - } catch (System.Exception ex) { - TheOtherRolesPlugin.Instance.Log.LogError(ex.ToString()); - System.Console.WriteLine(ex); - } - return false; + var dataString = await req.Content.ReadAsStringAsync(); + JObject data = JObject.Parse(dataString); + return new UpdateData(data); } - public static async Task downloadUpdate() { - try { - HttpClient http = new HttpClient(); - http.DefaultRequestHeaders.Add("User-Agent", "TheOtherRoles Updater"); - var response = await http.GetAsync(new System.Uri(updateURI), HttpCompletionOption.ResponseContentRead); - if (response.StatusCode != HttpStatusCode.OK || response.Content == null) { - System.Console.WriteLine("Server returned no data: " + response.StatusCode.ToString()); - return false; - } - string codeBase = Assembly.GetExecutingAssembly().CodeBase; - System.UriBuilder uri = new System.UriBuilder(codeBase); - string fullname = System.Uri.UnescapeDataString(uri.Path); - if (File.Exists(fullname + ".old")) // Clear old file in case it wasnt; - File.Delete(fullname + ".old"); - - File.Move(fullname, fullname + ".old"); // rename current executable to old - - using (var responseStream = await response.Content.ReadAsStreamAsync()) { - using (var fileStream = File.Create(fullname)) { // probably want to have proper name here - responseStream.CopyTo(fileStream); + [HideFromIl2Cpp] + public async Task DownloadUpdate() + { + var isSubmerged = TORUpdate is null; + var data = isSubmerged ? SubmergedUpdate : TORUpdate; + + var client = new HttpClient(); + client.DefaultRequestHeaders.Add("User-Agent", "TheOtherRoles Updater"); + + JToken assets = data.Request["assets"]; + string downloadURI = ""; + for (JToken current = assets.First; current != null; current = current.Next) + { + string browser_download_url = current["browser_download_url"]?.ToString(); + if (browser_download_url != null && current["content_type"] != null) { + if (current["content_type"].ToString().Equals("application/x-msdownload") && + browser_download_url.EndsWith(".dll")) { + downloadURI = browser_download_url; + break; } } - showPopup("The Other Roles\nupdated successfully\nPlease restart the game."); - return true; - } catch (System.Exception ex) { - TheOtherRolesPlugin.Instance.Log.LogError(ex.ToString()); - System.Console.WriteLine(ex); } - showPopup("Update wasn't successful\nTry again later,\nor update manually."); - return false; - } - private static void showPopup(string message) { - setPopupText(message); - InfoPopup.gameObject.SetActive(true); - } - public static void setPopupText(string message) { - if (InfoPopup == null) - return; - if (InfoPopup.TextAreaTMP != null) { - InfoPopup.TextAreaTMP.text = message; - } + if (downloadURI.Length == 0) return false; + + var res = await client.GetAsync(downloadURI, HttpCompletionOption.ResponseContentRead); + string codeBase = (isSubmerged ? SubmergedCompatibility.Assembly : Assembly.GetExecutingAssembly()).CodeBase; + UriBuilder uri = new UriBuilder(codeBase); + string fullname = Uri.UnescapeDataString(uri.Path); + if (File.Exists(fullname + ".old")) File.Delete(fullname + ".old"); + File.Move(fullname, fullname + ".old"); + + await using var responseStream = await res.Content.ReadAsStreamAsync(); + await using var fileStream = File.Create(fullname); + await responseStream.CopyToAsync(fileStream); + + return true; } } } \ No newline at end of file diff --git a/TheOtherRoles/Objects/Arrow.cs b/TheOtherRoles/Objects/Arrow.cs index 3a70fed..5324489 100644 --- a/TheOtherRoles/Objects/Arrow.cs +++ b/TheOtherRoles/Objects/Arrow.cs @@ -9,6 +9,7 @@ namespace TheOtherRoles.Objects { public SpriteRenderer image; public GameObject arrow; private Vector3 oldTarget; + private ArrowBehaviour arrowBehaviour; private static Sprite sprite; public static Sprite getSprite() { @@ -24,6 +25,8 @@ namespace TheOtherRoles.Objects { image = arrow.AddComponent(); image.sprite = getSprite(); image.color = color; + arrowBehaviour = arrow.AddComponent(); + arrowBehaviour.image = image; } public void Update() { @@ -39,41 +42,8 @@ namespace TheOtherRoles.Objects { if (color.HasValue) image.color = color.Value; - Camera main = Camera.main; - Vector2 vector = target - main.transform.position; - float num = vector.magnitude / (main.orthographicSize * perc); - image.enabled = ((double)num > 0.3); - Vector2 vector2 = main.WorldToViewportPoint(target); - if (Between(vector2.x, 0f, 1f) && Between(vector2.y, 0f, 1f)) - { - arrow.transform.position = target - (Vector3)vector.normalized * 0.6f; - float num2 = Mathf.Clamp(num, 0f, 1f); - arrow.transform.localScale = new Vector3(num2, num2, num2); - } - else - { - Vector2 vector3 = new Vector2(Mathf.Clamp(vector2.x * 2f - 1f, -1f, 1f), Mathf.Clamp(vector2.y * 2f - 1f, -1f, 1f)); - float orthographicSize = main.orthographicSize; - float num3 = main.orthographicSize * main.aspect; - Vector3 vector4 = new Vector3(Mathf.LerpUnclamped(0f, num3 * 0.88f, vector3.x), Mathf.LerpUnclamped(0f, orthographicSize * 0.79f, vector3.y), 0f); - arrow.transform.position = main.transform.position + vector4; - arrow.transform.localScale = Vector3.one; - } - - LookAt2d(arrow.transform, target); - } - - private void LookAt2d(Transform transform, Vector3 target) { - Vector3 vector = target - transform.position; - vector.Normalize(); - float num = Mathf.Atan2(vector.y, vector.x); - if (transform.lossyScale.x < 0f) - num += 3.1415927f; - transform.rotation = Quaternion.Euler(0f, 0f, num * 57.29578f); - } - - private bool Between(float value, float min, float max) { - return value > min && value < max; + arrowBehaviour.target = target; + arrowBehaviour.Update(); } } } \ No newline at end of file diff --git a/TheOtherRoles/Objects/Bloodytrail.cs b/TheOtherRoles/Objects/Bloodytrail.cs index 683200c..3fc312f 100644 --- a/TheOtherRoles/Objects/Bloodytrail.cs +++ b/TheOtherRoles/Objects/Bloodytrail.cs @@ -26,8 +26,9 @@ namespace TheOtherRoles.Objects { var index = rnd.Next(0, sp.Count); - blood = new GameObject("Blood" + index); - Vector3 position = new Vector3(player.transform.position.x, player.transform.position.y, player.transform.position.z + 1f); + blood = new GameObject("Blood" + index) { layer = 11 }; + Vector3 position = new Vector3(player.transform.position.x, player.transform.position.y, player.transform.position.y / 1000 + +0.001f); + blood.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover); blood.transform.position = position; blood.transform.localPosition = position; blood.transform.SetParent(player.transform.parent); diff --git a/TheOtherRoles/Objects/Footprint.cs b/TheOtherRoles/Objects/Footprint.cs index c9cfa14..7eb18e5 100644 --- a/TheOtherRoles/Objects/Footprint.cs +++ b/TheOtherRoles/Objects/Footprint.cs @@ -28,8 +28,9 @@ namespace TheOtherRoles.Objects { else this.color = Palette.PlayerColors[(int) player.Data.DefaultOutfit.ColorId]; - footprint = new GameObject("Footprint"); - Vector3 position = new Vector3(player.transform.position.x, player.transform.position.y, player.transform.position.z + 1f); + footprint = new GameObject("Footprint") { layer = 11 }; + footprint.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover); + Vector3 position = new Vector3(player.transform.position.x, player.transform.position.y, player.transform.position.y / 1000 + 0.001f); footprint.transform.position = position; footprint.transform.localPosition = position; footprint.transform.SetParent(player.transform.parent); diff --git a/TheOtherRoles/Objects/Garlic.cs b/TheOtherRoles/Objects/Garlic.cs index 5041d7f..0812fe8 100644 --- a/TheOtherRoles/Objects/Garlic.cs +++ b/TheOtherRoles/Objects/Garlic.cs @@ -25,13 +25,13 @@ namespace TheOtherRoles.Objects { } public Garlic(Vector2 p) { - garlic = new GameObject("Garlic"); - background = new GameObject("Background"); + garlic = new GameObject("Garlic"){layer = 11}; + garlic.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover); + background = new GameObject("Background"){layer = 11}; background.transform.SetParent(garlic.transform); - Vector3 position = new Vector3(p.x, p.y, PlayerControl.LocalPlayer.transform.localPosition.z + 0.001f); // just behind player + Vector3 position = new Vector3(p.x, p.y, p.y / 1000 + 0.001f); // just behind player garlic.transform.position = position; - garlic.transform.localPosition = position; - background.transform.localPosition = new Vector3(0 , 0, -0.01f); // before player + background.transform.localPosition = new Vector3(0 , 0, -1f); // before player var garlicRenderer = garlic.AddComponent(); garlicRenderer.sprite = getGarlicSprite(); diff --git a/TheOtherRoles/Objects/JackInTheBox.cs b/TheOtherRoles/Objects/JackInTheBox.cs index 8e2bd73..3195ff3 100644 --- a/TheOtherRoles/Objects/JackInTheBox.cs +++ b/TheOtherRoles/Objects/JackInTheBox.cs @@ -37,8 +37,9 @@ namespace TheOtherRoles.Objects { private SpriteRenderer boxRenderer; public JackInTheBox(Vector2 p) { - gameObject = new GameObject("JackInTheBox"); - Vector3 position = new Vector3(p.x, p.y, PlayerControl.LocalPlayer.transform.position.z + 1f); + gameObject = new GameObject("JackInTheBox"){layer = 11}; + gameObject.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover); + Vector3 position = new Vector3(p.x, p.y, p.y/1000f + 0.01f); position += (Vector3)PlayerControl.LocalPlayer.Collider.offset; // Add collider offset that DoMove moves the player up at a valid position // Create the marker gameObject.transform.position = position; @@ -48,6 +49,7 @@ namespace TheOtherRoles.Objects { // Create the vent var referenceVent = UnityEngine.Object.FindObjectOfType(); vent = UnityEngine.Object.Instantiate(referenceVent); + vent.gameObject.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover); vent.transform.position = gameObject.transform.position; vent.Left = null; vent.Right = null; diff --git a/TheOtherRoles/Objects/NinjaTrace.cs b/TheOtherRoles/Objects/NinjaTrace.cs index c9be1e2..87e3efe 100644 --- a/TheOtherRoles/Objects/NinjaTrace.cs +++ b/TheOtherRoles/Objects/NinjaTrace.cs @@ -18,8 +18,10 @@ namespace TheOtherRoles.Objects { } public NinjaTrace(Vector2 p, float duration=1f) { - trace = new GameObject("NinjaTrace"); - Vector3 position = new Vector3(p.x, p.y, PlayerControl.LocalPlayer.transform.localPosition.z + 0.001f); // just behind player + trace = new GameObject("NinjaTrace") { layer = 11 }; + trace.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover); + //Vector3 position = new Vector3(p.x, p.y, PlayerControl.LocalPlayer.transform.localPosition.z + 0.001f); // just behind player + Vector3 position = new Vector3(p.x, p.y, p.y / 1000f + 0.01f); trace.transform.position = position; trace.transform.localPosition = position; diff --git a/TheOtherRoles/Objects/Portal.cs b/TheOtherRoles/Objects/Portal.cs index de6a94e..513ecb9 100644 --- a/TheOtherRoles/Objects/Portal.cs +++ b/TheOtherRoles/Objects/Portal.cs @@ -9,7 +9,6 @@ namespace TheOtherRoles.Objects { public static Portal firstPortal = null; public static Portal secondPortal = null; public static bool bothPlacedAndEnabled = false; - // public static Sprite[] portalBgAnimationSprites = new Sprite[109]; public static Sprite[] portalFgAnimationSprites = new Sprite[205]; public static Sprite portalSprite; public static bool isTeleporting = false; @@ -28,14 +27,6 @@ namespace TheOtherRoles.Objects { public static List teleportedPlayers; - /*public static Sprite getBgAnimationSprite(int index) { - if (portalBgAnimationSprites == null || portalBgAnimationSprites.Length == 0) return null; - index = Mathf.Clamp(index, 0, portalBgAnimationSprites.Length - 1); - if (portalBgAnimationSprites[index] == null) - portalBgAnimationSprites[index] = (Helpers.loadSpriteFromResources($"TheOtherRoles.Resources.PortalAnimation.plattform.png", 115f)); - return portalBgAnimationSprites[index]; - }*/ - public static Sprite getFgAnimationSprite(int index) { if (portalFgAnimationSprites == null || portalFgAnimationSprites.Length == 0) return null; index = Mathf.Clamp(index, 0, portalFgAnimationSprites.Length - 1); @@ -70,13 +61,9 @@ namespace TheOtherRoles.Objects { secondPortal.animationFgRenderer.sprite = getFgAnimationSprite((int)(p * portalFgAnimationSprites.Length)); PlayerControl.SetPlayerMaterialColors(colorId, firstPortal.animationFgRenderer); PlayerControl.SetPlayerMaterialColors(colorId, secondPortal.animationFgRenderer); - /*firstPortal.animationBgRenderer.sprite = getBgAnimationSprite((int)(p * portalFgAnimationSprites.Length)); - secondPortal.animationBgRenderer.sprite = getBgAnimationSprite((int)(p * portalFgAnimationSprites.Length));*/ if (p == 1f) { firstPortal.animationFgRenderer.sprite = null; secondPortal.animationFgRenderer.sprite = null; - firstPortal.animationBgRenderer.sprite = null; - secondPortal.animationBgRenderer.sprite = null; isTeleporting = false; } } @@ -84,30 +71,26 @@ namespace TheOtherRoles.Objects { } public GameObject portalFgAnimationGameObject; - public GameObject portalBgAnimationGameObject; public GameObject portalGameObject; private SpriteRenderer animationFgRenderer; - private SpriteRenderer animationBgRenderer; private SpriteRenderer portalRenderer; public Portal(Vector2 p) { - portalGameObject = new GameObject("Portal"); - Vector3 position = new Vector3(p.x, p.y, PlayerControl.LocalPlayer.transform.position.z + 1f); - + portalGameObject = new GameObject("Portal"){ layer = 11 }; + //Vector3 position = new Vector3(p.x, p.y, PlayerControl.LocalPlayer.transform.position.z + 1f); + Vector3 position = new Vector3(p.x, p.y, p.y / 1000f + 0.01f); // Create the portal portalGameObject.transform.position = position; + portalGameObject.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover); portalRenderer = portalGameObject.AddComponent(); - animationBgRenderer = portalGameObject.AddComponent(); portalRenderer.sprite = portalSprite; - portalBgAnimationGameObject = new GameObject("PortalAnimationBG"); - portalBgAnimationGameObject.transform.position = position; - animationBgRenderer = portalBgAnimationGameObject.AddComponent(); - - Vector3 fgPosition = new Vector3(p.x, p.y, PlayerControl.LocalPlayer.transform.position.z - 0.1f); - portalFgAnimationGameObject = new GameObject("PortalAnimationFG"); - portalFgAnimationGameObject.transform.position = fgPosition; + Vector3 fgPosition = new Vector3(0, 0, -1f); + portalFgAnimationGameObject = new GameObject("PortalAnimationFG") { layer = 11 }; + portalFgAnimationGameObject.transform.SetParent(portalGameObject.transform); + portalFgAnimationGameObject.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover); + portalFgAnimationGameObject.transform.localPosition = fgPosition; animationFgRenderer = portalFgAnimationGameObject.AddComponent(); animationFgRenderer.material = DestroyableSingleton.Instance.PlayerMaterial; @@ -115,7 +98,6 @@ namespace TheOtherRoles.Objects { bool playerIsPortalmaker = PlayerControl.LocalPlayer == TheOtherRoles.Portalmaker.portalmaker; portalGameObject.SetActive(playerIsPortalmaker); portalFgAnimationGameObject.SetActive(true); - portalBgAnimationGameObject.SetActive(true); if (firstPortal == null) firstPortal = this; else if (secondPortal == null) { @@ -159,7 +141,6 @@ namespace TheOtherRoles.Objects { private static void preloadSprites() { for (int i = 0; i < portalFgAnimationSprites.Length; i++) { - /*getBgAnimationSprite(i);*/ getFgAnimationSprite(i); } portalSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.PortalAnimation.plattform.png", 115f); diff --git a/TheOtherRoles/Patches/CredentialsPatch.cs b/TheOtherRoles/Patches/CredentialsPatch.cs index e3dec1c..dde3159 100644 --- a/TheOtherRoles/Patches/CredentialsPatch.cs +++ b/TheOtherRoles/Patches/CredentialsPatch.cs @@ -21,7 +21,7 @@ Design by Bavari"; public static string contributorsCredentials = $@" Special thanks to K3ndo & Smeggy -GitHub Contributors: Gendelo3, Alex2911, amsyarasyiq, MaximeGillot, Psynomit"; +GitHub Contributors: Gendelo, Alex2911, amsyarasyiq, MaximeGillot, Psynomit"; [HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))] private static class VersionShowerPatch diff --git a/TheOtherRoles/Patches/EndGamePatch.cs b/TheOtherRoles/Patches/EndGamePatch.cs index 4080a8f..7045c49 100644 --- a/TheOtherRoles/Patches/EndGamePatch.cs +++ b/TheOtherRoles/Patches/EndGamePatch.cs @@ -434,7 +434,7 @@ namespace TheOtherRoles.Patches { } private static bool CheckAndEndGameForTaskWin(ShipStatus __instance) { - if (GameData.Instance.TotalTasks <= GameData.Instance.CompletedTasks) { + if (GameData.Instance.TotalTasks > 0 && GameData.Instance.TotalTasks <= GameData.Instance.CompletedTasks) { __instance.enabled = false; ShipStatus.RpcEndGame(GameOverReason.HumansByTask, false); return true; @@ -522,9 +522,8 @@ namespace TheOtherRoles.Patches { bool impLover = false; bool jackalLover = false; - for (int i = 0; i < GameData.Instance.PlayerCount; i++) + foreach (var playerInfo in GameData.Instance.AllPlayers) { - GameData.PlayerInfo playerInfo = GameData.Instance.AllPlayers[i]; if (!playerInfo.Disconnected) { if (!playerInfo.IsDead) diff --git a/TheOtherRoles/Patches/ExileControllerPatch.cs b/TheOtherRoles/Patches/ExileControllerPatch.cs index 3672e9c..9ecc9fe 100644 --- a/TheOtherRoles/Patches/ExileControllerPatch.cs +++ b/TheOtherRoles/Patches/ExileControllerPatch.cs @@ -14,8 +14,12 @@ using System.Reflection; namespace TheOtherRoles.Patches { [HarmonyPatch(typeof(ExileController), nameof(ExileController.Begin))] + [HarmonyPriority(Priority.First)] class ExileControllerBeginPatch { + public static GameData.PlayerInfo lastExiled; public static void Prefix(ExileController __instance, [HarmonyArgument(0)]ref GameData.PlayerInfo exiled, [HarmonyArgument(1)]bool tie) { + lastExiled = exiled; + // Medic shield if (Medic.medic != null && AmongUsClient.Instance.AmHost && Medic.futureShielded != null && !Medic.medic.Data.IsDead) { // We need to send the RPC from the host here, to make sure that the order of shifting and setting the shield is correct(for that reason the futureShifted and futureShielded are being synced) MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.MedicSetShielded, Hazel.SendOption.Reliable, -1); @@ -88,6 +92,8 @@ namespace TheOtherRoles.Patches { animator?.Stop(); vent.EnterVentAnim = vent.ExitVentAnim = null; vent.myRend.sprite = animator == null ? SecurityGuard.getStaticVentSealedSprite() : SecurityGuard.getAnimatedVentSealedSprite(); + if (SubmergedCompatibility.isSubmerged() && vent.Id == 0) vent.myRend.sprite = SecurityGuard.getSubmergedCentralUpperSealedSprite(); + if (SubmergedCompatibility.isSubmerged() && vent.Id == 14) vent.myRend.sprite = SecurityGuard.getSubmergedCentralLowerSealedSprite(); vent.myRend.color = Color.white; vent.name = "SealedVent_" + vent.name; } @@ -112,6 +118,15 @@ namespace TheOtherRoles.Patches { } } + // Workaround to add a "postfix" to the destroying of the exile controller (i.e. cutscene) of submerged + [HarmonyPatch(typeof(UnityEngine.Object), nameof(UnityEngine.Object.Destroy), new Type[] { typeof(GameObject) })] + public static void Prefix(GameObject obj) { + if (!SubmergedCompatibility.isSubmerged()) return; + if (obj.name.Contains("ExileCutscene")) { + WrapUpPostfix(ExileControllerBeginPatch.lastExiled); + } + } + static void WrapUpPostfix(GameData.PlayerInfo exiled) { // Mini exile lose condition if (exiled != null && Mini.mini != null && Mini.mini.PlayerId == exiled.PlayerId && !Mini.isGrownUp() && !Mini.mini.Data.Role.IsImpostor && !RoleInfo.getRoleInfoForPlayer(Mini.mini).Any(x => x.isNeutral)) { @@ -135,9 +150,11 @@ namespace TheOtherRoles.Patches { if (Seer.deadBodyPositions != null && Seer.seer != null && PlayerControl.LocalPlayer == Seer.seer && (Seer.mode == 0 || Seer.mode == 2)) { foreach (Vector3 pos in Seer.deadBodyPositions) { GameObject soul = new GameObject(); - soul.transform.position = pos; + //soul.transform.position = pos; + soul.transform.position = new Vector3(pos.x, pos.y, pos.y / 1000 - 1f); soul.layer = 5; var rend = soul.AddComponent(); + soul.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover); rend.sprite = Seer.getSoulSprite(); if(Seer.limitSoulDuration) { @@ -193,9 +210,11 @@ namespace TheOtherRoles.Patches { if (Medium.featureDeadBodies != null) { foreach ((DeadPlayer db, Vector3 ps) in Medium.featureDeadBodies) { GameObject s = new GameObject(); - s.transform.position = ps; + //s.transform.position = ps; + s.transform.position = new Vector3(ps.x, ps.y, ps.y / 1000 - 1f); s.layer = 5; var rend = s.AddComponent(); + s.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover); rend.sprite = Medium.getSoulSprite(); Medium.souls.Add(rend); } @@ -203,13 +222,13 @@ namespace TheOtherRoles.Patches { Medium.featureDeadBodies = new List>(); } } - // Lawyer add meeting - if (Lawyer.lawyer != null && PlayerControl.LocalPlayer == Lawyer.lawyer && !Lawyer.lawyer.Data.IsDead) - Lawyer.meetings++; // AntiTeleport set position if (AntiTeleport.antiTeleport.FindAll(x => x.PlayerId == PlayerControl.LocalPlayer.PlayerId).Count > 0) { PlayerControl.LocalPlayer.transform.position = AntiTeleport.position; + if (SubmergedCompatibility.isSubmerged()) { + SubmergedCompatibility.ChangeFloor(AntiTeleport.position.y > -7); + } } // Invert add meeting diff --git a/TheOtherRoles/Patches/GameStartManagerPatch.cs b/TheOtherRoles/Patches/GameStartManagerPatch.cs index 7e32327..abcbe32 100644 --- a/TheOtherRoles/Patches/GameStartManagerPatch.cs +++ b/TheOtherRoles/Patches/GameStartManagerPatch.cs @@ -174,6 +174,8 @@ namespace TheOtherRoles.Patches { possibleMaps.Add(2); if (CustomOptionHolder.dynamicMapEnableAirShip.getBool()) possibleMaps.Add(4); + if (CustomOptionHolder.dynamicMapEnableSubmerged.getBool()) + possibleMaps.Add(5); byte chosenMapId = possibleMaps[TheOtherRoles.rnd.Next(possibleMaps.Count)]; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.DynamicMapOption, Hazel.SendOption.Reliable, -1); diff --git a/TheOtherRoles/Patches/MainMenuPatch.cs b/TheOtherRoles/Patches/MainMenuPatch.cs index f98200d..83c0fa0 100644 --- a/TheOtherRoles/Patches/MainMenuPatch.cs +++ b/TheOtherRoles/Patches/MainMenuPatch.cs @@ -29,13 +29,11 @@ namespace TheOtherRoles.Modules { SpriteRenderer buttonSpriteDiscord = buttonDiscord.GetComponent(); passiveButtonDiscord.OnClick = new Button.ButtonClickedEvent(); - passiveButtonDiscord.OnClick.AddListener((UnityEngine.Events.UnityAction)delegate { - Application.OpenURL("https://discord.gg/77RkMJHWsM"); - }); + passiveButtonDiscord.OnClick.AddListener((System.Action)(() => Application.OpenURL("https://discord.gg/77RkMJHWsM"))); Color discordColor = new Color32(88, 101, 242, byte.MaxValue); buttonSpriteDiscord.color = textDiscord.color = discordColor; - passiveButtonDiscord.OnMouseOut.AddListener((UnityEngine.Events.UnityAction)delegate { + passiveButtonDiscord.OnMouseOut.AddListener((System.Action)delegate { buttonSpriteDiscord.color = textDiscord.color = discordColor; }); @@ -56,7 +54,7 @@ namespace TheOtherRoles.Modules { passiveHorseButton.OnClick = new ButtonClickedEvent(); - passiveHorseButton.OnClick.AddListener((UnityEngine.Events.UnityAction)delegate { + passiveHorseButton.OnClick.AddListener((System.Action)delegate { horseButtonState = horseModeSelectionBehavior.OnClick(); if (horseButtonState) { if (horseModeOnSprite == null) horseModeOnSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.HorseModeButtonOn.png", 75f); diff --git a/TheOtherRoles/Patches/MeetingPatch.cs b/TheOtherRoles/Patches/MeetingPatch.cs index 0687b0b..e08a052 100644 --- a/TheOtherRoles/Patches/MeetingPatch.cs +++ b/TheOtherRoles/Patches/MeetingPatch.cs @@ -298,7 +298,7 @@ namespace TheOtherRoles.Patches { exitButtonParent.transform.localPosition = new Vector3(2.725f, 2.1f, -5); exitButtonParent.transform.localScale = new Vector3(0.217f, 0.9f, 1); exitButton.GetComponent().OnClick.RemoveAllListeners(); - exitButton.GetComponent().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { + exitButton.GetComponent().OnClick.AddListener((System.Action)(() => { __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true)); UnityEngine.Object.Destroy(container.gameObject); })); @@ -333,7 +333,7 @@ namespace TheOtherRoles.Patches { int copiedIndex = i; button.GetComponent().OnClick.RemoveAllListeners(); - if (!PlayerControl.LocalPlayer.Data.IsDead) button.GetComponent().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { + if (!PlayerControl.LocalPlayer.Data.IsDead) button.GetComponent().OnClick.AddListener((System.Action)(() => { if (selectedButton != button) { selectedButton = button; buttons.ForEach(x => x.GetComponent().color = x == selectedButton ? Color.red : Color.white); @@ -413,7 +413,7 @@ namespace TheOtherRoles.Patches { swapperButtonList[i] = button; button.OnClick.RemoveAllListeners(); int copiedIndex = i; - button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => swapperOnClick(copiedIndex, __instance))); + button.OnClick.AddListener((System.Action)(() => swapperOnClick(copiedIndex, __instance))); selections[i] = false; renderers[i] = renderer; @@ -492,7 +492,7 @@ namespace TheOtherRoles.Patches { PassiveButton button = targetBox.GetComponent(); button.OnClick.RemoveAllListeners(); int copiedIndex = i; - button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => guesserOnClick(copiedIndex, __instance))); + button.OnClick.AddListener((System.Action)(() => guesserOnClick(copiedIndex, __instance))); } } } diff --git a/TheOtherRoles/Patches/PlayerControlPatch.cs b/TheOtherRoles/Patches/PlayerControlPatch.cs index 6b66b50..dac61b6 100644 --- a/TheOtherRoles/Patches/PlayerControlPatch.cs +++ b/TheOtherRoles/Patches/PlayerControlPatch.cs @@ -23,9 +23,8 @@ namespace TheOtherRoles.Patches { if (targetingPlayer.Data.IsDead) return result; Vector2 truePosition = targetingPlayer.GetTruePosition(); - Il2CppSystem.Collections.Generic.List allPlayers = GameData.Instance.AllPlayers; - for (int i = 0; i < allPlayers.Count; i++) { - GameData.PlayerInfo playerInfo = allPlayers[i]; + foreach (var playerInfo in GameData.Instance.AllPlayers) + { if (!playerInfo.Disconnected && playerInfo.PlayerId != targetingPlayer.PlayerId && !playerInfo.IsDead && (!onlyCrewmates || !playerInfo.Role.IsImpostor)) { PlayerControl @object = playerInfo.Object; if (untargetablePlayers != null && untargetablePlayers.Any(x => x == @object)) { @@ -109,6 +108,9 @@ namespace TheOtherRoles.Patches { else if (localPlayerPositions.Any(x => x.Item2 == true)) { PlayerControl.LocalPlayer.transform.position = next.Item1; } + if (SubmergedCompatibility.isSubmerged()) { + SubmergedCompatibility.ChangeFloor(next.Item1.y > -7); + } localPlayerPositions.RemoveAt(0); @@ -517,6 +519,7 @@ namespace TheOtherRoles.Patches { for (int i = 0; i < ShipStatus.Instance.AllVents.Length; i++) { Vent vent = ShipStatus.Instance.AllVents[i]; if (vent.gameObject.name.StartsWith("JackInTheBoxVent_") || vent.gameObject.name.StartsWith("SealedVent_") || vent.gameObject.name.StartsWith("FutureSealedVent_")) continue; + if (SubmergedCompatibility.isSubmerged() && vent.Id == 9) continue; // cannot seal submergeds exit only vent! float distance = Vector2.Distance(vent.transform.position, truePosition); if (distance <= vent.UsableDistance && distance < closestDistance) { closestDistance = distance; @@ -706,15 +709,6 @@ namespace TheOtherRoles.Patches { public static void lawyerUpdate() { if (Lawyer.lawyer == null || Lawyer.lawyer != PlayerControl.LocalPlayer) return; - // Meeting win - if (Lawyer.winsAfterMeetings && Lawyer.neededMeetings == Lawyer.meetings && Lawyer.target != null && !Lawyer.target.Data.IsDead) { - Lawyer.winsAfterMeetings = false; // Avoid sending mutliple RPCs until the host finshes the game - MessageWriter winWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.LawyerWin, Hazel.SendOption.Reliable, -1); - AmongUsClient.Instance.FinishRpcImmediately(winWriter); - RPCProcedure.lawyerWin(); - return; - } - // Promote to Pursuer if (Lawyer.target != null && Lawyer.target.Data.Disconnected) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.LawyerPromotesToPursuer, Hazel.SendOption.Reliable, -1); @@ -1188,12 +1182,18 @@ namespace TheOtherRoles.Patches { RPCProcedure.sidekickPromotes(); } - // Pursuer promotion trigger on exile (the host sends the call such that everyone recieves the update before a possible game End) + // Pursuer promotion trigger on exile & suicide (the host sends the call such that everyone recieves the update before a possible game End) if (__instance == Lawyer.target && Lawyer.target != Jester.jester && AmongUsClient.Instance.AmHost) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.LawyerPromotesToPursuer, Hazel.SendOption.Reliable, -1); AmongUsClient.Instance.FinishRpcImmediately(writer); RPCProcedure.lawyerPromotesToPursuer(); } + if (__instance == Lawyer.target) + { + if (Lawyer.lawyer != null) Lawyer.lawyer.Exiled(); + if (Pursuer.pursuer != null) Pursuer.pursuer.Exiled(); + } + } } diff --git a/TheOtherRoles/Patches/ShipStatusPatch.cs b/TheOtherRoles/Patches/ShipStatusPatch.cs index 8c3cae4..a5cdbb3 100644 --- a/TheOtherRoles/Patches/ShipStatusPatch.cs +++ b/TheOtherRoles/Patches/ShipStatusPatch.cs @@ -1,50 +1,79 @@ +using System; +using System.Linq; using HarmonyLib; +using UnhollowerRuntimeLib; using static TheOtherRoles.TheOtherRoles; using UnityEngine; namespace TheOtherRoles.Patches { [HarmonyPatch(typeof(ShipStatus))] - public class ShipStatusPatch { - - [HarmonyPostfix] + public class ShipStatusPatch + { + [HarmonyPrefix] [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.CalculateLightRadius))] public static bool Prefix(ref float __result, ShipStatus __instance, [HarmonyArgument(0)] GameData.PlayerInfo player) { - ISystemType systemType = __instance.Systems.ContainsKey(SystemTypes.Electrical) ? __instance.Systems[SystemTypes.Electrical] : null; - if (systemType == null) return true; - SwitchSystem switchSystem = systemType.TryCast(); - if (switchSystem == null) return true; + if (!__instance.Systems.ContainsKey(SystemTypes.Electrical)) return true; - float num = (float)switchSystem.Value / 255f; - - if (player == null || player.IsDead) {// IsDead - __result = __instance.MaxLightRadius; - return false; - } - else if (player.Role.IsImpostor + // If player is a role which has Impostor vision + if (player.Role.IsImpostor || (Jackal.jackal != null && Jackal.jackal.PlayerId == player.PlayerId && Jackal.hasImpostorVision) || (Sidekick.sidekick != null && Sidekick.sidekick.PlayerId == player.PlayerId && Sidekick.hasImpostorVision) || (Spy.spy != null && Spy.spy.PlayerId == player.PlayerId && Spy.hasImpostorVision) - || (Jester.jester != null && Jester.jester.PlayerId == player.PlayerId && Jester.hasImpostorVision)) // Impostor, Jackal/Sidekick, Spy or Jester with Impostor vision - __result = __instance.MaxLightRadius * PlayerControl.GameOptions.ImpostorLightMod; - else if (Lighter.lighter != null && Lighter.lighter.PlayerId == player.PlayerId && Lighter.lighterTimer > 0f) // if player is Lighter and Lighter has his ability active - __result = Mathf.Lerp(__instance.MaxLightRadius * Lighter.lighterModeLightsOffVision, __instance.MaxLightRadius * Lighter.lighterModeLightsOnVision, num); + || (Jester.jester != null && Jester.jester.PlayerId == player.PlayerId && Jester.hasImpostorVision)) { + //__result = __instance.MaxLightRadius * PlayerControl.GameOptions.ImpostorLightMod; + __result = GetNeutralLightRadius(__instance, true); + return false; + } + + // If player is Lighter with ability active + if (Lighter.lighter != null && Lighter.lighter.PlayerId == player.PlayerId && Lighter.lighterTimer > 0f) { + float unlerped = Mathf.InverseLerp(__instance.MinLightRadius, __instance.MaxLightRadius, GetNeutralLightRadius(__instance, true)); + __result = Mathf.Lerp(__instance.MaxLightRadius * Lighter.lighterModeLightsOffVision, __instance.MaxLightRadius * Lighter.lighterModeLightsOnVision, unlerped); + } + + // If there is a Trickster with their ability active else if (Trickster.trickster != null && Trickster.lightsOutTimer > 0f) { float lerpValue = 1f; - if (Trickster.lightsOutDuration - Trickster.lightsOutTimer < 0.5f) lerpValue = Mathf.Clamp01((Trickster.lightsOutDuration - Trickster.lightsOutTimer) * 2); - else if (Trickster.lightsOutTimer < 0.5) lerpValue = Mathf.Clamp01(Trickster.lightsOutTimer * 2); - __result = Mathf.Lerp(__instance.MinLightRadius, __instance.MaxLightRadius, 1 - lerpValue) * PlayerControl.GameOptions.CrewLightMod; // Instant lights out? Maybe add a smooth transition? + if (Trickster.lightsOutDuration - Trickster.lightsOutTimer < 0.5f) { + lerpValue = Mathf.Clamp01((Trickster.lightsOutDuration - Trickster.lightsOutTimer) * 2); + } else if (Trickster.lightsOutTimer < 0.5) { + lerpValue = Mathf.Clamp01(Trickster.lightsOutTimer * 2); + } + + __result = Mathf.Lerp(__instance.MinLightRadius, __instance.MaxLightRadius, 1 - lerpValue) * PlayerControl.GameOptions.CrewLightMod; + } + + // If player is Lawyer, apply Lawyer vision modifier + else if (Lawyer.lawyer != null && Lawyer.lawyer.PlayerId == player.PlayerId) { + float unlerped = Mathf.InverseLerp(__instance.MinLightRadius, __instance.MaxLightRadius, GetNeutralLightRadius(__instance, false)); + __result = Mathf.Lerp(__instance.MinLightRadius, __instance.MaxLightRadius * Lawyer.vision, unlerped); + return false; + } + + // Default light radius + else { + __result = GetNeutralLightRadius(__instance, false); } - else if (Lawyer.lawyer != null && Lawyer.lawyer.PlayerId == player.PlayerId) // Lawyer - __result = Mathf.Lerp(__instance.MinLightRadius, __instance.MaxLightRadius * Lawyer.vision, num); - else - __result = Mathf.Lerp(__instance.MinLightRadius, __instance.MaxLightRadius, num) * PlayerControl.GameOptions.CrewLightMod; if (Sunglasses.sunglasses.FindAll(x => x.PlayerId == player.PlayerId).Count > 0) // Sunglasses __result *= 1f - Sunglasses.vision * 0.1f; return false; } + public static float GetNeutralLightRadius(ShipStatus shipStatus, bool isImpostor) { + if (SubmergedCompatibility.isSubmerged()) { + return SubmergedCompatibility.GetSubmergedNeutralLightRadius(isImpostor); + } + + if (isImpostor) return shipStatus.MaxLightRadius * PlayerControl.GameOptions.ImpostorLightMod; + + SwitchSystem switchSystem = shipStatus.Systems[SystemTypes.Electrical].TryCast(); + float lerpValue = switchSystem.Value / 255f; + + return Mathf.Lerp(shipStatus.MinLightRadius, shipStatus.MaxLightRadius, lerpValue) * PlayerControl.GameOptions.CrewLightMod; + } + [HarmonyPostfix] [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.IsGameOverDueToDeath))] public static void Postfix2(ShipStatus __instance, ref bool __result) @@ -81,7 +110,5 @@ namespace TheOtherRoles.Patches { PlayerControl.GameOptions.NumShortTasks = originalNumShortTasksOption; PlayerControl.GameOptions.NumLongTasks = originalNumLongTasksOption; } - } - -} \ No newline at end of file +} diff --git a/TheOtherRoles/Patches/UsablesPatch.cs b/TheOtherRoles/Patches/UsablesPatch.cs index db1e725..2286552 100644 --- a/TheOtherRoles/Patches/UsablesPatch.cs +++ b/TheOtherRoles/Patches/UsablesPatch.cs @@ -21,6 +21,38 @@ namespace TheOtherRoles.Patches { bool roleCouldUse = @object.roleCanUseVents(); + if (__instance.name.StartsWith("SealedVent_")) { + canUse = couldUse = false; + __result = num; + return false; + } + + // Submerged Compatability if needed: + if (SubmergedCompatibility.isSubmerged()) { + // as submerged does, only change stuff for vents 9 and 14 of submerged. Code partially provided by AlexejheroYTB + if (SubmergedCompatibility.getInTransition()) { + __result = float.MaxValue; + return canUse = couldUse = false; + } + switch (__instance.Id) { + case 9: // Cannot enter vent 9 (Engine Room Exit Only Vent)! + if (PlayerControl.LocalPlayer.inVent) break; + __result = float.MaxValue; + return canUse = couldUse = false; + case 14: // Lower Central + __result = float.MaxValue; + couldUse = roleCouldUse && !pc.IsDead && (@object.CanMove || @object.inVent); + canUse = couldUse; + if (canUse) { + Vector3 center = @object.Collider.bounds.center; + Vector3 position = __instance.transform.position; + __result = Vector2.Distance(center, position); + canUse &= __result <= __instance.UsableDistance; + } + return false; + } + } + var usableDistance = __instance.UsableDistance; if (__instance.name.StartsWith("JackInTheBoxVent_")) { if(Trickster.trickster != PlayerControl.LocalPlayer) { @@ -33,10 +65,6 @@ namespace TheOtherRoles.Patches { // Reduce the usable distance to reduce the risk of gettings stuck while trying to jump into the box if it's placed near objects usableDistance = 0.4f; } - } else if (__instance.name.StartsWith("SealedVent_")) { - canUse = couldUse = false; - __result = num; - return false; } couldUse = (@object.inVent || roleCouldUse) && !pc.IsDead && (@object.CanMove || @object.inVent); @@ -192,11 +220,6 @@ namespace TheOtherRoles.Patches { roleCanCallEmergency = false; statusText = "The Jester can't start an emergency meeting"; } - // Potentially deactivate emergency button for Lawyer - if (Lawyer.lawyer != null && Lawyer.lawyer == PlayerControl.LocalPlayer && Lawyer.winsAfterMeetings) { - roleCanCallEmergency = false; - statusText = "The Lawyer can't start an emergency meeting (" + Lawyer.meetings + "/" + Lawyer.neededMeetings + " meetings)"; - } if (!roleCanCallEmergency) { __instance.StatusText.text = statusText; @@ -293,7 +316,7 @@ namespace TheOtherRoles.Patches { if (Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer && Hacker.hackerTimer > 0) { for (int k = 0; k < __instance.vitals.Length; k++) { VitalsPanel vitalsPanel = __instance.vitals[k]; - GameData.PlayerInfo player = GameData.Instance.AllPlayers[k]; + GameData.PlayerInfo player = vitalsPanel.PlayerInfo; // Hacker update if (vitalsPanel.IsDead) { @@ -316,7 +339,7 @@ namespace TheOtherRoles.Patches { [HarmonyPatch] class AdminPanelPatch { - static Dictionary> players = new Dictionary>(); + static Dictionary> players = new Dictionary>(); [HarmonyPatch(typeof(MapCountOverlay), nameof(MapCountOverlay.Update))] class MapCountOverlayUpdatePatch { @@ -417,9 +440,10 @@ namespace TheOtherRoles.Patches { bool showHackerInfo = Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer && Hacker.hackerTimer > 0; if (players.ContainsKey(__instance.RoomType)) { List colors = players[__instance.RoomType]; - - for (int i = 0; i < __instance.myIcons.Count; i++) { - PoolableBehavior icon = __instance.myIcons[i]; + int i = -1; + foreach (var icon in __instance.myIcons) + { + i += 1; SpriteRenderer renderer = icon.GetComponent(); if (renderer != null) { diff --git a/TheOtherRoles/RPC.cs b/TheOtherRoles/RPC.cs index 938c7ca..3247ca5 100644 --- a/TheOtherRoles/RPC.cs +++ b/TheOtherRoles/RPC.cs @@ -87,6 +87,7 @@ namespace TheOtherRoles // Role functionality EngineerFixLights = 91, + EngineerFixSubmergedOxygen, EngineerUsedRepair, CleanBody, MedicSetShielded, @@ -367,8 +368,8 @@ namespace TheOtherRoles public static void uncheckedCmdReportDeadBody(byte sourceId, byte targetId) { PlayerControl source = Helpers.playerById(sourceId); - PlayerControl target = Helpers.playerById(targetId); - if (source != null && target != null) source.ReportDeadBody(target.Data); + var t = targetId == Byte.MaxValue ? null : Helpers.playerById(targetId).Data; + if (source != null) source.ReportDeadBody(t); } public static void uncheckedExilePlayer(byte targetId) { @@ -387,6 +388,10 @@ namespace TheOtherRoles switchSystem.ActualSwitches = switchSystem.ExpectedSwitches; } + public static void engineerFixSubmergedOxygen() { + SubmergedCompatibility.RepairOxygen(); + } + public static void engineerUsedRepair() { Engineer.remainingFixes--; } @@ -770,6 +775,16 @@ namespace TheOtherRoles camera.Offset = new Vector3(0f, 0f, camera.Offset.z); if (PlayerControl.GameOptions.MapId == 2 || PlayerControl.GameOptions.MapId == 4) camera.transform.localRotation = new Quaternion(0, 0, 1, 1); // Polus and Airship + if (SubmergedCompatibility.isSubmerged()) { + // remove 2d box collider of console, so that no barrier can be created. (irrelevant for now, but who knows... maybe we need it later) + var fixConsole = camera.transform.FindChild("FixConsole"); + if (fixConsole != null) { + var boxCollider = fixConsole.GetComponent(); + if (boxCollider != null) UnityEngine.Object.Destroy(boxCollider); + } + } + + if (PlayerControl.LocalPlayer == SecurityGuard.securityGuard) { camera.gameObject.SetActive(true); camera.gameObject.GetComponent().color = new Color(1f, 1f, 1f, 0.5f); @@ -789,6 +804,8 @@ namespace TheOtherRoles animator?.Stop(); vent.EnterVentAnim = vent.ExitVentAnim = null; vent.myRend.sprite = animator == null ? SecurityGuard.getStaticVentSealedSprite() : SecurityGuard.getAnimatedVentSealedSprite(); + if (SubmergedCompatibility.isSubmerged() && vent.Id == 0) vent.myRend.sprite = SecurityGuard.getSubmergedCentralUpperSealedSprite(); + if (SubmergedCompatibility.isSubmerged() && vent.Id == 14) vent.myRend.sprite = SecurityGuard.getSubmergedCentralLowerSealedSprite(); vent.myRend.color = new Color(1f, 1f, 1f, 0.5f); vent.name = "FutureSealedVent_" + vent.name; } @@ -818,7 +835,7 @@ namespace TheOtherRoles public static void lawyerPromotesToPursuer() { PlayerControl player = Lawyer.lawyer; PlayerControl client = Lawyer.target; - Lawyer.clearAndReload(); + Lawyer.clearAndReload(false); Pursuer.pursuer = player; if (player.PlayerId == PlayerControl.LocalPlayer.PlayerId && client != null) { @@ -976,6 +993,9 @@ namespace TheOtherRoles case (byte)CustomRPC.EngineerFixLights: RPCProcedure.engineerFixLights(); break; + case (byte)CustomRPC.EngineerFixSubmergedOxygen: + RPCProcedure.engineerFixSubmergedOxygen(); + break; case (byte)CustomRPC.EngineerUsedRepair: RPCProcedure.engineerUsedRepair(); break; diff --git a/TheOtherRoles/Resources/CentralLowerBlocked.png b/TheOtherRoles/Resources/CentralLowerBlocked.png new file mode 100644 index 0000000..952fd9c Binary files /dev/null and b/TheOtherRoles/Resources/CentralLowerBlocked.png differ diff --git a/TheOtherRoles/Resources/CentralUpperBlocked.png b/TheOtherRoles/Resources/CentralUpperBlocked.png new file mode 100644 index 0000000..8752326 Binary files /dev/null and b/TheOtherRoles/Resources/CentralUpperBlocked.png differ diff --git a/TheOtherRoles/Resources/EmergencyButton.png b/TheOtherRoles/Resources/EmergencyButton.png new file mode 100644 index 0000000..57d3b88 Binary files /dev/null and b/TheOtherRoles/Resources/EmergencyButton.png differ diff --git a/TheOtherRoles/SubmergedCompatibility.cs b/TheOtherRoles/SubmergedCompatibility.cs new file mode 100644 index 0000000..2b4569c --- /dev/null +++ b/TheOtherRoles/SubmergedCompatibility.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using BepInEx; +using BepInEx.IL2CPP; +using HarmonyLib; +using UnhollowerRuntimeLib; +using UnityEngine; + +namespace TheOtherRoles +{ + public static class SubmergedCompatibility + { + public static class Classes + { + public const string ElevatorMover = "ElevatorMover"; + } + + public const string SUBMERGED_GUID = "Submerged"; + public const ShipStatus.MapType SUBMERGED_MAP_TYPE = (ShipStatus.MapType) 5; + + public static SemanticVersioning.Version Version { get; private set; } + public static bool Loaded { get; private set; } + public static BasePlugin Plugin { get; private set; } + public static Assembly Assembly { get; private set; } + public static Type[] Types { get; private set; } + public static Dictionary InjectedTypes { get; private set; } + + private static MonoBehaviour _submarineStatus; + public static MonoBehaviour SubmarineStatus + { + get + { + if (!Loaded) return null; + + if (_submarineStatus is null || _submarineStatus.WasCollected || !_submarineStatus || _submarineStatus == null) + { + if (ShipStatus.Instance is null || ShipStatus.Instance.WasCollected || !ShipStatus.Instance || ShipStatus.Instance == null) + { + return _submarineStatus = null; + } + else + { + if (ShipStatus.Instance.Type == SUBMERGED_MAP_TYPE) + { + return _submarineStatus = ShipStatus.Instance.GetComponent(Il2CppType.From(SubmarineStatusType))?.TryCast(SubmarineStatusType) as MonoBehaviour; + } + else + { + return _submarineStatus = null; + } + } + } + else + { + return _submarineStatus; + } + } + } + + public static bool DisableO2MaskCheckForEmergency + { + set + { + if (!Loaded) return; + DisableO2MaskCheckField.SetValue(null, value); + } + } + + private static Type SubmarineStatusType; + private static MethodInfo CalculateLightRadiusMethod; + + private static Type TaskIsEmergencyPatchType; + private static FieldInfo DisableO2MaskCheckField; + + private static MethodInfo RpcRequestChangeFloorMethod; + private static Type FloorHandlerType; + private static MethodInfo GetFloorHandlerMethod; + + private static Type Vent_MoveToVent_PatchType; + private static FieldInfo InTransitionField; + + private static Type CustomTaskTypesType; + private static FieldInfo RetrieveOxigenMaskField; + public static TaskTypes RetrieveOxygenMask; + private static Type SubmarineOxygenSystemType; + private static FieldInfo SubmarineOxygenSystemInstanceField; + private static MethodInfo RepairDamageMethod; + + + public static void Initialize() + { + Loaded = IL2CPPChainloader.Instance.Plugins.TryGetValue(SUBMERGED_GUID, out PluginInfo plugin); + if (!Loaded) return; + + Plugin = plugin!.Instance as BasePlugin; + Version = plugin.Metadata.Version; + + Assembly = Plugin!.GetType().Assembly; + Types = AccessTools.GetTypesFromAssembly(Assembly); + + InjectedTypes = (Dictionary) AccessTools.PropertyGetter(Types.FirstOrDefault(t => t.Name == "RegisterInIl2CppAttribute"), "RegisteredTypes") + .Invoke(null, Array.Empty()); + + SubmarineStatusType = Types.First(t => t.Name == "SubmarineStatus"); + CalculateLightRadiusMethod = AccessTools.Method(SubmarineStatusType, "CalculateLightRadius"); + + TaskIsEmergencyPatchType = Types.First(t => t.Name == "PlayerTask_TaskIsEmergency_Patch"); + DisableO2MaskCheckField = AccessTools.Field(TaskIsEmergencyPatchType, "DisableO2MaskCheck"); + + FloorHandlerType = Types.First(t => t.Name == "FloorHandler"); + GetFloorHandlerMethod = AccessTools.Method(FloorHandlerType, "GetFloorHandler", new Type[] {typeof(PlayerControl)}); + RpcRequestChangeFloorMethod = AccessTools.Method(FloorHandlerType, "RpcRequestChangeFloor"); + + Vent_MoveToVent_PatchType = Types.First(t => t.Name == "Vent_MoveToVent_Patch"); + InTransitionField = AccessTools.Field(Vent_MoveToVent_PatchType, "InTransition"); + + CustomTaskTypesType = Types.First(t => t.Name == "CustomTaskTypes"); + RetrieveOxigenMaskField = AccessTools.Field(CustomTaskTypesType, "RetrieveOxygenMask"); + RetrieveOxygenMask = (TaskTypes)RetrieveOxigenMaskField.GetValue(null); + + SubmarineOxygenSystemType = Types.First(t => t.Name == "SubmarineOxygenSystem"); + SubmarineOxygenSystemInstanceField = AccessTools.Field(SubmarineOxygenSystemType, "Instance"); + RepairDamageMethod = AccessTools.Method(SubmarineOxygenSystemType, "RepairDamage"); + } + + public static MonoBehaviour AddSubmergedComponent(this GameObject obj, string typeName) + { + if (!Loaded) return obj.AddComponent(); + bool validType = InjectedTypes.TryGetValue(typeName, out Type type); + return validType ? obj.AddComponent(Il2CppType.From(type)).TryCast() : obj.AddComponent(); + } + + public static float GetSubmergedNeutralLightRadius(bool isImpostor) { + if (!Loaded) return 0; + return (float)CalculateLightRadiusMethod.Invoke(SubmarineStatus, new object[] { null, true, isImpostor }); + } + + public static void ChangeFloor(bool toUpper) { + if (!Loaded) return; + MonoBehaviour _floorHandler = ((Component)GetFloorHandlerMethod.Invoke(null, new object[] { PlayerControl.LocalPlayer })).TryCast(FloorHandlerType) as MonoBehaviour; + RpcRequestChangeFloorMethod.Invoke(_floorHandler, new object[] { toUpper }); + } + + public static bool getInTransition() { + if (!Loaded) return false; + return (bool)InTransitionField.GetValue(null); + } + + public static void RepairOxygen() { + if (!Loaded) return; + try { + ShipStatus.Instance.RpcRepairSystem((SystemTypes)130, 64); + RepairDamageMethod.Invoke(SubmarineOxygenSystemInstanceField.GetValue(null), new object[] { PlayerControl.LocalPlayer, 64 }); + } + catch (System.NullReferenceException) { + TheOtherRolesPlugin.Logger.LogMessage("null reference in engineer oxygen fix"); + } + + } + + public static bool isSubmerged() { + return Loaded && ShipStatus.Instance && ShipStatus.Instance.Type == SUBMERGED_MAP_TYPE; + } + } + + public class MissingSubmergedBehaviour : MonoBehaviour + { + static MissingSubmergedBehaviour() => ClassInjector.RegisterTypeInIl2Cpp(); + public MissingSubmergedBehaviour(IntPtr ptr) : base(ptr) { } + } +} diff --git a/TheOtherRoles/TasksHandler.cs b/TheOtherRoles/TasksHandler.cs index a9b4c63..640327e 100644 --- a/TheOtherRoles/TasksHandler.cs +++ b/TheOtherRoles/TasksHandler.cs @@ -17,12 +17,10 @@ namespace TheOtherRoles { playerInfo.Role && playerInfo.Role.TasksCountTowardProgress && !playerInfo.Object.hasFakeTasks() ) { - - for (int j = 0; j < playerInfo.Tasks.Count; j++) { + foreach (var playerInfoTask in playerInfo.Tasks) + { + if (playerInfoTask.Complete) CompletedTasks++; TotalTasks++; - if (playerInfo.Tasks[j].Complete) { - CompletedTasks++; - } } } return Tuple.Create(CompletedTasks, TotalTasks); @@ -33,13 +31,13 @@ namespace TheOtherRoles { private static bool Prefix(GameData __instance) { __instance.TotalTasks = 0; __instance.CompletedTasks = 0; - for (int i = 0; i < __instance.AllPlayers.Count; i++) { - GameData.PlayerInfo playerInfo = __instance.AllPlayers[i]; + foreach (var playerInfo in GameData.Instance.AllPlayers) + { if (playerInfo.Object - && playerInfo.Object.hasAliveKillingLover() // Tasks do not count if a Crewmate has an alive killing Lover - || playerInfo.PlayerId == Lawyer.lawyer?.PlayerId // Tasks of the Lawyer do not count - || (playerInfo.PlayerId == Pursuer.pursuer?.PlayerId && Pursuer.pursuer.Data.IsDead) // Tasks of the Pursuer only count, if he's alive - ) + && playerInfo.Object.hasAliveKillingLover() // Tasks do not count if a Crewmate has an alive killing Lover + || playerInfo.PlayerId == Lawyer.lawyer?.PlayerId // Tasks of the Lawyer do not count + || (playerInfo.PlayerId == Pursuer.pursuer?.PlayerId && Pursuer.pursuer.Data.IsDead) // Tasks of the Pursuer only count, if he's alive + ) continue; var (playerCompleted, playerTotal) = taskInfo(playerInfo); __instance.TotalTasks += playerTotal; diff --git a/TheOtherRoles/TheOtherRoles.cs b/TheOtherRoles/TheOtherRoles.cs index 179c162..368c0e6 100644 --- a/TheOtherRoles/TheOtherRoles.cs +++ b/TheOtherRoles/TheOtherRoles.cs @@ -133,13 +133,28 @@ namespace TheOtherRoles public static class Mayor { public static PlayerControl mayor; public static Color color = new Color32(32, 77, 66, byte.MaxValue); + public static Minigame emergency = null; + public static Sprite emergencySprite = null; + public static bool canSeeVoteColors = false; public static int tasksNeededToSeeVoteColors; + public static bool meetingButton = true; + + public static Sprite getMeetingSprite() + { + if (emergencySprite) return emergencySprite; + emergencySprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.EmergencyButton.png", 550f); + return emergencySprite; + } public static void clearAndReload() { mayor = null; + emergency = null; + emergencySprite = null; + canSeeVoteColors = CustomOptionHolder.mayorCanSeeVoteColors.getBool(); tasksNeededToSeeVoteColors = (int)CustomOptionHolder.mayorTasksNeededToSeeVoteColors.getFloat(); + meetingButton = CustomOptionHolder.mayorMeetingButton.getBool(); } } @@ -1074,9 +1089,16 @@ namespace TheOtherRoles } private static Sprite animatedVentSealedSprite; + private static float lastPPU; public static Sprite getAnimatedVentSealedSprite() { + float ppu = 185f; + if (SubmergedCompatibility.isSubmerged()) ppu = 120f; + if (lastPPU != ppu) { + animatedVentSealedSprite = null; + lastPPU = ppu; + } if (animatedVentSealedSprite) return animatedVentSealedSprite; - animatedVentSealedSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.AnimatedVentSealed.png", 185f); + animatedVentSealedSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.AnimatedVentSealed.png", ppu); return animatedVentSealedSprite; } @@ -1087,6 +1109,20 @@ namespace TheOtherRoles return staticVentSealedSprite; } + private static Sprite submergedCentralUpperVentSealedSprite; + public static Sprite getSubmergedCentralUpperSealedSprite() { + if (submergedCentralUpperVentSealedSprite) return submergedCentralUpperVentSealedSprite; + submergedCentralUpperVentSealedSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.CentralUpperBlocked.png", 145f); + return submergedCentralUpperVentSealedSprite; + } + + private static Sprite submergedCentralLowerVentSealedSprite; + public static Sprite getSubmergedCentralLowerSealedSprite() { + if (submergedCentralLowerVentSealedSprite) return submergedCentralLowerVentSealedSprite; + submergedCentralLowerVentSealedSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.CentralLowerBlocked.png", 145f); + return submergedCentralLowerVentSealedSprite; + } + private static Sprite camSprite; public static Sprite getCamSprite() { if (camSprite) return camSprite; @@ -1338,11 +1374,8 @@ namespace TheOtherRoles public static Color color = new Color32(134, 153, 25, byte.MaxValue); public static Sprite targetSprite; public static bool triggerLawyerWin = false; - public static int meetings = 0; public static float vision = 1f; - public static bool winsAfterMeetings = false; - public static int neededMeetings = 4; public static bool lawyerKnowsRole = false; public static bool targetCanBeJester = false; @@ -1352,14 +1385,11 @@ namespace TheOtherRoles return targetSprite; } - public static void clearAndReload() { + public static void clearAndReload(bool clearTarget = true) { lawyer = null; - target = null; + if (clearTarget) target = null; triggerLawyerWin = false; - meetings = 0; - winsAfterMeetings = CustomOptionHolder.lawyerWinsAfterMeetings.getBool(); - neededMeetings = Mathf.RoundToInt(CustomOptionHolder.lawyerNeededMeetings.getFloat()); vision = CustomOptionHolder.lawyerVision.getFloat(); lawyerKnowsRole = CustomOptionHolder.lawyerKnowsRole.getBool(); targetCanBeJester = CustomOptionHolder.lawyerTargetCanBeJester.getBool();