From: Eisbison Date: Mon, 26 Apr 2021 17:34:45 +0000 (+0200) Subject: Version 2.5.0 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=10ac65fef4b7f39d34bb7c295a1237a0c6b97608;p=rhonda%2Ftheotherroles.git Version 2.5.0 --- diff --git a/Images/Preview_2.3.0.png b/Images/Preview_2.3.0.png deleted file mode 100644 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 index 0000000..3c43d5d Binary files /dev/null and b/Images/Preview_2.5.0.png differ diff --git a/README.md b/README.md index da45dd8..72cf984 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Discord](https://discord.com/assets/e4923594e694a21542a489471ecffa50.svg)](https://discord.gg/77RkMJHWsM) ![eisbison banner](./Images/Banner.png) -![eisbison infographic](./Images/Preview_2.3.0.png) +![eisbison infographic](./Images/Preview_2.5.0.png) # The Other Roles @@ -32,6 +32,7 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein # Releases | Among Us - Version| Mod Version | Link | |----------|-------------|-----------------| +| 2021.4.14s| v2.5.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.5.0/TheOtherRoles.zip) | 2021.4.14s| v2.4.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.4.0/TheOtherRoles.zip) | 2021.4.14s| v2.3.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.3.0/TheOtherRoles.zip) | 2021.4.14s| v2.2.2| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.2.2/TheOtherRoles.zip) @@ -55,6 +56,11 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein
Click to show the Changelog +**Version 2.5.0** +- **New Role:** [Security Guard](#security-guard) +- Fixed a bug where the game would stop after the first meeting +- Fixed a bug where killing with the hotkey Q ignored shields + **Version 2.4.0** - **New Role:** [Warlock](#warlock) - Added an option that allows ghosts to see the roles and remaining tasks of other players @@ -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 ----------------------- +## Security Guard +### **Team: Crewmates** +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.\ +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.\ +The new camera will be visible after the next meeting and accessible by everyone.\ +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.\ +**NOTE:** +- Tickster boxes can't be sealed +- The remaining number of screws can be seen above his special button +- 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. + + +### Game Options +| Name | Description +|----------|:-------------:| +| Security Guard Spawn Chance | +| Security Guard Cooldown | +| Security Guard Number Of Screws | The number of screws that a Security Guard can use in a game +| Number Of Screws Per Cam | The number of screws it takes to place a camera +| Number Of Screws Per Vent | The number of screws it takes to seal a vent +----------------------- + # Source code It's bad I know, this is a side project and my second week of modding. So there are no best practices around here. diff --git a/Source Code/Buttons.cs b/Source Code/Buttons.cs index b65493d..fb54c6e 100644 --- a/Source Code/Buttons.cs +++ b/Source Code/Buttons.cs @@ -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(); diff --git a/Source Code/ChatCommands.cs b/Source Code/ChatCommands.cs index fb7df17..930fa85 100644 --- a/Source Code/ChatCommands.cs +++ b/Source Code/ChatCommands.cs @@ -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 index 0000000..fff8aa8 --- /dev/null +++ b/Source Code/ClientOptionsPatch.cs @@ -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.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 diff --git a/Source Code/CustomButton.cs b/Source Code/CustomButton.cs index c18999b..d47c77e 100644 --- a/Source Code/CustomButton.cs +++ b/Source Code/CustomButton.cs @@ -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(); - button.OnClick.RemoveAllListeners(); + button.OnClick = new Button.ButtonClickedEvent(); button.OnClick.AddListener((UnityEngine.Events.UnityAction)onClickEvent); setActive(false); diff --git a/Source Code/CustomOptions.cs b/Source Code/CustomOptions.cs index a3bd672..fa60fb7 100644 --- a/Source Code/CustomOptions.cs +++ b/Source Code/CustomOptions.cs @@ -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 blockedRolePairings = new Dictionary(); @@ -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(); diff --git a/Source Code/GameStartManagerPatch.cs b/Source Code/GameStartManagerPatch.cs index d01d123..597ace7 100644 --- a/Source Code/GameStartManagerPatch.cs +++ b/Source Code/GameStartManagerPatch.cs @@ -61,10 +61,10 @@ namespace TheOtherRoles { continue; else if (!playerVersions.ContainsKey(client.Id)) { blockStart = true; - message += $"{client.Character.Data.PlayerName} has an outdated or no version of The Other Roles\n"; + message += $"{client.Character.Data.PlayerName} has a different or no version of The Other Roles\n"; } else if (playerVersions[client.Id].Item1 != TheOtherRolesPlugin.Major || playerVersions[client.Id].Item2 != TheOtherRolesPlugin.Minor || playerVersions[client.Id].Item3 != TheOtherRolesPlugin.Patch) { blockStart = true; - message += $"{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"; + message += $"{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"; } } if (blockStart) { diff --git a/Source Code/Helpers.cs b/Source Code/Helpers.cs index 5706551..3a5f52a 100644 --- a/Source Code/Helpers.cs +++ b/Source Code/Helpers.cs @@ -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) { diff --git a/Source Code/Main.cs b/Source Code/Main.cs index a6681c4..a20402a 100644 --- a/Source Code/Main.cs +++ b/Source Code/Main.cs @@ -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 DebugMode { get; private set; } public static ConfigEntry StreamerMode { get; set; } + public static ConfigEntry GhostsSeeTasks { get; set; } + public static ConfigEntry GhostsSeeRoles { get; set; } + public static ConfigEntry HostSeesVotesLog { get; set; } public static ConfigEntry StreamerModeReplacementText { get; set; } public static ConfigEntry StreamerModeReplacementColor { get; set; } public static ConfigEntry 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; + } } } diff --git a/Source Code/MapOptions.cs b/Source Code/MapOptions.cs index e324c96..7a20b66 100644 --- a/Source Code/MapOptions.cs +++ b/Source Code/MapOptions.cs @@ -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 camerasToAdd = new List(); + public static List ventsToSeal = new List(); public static void clearAndReloadMapOptions() { meetingsCount = 0; + camerasToAdd = new List(); + ventsToSeal = new List(); 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 diff --git a/Source Code/MeetingPatch.cs b/Source Code/MeetingPatch.cs index d6dbfbb..a794666 100644 --- a/Source Code/MeetingPatch.cs +++ b/Source Code/MeetingPatch.cs @@ -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(); + + foreach (Vent vent in MapOptions.ventsToSeal) { + PowerTools.SpriteAnim animator = vent.GetComponent(); + animator?.Stop(); + vent.myRend.sprite = animator == null ? SecurityGuard.getStaticVentSealedSprite() : SecurityGuard.getAnimatedVentSealedSprite(); + vent.name = "SealedVent_" + vent.name; + } + MapOptions.ventsToSeal = new List(); } } @@ -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."; } diff --git a/Source Code/PlayerControlPatch.cs b/Source Code/PlayerControlPatch.cs index 841a72a..9d18c56 100644 --- a/Source Code/PlayerControlPatch.cs +++ b/Source Code/PlayerControlPatch.cs @@ -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 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 ? $"({tasksCompleted}/{tasksTotal})" : ""; - 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; } } diff --git a/Source Code/RPC.cs b/Source Code/RPC.cs index 87f4b34..3d62221 100644 --- a/Source Code/RPC.cs +++ b/Source Code/RPC.cs @@ -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(); + 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(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 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 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 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 index 0000000..8b2abad Binary files /dev/null and b/Source Code/Resources/StaticVentSealed.png differ diff --git a/Source Code/RoleAssignmentPatch.cs b/Source Code/RoleAssignmentPatch.cs index e511cba..146a237 100644 --- a/Source Code/RoleAssignmentPatch.cs +++ b/Source Code/RoleAssignmentPatch.cs @@ -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)) { diff --git a/Source Code/RoleInfo.cs b/Source Code/RoleInfo.cs index e56172c..fec354d 100644 --- a/Source Code/RoleInfo.cs +++ b/Source Code/RoleInfo.cs @@ -173,7 +173,7 @@ namespace TheOtherRoles "Finish your tasks to find the Impostors", "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 Impostors to win", @@ -197,6 +197,13 @@ namespace TheOtherRoles "Confuse the Impostors", "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 index 6c2c994..0000000 --- a/Source Code/StreamerModePatch.cs +++ /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(); - 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 diff --git a/Source Code/TheOtherRoles.cs b/Source Code/TheOtherRoles.cs index 727e0ab..8c43366 100644 --- a/Source Code/TheOtherRoles.cs +++ b/Source Code/TheOtherRoles.cs @@ -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 diff --git a/Source Code/TheOtherRoles.csproj b/Source Code/TheOtherRoles.csproj index 203c8d4..88a74b4 100644 --- a/Source Code/TheOtherRoles.csproj +++ b/Source Code/TheOtherRoles.csproj @@ -1,7 +1,7 @@ netstandard2.1 - 2.4.0 + 2.5.0 TheOtherRoles Eisbison diff --git a/Source Code/UpdatePatch.cs b/Source Code/UpdatePatch.cs index 37ddf20..739b187 100644 --- a/Source Code/UpdatePatch.cs +++ b/Source Code/UpdatePatch.cs @@ -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() { diff --git a/Source Code/UsablesPatch.cs b/Source Code/UsablesPatch.cs index 3f19dae..207f7c3 100644 --- a/Source Code/UsablesPatch.cs +++ b/Source Code/UsablesPatch.cs @@ -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(__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(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(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