]> git.deb.at Git - rhonda/theotherroles.git/commitdiff
version 4.1.0
authorMallöris <|||||@||.||>
Sun, 1 May 2022 18:00:21 +0000 (20:00 +0200)
committerMallöris <|||||@||.||>
Sun, 1 May 2022 18:00:21 +0000 (20:00 +0200)
30 files changed:
TheOtherRoles/Buttons.cs
TheOtherRoles/CustomOptionHolder.cs
TheOtherRoles/Helpers.cs
TheOtherRoles/Main.cs
TheOtherRoles/Modules/CustomHats.cs
TheOtherRoles/Modules/CustomOptions.cs
TheOtherRoles/Modules/ModUpdater.cs
TheOtherRoles/Objects/Arrow.cs
TheOtherRoles/Objects/Bloodytrail.cs
TheOtherRoles/Objects/Footprint.cs
TheOtherRoles/Objects/Garlic.cs
TheOtherRoles/Objects/JackInTheBox.cs
TheOtherRoles/Objects/NinjaTrace.cs
TheOtherRoles/Objects/Portal.cs
TheOtherRoles/Patches/CredentialsPatch.cs
TheOtherRoles/Patches/EndGamePatch.cs
TheOtherRoles/Patches/ExileControllerPatch.cs
TheOtherRoles/Patches/GameStartManagerPatch.cs
TheOtherRoles/Patches/MainMenuPatch.cs
TheOtherRoles/Patches/MeetingPatch.cs
TheOtherRoles/Patches/PlayerControlPatch.cs
TheOtherRoles/Patches/ShipStatusPatch.cs
TheOtherRoles/Patches/UsablesPatch.cs
TheOtherRoles/RPC.cs
TheOtherRoles/Resources/CentralLowerBlocked.png [new file with mode: 0644]
TheOtherRoles/Resources/CentralUpperBlocked.png [new file with mode: 0644]
TheOtherRoles/Resources/EmergencyButton.png [new file with mode: 0644]
TheOtherRoles/SubmergedCompatibility.cs [new file with mode: 0644]
TheOtherRoles/TasksHandler.cs
TheOtherRoles/TheOtherRoles.cs

index f17189cd485f0bfdf41ac23acc520905e39c9a70..4ad84e9fb3b3e3993d94109a9d2b4dda0cae2230 100644 (file)
@@ -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<byte, List<CustomButton>> 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<HudManager>.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;
index 9bbf8ac95586016731151987c654b004b249a2d7..833b4989f32ae1fcf80926bde59ccbf1e52a5fbc 100644 (file)
@@ -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<byte, byte[]> blockedRolePairings = new Dictionary<byte, byte[]>();
@@ -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});
index f1541fff80a8d9a327e373798fa5c09e62a082da..ab8a14440586fc95e5a03321eb6586e64af801b8 100644 (file)
@@ -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<byte>(length);
+                stream.Read(new Span<byte>(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<d_LoadImage>("UnityEngine.ImageConversion::LoadImage");
-            var il2cppArray = (Il2CppStructArray<byte>) 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<object>());
+        }
     }
 }
index 2976ea8719652499a3783ebcf6b6e69ce21a6e0c..9aebb39bfea082502c2d48bf1aa46399fd101d5a 100644 (file)
@@ -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<ModUpdateBehaviour>();
         }
         public static Sprite GetModStamp() {
             if (ModStamp) return ModStamp;
index 5cd1a036875ad30d8d523ad7b444c296f8a3adf6..ee49664fd3781759d4ff9bd54ad1c7c4fccc01dc 100644 (file)
@@ -281,11 +281,11 @@ namespace TheOtherRoles.Modules {
                     float ypos = offset - (i / __instance.NumPerRow) * (isDefaultPackage ? 1f : 1.5f) * __instance.YOffset;
                     ColorChip colorChip = UnityEngine.Object.Instantiate<ColorChip>(__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<HatManager>.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<HatManager>.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<string> markedfordownload = new List<string>();
 
                 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))
index 4cc2e362c54f6ee00bc26c5fea63f89d645546c5..17b5c3fba35ad63dc7153828a77b472603849ee1 100644 (file)
@@ -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);
index aab8679f1b4733dddfd61b11b957b4fda9373179..1d68ac8c686b5d87faa3cb6c770bf134ac72b572 100644 (file)
 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<Scene, LoadSceneMode>) (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<PassiveButton>();
             SpriteRenderer buttonSprite = button.GetComponent<SpriteRenderer>();
             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<TMPro.TMP_Text>();
-            __instance.StartCoroutine(Effects.Lerp(0.1f, new System.Action<float>((p) => {
-                text.SetText("Update");
-            })));
+            var text = button.transform.GetChild(0).GetComponent<TMP_Text>();
+            string t = "Update";
+            if (TORUpdate is null) t = SubmergedCompatibility.Loaded ? $"Update\nSubmerged" : $"Download\nSubmerged";
+
+            StartCoroutine(Effects.Lerp(0.1f, (Action<float>)(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<TwitchManager>.Instance;
-            ModUpdater.InfoPopup = UnityEngine.Object.Instantiate<GenericPopup>(man.TwitchPopup);
-            ModUpdater.InfoPopup.TextAreaTMP.fontSize *= 0.7f;
-            ModUpdater.InfoPopup.TextAreaTMP.enableAutoSizing = false;
+            var isSubmerged = TORUpdate == null;
+            var announcement = $"<size=150%>A new <color=#FC0303>{(isSubmerged ? "Submerged" : "THE OTHER ROLES")}</color> update to {(isSubmerged ? SubmergedUpdate.Tag : TORUpdate.Tag)} is available</size>\n{(isSubmerged ? SubmergedUpdate.Content : TORUpdate.Content)}";
+            var mgr = FindObjectOfType<MainMenuManager>(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<AnnouncementPopUp>(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<MainMenuManager>.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<float>((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<bool> 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<UpdateData> 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 = $@"<size=150%>A new <color=#FC0303>THE OTHER ROLES</color>
-update to v{ver} is available</size>
-
-{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 = $@"<size=150%><color=#FC0303>THE OTHER ROLES</color> Version {ver}:</size>
+            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<bool> 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<bool> 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
index 3a70feddb7c8c0721ddbcd0659753b59ef44d9b3..5324489ab57709c861638a80a7966cd5b653b6f9 100644 (file)
@@ -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<SpriteRenderer>();
             image.sprite = getSprite();
             image.color = color;
+            arrowBehaviour = arrow.AddComponent<ArrowBehaviour>();
+            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
index 683200c982e908ec6e984417d298b4e4cf734ce0..3fc312fd957bb56d0c0f3ac9d1f33525cd7fd63a 100644 (file)
@@ -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);
index c9cfa148a386fd03d9b2a27737ac6f67c0d08f3f..7eb18e57e7f22ae7933e51e12896de2d57d4f421 100644 (file)
@@ -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);
index 5041d7f128ebe4a4b1d7949eb3145550ed9056ce..0812fe855483fd2b32e7e4d5cce761535d0b0e00 100644 (file)
@@ -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<SpriteRenderer>();
             garlicRenderer.sprite = getGarlicSprite();
index 8e2bd739797ff78a0974d89dc68d72b7ee9fe94d..3195ff377aa3aa717d9651968ca8c97a245be4d4 100644 (file)
@@ -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>();
             vent = UnityEngine.Object.Instantiate<Vent>(referenceVent);
+            vent.gameObject.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover);
             vent.transform.position = gameObject.transform.position;
             vent.Left = null;
             vent.Right = null;
index c9be1e2c1ed6bc0cd0c99ea2428d5f96014db65b..87e3efe064b56920b915f2cfb7a993b378abcc97 100644 (file)
@@ -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;
             
index de6a94ea6236078699332618bcf4148793e9c6e2..513ecb9d163dccb508ab7319877822b7be79d4fd 100644 (file)
@@ -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<tpLogEntry> 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<SpriteRenderer>();
-            animationBgRenderer = portalGameObject.AddComponent<SpriteRenderer>();
             portalRenderer.sprite = portalSprite;
 
-            portalBgAnimationGameObject = new GameObject("PortalAnimationBG");
-            portalBgAnimationGameObject.transform.position = position;
-            animationBgRenderer = portalBgAnimationGameObject.AddComponent<SpriteRenderer>();
-
-            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<SpriteRenderer>();
             animationFgRenderer.material = DestroyableSingleton<HatManager>.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);
index e3dec1cce9dd395cc4c87dd1122c26756c3dcbbe..dde315922d8514915386aff39eb1654758d48282 100644 (file)
@@ -21,7 +21,7 @@ Design by <color=#FCCE03FF>Bavari</color>";
 
         public static string contributorsCredentials =
 $@"<size=60%> <color=#FCCE03FF>Special thanks to K3ndo & Smeggy</color> 
-GitHub Contributors: Gendelo3, Alex2911, amsyarasyiq, MaximeGillot, Psynomit</size>";
+GitHub Contributors: Gendelo, Alex2911, amsyarasyiq, MaximeGillot, Psynomit</size>";
 
         [HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))]
         private static class VersionShowerPatch
index 4080a8f92236acd69ad770e6ae795fecc946f4af..7045c49ab9d0bed1daf16816225fbecfb5c44a72 100644 (file)
@@ -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)
index 3672e9c7c928b81e8a03e0dc1d9e0d83f6cf8eef..9ecc9feb624bf35d4d36b75409c17a6b2d1c4dd7 100644 (file)
@@ -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<SpriteRenderer>();
+                    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<SpriteRenderer>();
+                        s.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover);
                         rend.sprite = Medium.getSoulSprite();
                         Medium.souls.Add(rend);
                     }
@@ -203,13 +222,13 @@ namespace TheOtherRoles.Patches {
                     Medium.featureDeadBodies = new List<Tuple<DeadPlayer, Vector3>>();
                 }
             }
-            // 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
index 7e323277badf9df6732ed3aa71ffa6f2a528ad30..abcbe32d5baa0b716548855ebb7b4a9924f91fc1 100644 (file)
@@ -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);
index f98200d3dfe25ce5f9906a73470ce01911c37852..83c0fa038ee73431e93f4a340d0ce16c4347df2d 100644 (file)
@@ -29,13 +29,11 @@ namespace TheOtherRoles.Modules {
             SpriteRenderer buttonSpriteDiscord = buttonDiscord.GetComponent<SpriteRenderer>();
 
             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);
index 0687b0b8a5607888f3ed2104558515ea9d444e80..e08a0522b60fe2516f66b2209c8b275dd2008ce2 100644 (file)
@@ -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<PassiveButton>().OnClick.RemoveAllListeners();
-            exitButton.GetComponent<PassiveButton>().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => {
+            exitButton.GetComponent<PassiveButton>().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<PassiveButton>().OnClick.RemoveAllListeners();
-                if (!PlayerControl.LocalPlayer.Data.IsDead) button.GetComponent<PassiveButton>().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => {
+                if (!PlayerControl.LocalPlayer.Data.IsDead) button.GetComponent<PassiveButton>().OnClick.AddListener((System.Action)(() => {
                     if (selectedButton != button) {
                         selectedButton = button;
                         buttons.ForEach(x => x.GetComponent<SpriteRenderer>().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<PassiveButton>();
                     button.OnClick.RemoveAllListeners();
                     int copiedIndex = i;
-                    button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => guesserOnClick(copiedIndex, __instance)));
+                    button.OnClick.AddListener((System.Action)(() => guesserOnClick(copiedIndex, __instance)));
                 }
             }
         }
index 6b66b506e6cbcbe2774ec50f1b25059ed8a26747..dac61b6cced6d0cabdb81046133e9ebeca2c49b8 100644 (file)
@@ -23,9 +23,8 @@ namespace TheOtherRoles.Patches {
             if (targetingPlayer.Data.IsDead) return result;
 
             Vector2 truePosition = targetingPlayer.GetTruePosition();
-            Il2CppSystem.Collections.Generic.List<GameData.PlayerInfo> 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();
+            }
+
         }
     }
 
index 8c3cae441146ad9ff851ed95ce8f4d30db08f654..a5cdbb3748f8929c55b811ec8f2c4f3f91e49d61 100644 (file)
@@ -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<SwitchSystem>();
-            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<SwitchSystem>();
+            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
+}
index db1e725daa3b0463699b5c746ddab43ba1c07efa..22865527b1c4235d24e8892048a29e7788fc1cf4 100644 (file)
@@ -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<SystemTypes, List<Color>> players = new Dictionary<SystemTypes, List<Color>>();
+        static Dictionary<SystemTypes, List<Color>> players = new Dictionary<SystemTypes, System.Collections.Generic.List<Color>>();
 
         [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<Color> 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<SpriteRenderer>();
 
                         if (renderer != null) {
index 938c7ca7f9b846a547fa95c30911bc3de784f5c5..3247ca510e845124f94b3cd23b9fb96903c75a04 100644 (file)
@@ -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<BoxCollider2D>();
+                    if (boxCollider != null) UnityEngine.Object.Destroy(boxCollider);
+                }
+            }
+
+
             if (PlayerControl.LocalPlayer == SecurityGuard.securityGuard) {
                 camera.gameObject.SetActive(true);
                 camera.gameObject.GetComponent<SpriteRenderer>().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 (file)
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 (file)
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 (file)
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 (file)
index 0000000..2b4569c
--- /dev/null
@@ -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<string, Type> 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<string, Type>) AccessTools.PropertyGetter(Types.FirstOrDefault(t => t.Name == "RegisterInIl2CppAttribute"), "RegisteredTypes")
+                .Invoke(null, Array.Empty<object>());
+            
+            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<MissingSubmergedBehaviour>();
+            bool validType = InjectedTypes.TryGetValue(typeName, out Type type);
+            return validType ? obj.AddComponent(Il2CppType.From(type)).TryCast<MonoBehaviour>() : obj.AddComponent<MissingSubmergedBehaviour>();
+        }
+
+        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<MissingSubmergedBehaviour>();
+        public MissingSubmergedBehaviour(IntPtr ptr) : base(ptr) { }
+    }
+}
index a9b4c63a045b4a46b3fecc45777173175370d2fa..640327e438f649a103473b853f8f4eacd54506b9 100644 (file)
@@ -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;
index 179c162c37f079a2d53e62c4c03acc5f0924bb76..368c0e6668d128888d6f27541d25a45e10f39cfe 100644 (file)
@@ -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();