]> git.deb.at Git - rhonda/theotherroles.git/commitdiff
Version 2.5.0
authorEisbison <eisbison.creator@gmail.com>
Mon, 26 Apr 2021 17:34:45 +0000 (19:34 +0200)
committerEisbison <eisbison.creator@gmail.com>
Mon, 26 Apr 2021 17:34:45 +0000 (19:34 +0200)
26 files changed:
Images/Preview_2.3.0.png [deleted file]
Images/Preview_2.5.0.png [new file with mode: 0644]
README.md
Source Code/Buttons.cs
Source Code/ChatCommands.cs
Source Code/ClientOptionsPatch.cs [new file with mode: 0644]
Source Code/CustomButton.cs
Source Code/CustomOptions.cs
Source Code/GameStartManagerPatch.cs
Source Code/Helpers.cs
Source Code/Main.cs
Source Code/MapOptions.cs
Source Code/MeetingPatch.cs
Source Code/PlayerControlPatch.cs
Source Code/RPC.cs
Source Code/Resources/AnimatedVentSealed.png [new file with mode: 0644]
Source Code/Resources/CloseVentButton.png [new file with mode: 0644]
Source Code/Resources/PlaceCameraButton.png [new file with mode: 0644]
Source Code/Resources/StaticVentSealed.png [new file with mode: 0644]
Source Code/RoleAssignmentPatch.cs
Source Code/RoleInfo.cs
Source Code/StreamerModePatch.cs [deleted file]
Source Code/TheOtherRoles.cs
Source Code/TheOtherRoles.csproj
Source Code/UpdatePatch.cs
Source Code/UsablesPatch.cs

diff --git a/Images/Preview_2.3.0.png b/Images/Preview_2.3.0.png
deleted file mode 100644 (file)
index 8da11cc..0000000
Binary files a/Images/Preview_2.3.0.png and /dev/null differ
diff --git a/Images/Preview_2.5.0.png b/Images/Preview_2.5.0.png
new file mode 100644 (file)
index 0000000..3c43d5d
Binary files /dev/null and b/Images/Preview_2.5.0.png differ
index da45dd81d92d019c17c7f9ea7d8b6efd4d76c721..72cf98464f5caf9aeb088b7b022f731fdfee2125 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 [![Discord](https://discord.com/assets/e4923594e694a21542a489471ecffa50.svg)](https://discord.gg/77RkMJHWsM)\r
 ![eisbison banner](./Images/Banner.png)\r
-![eisbison infographic](./Images/Preview_2.3.0.png)\r
+![eisbison infographic](./Images/Preview_2.5.0.png)\r
 \r
 \r
 # The Other Roles\r
@@ -32,6 +32,7 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein
 # Releases\r
 | Among Us - Version| Mod Version | Link |\r
 |----------|-------------|-----------------|\r
+| 2021.4.14s| v2.5.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.5.0/TheOtherRoles.zip)\r
 | 2021.4.14s| v2.4.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.4.0/TheOtherRoles.zip)\r
 | 2021.4.14s| v2.3.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.3.0/TheOtherRoles.zip)\r
 | 2021.4.14s| v2.2.2| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.2.2/TheOtherRoles.zip)\r
@@ -55,6 +56,11 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein
 <details>\r
   <summary>Click to show the Changelog</summary>\r
 \r
+**Version 2.5.0**\r
+- **New Role:** [Security Guard](#security-guard)\r
+- Fixed a bug where the game would stop after the first meeting\r
+- Fixed a bug where killing with the hotkey Q ignored shields\r
+\r
 **Version 2.4.0**\r
 - **New Role:** [Warlock](#warlock)\r
 - Added an option that allows ghosts to see the roles and remaining tasks of other players\r
@@ -862,6 +868,28 @@ You can set whether the Sheriff can kill the Spy or not (in order to keep the li
 | Impostors Can Kill Anyone If There Is A Spy | This allows the Impostors to kill both the Spy and their Impostor partners\r
 -----------------------\r
 \r
+## Security Guard\r
+### **Team: Crewmates**\r
+The Security Guard is a Crewmate that has a certain amount of screws that he can use for either sealing vents or for placing new cameras.\\r
+Placing a new camera and sealing vents takes a configurable amount of screws. The total number of screws that a SecurityGuard has can also be configured.\\r
+The new camera will be visible after the next meeting and accessible by everyone.\\r
+The vents will be sealed after the next meeting, players can't enter or exit sealed vents, but they can still "move to them" underground.\\r
+**NOTE:**\r
+- Tickster boxes can't be sealed\r
+- The remaining number of screws can be seen above his special button\r
+- On Skeld the four cameras will be replaced every 3 seconds (with the next four cameras). You can also navigate manually using the arrow keys.\r
+\r
+\r
+### Game Options\r
+| Name | Description\r
+|----------|:-------------:|\r
+| Security Guard Spawn Chance |\r
+| Security Guard Cooldown | \r
+| Security Guard Number Of Screws | The number of screws that a Security Guard can use in a game\r
+| Number Of Screws Per Cam | The number of screws it takes to place a camera\r
+| Number Of Screws Per Vent | The number of screws it takes to seal a vent\r
+-----------------------\r
+\r
 \r
 # Source code\r
 It's bad I know, this is a side project and my second week of modding. So there are no best practices around here.\r
index b65493d4fe59586ff3f68bbd7d12607016986234..fb54c6e49f2b9c8f2931bf93253424cd5518c50c 100644 (file)
@@ -30,6 +30,8 @@ namespace TheOtherRoles
         private static CustomButton lightsOutButton;
         public static CustomButton cleanerCleanButton;
         public static CustomButton warlockCurseButton;
+        public static CustomButton securityGuardButton;
+        public static TMPro.TMP_Text securityGuardButtonScrewsText;
 
         public static void setCustomButtonCooldowns() {
             engineerRepairButton.MaxTimer = 0f;
@@ -53,6 +55,7 @@ namespace TheOtherRoles
             lightsOutButton.MaxTimer = Trickster.lightsOutCooldown;
             cleanerCleanButton.MaxTimer = Cleaner.cooldown;
             warlockCurseButton.MaxTimer = Warlock.cooldown;
+            securityGuardButton.MaxTimer = SecurityGuard.cooldown;
 
             timeMasterShieldButton.EffectDuration = TimeMaster.shieldDuration;
             hackerButton.EffectDuration = Hacker.duration;
@@ -359,7 +362,12 @@ namespace TheOtherRoles
                 () => {
                     if (Helpers.handleMurderAttempt(Vampire.currentTarget)) {
                         if (Vampire.targetNearGarlic) {
-                                       PlayerControl.LocalPlayer.RpcMurderPlayer(Vampire.currentTarget);
+                            MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1);
+                            writer.Write(Vampire.vampire.PlayerId);
+                            writer.Write(Vampire.currentTarget.PlayerId);
+                            AmongUsClient.Instance.FinishRpcImmediately(writer);
+                            RPCProcedure.uncheckedMurderPlayer(Vampire.vampire.PlayerId, Vampire.currentTarget.PlayerId);
+
                             vampireKillButton.HasEffect = false; // Block effect on this click
                             vampireKillButton.Timer = vampireKillButton.MaxTimer;
                         } else {
@@ -623,6 +631,7 @@ namespace TheOtherRoles
                 __instance,
                 KeyCode.F
             );
+
             // Warlock curse
             warlockCurseButton = new CustomButton(
                 () => {
@@ -670,6 +679,47 @@ namespace TheOtherRoles
                 KeyCode.F
             );
 
+            // SecurityGuard button
+            securityGuardButton = new CustomButton(
+                () => {
+                    if (SecurityGuard.ventTarget == null) { // Place camera
+                        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));
+                        Buffer.BlockCopy(BitConverter.GetBytes(pos.y), 0, buff, 1*sizeof(float), sizeof(float));
+
+                        MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.PlaceCamera, Hazel.SendOption.Reliable);
+                        writer.WriteBytesAndSize(buff);
+                        writer.EndMessage();
+                        RPCProcedure.placeCamera(buff); 
+                    } else { // Seal vent
+                        MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SealVent, Hazel.SendOption.Reliable);
+                        writer.WritePacked(SecurityGuard.ventTarget.Id);
+                        writer.EndMessage();
+                        RPCProcedure.sealVent(SecurityGuard.ventTarget.Id);
+                        SecurityGuard.ventTarget = null;
+                    }
+                    securityGuardButton.Timer = securityGuardButton.MaxTimer;
+                },
+                () => { return SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+                () => {
+                    securityGuardButton.killButtonManager.renderer.sprite = (SecurityGuard.ventTarget == null) ? SecurityGuard.getPlaceCameraButtonSprite() : SecurityGuard.getCloseVentButtonSprite(); 
+                    if (securityGuardButtonScrewsText != null) securityGuardButtonScrewsText.text = $"{SecurityGuard.remainingScrews}/{SecurityGuard.totalScrews}";
+                    return SecurityGuard.remainingScrews >= (SecurityGuard.ventTarget == null ? SecurityGuard.camPrice : SecurityGuard.ventPrice) && PlayerControl.LocalPlayer.CanMove;
+                },
+                () => { securityGuardButton.Timer = securityGuardButton.MaxTimer; },
+                SecurityGuard.getPlaceCameraButtonSprite(),
+                new Vector3(-1.3f, 0f, 0f),
+                __instance,
+                KeyCode.Q
+            );
+
+            // SecurityGuard button screws counter
+            securityGuardButtonScrewsText = GameObject.Instantiate(securityGuardButton.killButtonManager.TimerText, securityGuardButton.killButtonManager.TimerText.transform.parent);
+            securityGuardButtonScrewsText.text = "";
+            securityGuardButtonScrewsText.enableWordWrapping = false;
+            securityGuardButtonScrewsText.transform.localScale = Vector3.one * 0.5f;
+            securityGuardButtonScrewsText.transform.localPosition += new Vector3(-0.05f, 0.7f, 0);
 
             // Set the default (or settings from the previous game) timers/durations when spawning the buttons
             setCustomButtonCooldowns();
index fb7df172663c10b89b35dd3f4618f5f48a989f60..930fa85dcfd7ebc138e442cb85ad143716cf4404 100644 (file)
@@ -32,10 +32,9 @@ namespace TheOtherRoles {
                 if (handled) {
                     __instance.TextArea.Clear();
                     __instance.quickChatMenu.ResetGlyphs();
-                    System.Console.WriteLine("Chat Clear");
                 }
                 return !handled;
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/Source Code/ClientOptionsPatch.cs b/Source Code/ClientOptionsPatch.cs
new file mode 100644 (file)
index 0000000..fff8aa8
--- /dev/null
@@ -0,0 +1,87 @@
+  
+using HarmonyLib;
+using UnityEngine;
+using System.Collections.Generic;
+using Hazel;
+using System;
+using UnityEngine.UI;
+using UnityEngine.Events;
+
+namespace TheOtherRoles {
+    [HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Start))]
+    public class OptionsMenuBehaviourStartPatch {
+        private static Vector3? origin;
+        private static ToggleButtonBehaviour streamerModeButton;
+        private static ToggleButtonBehaviour ghostsSeeTasksButton;
+        private static ToggleButtonBehaviour ghostsSeeRolesButton;
+
+        private static void updateToggle(ToggleButtonBehaviour button, string text, bool on) {
+            if (button == null || button.gameObject == null) return;
+
+            Color color = on ? new Color(0f, 1f, 0.16470589f, 1f) : Color.white;
+            button.Background.color = color;
+            button.Text.text = $"{text}{(on ? "On" : "Off")}";
+            if (button.Rollover) button.Rollover.ChangeOutColor(color);
+        }
+
+        private static ToggleButtonBehaviour createCustomToggle(string text, bool on, Vector3 offset, UnityEngine.Events.UnityAction onClick, OptionsMenuBehaviour __instance) {
+            if (__instance.CensorChatButton != null) {
+                var button = UnityEngine.Object.Instantiate(__instance.CensorChatButton, __instance.CensorChatButton.transform.parent);
+                button.transform.localPosition = (origin ?? Vector3.zero) + offset;
+                PassiveButton passiveButton = button.GetComponent<PassiveButton>();
+                passiveButton.OnClick = new Button.ButtonClickedEvent();
+                passiveButton.OnClick.AddListener(onClick);
+                updateToggle(button, text, on);
+                
+                return button;
+            }
+            return null;
+        }
+
+        public static void Postfix(OptionsMenuBehaviour __instance) {
+            if (__instance.CensorChatButton != null) {
+                if (origin == null) origin = __instance.CensorChatButton.transform.localPosition + Vector3.up * 0.25f;
+                __instance.CensorChatButton.transform.localPosition = origin.Value + Vector3.left * 1.3f;
+            }
+
+            if ((streamerModeButton == null || streamerModeButton.gameObject == null)) {
+                streamerModeButton = createCustomToggle("Streamer Mode: ", TheOtherRolesPlugin.StreamerMode.Value, Vector3.right * 1.3f, (UnityEngine.Events.UnityAction)streamerModeToggle, __instance);
+
+                void streamerModeToggle() {
+                    TheOtherRolesPlugin.StreamerMode.Value = !TheOtherRolesPlugin.StreamerMode.Value;
+                    updateToggle(streamerModeButton, "Streamer Mode: ", TheOtherRolesPlugin.StreamerMode.Value);
+                }
+            }
+
+            if ((ghostsSeeTasksButton == null || ghostsSeeTasksButton.gameObject == null)) {
+                ghostsSeeTasksButton = createCustomToggle("Ghosts See Remaining Tasks: ", TheOtherRolesPlugin.GhostsSeeTasks.Value, new Vector2(-1.3f, -0.5f), (UnityEngine.Events.UnityAction)ghostsSeeTaskToggle, __instance);
+
+                void ghostsSeeTaskToggle() {
+                    TheOtherRolesPlugin.GhostsSeeTasks.Value = !TheOtherRolesPlugin.GhostsSeeTasks.Value;
+                    MapOptions.ghostsSeeTasks = TheOtherRolesPlugin.GhostsSeeTasks.Value; 
+                    updateToggle(ghostsSeeTasksButton, "Ghosts See Remaining Tasks: ", TheOtherRolesPlugin.GhostsSeeTasks.Value);
+                }
+            }
+
+            if ((ghostsSeeRolesButton == null || ghostsSeeRolesButton.gameObject == null)) {
+                ghostsSeeRolesButton = createCustomToggle("Ghosts See Roles: ", TheOtherRolesPlugin.GhostsSeeRoles.Value, new Vector2(1.3f, -0.5f), (UnityEngine.Events.UnityAction)ghostsSeeRolesToggle, __instance);
+
+                void ghostsSeeRolesToggle() {
+                    TheOtherRolesPlugin.GhostsSeeRoles.Value = !TheOtherRolesPlugin.GhostsSeeRoles.Value;
+                    MapOptions.ghostsSeeRoles = TheOtherRolesPlugin.GhostsSeeRoles.Value; 
+                    updateToggle(ghostsSeeRolesButton, "Ghosts See Roles: ", TheOtherRolesPlugin.GhostsSeeRoles.Value);
+                }
+            }
+        }
+    }
+
+    [HarmonyPatch(typeof(TextBoxTMP), nameof(TextBoxTMP.SetText))]
+       public static class HiddenTextPatch
+       {
+               private static void Postfix(TextBoxTMP __instance)
+               {
+                       bool flag = TheOtherRolesPlugin.StreamerMode.Value && (__instance.name == "GameIdText" || __instance.name == "IpTextBox" || __instance.name == "PortTextBox");
+                       if (flag) __instance.outputText.text = new string('*', __instance.text.Length);
+               }
+       }
+}
\ No newline at end of file
index c18999bf75eb44101a69002c64998e3aa20b68f2..d47c77e460a6197d5d0260944101edbcf3f5e738 100644 (file)
@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.Reflection;
 using UnityEngine;
+using UnityEngine.UI;
 
 public class CustomButton
 {
@@ -42,7 +43,7 @@ public class CustomButton
         buttons.Add(this);
         killButtonManager = UnityEngine.Object.Instantiate(hudManager.KillButton, hudManager.transform);
         PassiveButton button = killButtonManager.GetComponent<PassiveButton>();
-        button.OnClick.RemoveAllListeners();
+        button.OnClick = new Button.ButtonClickedEvent();
         button.OnClick.AddListener((UnityEngine.Events.UnityAction)onClickEvent);
 
         setActive(false);
index a3bd672395f942a4de2117b77b09cb686d9f7c99..fa60fb7ded099f4a27a689e9efb18b94f902dc3e 100644 (file)
@@ -128,11 +128,16 @@ namespace TheOtherRoles {
         public static CustomOption warlockCooldown;
         public static CustomOption warlockRootTime;
 
+        public static CustomOption securityGuardSpawnRate;
+        public static CustomOption securityGuardCooldown;
+        public static CustomOption securityGuardTotalScrews;
+        public static CustomOption securityGuardCamPrice;
+        public static CustomOption securityGuardVentPrice;
+
         public static CustomOption maxNumberOfMeetings;
         public static CustomOption blockSkippingInEmergencyMeetings;
         public static CustomOption noVoteIsSelfVote;
         public static CustomOption hidePlayerNames;
-        public static CustomOption showGhostInfo;
 
         internal static Dictionary<byte, byte[]> blockedRolePairings = new Dictionary<byte, byte[]>();
 
@@ -262,12 +267,17 @@ namespace TheOtherRoles {
             spyCanDieToSheriff = CustomOption.Create(241, "Spy Can Die To Sheriff", false, spySpawnRate);
             spyImpostorsCanKillAnyone = CustomOption.Create(242, "Impostors Can Kill Anyone If There Is A Spy", true, spySpawnRate);
 
+            securityGuardSpawnRate = CustomOption.Create(280, cs(SecurityGuard.color, "Security Guard"), rates, null, true);
+            securityGuardCooldown = CustomOption.Create(281, "Security Guard Cooldown", 30f, 10f, 60f, 2.5f, securityGuardSpawnRate);
+            securityGuardTotalScrews = CustomOption.Create(282, "Security Guard Number Of Screws", 7f, 1f, 15f, 1f, securityGuardSpawnRate);
+            securityGuardCamPrice = CustomOption.Create(283, "Number Of Screws Per Cam", 2f, 1f, 15f, 1f, securityGuardSpawnRate);
+            securityGuardVentPrice = CustomOption.Create(284, "Number Of Screws Per Vent", 1f, 1f, 15f, 1f, securityGuardSpawnRate);
+
             // Other options
             maxNumberOfMeetings = CustomOption.Create(3, "Number Of Meetings (excluding Mayor meeting)", 10, 0, 15, 1, null, true);
             blockSkippingInEmergencyMeetings = CustomOption.Create(4, "Block Skipping In Emergency Meetings", false);
             noVoteIsSelfVote = CustomOption.Create(5, "No Vote Is Self Vote", false, blockSkippingInEmergencyMeetings);
             hidePlayerNames = CustomOption.Create(6, "Hide Player Names", false);
-            showGhostInfo = CustomOption.Create(7, "Ghosts Can See Roles And Remaining Tasks", true);
 
             blockedRolePairings.Add((byte)RoleId.Vampire, new [] { (byte)RoleId.Warlock});
             blockedRolePairings.Add((byte)RoleId.Warlock, new [] { (byte)RoleId.Vampire});
@@ -518,7 +528,7 @@ namespace TheOtherRoles {
             var hudString = sb.ToString();
 
             int defaultSettingsLines = 19;
-            int roleSettingsLines = defaultSettingsLines + 29;
+            int roleSettingsLines = defaultSettingsLines + 30;
             int detailedSettingsP1 = roleSettingsLines + 34;
             int detailedSettingsP2 = detailedSettingsP1 + 36;
             int end1 = hudString.TakeWhile(c => (defaultSettingsLines -= (c == '\n' ? 1 : 0)) > 0).Count();
index d01d123582039b8c68cf09e1332207bd2b19c853..597ace7e1bbaf52334c258e39497bda0801370de 100644 (file)
@@ -61,10 +61,10 @@ namespace TheOtherRoles {
                             continue;
                         else if (!playerVersions.ContainsKey(client.Id))  {
                             blockStart = true;
-                            message += $"<color=#FF0000FF>{client.Character.Data.PlayerName} has an outdated or no version of The Other Roles\n</color>";
+                            message += $"<color=#FF0000FF>{client.Character.Data.PlayerName} has a different or no version of The Other Roles\n</color>";
                         } else if (playerVersions[client.Id].Item1 != TheOtherRolesPlugin.Major || playerVersions[client.Id].Item2 != TheOtherRolesPlugin.Minor || playerVersions[client.Id].Item3 != TheOtherRolesPlugin.Patch) {
                             blockStart = true;
-                            message += $"<color=#FF0000FF>{client.Character.Data.PlayerName} has an outdated version (v{playerVersions[client.Id].Item1}.{playerVersions[client.Id].Item2}.{playerVersions[client.Id].Item3}) of The Other Roles\n</color>";
+                            message += $"<color=#FF0000FF>{client.Character.Data.PlayerName} has a different version (v{playerVersions[client.Id].Item1}.{playerVersions[client.Id].Item2}.{playerVersions[client.Id].Item3}) of The Other Roles\n</color>";
                         }
                     }
                     if (blockStart) {
index 570655148aa968f48a03e03658e4d88a33c72bd1..3a5f52ab15600550d1f998e38d99a81694ddf5b4 100644 (file)
@@ -180,12 +180,16 @@ 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];
                 playerTask.OnRemove();
                 UnityEngine.Object.Destroy(playerTask.gameObject);
             }
             player.myTasks.Clear();
+            
+            if (player.Data != null && player.Data.Tasks != null)
+                player.Data.Tasks.Clear();
         }
 
         public static string cs(Color c, string s) {
index a6681c42a70b8942aa02e8a3b208e6a80db12aca..a20402a4b5e8c341009782a32c3404af24ce5900 100644 (file)
@@ -19,9 +19,9 @@ namespace TheOtherRoles
     public class TheOtherRolesPlugin : BasePlugin
     {
         public const string Id = "me.eisbison.theotherroles";
-        public const string Version = "2.4.0";
+        public const string Version = "2.5.0";
         public const byte Major = 2;
-        public const byte Minor = 4;
+        public const byte Minor = 5;
         public const byte Patch = 0;
 
         public Harmony Harmony { get; } = new Harmony(Id);
@@ -31,6 +31,9 @@ namespace TheOtherRoles
 
         public static ConfigEntry<bool> DebugMode { get; private set; }
         public static ConfigEntry<bool> StreamerMode { get; set; }
+        public static ConfigEntry<bool> GhostsSeeTasks { get; set; }
+        public static ConfigEntry<bool> GhostsSeeRoles { get; set; }
+        public static ConfigEntry<bool> HostSeesVotesLog { get; set; }
         public static ConfigEntry<string> StreamerModeReplacementText { get; set; }
         public static ConfigEntry<string> StreamerModeReplacementColor { get; set; }
         public static ConfigEntry<string> Ip { get; set; }
@@ -49,8 +52,11 @@ namespace TheOtherRoles
         }
 
         public override void Load() {
-            DebugMode  = Config.Bind("Custom", "Enable Debug Mode", false);
-            StreamerMode  = Config.Bind("Custom", "Enable Streamer Mode", false);
+            DebugMode = Config.Bind("Custom", "Enable Debug Mode", false);
+            StreamerMode = Config.Bind("Custom", "Enable Streamer Mode", false);
+            GhostsSeeTasks = Config.Bind("Custom", "Ghosts See Remaining Tasks", true);
+            GhostsSeeRoles = Config.Bind("Custom", "Ghosts See Roles", true);
+            HostSeesVotesLog = Config.Bind("Custom", "Host Sees Votes Log", false);
             StreamerModeReplacementText = Config.Bind("Custom", "Streamer Mode Replacement Text", "\n\nThe Other Roles");
             StreamerModeReplacementColor = Config.Bind("Custom", "Streamer Mode Replacement Text Hex Color", "#87AAF5FF");
             
@@ -85,8 +91,10 @@ namespace TheOtherRoles
     [HarmonyPatch(typeof(ChatController), nameof(ChatController.Awake))]
     public static class ChatControllerAwakePatch {
         private static void Prefix() {
-            SaveManager.chatModeType = 1;
-            SaveManager.isGuest = false;
+            if (!EOSManager.Instance.IsMinor()) {
+                SaveManager.chatModeType = 1;
+                SaveManager.isGuest = false;
+            }
         }
     }
     
index e324c96305ab0cac5c9bfeb14aa815f8b07d6ff6..7a20b66f35d06228fcb9949c38605c0ff03dee42 100644 (file)
@@ -11,19 +11,25 @@ namespace TheOtherRoles{
         public static bool blockSkippingInEmergencyMeetings = false;
         public static bool noVoteIsSelfVote = false;
         public static bool hidePlayerNames = false;
-        public static bool showGhostInfo = true;
+        public static bool ghostsSeeRoles = true;
+        public static bool ghostsSeeTasks = true;
 
         // Updating values
         public static int meetingsCount = 0;
+        public static List<SurvCamera> camerasToAdd = new List<SurvCamera>();
+        public static List<Vent> ventsToSeal = new List<Vent>();
 
         public static void clearAndReloadMapOptions() {
             meetingsCount = 0;
+            camerasToAdd = new List<SurvCamera>();
+            ventsToSeal = new List<Vent>();
 
             maxNumberOfMeetings = Mathf.RoundToInt(CustomOptionHolder.maxNumberOfMeetings.getSelection());
             blockSkippingInEmergencyMeetings = CustomOptionHolder.blockSkippingInEmergencyMeetings.getBool();
             noVoteIsSelfVote = CustomOptionHolder.noVoteIsSelfVote.getBool();
             hidePlayerNames = CustomOptionHolder.hidePlayerNames.getBool();
-            showGhostInfo = CustomOptionHolder.showGhostInfo.getBool();
+            ghostsSeeRoles = TheOtherRolesPlugin.GhostsSeeRoles.Value;
+            ghostsSeeTasks = TheOtherRolesPlugin.GhostsSeeTasks.Value;
         }
     }
 } 
\ No newline at end of file
index d6dbfbbb267ebf63c416b87f07abe1783362601e..a794666d934613daaf496d9d5bf968f2bca64b83 100644 (file)
@@ -23,7 +23,7 @@ namespace TheOtherRoles
         [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.CheckForEndVoting))]
         class MeetingCalculateVotesPatch {
             private static byte[] calculateVotes(MeetingHud __instance) {
-                byte[] array = new byte[__instance.playerStates.Length + 1];
+                byte[] array = new byte[16];
                 for (int i = 0; i < __instance.playerStates.Length; i++)
                 {
                     PlayerVoteArea playerVoteArea = __instance.playerStates[i];
@@ -82,10 +82,8 @@ namespace TheOtherRoles
                 return result;
             }
 
-            static bool Prefix(MeetingHud __instance)
-            {
-                if (__instance.playerStates.All((PlayerVoteArea ps) => ps.isDead || ps.didVote))
-                {
+            static bool Prefix(MeetingHud __instance) {
+                if (__instance.playerStates.All((PlayerVoteArea ps) => ps.isDead || ps.didVote)) {
                     // If skipping is disabled, replace skipps/no-votes with self vote
                     if (target == null && blockSkippingInEmergencyMeetings && noVoteIsSelfVote) {
                         foreach (PlayerVoteArea playerVoteArea in __instance.playerStates) {
@@ -103,12 +101,12 @@ namespace TheOtherRoles
                             break;
                         }
                     }
-                    byte[] array = new byte[__instance.playerStates.Length];
-                    for (int i = 0; i < __instance.playerStates.Length; i++)
-                    {
+                    byte[] array = new byte[15];
+                    for (int i = 0; i < __instance.playerStates.Length; i++) {
                         PlayerVoteArea playerVoteArea = __instance.playerStates[i];
                         array[(int)playerVoteArea.TargetPlayerId] = playerVoteArea.GetState();
                     }
+
                     // RPCVotingComplete
                     if (AmongUsClient.Instance.AmClient)
                         __instance.VotingComplete(array, exiled, tie);
@@ -375,6 +373,26 @@ namespace TheOtherRoles
                     __instance.SkipVoteButton.gameObject.SetActive(false);
             }
         }
+
+        [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.CastVote))]
+        class MeetingHudCastVotePatch {
+            static void Postfix([HarmonyArgument(0)]byte srcPlayerId, [HarmonyArgument(1)]sbyte suspectPlayerId) {
+                var source = Helpers.playerById(srcPlayerId);
+                if (source != null && source.Data != null && AmongUsClient.Instance.AmHost && TheOtherRolesPlugin.HostSeesVotesLog.Value) {
+                    string target = null;
+                    if (suspectPlayerId == -2) target = "didn't vote";
+                    else if (suspectPlayerId == -1) target = "skipped";
+                    else if (suspectPlayerId >= 0) {
+                        System.Console.WriteLine(suspectPlayerId);
+                        System.Console.WriteLine((byte)suspectPlayerId);
+                        var targetPlayer = Helpers.playerById((byte)suspectPlayerId);
+                        if (targetPlayer != null && targetPlayer.Data != null) target = $"voted {targetPlayer.Data.PlayerName}";
+                    }                    
+                    
+                    if (target != null) System.Console.WriteLine($"{source.Data.PlayerName} {target}");
+                }
+            }
+        }
     }
 
     [HarmonyPatch(typeof(ExileController), "Begin")]
@@ -406,6 +424,21 @@ namespace TheOtherRoles
             if (Trickster.trickster != null && JackInTheBox.hasJackInTheBoxLimitReached()) {
                 JackInTheBox.convertToVents();
             }
+
+            // SecurityGuard vents and cameras
+            MapOptions.camerasToAdd.ForEach(x => x.gameObject.SetActive(true));
+            var allCameras = ShipStatus.Instance.AllCameras.ToList();
+            allCameras.AddRange(MapOptions.camerasToAdd);
+            ShipStatus.Instance.AllCameras = allCameras.ToArray();
+            MapOptions.camerasToAdd = new List<SurvCamera>();
+
+            foreach (Vent vent in MapOptions.ventsToSeal) {
+                PowerTools.SpriteAnim animator = vent.GetComponent<PowerTools.SpriteAnim>(); 
+                animator?.Stop();
+                vent.myRend.sprite = animator == null ? SecurityGuard.getStaticVentSealedSprite() : SecurityGuard.getAnimatedVentSealedSprite();
+                vent.name = "SealedVent_" + vent.name;
+            }
+            MapOptions.ventsToSeal = new List<Vent>();
         }
     }
 
@@ -501,6 +534,8 @@ namespace TheOtherRoles
                         __result = ExileController.Instance.exiled.PlayerName + " was The Sidekick.";
                     else if(Spy.spy != null && ExileController.Instance.exiled.Object.PlayerId == Spy.spy.PlayerId)
                         __result = ExileController.Instance.exiled.PlayerName + " was The Spy.";
+                    else if(SecurityGuard.securityGuard != null && ExileController.Instance.exiled.Object.PlayerId == SecurityGuard.securityGuard.PlayerId)
+                        __result = ExileController.Instance.exiled.PlayerName + " was The SecurityGuard.";
                     else
                         __result = ExileController.Instance.exiled.PlayerName + " was not The Impostor.";
                 }
index 841a72a98c8be2304b832adff6ea5e99aa37d4a2..9d18c565d016d9d67daf583a9e67243005a1b295 100644 (file)
@@ -20,7 +20,8 @@ namespace TheOtherRoles {
             float num = GameOptionsData.KillDistances[Mathf.Clamp(PlayerControl.GameOptions.KillDistance, 0, 2)];
             if (!ShipStatus.Instance) return result;
             if (targetingPlayer == null) targetingPlayer = PlayerControl.LocalPlayer;
-
+            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++)
@@ -338,8 +339,6 @@ namespace TheOtherRoles {
         }
 
         public static void updateGhostInfo() {
-            if (!MapOptions.showGhostInfo) return;
-
             foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
                 if (p != PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead) continue;
 
@@ -365,11 +364,39 @@ namespace TheOtherRoles {
                 var (tasksCompleted, tasksTotal) = TasksHandler.taskInfo(p.Data);
                 string roleNames = String.Join(", ", RoleInfo.getRoleInfoForPlayer(p).Select(x => Helpers.cs(x.color, x.name)).ToArray());
                 string taskInfo = tasksTotal > 0 ? $"<color=#FAD934FF>({tasksCompleted}/{tasksTotal})</color>" : "";
-                playerGhostInfo.text = $"{roleNames} {taskInfo}".Trim();
-                if (meetingGhostInfo != null) meetingGhostInfo.text = MeetingHud.Instance.state == MeetingHud.VoteStates.Results ? "" : $"{roleNames} {taskInfo}".Trim();
+                
+                string info = "";
+                if (p == PlayerControl.LocalPlayer || (MapOptions.ghostsSeeRoles && MapOptions.ghostsSeeTasks))
+                    info = $"{roleNames} {taskInfo}".Trim();
+                else if (MapOptions.ghostsSeeTasks)
+                    info = $"{taskInfo}".Trim();
+                else if (MapOptions.ghostsSeeRoles)
+                    info = $"{roleNames}";
+
+                playerGhostInfo.text = info;
+                playerGhostInfo.gameObject.SetActive(p.Visible);
+                if (meetingGhostInfo != null) meetingGhostInfo.text = MeetingHud.Instance.state == MeetingHud.VoteStates.Results ? "" : info;
             }
         }
 
+        public static void securityGuardSetTarget() {
+            if (SecurityGuard.securityGuard == null || SecurityGuard.securityGuard != PlayerControl.LocalPlayer || ShipStatus.Instance == null || ShipStatus.Instance.AllVents == null) return;
+
+            Vent target = null;
+            Vector2 truePosition = PlayerControl.LocalPlayer.GetTruePosition();
+            float closestDistance = float.MaxValue;
+            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_")) continue;
+                float distance = Vector2.Distance(vent.transform.position, truePosition);
+                if (distance <= vent.UsableDistance && distance < closestDistance) {
+                    closestDistance = distance;
+                    target = vent;
+                }
+            }
+            SecurityGuard.ventTarget = target;
+        }
+
         public static void Postfix(PlayerControl __instance) {
             if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return;
 
@@ -419,6 +446,8 @@ namespace TheOtherRoles {
                 warlockSetTarget();
                 // Check for sidekick promotion on Jackal disconnect
                 sidekickCheckPromotion();
+                // SecurityGuard
+                securityGuardSetTarget();
             } 
         }
     }
@@ -452,23 +481,20 @@ namespace TheOtherRoles {
             }
         }
     }
-    [HarmonyPatch(typeof(KillButtonManager), nameof(KillButtonManager.PerformKill))]
-    class PerformKillPatch {
-        public static bool Prefix(KillButtonManager __instance) {
-            if (__instance.isActiveAndEnabled && __instance.CurrentTarget && !__instance.isCoolingDown && !PlayerControl.LocalPlayer.Data.IsDead && PlayerControl.LocalPlayer.CanMove) { // Among Us default checks
-                if (Helpers.handleMurderAttempt(__instance.CurrentTarget)) { // Custom checks
-                    if (Child.child != null && PlayerControl.LocalPlayer == Child.child) { // Not checked by official servers
-                        MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1);
-                        writer.Write(PlayerControl.LocalPlayer.PlayerId);
-                        writer.Write(__instance.CurrentTarget.PlayerId);
-                        AmongUsClient.Instance.FinishRpcImmediately(writer);
-                        RPCProcedure.uncheckedMurderPlayer(PlayerControl.LocalPlayer.PlayerId, __instance.CurrentTarget.PlayerId);
-                    } else { // Checked by official servers
-                        PlayerControl.LocalPlayer.RpcMurderPlayer(__instance.CurrentTarget);
-                    }
-                    __instance.SetTarget(null);
+    [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcMurderPlayer))]
+    class RpcMurderPlayer {
+        public static bool Prefix([HarmonyArgument(0)]PlayerControl target) {
+            if (Helpers.handleMurderAttempt(target)) { // Custom checks
+                if (Child.child != null && PlayerControl.LocalPlayer == Child.child) { // Not checked by official servers
+                    MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1);
+                    writer.Write(PlayerControl.LocalPlayer.PlayerId);
+                    writer.Write(target.PlayerId);
+                    AmongUsClient.Instance.FinishRpcImmediately(writer);
+                    RPCProcedure.uncheckedMurderPlayer(PlayerControl.LocalPlayer.PlayerId, target.PlayerId);
+                } else { // Checked by official servers
+                    return true;
                 }
-                   }
+            }
             return false;
         }
     }
index 87f4b34cf947e206206a604e0f8c771f42c621c3..3d622214514dd214274b788a29676a070f1040a7 100644 (file)
@@ -41,7 +41,8 @@ namespace TheOtherRoles
         Spy,
         Trickster,
         Cleaner,
-        Warlock
+        Warlock,
+        SecurityGuard
     }
 
     enum CustomRPC
@@ -85,7 +86,9 @@ namespace TheOtherRoles
         SetFutureShifted,
         PlaceJackInTheBox,
         LightsOut,
-        WarlockCurseKill
+        WarlockCurseKill,
+        PlaceCamera,
+        SealVent
     }
 
     public static class RPCProcedure {
@@ -212,6 +215,9 @@ namespace TheOtherRoles
                     case RoleId.Warlock:
                         Warlock.warlock = player;
                         break;
+                    case RoleId.SecurityGuard:
+                        SecurityGuard.securityGuard = player;
+                        break;
                     }
                 }
         }
@@ -386,6 +392,8 @@ namespace TheOtherRoles
                 Snitch.snitch = oldShifter;
             } else if (Spy.spy != null && Spy.spy == player) {
                 Spy.spy = oldShifter;
+            } else if (SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == player) {
+                SecurityGuard.securityGuard = oldShifter;
             } else { // Crewmate
             }
             
@@ -529,6 +537,7 @@ namespace TheOtherRoles
             if (player == Snitch.snitch) Snitch.clearAndReload();
             if (player == Swapper.swapper) Swapper.clearAndReload();
             if (player == Spy.spy) Spy.clearAndReload();
+            if (player == SecurityGuard.securityGuard) SecurityGuard.clearAndReload();
 
             // Impostor roles
             if (player == Morphling.morphling) Morphling.clearAndReload();
@@ -591,6 +600,33 @@ namespace TheOtherRoles
                 }
             }
         }
+
+        public static void placeCamera(byte[] buff) {
+            var referenceCamera = UnityEngine.Object.FindObjectOfType<SurvCamera>(); 
+            if (referenceCamera == null) return; // Mira HQ
+
+            SecurityGuard.remainingScrews -= SecurityGuard.camPrice;
+            SecurityGuard.placedCameras++;
+
+            Vector3 position = Vector3.zero;
+            position.x = BitConverter.ToSingle(buff, 0*sizeof(float));
+            position.y = BitConverter.ToSingle(buff, 1*sizeof(float));
+
+            var camera = UnityEngine.Object.Instantiate<SurvCamera>(referenceCamera);
+            camera.transform.position = new Vector3(position.x, position.y, referenceCamera.transform.position.z - 1f);
+            camera.CamName = $"Security Guard Camera {SecurityGuard.placedCameras}";
+            if (PlayerControl.GameOptions.MapId == 2 || PlayerControl.GameOptions.MapId == 4) camera.transform.localRotation = new Quaternion(0, 0, 1, 1); // Polus and Airship 
+            camera.gameObject.SetActive(false);
+            MapOptions.camerasToAdd.Add(camera);
+        }
+
+        public static void sealVent(int ventId) {
+            Vent vent = ShipStatus.Instance.AllVents.FirstOrDefault((x) => x != null && x.Id == ventId);
+            if (vent == null) return;
+
+            SecurityGuard.remainingScrews -= SecurityGuard.ventPrice;
+            MapOptions.ventsToSeal.Add(vent);
+        }
     }
 
     [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.HandleRpc))]
@@ -730,6 +766,12 @@ namespace TheOtherRoles
                 case (byte)CustomRPC.WarlockCurseKill:
                     RPCProcedure.warlockCurseKill(reader.ReadByte());
                     break;
+                case (byte)CustomRPC.PlaceCamera:
+                    RPCProcedure.placeCamera(reader.ReadBytesAndSize());
+                    break;
+                case (byte)CustomRPC.SealVent:
+                    RPCProcedure.sealVent(reader.ReadPackedInt32());
+                    break;
             }
         }
     }
diff --git a/Source Code/Resources/AnimatedVentSealed.png b/Source Code/Resources/AnimatedVentSealed.png
new file mode 100644 (file)
index 0000000..68a0d2d
Binary files /dev/null and b/Source Code/Resources/AnimatedVentSealed.png differ
diff --git a/Source Code/Resources/CloseVentButton.png b/Source Code/Resources/CloseVentButton.png
new file mode 100644 (file)
index 0000000..ffe2bb2
Binary files /dev/null and b/Source Code/Resources/CloseVentButton.png differ
diff --git a/Source Code/Resources/PlaceCameraButton.png b/Source Code/Resources/PlaceCameraButton.png
new file mode 100644 (file)
index 0000000..313226d
Binary files /dev/null and b/Source Code/Resources/PlaceCameraButton.png differ
diff --git a/Source Code/Resources/StaticVentSealed.png b/Source Code/Resources/StaticVentSealed.png
new file mode 100644 (file)
index 0000000..8b2abad
Binary files /dev/null and b/Source Code/Resources/StaticVentSealed.png differ
index e511cbab4d8075afed1779c2a20ff5380457777a..146a237d835b422f5413ab2f02df7da8f0413ee4 100644 (file)
@@ -76,6 +76,7 @@ namespace TheOtherRoles
             crewSettings.Add((byte)RoleId.Snitch, CustomOptionHolder.snitchSpawnRate.getSelection());
             crewSettings.Add((byte)RoleId.Jackal, CustomOptionHolder.jackalSpawnRate.getSelection());
             crewSettings.Add((byte)RoleId.Spy, CustomOptionHolder.spySpawnRate.getSelection());
+            crewSettings.Add((byte)RoleId.SecurityGuard, CustomOptionHolder.securityGuardSpawnRate.getSelection());
 
             // Set special roles
             if (impostors.Count >= 3 && maxImpostorRoles >= 3 && (rnd.Next(1, 101) <= CustomOptionHolder.mafiaSpawnRate.getSelection() * 10)) {
index e56172c782d9d4b09b10c679c920820c04b2a866..fec354d7402ff8bdcb57d440f5f86d1bb7e0d836 100644 (file)
@@ -173,7 +173,7 @@ namespace TheOtherRoles
                 "Finish your tasks to find the <color=#FF1919FF>Impostors</color>",
                 "Finish your tasks"));
             }
-            if (Jackal.jackal != null && p == Jackal.jackal) {
+            if ((Jackal.jackal != null && p == Jackal.jackal) || (Jackal.formerJackals != null && Jackal.formerJackals.Contains(p))) {
                 infos.Add(new RoleInfo("Jackal",
                 Jackal.color,
                 "Kill all Crewmates and <color=#FF1919FF>Impostors</color> to win",
@@ -197,6 +197,13 @@ namespace TheOtherRoles
                 "Confuse the <color=#FF1919FF>Impostors</color>",
                 "Confuse the Impostors"));
             }
+            if (SecurityGuard.securityGuard != null && p == SecurityGuard.securityGuard) {
+                infos.Add(new RoleInfo("Security Guard",
+                SecurityGuard.color,
+                "Seal vents and place cameras",
+                "Seal vents and place cameras"));
+            }
+
             if (infos.Count == 0 && p.Data.IsImpostor) { // Just Impostor
                 infos.Add(new RoleInfo("Impostor",
                 Palette.ImpostorRed,
diff --git a/Source Code/StreamerModePatch.cs b/Source Code/StreamerModePatch.cs
deleted file mode 100644 (file)
index 6c2c994..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-  
-using HarmonyLib;
-using UnityEngine;
-using System.Collections.Generic;
-using Hazel;
-using System;
-using UnityEngine.UI;
-using UnityEngine.Events;
-
-namespace TheOtherRoles {
-    [HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Start))]
-    public class OptionsMenuBehaviourStartPatch {
-        public static ToggleButtonBehaviour streamerModeButton;
-
-        private static void updateStreamerModeButton() {
-            if (streamerModeButton == null || streamerModeButton.gameObject == null) return;
-
-            bool on = TheOtherRolesPlugin.StreamerMode.Value;
-            Color color = on ? new Color(0f, 1f, 0.16470589f, 1f) : Color.white;
-            streamerModeButton.Background.color = color;
-            streamerModeButton.Text.text = $"Streamer Mode: {(on ? "On" : "Off")}";
-            if (streamerModeButton.Rollover) streamerModeButton.Rollover.ChangeOutColor(color);
-        }
-
-        public static void Postfix(OptionsMenuBehaviour __instance) {
-            if ((streamerModeButton == null || streamerModeButton.gameObject == null) && __instance.CensorChatButton != null) {
-                streamerModeButton = UnityEngine.Object.Instantiate(__instance.CensorChatButton, __instance.CensorChatButton.transform.parent);
-                streamerModeButton.transform.localPosition += Vector3.down * 0.25f;
-                __instance.CensorChatButton.transform.localPosition += Vector3.up * 0.25f;
-                PassiveButton button = streamerModeButton.GetComponent<PassiveButton>();
-                button.OnClick = new Button.ButtonClickedEvent();
-                button.OnClick.AddListener((UnityEngine.Events.UnityAction)onClick);
-                updateStreamerModeButton();
-            }
-
-            void onClick() {
-                TheOtherRolesPlugin.StreamerMode.Value = !TheOtherRolesPlugin.StreamerMode.Value;
-                updateStreamerModeButton();
-            }
-        }
-    }
-
-    [HarmonyPatch(typeof(TextBoxTMP), nameof(TextBoxTMP.SetText))]
-       public static class HiddenTextPatch
-       {
-               private static void Postfix(TextBoxTMP __instance)
-               {
-                       bool flag = TheOtherRolesPlugin.StreamerMode.Value && (__instance.name == "GameIdText" || __instance.name == "IpTextBox" || __instance.name == "PortTextBox");
-                       if (flag) __instance.outputText.text = new string('*', __instance.text.Length);
-               }
-       }
-}
\ No newline at end of file
index 727e0abce2a6189075956afc6971e127c5e01ff9..8c43366852713ef4f7c1ca6ee153dc203e1cc3e0 100644 (file)
@@ -48,6 +48,7 @@ namespace TheOtherRoles
             Trickster.clearAndReload();
             Cleaner.clearAndReload();
             Warlock.clearAndReload();
+            SecurityGuard.clearAndReload();
         }
 
         public static class Jester {
@@ -620,7 +621,7 @@ namespace TheOtherRoles
         }
 
         public static void removeCurrentJackal() {
-            if (jackal != null && !formerJackals.Contains(jackal)) formerJackals.Add(jackal);
+            if (!formerJackals.Contains(jackal)) formerJackals.Add(jackal);
             jackal = null;
             currentTarget = null;
             fakeSidekick = null;
@@ -808,7 +809,56 @@ namespace TheOtherRoles
             curseVictimTarget = null;
             curseKillTarget = null;
         }
-        
     }
 
+    public static class SecurityGuard {
+        public static PlayerControl securityGuard;
+        public static Color color = new Color(171/255f, 159f/255f, 55f/255f, 1f);
+
+        public static float cooldown = 30f;
+        public static int remainingScrews = 7;
+        public static int totalScrews = 7;
+        public static int ventPrice = 1;
+        public static int camPrice = 2;
+        public static int placedCameras = 0;
+        public static Vent ventTarget = null;
+
+        private static Sprite closeVentButtonSprite;
+        public static Sprite getCloseVentButtonSprite() {
+            if (closeVentButtonSprite) return closeVentButtonSprite;
+            closeVentButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.CloseVentButton.png", 115f);
+            return closeVentButtonSprite;
+        }
+
+        private static Sprite placeCameraButtonSprite;
+        public static Sprite getPlaceCameraButtonSprite() {
+            if (placeCameraButtonSprite) return placeCameraButtonSprite;
+            placeCameraButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.PlaceCameraButton.png", 115f);
+            return placeCameraButtonSprite;
+        }
+
+        private static Sprite animatedVentSealedSprite;
+        public static Sprite getAnimatedVentSealedSprite() {
+            if (animatedVentSealedSprite) return animatedVentSealedSprite;
+            animatedVentSealedSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.AnimatedVentSealed.png", 160f); // Change sprite and pixelPerUnit
+            return animatedVentSealedSprite;
+        }
+
+        private static Sprite staticVentSealedSprite;
+        public static Sprite getStaticVentSealedSprite() {
+            if (staticVentSealedSprite) return staticVentSealedSprite;
+            staticVentSealedSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.StaticVentSealed.png", 160f); // Change sprite and pixelPerUnit
+            return staticVentSealedSprite;
+        }
+
+        public static void clearAndReload() {
+            securityGuard = null;
+            ventTarget = null;
+            placedCameras = 0;
+            cooldown = CustomOptionHolder.securityGuardCooldown.getFloat();
+            totalScrews = remainingScrews = Mathf.RoundToInt(CustomOptionHolder.securityGuardTotalScrews.getFloat());
+            camPrice = Mathf.RoundToInt(CustomOptionHolder.securityGuardCamPrice.getFloat());
+            ventPrice = Mathf.RoundToInt(CustomOptionHolder.securityGuardVentPrice.getFloat());
+        }
+    }
 }
\ No newline at end of file
index 203c8d4df05696b4f02002ac371513638c675b12..88a74b4e1bc9de704720ca7c798faedfd5bbf504 100644 (file)
@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
     <PropertyGroup>
         <TargetFramework>netstandard2.1</TargetFramework>
-        <Version>2.4.0</Version>
+        <Version>2.5.0</Version>
         <Description>TheOtherRoles</Description>
         <Authors>Eisbison</Authors>
     </PropertyGroup>
index 37ddf20b825bf6e133774dce233aacf610e4da23..739b1872bd58f8e3dcdb860a52fc262ca6c61ae8 100644 (file)
@@ -114,6 +114,8 @@ namespace TheOtherRoles
             }
             else if (Spy.spy != null && Spy.spy == PlayerControl.LocalPlayer) {
                 setPlayerNameColor(Spy.spy, Spy.color);
+            } else if (SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == PlayerControl.LocalPlayer) {
+                setPlayerNameColor(SecurityGuard.securityGuard, SecurityGuard.color);
             }
             
             // No else if here, as a Lover of team Jackal needs the colors
@@ -131,7 +133,7 @@ namespace TheOtherRoles
             }
 
             // Crewmate roles with no changes: Child
-            // Impostor roles with no changes: Morphling, Camouflager, Vampire, Godfather, Eraser, Janitor and Mafioso
+            // Impostor roles with no changes: Morphling, Camouflager, Vampire, Godfather, Eraser, Janitor, Cleaner, Warlock and Mafioso
         }
 
         static void setMafiaNameTags() {
index 3f19daea4a861341709815c91ed6c49128f14d4a..207f7c3fdb51b9838b6e83656138341ecfda7835 100644 (file)
@@ -49,9 +49,13 @@ namespace TheOtherRoles
                     // 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));
+            couldUse = (@object.inVent || roleCouldUse) && !pc.IsDead && (@object.CanMove || @object.inVent);
             canUse = couldUse;
             if (canUse)
             {
@@ -179,7 +183,6 @@ namespace TheOtherRoles
         }
     }
 
-
     [HarmonyPatch(typeof(TuneRadioMinigame), nameof(TuneRadioMinigame.Begin))]
     class CommsMinigameBeginPatch {
         static void Postfix(TuneRadioMinigame __instance) {
@@ -372,4 +375,72 @@ namespace TheOtherRoles
             }
         }
     }
+
+    [HarmonyPatch]
+    class SurveillanceMinigamePatch {
+        private static int page = 0;
+        private static float timer = 0f;
+        [HarmonyPatch(typeof(SurveillanceMinigame), nameof(SurveillanceMinigame.Begin))]
+        class SurveillanceMinigameBeginPatch {
+            public static void Postfix(SurveillanceMinigame __instance) {
+                // Add securityGuard cameras
+                page = 0;
+                timer = 0;
+                if (ShipStatus.Instance.AllCameras.Length > 4 && __instance.FilteredRooms.Length > 0) {
+                    __instance.textures = __instance.textures.ToList().Concat(new RenderTexture[ShipStatus.Instance.AllCameras.Length - 4]).ToArray();
+                    for (int i = 4; i < ShipStatus.Instance.AllCameras.Length; i++) {
+                        SurvCamera surv = ShipStatus.Instance.AllCameras[i];
+                        Camera camera = UnityEngine.Object.Instantiate<Camera>(__instance.CameraPrefab);
+                        camera.transform.SetParent(__instance.transform);
+                        camera.transform.position = new Vector3(surv.transform.position.x, surv.transform.position.y, 8f);
+                        camera.orthographicSize = 2.35f;
+                        RenderTexture temporary = RenderTexture.GetTemporary(256, 256, 16, 0);
+                        __instance.textures[i] = temporary;
+                        camera.targetTexture = temporary;
+                    }
+                }
+            }
+        }
+
+        [HarmonyPatch(typeof(SurveillanceMinigame), nameof(SurveillanceMinigame.Update))]
+        class SurveillanceMinigameUpdatePatch {
+
+            public static bool Prefix(SurveillanceMinigame __instance) {
+                // Update normal and securityGuard cameras
+                timer += Time.deltaTime;
+                int numberOfPages = Mathf.CeilToInt(ShipStatus.Instance.AllCameras.Length / 4f);
+
+                bool update = false;
+
+                if (timer > 3f || Input.GetKeyDown(KeyCode.RightArrow)) {
+                    update = true;
+                    timer = 0f;
+                    page = (page + 1) % numberOfPages;
+                } else if (Input.GetKeyDown(KeyCode.LeftArrow)) {
+                    page = (page + numberOfPages - 1) % numberOfPages;
+                    update = true;
+                    timer = 0f;
+                }
+
+                if ((__instance.isStatic || update) && !PlayerTask.PlayerHasTaskOfType<IHudOverrideTask>(PlayerControl.LocalPlayer)) {
+                    __instance.isStatic = false;
+                    for (int i = 0; i < __instance.ViewPorts.Length; i++) {
+                        __instance.ViewPorts[i].sharedMaterial = __instance.DefaultMaterial;
+                        __instance.SabText[i].gameObject.SetActive(false);
+                        if (page * 4 + i < __instance.textures.Length)
+                            __instance.ViewPorts[i].material.SetTexture("_MainTex", __instance.textures[page * 4 + i]);
+                        else
+                            __instance.ViewPorts[i].sharedMaterial = __instance.StaticMaterial;
+                    }
+                } else if (!__instance.isStatic && PlayerTask.PlayerHasTaskOfType<HudOverrideTask>(PlayerControl.LocalPlayer)) {
+                    __instance.isStatic = true;
+                    for (int j = 0; j < __instance.ViewPorts.Length; j++) {
+                        __instance.ViewPorts[j].sharedMaterial = __instance.StaticMaterial;
+                        __instance.SabText[j].gameObject.SetActive(true);
+                    }
+                }
+                return false;
+            }
+        }
+    }
 }
\ No newline at end of file