]> git.deb.at Git - rhonda/theotherroles.git/commitdiff
Version 3.4.0
authordwd0tcom <info@dominikwierl.com>
Sat, 22 Jan 2022 16:31:19 +0000 (17:31 +0100)
committerdwd0tcom <info@dominikwierl.com>
Sat, 22 Jan 2022 16:31:19 +0000 (17:31 +0100)
25 files changed:
Images/TOR_roles.jpg
README.md
TheOtherRoles/Buttons.cs
TheOtherRoles/CustomOptionHolder.cs
TheOtherRoles/Helpers.cs
TheOtherRoles/Main.cs
TheOtherRoles/Modules/ChatCommands.cs
TheOtherRoles/Modules/ModUpdater.cs
TheOtherRoles/Objects/CustomButton.cs
TheOtherRoles/Patches/CredentialsPatch.cs
TheOtherRoles/Patches/ExileControllerPatch.cs
TheOtherRoles/Patches/IntroPatch.cs
TheOtherRoles/Patches/MeetingPatch.cs
TheOtherRoles/Patches/PlayerControlPatch.cs
TheOtherRoles/Patches/RoleAssignmentPatch.cs
TheOtherRoles/Patches/UpdatePatch.cs
TheOtherRoles/Patches/UsablesPatch.cs
TheOtherRoles/RPC.cs
TheOtherRoles/Resources/DeputizeButton.png [new file with mode: 0644]
TheOtherRoles/Resources/DeputyHandcuffButton.png [new file with mode: 0644]
TheOtherRoles/Resources/DeputyHandcuffed.png [new file with mode: 0644]
TheOtherRoles/Resources/RepairButton.png
TheOtherRoles/RoleInfo.cs
TheOtherRoles/TheOtherRoles.cs
TheOtherRoles/TheOtherRoles.csproj

index c854825eabccb9793e1f46bd56355c950f6d1921..fcbe686cfed2757f8b3c7b525fc60d429f9d0f38 100644 (file)
Binary files a/Images/TOR_roles.jpg and b/Images/TOR_roles.jpg differ
index cc272772d2f0bfecdfc44bb4005669c299832d27..1eba5e90c5ea0ca68046d2bbb5b8cea0b0adb40b 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,4 +1,6 @@
 ![eisbison banner](./Images/TOR_logo.png)
+<p align="center"><a href="https://github.com/Eisbison/TheOtherRoles/releases/"><img src="https://badgen.net/github/release/eisbison/theotherroles"></a></p>
+
 <p align="center">
 This mod is not affiliated with Among Us or Innersloth LLC, and the content contained therein is not endorsed or otherwise sponsored by Innersloth LLC. Portions of the materials contained herein are property of Innersloth LLC. © Innersloth LLC.</p>
 
@@ -23,10 +25,11 @@ Even more roles are coming soon :)
 | [Mafioso (Mafia)](#mafia) | [Medic](#medic) |  |  |
 | [Janitor (Mafia)](#mafia)  | [Security Guard](#security-guard) |  |  |
 | [Morphling](#morphling) | [Seer](#seer) |  |  |
-| [Trickster](#trickster) | [Sheriff](#sheriff) |  |  |
-| [Vampire](#vampire) | [Shifter](#shifter) |  |  |
-| [Warlock](#warlock) | [Snitch](#snitch) |  |  |
-| [Witch](#witch) | [Spy](#spy) |  |  |
+| [Trickster](#trickster) | [Sheriff](#sheriff) 
+| [Vampire](#vampire) | [Deputy](#deputy) |  |
+| [Warlock](#warlock) | [Shifter](#shifter) |  |  |
+| [Witch](#witch) | [Snitch](#snitch) |  |  |
+|  | [Spy](#spy) |  |  |
 |  | [Swapper](#swapper) |  |  |
 |  | [Time Master](#time-master) |  |  |
 |  |  [Tracker](#tracker) |  |  |
@@ -38,6 +41,8 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein
 # Releases
 | Among Us - Version| Mod Version | Link |
 |----------|-------------|-----------------|
+| 2021.12.15s| v3.4.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v3.4.0/TheOtherRoles.zip)
+| 2021.12.15s| v3.3.4| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v3.3.4/TheOtherRoles.zip)
 | 2021.12.15s| v3.3.3| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v3.3.3/TheOtherRoles.zip)
 | 2021.12.15s| v3.3.2| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v3.3.2/TheOtherRoles.zip)
 | 2021.12.14s| v3.3.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v3.3.1/TheOtherRoles.zip)
@@ -91,6 +96,20 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein
 # Changelog
 <details>
   <summary>Click to show the Changelog</summary>
+  
+**Version 3.4.0**
+- Added new Role [Deputy](#deputy) thanks [gendelo3](https://github.com/gendelo3)
+- Added Hacker option "Cant Move During Mobile Gadget Duration"
+- Added Security Guard mobile cams after placing all screws
+- Added Lover option "Enable Lover Chat"
+- Added return votes in meetings: You'll now get your votes back if your target got shot by the Guesser
+- Added New Option for Guesser: Guesser can't guess Snitch if they has done all tasks (created by Mr_Fluff)
+- Added The Other Roles changelog announcement popup
+- Changed that the Bounty Hunter exclude their Lover
+- Changed the position of the Witch icon in meetings for better visibility
+- Fixed a bug where the spy had a white name for Impostors in chat
+- Fixed a bug where the Guesser and Swapper UI in meetings was behind the visor cosmetics
+
 
 **Version 3.3.3**
 - Fixed a bug where a guessed Guesser could guess
@@ -98,7 +117,7 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein
 - Removed Hacker vitals for Skeld & dlekS
 - Changed the Guesser option "Other Guesser Spawn Rate" to "Both Guesser Spawn Rate" (now only take effect when the chance for the first guesser was successful)
 - Changed Hacker vitals to doorlog for MIRA HQ
-  
+
 **Version 3.3.2**
 - Fixed a bug where you can't create a lobby on Among Us 2021.12.15
 
@@ -777,17 +796,14 @@ The Warlock can still perform normal kills, but the two buttons share the same c
 ## Bounty Hunter
 ### **Team: Impostors**
 \
-Created by  [Mallöris](https://github.com/Mallaris)\
-\
 The Bounty Hunter is an Impostor, that continuously get bounties (the targeted player doesn't get notified).\
 The target of the Bounty Hunter swaps after every meeting and after a configurable amount of time.\
 If the Bounty Hunter kills their target, their kill cooldown will be a lot less than usual.\
 Killing a player that's not their current target results in an increased kill cooldown.\
 Depending on the options, there'll be an arrow pointing towards the current target.\
-
 \
 **NOTE:**
-- The target won't be an Impostor or a Spy
+- The target won't be an Impostor, a Spy or the Bounty Hunter's Lover.
 - Killing the target resets the timer and a new target will be selected.
 
 ### Game Options
@@ -815,7 +831,7 @@ Depending on the options you can choose whether voting the Witch out will save a
 **NOTE:**
 - The spellbound players will die before the voted player dies (which might trigger e.g. trigger an Impostor win condition, even if the Witch is the one being voted)
 
-\
+
 ### Game Options
 | Name | Description |
 |----------|:-------------:|
@@ -839,7 +855,7 @@ You can only shoot during the voting time.\
 Depending on the options, the Guesser can't guess the shielded player and depending on the Medic options the Medic/shielded player might be notified (no one will die, independently of what the Guesser guessed).\
 \
 **NOTE:**
-- The vote of a shot player, won't be counted
+- If a player gets shot, you'll get back your votes
 - You can't guess the role **Nice Mini** for obvious reasons
 - You can't guess the role **Lover**, you'll have to guess the primary role of one of the Lovers, to kill both of them
 - Jester wins won't be triggered, if the Guesser shoots the Jester before the Jester gets voted out
@@ -849,12 +865,13 @@ Depending on the options, the Guesser can't guess the shielded player and depend
 |----------|:-------------:|
 | Guesser Spawn Chance | -
 | Chance That The Guesser Is An Impostor | -
-| Guesser Number Of Shots Per Game| -
-| Guesser Can Shoot Multiple Times Per Meeting| -
-| Guesses Visible In Ghost Chat| -
-| Guesses Ignore The Medic Shield| -
-| Evil Guesser Can Guess The Spy| -
-| Both Guesser Spawn Rate| -
+| Guesser Number Of Shots Per Game | -
+| Guesser Can Shoot Multiple Times Per Meeting |  -
+| Guesses Visible In Ghost Chat | -
+| Guesses Ignore The Medic Shield | -
+| Evil Guesser Can Guess The Spy | -
+| Both Guesser Spawn Rate | -
+| Guesser Can't Guess Snitch When Tasks Completed | -
 
 -----------------------
 
@@ -870,6 +887,7 @@ The Lovers win, if they are both alive when the game ends. They can also win wit
 If one of the Lovers is a killer (i.e. Jackal/Sidekick/Impostor), they can achieve a "Lovers solo win" where only the Lovers win.\
 If there is no killer among the Lovers (e.g. an Arsonist Lover + Crewmate Lover) and they are both alive when the game ends, they win together with the Crewmates.\
 If there's a team Impostor/Jackal Lover in the game, the tasks of a Crewmate Lover won't be counted (for a task win) as long as they're alive. If the Lover dies, their tasks will also be counted.\
+You can enable an exclusive chat only for Lovers\
 \
 **NOTE:**
 - In a 2 Cremates vs 2 Impostors (or 2 members of team Jackal) and the Lovers are not in the same team, the game is not automatically over since the Lovers can still achieve a solo win. E.g. if there are the following roles Impostor + ImpLover + Lover + Crewmate left, the game will not end and the next kill will decide if the Impostors or Lovers win.
@@ -882,6 +900,7 @@ If there's a team Impostor/Jackal Lover in the game, the tasks of a Crewmate Lov
 | Chance That One Lover Is Impostor | -
 | Both Lovers Die | Whether the second Lover suicides, if the first one dies
 | Lovers Can Have Another Role | If set to true, the Lovers can have a second role
+| Enable Lover Chat | -
 -----------------------
 
 
@@ -899,7 +918,33 @@ If they try to kill a Crewmate, they die instead.
 |----------|:-------------:|
 | Sheriff Spawn Chance | -
 | Sheriff Cooldown | -
-| Neutrals Can Die To Sheriff | -
+| Sheriff Can Kill Neutrals | -
+| Sheriff Has A Deputy | Deputy can not be in game without Sheriff
+-----------------------
+
+## Deputy
+### **Team: Crewmates**
+The Deputy has the ability to handcuff player.
+Handcuffs will be hidden until the handcuffed player try to use a disabled button/hotkey.
+Handcuffs disable: 
+- Kill
+- Abilities
+- Vent
+- Report
+
+**NOTE:**
+- Duration starts after the handcuffs become visible.
+- Deputy can not be in game without Sheriff.
+
+### Game Options
+| Name | Description |
+|----------|:-------------:|
+| Deputy Number Of Handcuffs | -
+| Handcuff Cooldown| -
+| Handcuff Duration | -
+| Sheriff And Deputy Know Each Other | -
+| Deputy Gets Promoted To Sheriff | "Off", "On (Immediately)" or "On (After Meeting)"
+| Deputy Keeps Handcuffs When Promoted |-
 -----------------------
 
 ## Jester
@@ -1066,6 +1111,7 @@ Otherwise they see the same information as everyone else.
 **Admin table:** The Hacker can see the colors (or color types) of the players on the table.\
 **Vitals**: The Hacker can see how long dead players have been dead for.\
 The Hacker can access his mobile gadgets (vitals & admin table), with a maximum of charges (uses) and a configurable amount of tasks needed to recharge.\
+While accessing those mobile gadgets, the Hacker is not able to move.\
 \
 **NOTE:**
 - If the Morphling morphs or the Camouflager camouflages, the colors on the admin table change accordingly
@@ -1076,11 +1122,12 @@ The Hacker can access his mobile gadgets (vitals & admin table), with a maximum
 | Name | Description |
 |----------|:-------------:|
 | Hacker Spawn Chance | -
-| Hacker Cooldown| -
+| Hacker Cooldown | -
 | Hacker Duration | Sets how long the "Hacker mode" remains active
 | Hacker Only Sees Color Type | Sets if the Hacker sees the player colors on the admin table or only white/gray (for Lighter and darker colors)
-| Max Mobile Gadget Charges| -
+| Max Mobile Gadget Charges | -
 | Number Of Tasks Needed For Recharging | Number of tasks to get a charge
+| Can't Move During Cam Duration | -
 -----------------------
 
 
@@ -1170,6 +1217,7 @@ Depending on the options, the Tracker has another ability: They can track all co
 When the Snitch finishes all the tasks, arrows will appear (only visible to the Snitch) that point to the Impostors (depending on the options also to members of team Jackal).
 When the Snitch has one task left (configurable) the Snitch will be revealed to the Impostors (depending on the options also to members of team Jackal) with an arrow pointing to the Snitch.
 
+
 ### Game Options
 | Name | Description
 |----------|:-------------:|
@@ -1177,6 +1225,7 @@ When the Snitch has one task left (configurable) the Snitch will be revealed to
 | Task Count Where The Snitch Will Be Revealed | -
 | Include Team Jackal | -
 | Use Different Arrow Color For Team Jackal | -
+| Snitch can't be guessed after finishing all their tasks | -
 -----------------------
 
 ## Jackal
@@ -1269,12 +1318,15 @@ The Security Guard is a Crewmate that has a certain number of screws that they c
 Placing a new camera and sealing vents takes a configurable amount of screws. The total number of screws that a Security Guard 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:**
-- Trickster boxes can't be sealed
-- The Security Guard can't place Cameras on MiraHQ
-- The remaining number of screws can be seen above their 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.
 
+- Trickster boxes can't be sealed
+- The Security Guard can't place cameras on MiraHQ
+- The remaining number of screws can be seen above their 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
+- Security Guard can access mobile cameras after placing all screws
+- While accessing the mobile cameras, the Security Guard is not able to move
 
 ### Game Options
 | Name | Description
@@ -1284,13 +1336,15 @@ The vents will be sealed after the next meeting, players can't enter or exit sea
 | 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
+| Security Guard Duration | -
+| Gadget Max Charges | -
+| Number Of Tasks Needed For Recharging | -
+| Can't Move During Cam Duration | -
 -----------------------
 
 ## Bait
 ### **Team: Crewmates**
-\
-Created by [Mallöris](https://github.com/Mallaris)\
-\
+
 The Bait is a Crewmate that if killed, forces the killer to self report the body (you can configure a delay in the options).
 Additionally, the Bait can see if someone is inside a vent (depending on the options the exact vent gets
 an outline or all vents do).
@@ -1306,9 +1360,7 @@ an outline or all vents do).
 
 ## Medium
 ### **Team: Crewmates**
-\
-Created by [Mallöris](https://github.com/Mallaris)\
-\
+
 The medium is a crewmate who can ask the souls of dead players for information. Like the Seer, it sees the places where the players have died (after the next meeting) and can question them. It then gets random information about the soul or the killer in the chat. The souls only stay for one round, i.e. until the next meeting. Depending on the options, the souls can only be questioned once and then disappear.
 During the meetings you can see, whether a player wears a darker or a lighter color, represented by (D) or (L) in the names.
 
@@ -1329,9 +1381,7 @@ What is your killers role? (mini exluded)
 
 ## Vulture
 ### **Team: Neutral**
-\
-Created by [Mallöris](https://github.com/Mallaris)\
-\
+
 The Vulture does not have any tasks, they have to win the game as a solo.\
 The Vulture is a neutral role that must eat a specified number of corpses (depending on the options) in order to win.\
 Depending on the options, when a player dies, the Vulture gets an arrow pointing to the corpse.
index de050d2cb297ef954656a2da9e2246fea1a7752b..2687284f1d4818b6f2b096f3853d1d26cf61a0e3 100644 (file)
@@ -5,6 +5,7 @@ using UnityEngine;
 using static TheOtherRoles.TheOtherRoles;
 using TheOtherRoles.Objects;
 using System.Linq;
+using System.Collections.Generic;
 
 namespace TheOtherRoles
 {
@@ -14,6 +15,7 @@ namespace TheOtherRoles
         private static CustomButton engineerRepairButton;
         private static CustomButton janitorCleanButton;
         private static CustomButton sheriffKillButton;
+        private static CustomButton deputyHandcuffButton;
         private static CustomButton timeMasterShieldButton;
         private static CustomButton medicShieldButton;
         private static CustomButton shifterShiftButton;
@@ -36,13 +38,18 @@ namespace TheOtherRoles
         public static CustomButton cleanerCleanButton;
         public static CustomButton warlockCurseButton;
         public static CustomButton securityGuardButton;
+        public static CustomButton securityGuardCamButton;
         public static CustomButton arsonistButton;
         public static CustomButton vultureEatButton;
         public static CustomButton mediumButton;
         public static CustomButton pursuerButton;
         public static CustomButton witchSpellButton;
 
+        public static Dictionary<byte, List<CustomButton>> deputyHandcuffedButtons = null;
+
         public static TMPro.TMP_Text securityGuardButtonScrewsText;
+        public static TMPro.TMP_Text securityGuardChargesText;
+        public static TMPro.TMP_Text deputyButtonHandcuffsText;
         public static TMPro.TMP_Text pursuerButtonBlanksText;
         public static TMPro.TMP_Text hackerAdminTableChargesText;
         public static TMPro.TMP_Text hackerVitalsChargesText;
@@ -51,6 +58,7 @@ namespace TheOtherRoles
             engineerRepairButton.MaxTimer = 0f;
             janitorCleanButton.MaxTimer = Janitor.cooldown;
             sheriffKillButton.MaxTimer = Sheriff.cooldown;
+            deputyHandcuffButton.MaxTimer = Deputy.handcuffCooldown;
             timeMasterShieldButton.MaxTimer = TimeMaster.cooldown;
             medicShieldButton.MaxTimer = 0f;
             shifterShiftButton.MaxTimer = 0f;
@@ -72,6 +80,7 @@ namespace TheOtherRoles
             cleanerCleanButton.MaxTimer = Cleaner.cooldown;
             warlockCurseButton.MaxTimer = Warlock.cooldown;
             securityGuardButton.MaxTimer = SecurityGuard.cooldown;
+            securityGuardCamButton.MaxTimer = SecurityGuard.cooldown;
             arsonistButton.MaxTimer = Arsonist.cooldown;
             vultureEatButton.MaxTimer = Vulture.cooldown;
             mediumButton.MaxTimer = Medium.cooldown;
@@ -92,6 +101,7 @@ namespace TheOtherRoles
             mediumButton.EffectDuration = Medium.duration;
             trackerTrackCorpsesButton.EffectDuration = Tracker.corpsesTrackingDuration;
             witchSpellButton.EffectDuration = Witch.spellCastingDuration;
+            securityGuardCamButton.EffectDuration = SecurityGuard.duration;
             // Already set the timer to the max, as the button is enabled during the game and not available at the start
             lightsOutButton.Timer = lightsOutButton.MaxTimer;
         }
@@ -102,6 +112,73 @@ namespace TheOtherRoles
             timeMasterShieldButton.actionButton.cooldownTimerText.color = Palette.EnabledColor;
         }
 
+        private static void addReplacementHandcuffedButton(CustomButton button, Vector3? positionOffset = null, Func<bool> couldUse = null)
+        {
+            Vector3 positionOffsetValue = positionOffset ?? button.PositionOffset;  // For non custom buttons, we can set these manually.
+            positionOffsetValue.z = -0.1f;
+            couldUse = couldUse ?? button.CouldUse;
+            CustomButton replacementHandcuffedButton = new CustomButton(() => { }, () => { return true; }, couldUse, () => { }, Deputy.getHandcuffedButtonSprite(), positionOffsetValue, button.hudManager, button.hotkey,
+                true, Deputy.handcuffDuration, () => { }, button.mirror);
+            replacementHandcuffedButton.Timer = replacementHandcuffedButton.EffectDuration;
+            replacementHandcuffedButton.actionButton.cooldownTimerText.color = new Color(0F, 0.8F, 0F);
+            replacementHandcuffedButton.isEffectActive = true;
+            if (deputyHandcuffedButtons.ContainsKey(PlayerControl.LocalPlayer.PlayerId))
+                deputyHandcuffedButtons[PlayerControl.LocalPlayer.PlayerId].Add(replacementHandcuffedButton);
+            else
+                deputyHandcuffedButtons.Add(PlayerControl.LocalPlayer.PlayerId, new List<CustomButton> { replacementHandcuffedButton });
+        }
+        
+        // Disables / Enables all Buttons (except the ones disabled in the Deputy class), and replaces them with new buttons.
+        public static void setAllButtonsHandcuffedStatus(bool handcuffed, bool reset = false)
+        {
+            if (reset) {
+                deputyHandcuffedButtons = new Dictionary<byte, List<CustomButton>>();
+                return;
+            }
+            if (handcuffed && !deputyHandcuffedButtons.ContainsKey(PlayerControl.LocalPlayer.PlayerId))
+            {
+                int maxI = CustomButton.buttons.Count;
+                for (int i = 0; i < maxI; i++)
+                {
+                    try
+                    {
+                        if (CustomButton.buttons[i].HasButton())  // For each custombutton the player has
+                        {
+                            addReplacementHandcuffedButton(CustomButton.buttons[i]);  // The new buttons are the only non-handcuffed buttons now!
+                        }
+                        CustomButton.buttons[i].isHandcuffed = true;
+                    }
+                    catch (NullReferenceException)
+                    {
+                        System.Console.WriteLine("[WARNING] NullReferenceException from MeetingEndedUpdate().HasButton(), if theres only one warning its fine");  // Note: idk what this is good for, but i copied it from above /gendelo
+                    }
+                }
+
+                // Non Custom (Vanilla) Buttons. The Originals are disabled / hidden in UpdatePatch.cs already, just need to replace them. Can use any button, as we replace onclick etc anyways.
+                // Kill Button if enabled for the Role
+                if (HudManager.Instance.KillButton.isActiveAndEnabled) addReplacementHandcuffedButton(arsonistButton, new Vector3(0, 1f, 0), couldUse: () => { return HudManager.Instance.KillButton.currentTarget != null; });
+                // Vent Button if enabled
+                if (PlayerControl.LocalPlayer.roleCanUseVents()) addReplacementHandcuffedButton(arsonistButton, new Vector3(-1.8f, 1f, 0), couldUse: () => { return HudManager.Instance.ImpostorVentButton.currentTarget != null; });
+                // Report Button
+                addReplacementHandcuffedButton(arsonistButton, new Vector3(-0.9f, -0.06f, 0), () => { return HudManager.Instance.ReportButton.enabled; });
+            }
+            else if (!handcuffed && deputyHandcuffedButtons.ContainsKey(PlayerControl.LocalPlayer.PlayerId))  // Reset to original. Disables the replacements, enables the original buttons.
+            {
+                foreach (CustomButton replacementButton in deputyHandcuffedButtons[PlayerControl.LocalPlayer.PlayerId])
+                {
+                    replacementButton.HasButton = () => { return false; };
+                    replacementButton.Update(); // To make it disappear properly.
+                    CustomButton.buttons.Remove(replacementButton);
+                }
+                deputyHandcuffedButtons.Remove(PlayerControl.LocalPlayer.PlayerId);
+
+                foreach (CustomButton button in CustomButton.buttons)
+                {
+                    button.isHandcuffed = false;
+                }
+            }
+        }
+
         public static void Postfix(HudManager __instance)
         {
             // Engineer Repair
@@ -112,7 +189,7 @@ namespace TheOtherRoles
                     MessageWriter usedRepairWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.EngineerUsedRepair, Hazel.SendOption.Reliable, -1);
                     AmongUsClient.Instance.FinishRpcImmediately(usedRepairWriter);
                     RPCProcedure.engineerUsedRepair();
+
                     foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks) {
                         if (task.TaskType == TaskTypes.FixLights) {
                             MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.EngineerFixLights, Hazel.SendOption.Reliable, -1);
@@ -163,7 +240,7 @@ namespace TheOtherRoles
                                 if (Vector2.Distance(truePosition2, truePosition) <= PlayerControl.LocalPlayer.MaxReportDistance && PlayerControl.LocalPlayer.CanMove && !PhysicsHelpers.AnythingBetween(truePosition, truePosition2, Constants.ShipAndObjectsMask, false))
                                 {
                                     GameData.PlayerInfo playerInfo = GameData.Instance.GetPlayerById(component.ParentId);
-                                    
+
                                     MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.CleanBody, Hazel.SendOption.Reliable, -1);
                                     writer.Write(playerInfo.PlayerId);
                                     AmongUsClient.Instance.FinishRpcImmediately(writer);
@@ -177,7 +254,7 @@ namespace TheOtherRoles
                     }
                 },
                 () => { return Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
-                () => { return __instance.ReportButton.graphic.color == Palette.EnabledColor  && PlayerControl.LocalPlayer.CanMove; },
+                () => { return __instance.ReportButton.graphic.color == Palette.EnabledColor && PlayerControl.LocalPlayer.CanMove; },
                 () => { janitorCleanButton.Timer = janitorCleanButton.MaxTimer; },
                 Janitor.getButtonSprite(),
                 new Vector3(-1.8f, -0.06f, 0),
@@ -223,6 +300,37 @@ namespace TheOtherRoles
                 KeyCode.Q
             );
 
+            // Deputy Handcuff
+            deputyHandcuffButton = new CustomButton(
+                () => {
+                    byte targetId = 0;
+                    targetId = Sheriff.sheriff == PlayerControl.LocalPlayer ? Sheriff.currentTarget.PlayerId : Deputy.currentTarget.PlayerId;  // If the deputy is now the sheriff, sheriffs target, else deputies target
+
+                    MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.DeputyUsedHandcuffs, Hazel.SendOption.Reliable, -1);
+                    writer.Write(targetId);
+                    AmongUsClient.Instance.FinishRpcImmediately(writer);
+                    RPCProcedure.deputyUsedHandcuffs(targetId);
+                    Deputy.currentTarget = null;
+                    deputyHandcuffButton.Timer = deputyHandcuffButton.MaxTimer;
+                },
+                () => { return (Deputy.deputy != null && Deputy.deputy == PlayerControl.LocalPlayer || Sheriff.sheriff != null && Sheriff.sheriff == PlayerControl.LocalPlayer && Sheriff.sheriff == Sheriff.formerDeputy && Deputy.keepsHandcuffsOnPromotion) && !PlayerControl.LocalPlayer.Data.IsDead; },
+                () => {
+                    if (deputyButtonHandcuffsText != null) deputyButtonHandcuffsText.text = $"{Deputy.remainingHandcuffs}";
+                    return ((Deputy.deputy != null && Deputy.deputy == PlayerControl.LocalPlayer && Deputy.currentTarget || Sheriff.sheriff != null && Sheriff.sheriff == PlayerControl.LocalPlayer && Sheriff.sheriff == Sheriff.formerDeputy && Sheriff.currentTarget) && Deputy.remainingHandcuffs > 0 && PlayerControl.LocalPlayer.CanMove);
+                },
+                () => { deputyHandcuffButton.Timer = deputyHandcuffButton.MaxTimer; },
+                Deputy.getButtonSprite(),
+                new Vector3(-1.8f, -0.06f, 0),
+                __instance,
+                KeyCode.F
+            );
+            // Deputy Handcuff button handcuff counter
+            deputyButtonHandcuffsText = GameObject.Instantiate(deputyHandcuffButton.actionButton.cooldownTimerText, deputyHandcuffButton.actionButton.cooldownTimerText.transform.parent);
+            deputyButtonHandcuffsText.text = "";
+            deputyButtonHandcuffsText.enableWordWrapping = false;
+            deputyButtonHandcuffsText.transform.localScale = Vector3.one * 0.5f;
+            deputyButtonHandcuffsText.transform.localPosition += new Vector3(-0.05f, 0.7f, 0);
+
             // Time Master Rewind Time
             timeMasterShieldButton = new CustomButton(
                 () => {
@@ -374,12 +482,15 @@ namespace TheOtherRoles
                    if (!MapBehaviour.Instance || !MapBehaviour.Instance.isActiveAndEnabled)
                        DestroyableSingleton<HudManager>.Instance.ShowMap((System.Action<MapBehaviour>)(m => m.ShowCountOverlay()));
 
+                   if (Hacker.cantMove) PlayerControl.LocalPlayer.moveable = false;
+                   PlayerControl.LocalPlayer.NetTransform.Halt(); // Stop current movement 
+
                    Hacker.chargesAdminTable--;
                },
                () => { return Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead;},
                () => {
                    if (hackerAdminTableChargesText != null) hackerAdminTableChargesText.text = $"{Hacker.chargesAdminTable} / {Hacker.toolsNumber}";
-                   return PlayerControl.LocalPlayer.CanMove && Hacker.chargesAdminTable > 0; 
+                   return Hacker.chargesAdminTable > 0; 
                },
                () => {
                    hackerAdminTableButton.Timer = hackerAdminTableButton.MaxTimer;
@@ -394,6 +505,7 @@ namespace TheOtherRoles
                0f,
                () => { 
                    hackerAdminTableButton.Timer = hackerAdminTableButton.MaxTimer;
+                   if (!hackerVitalsButton.isEffectActive) PlayerControl.LocalPlayer.moveable = true;
                    if (MapBehaviour.Instance && MapBehaviour.Instance.isActiveAndEnabled) MapBehaviour.Instance.Close();
                },
                PlayerControl.GameOptions.MapId == 3,
@@ -428,6 +540,10 @@ namespace TheOtherRoles
                        Hacker.doorLog.transform.localPosition = new Vector3(0.0f, 0.0f, -50f);
                        Hacker.doorLog.Begin(null);
                    }
+
+                   if (Hacker.cantMove) PlayerControl.LocalPlayer.moveable = false;
+                   PlayerControl.LocalPlayer.NetTransform.Halt(); // Stop current movement 
+
                    Hacker.chargesVitals--;
                },
                () => { return Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead && PlayerControl.GameOptions.MapId != 0 && PlayerControl.GameOptions.MapId != 3; },
@@ -435,7 +551,7 @@ namespace TheOtherRoles
                    if (hackerVitalsChargesText != null) hackerVitalsChargesText.text = $"{Hacker.chargesVitals} / {Hacker.toolsNumber}";
                    hackerVitalsButton.actionButton.graphic.sprite = PlayerControl.GameOptions.MapId == 1 ? Hacker.getLogSprite() : Hacker.getVitalsSprite();
                    hackerVitalsButton.actionButton.OverrideText(PlayerControl.GameOptions.MapId == 1 ? "DOORLOG" : "VITALS");
-                   return PlayerControl.LocalPlayer.CanMove && Hacker.chargesVitals > 0;
+                   return Hacker.chargesVitals > 0;
                },
                () => {
                    hackerVitalsButton.Timer = hackerVitalsButton.MaxTimer;
@@ -450,6 +566,7 @@ namespace TheOtherRoles
                0f,
                () => { 
                    hackerVitalsButton.Timer = hackerVitalsButton.MaxTimer;
+                   if(!hackerAdminTableButton.isEffectActive) PlayerControl.LocalPlayer.moveable = true;
                    if (Minigame.Instance) {
                        if (PlayerControl.GameOptions.MapId == 1) Hacker.doorLog.ForceClose();
                        else Hacker.vitals.ForceClose();
@@ -861,6 +978,68 @@ namespace TheOtherRoles
             securityGuardButtonScrewsText.transform.localScale = Vector3.one * 0.5f;
             securityGuardButtonScrewsText.transform.localPosition += new Vector3(-0.05f, 0.7f, 0);
 
+            securityGuardCamButton = new CustomButton(
+                () => {
+                    if (PlayerControl.GameOptions.MapId != 1) {
+                        if (SecurityGuard.minigame == null) {
+                            var e = UnityEngine.Object.FindObjectsOfType<SystemConsole>().FirstOrDefault(x => x.gameObject.name.Contains("Surv_Panel"));
+                            if (e == null || Camera.main == null) return;
+                            SecurityGuard.minigame = UnityEngine.Object.Instantiate(e.MinigamePrefab, Camera.main.transform, false);
+                        }
+                        SecurityGuard.minigame.transform.SetParent(Camera.main.transform, false);
+                        SecurityGuard.minigame.transform.localPosition = new Vector3(0.0f, 0.0f, -50f);
+                        SecurityGuard.minigame.Begin(null);
+                    } else {
+                        if (SecurityGuard.minigame == null) {
+                            var e = UnityEngine.Object.FindObjectsOfType<SystemConsole>().FirstOrDefault(x => x.gameObject.name.Contains("SurvLogConsole"));
+                            if (e == null || Camera.main == null) return;
+                            SecurityGuard.minigame = UnityEngine.Object.Instantiate(e.MinigamePrefab, Camera.main.transform, false);
+                        }
+                        SecurityGuard.minigame.transform.SetParent(Camera.main.transform, false);
+                        SecurityGuard.minigame.transform.localPosition = new Vector3(0.0f, 0.0f, -50f);
+                        SecurityGuard.minigame.Begin(null);
+                    }
+                    SecurityGuard.charges--;
+
+                    if (SecurityGuard.cantMove) PlayerControl.LocalPlayer.moveable = false;
+                    PlayerControl.LocalPlayer.NetTransform.Halt(); // Stop current movement 
+                },
+                () => { return SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead && SecurityGuard.remainingScrews < Mathf.Min(SecurityGuard.ventPrice, SecurityGuard.camPrice); },
+                () => {
+                    if (securityGuardChargesText != null) securityGuardChargesText.text = $"{SecurityGuard.charges} / {SecurityGuard.maxCharges}";
+                    securityGuardCamButton.actionButton.graphic.sprite = PlayerControl.GameOptions.MapId == 1 ? SecurityGuard.getLogSprite() : SecurityGuard.getCamSprite();
+                    securityGuardCamButton.actionButton.OverrideText(PlayerControl.GameOptions.MapId == 1 ? "DOORLOG" : "SECURITY");
+                    return PlayerControl.LocalPlayer.CanMove && SecurityGuard.charges > 0;
+                },
+                () => {
+                    securityGuardCamButton.Timer = securityGuardCamButton.MaxTimer;
+                    securityGuardCamButton.isEffectActive = false;
+                    securityGuardCamButton.actionButton.cooldownTimerText.color = Palette.EnabledColor;
+                },
+                SecurityGuard.getCamSprite(),
+                new Vector3(-1.8f, -0.06f, 0),
+                __instance,
+                KeyCode.Q,
+                true,
+                0f,
+                () => {
+                    securityGuardCamButton.Timer = securityGuardCamButton.MaxTimer;
+                    if (Minigame.Instance) {
+                        SecurityGuard.minigame.ForceClose();
+                    }
+                    PlayerControl.LocalPlayer.moveable = true;
+                },
+                false,
+                PlayerControl.GameOptions.MapId == 1 ? "DOORLOG" : "SECURITY"
+            );
+
+            // Security Guard cam button charges
+            securityGuardChargesText = GameObject.Instantiate(securityGuardCamButton.actionButton.cooldownTimerText, securityGuardCamButton.actionButton.cooldownTimerText.transform.parent);
+            securityGuardChargesText.text = "";
+            securityGuardChargesText.enableWordWrapping = false;
+            securityGuardChargesText.transform.localScale = Vector3.one * 0.5f;
+            securityGuardChargesText.transform.localPosition += new Vector3(-0.05f, 0.7f, 0);
+
             // Arsonist button
             arsonistButton = new CustomButton(
                 () => {
@@ -1121,6 +1300,7 @@ namespace TheOtherRoles
 
             // Set the default (or settings from the previous game) timers/durations when spawning the buttons
             setCustomButtonCooldowns();
+            deputyHandcuffedButtons = null;
         }
     }
 }
\ No newline at end of file
index 5f1a2fdd1b09f97752ae449895e26ccecd599dc2..c9c80f6fd037d20cc214fae2a6ad28b68b1874df 100644 (file)
@@ -50,6 +50,7 @@ namespace TheOtherRoles {
         public static CustomOption loversImpLoverRate;
         public static CustomOption loversBothDie;
         public static CustomOption loversCanHaveAnotherRole;
+        public static CustomOption loversEnableChat;
 
         public static CustomOption guesserSpawnRate;
         public static CustomOption guesserIsImpGuesserRate;
@@ -59,6 +60,7 @@ namespace TheOtherRoles {
         public static CustomOption guesserKillsThroughShield;
         public static CustomOption guesserEvilCanKillSpy;
         public static CustomOption guesserSpawnBothRate;
+        public static CustomOption guesserCantGuessSnitchIfTaksDone;
 
         public static CustomOption jesterSpawnRate;
         public static CustomOption jesterCanCallEmergency;
@@ -107,6 +109,14 @@ namespace TheOtherRoles {
         public static CustomOption sheriffSpawnRate;
         public static CustomOption sheriffCooldown;
         public static CustomOption sheriffCanKillNeutrals;
+        public static CustomOption deputySpawnRate;
+
+        public static CustomOption deputyNumberOfHandcuffs;
+        public static CustomOption deputyHandcuffCooldown;
+        public static CustomOption deputyGetsPromoted;
+        public static CustomOption deputyKeepsHandcuffs;
+        public static CustomOption deputyHandcuffDuration;
+        public static CustomOption deputyKnowsSheriff;
 
         public static CustomOption lighterSpawnRate;
         public static CustomOption lighterModeLightsOnVision;
@@ -147,6 +157,7 @@ namespace TheOtherRoles {
         public static CustomOption hackerOnlyColorType;
         public static CustomOption hackerToolsNumber;
         public static CustomOption hackerRechargeTasksNumber;
+        public static CustomOption hackerNoMove;
 
         public static CustomOption trackerSpawnRate;
         public static CustomOption trackerUpdateIntervall;
@@ -183,6 +194,10 @@ namespace TheOtherRoles {
         public static CustomOption securityGuardTotalScrews;
         public static CustomOption securityGuardCamPrice;
         public static CustomOption securityGuardVentPrice;
+        public static CustomOption securityGuardCamDuration;
+        public static CustomOption securityGuardCamMaxCharges;
+        public static CustomOption securityGuardCamRechargeTasksNumber;
+        public static CustomOption securityGuardNoMove;
 
         public static CustomOption baitSpawnRate;
         public static CustomOption baitHighlightAllVents;
@@ -263,7 +278,7 @@ namespace TheOtherRoles {
             eraserCanEraseAnyone = CustomOption.Create(232, "Eraser Can Erase Anyone", false, eraserSpawnRate);
 
             tricksterSpawnRate = CustomOption.Create(250, cs(Trickster.color, "Trickster"), rates, null, true);
-            tricksterPlaceBoxCooldown = CustomOption.Create(251, "Trickster Box Cooldown", 10f, 0f, 30f, 2.5f, tricksterSpawnRate);
+            tricksterPlaceBoxCooldown = CustomOption.Create(251, "Trickster Box Cooldown", 10f, 2.5f, 30f, 2.5f, tricksterSpawnRate);
             tricksterLightsOutCooldown = CustomOption.Create(252, "Trickster Lights Out Cooldown", 30f, 10f, 60f, 5f, tricksterSpawnRate);
             tricksterLightsOutDuration = CustomOption.Create(253, "Trickster Lights Out Duration", 15f, 5f, 60f, 2.5f, tricksterSpawnRate);
 
@@ -296,6 +311,7 @@ namespace TheOtherRoles {
             loversImpLoverRate = CustomOption.Create(51, "Chance That One Lover Is Impostor", rates, loversSpawnRate);
             loversBothDie = CustomOption.Create(52, "Both Lovers Die", true, loversSpawnRate);
             loversCanHaveAnotherRole = CustomOption.Create(53, "Lovers Can Have Another Role", true, loversSpawnRate);
+            loversEnableChat = CustomOption.Create(54, "Enable Lover Chat", true, loversSpawnRate);
 
             guesserSpawnRate = CustomOption.Create(310, cs(Guesser.color, "Guesser"), rates, null, true);
             guesserIsImpGuesserRate = CustomOption.Create(311, "Chance That The Guesser Is An Impostor", rates, guesserSpawnRate);
@@ -305,6 +321,7 @@ namespace TheOtherRoles {
             guesserKillsThroughShield  = CustomOption.Create(315, "Guesses Ignore The Medic Shield", true, guesserSpawnRate);
             guesserEvilCanKillSpy  = CustomOption.Create(316, "Evil Guesser Can Guess The Spy", true, guesserSpawnRate);
             guesserSpawnBothRate = CustomOption.Create(317, "Both Guesser Spawn Rate", rates, guesserSpawnRate);
+            guesserCantGuessSnitchIfTaksDone = CustomOption.Create(318, "Guesser Can't Guess Snitch When Tasks Completed", true, guesserSpawnRate);
 
             jesterSpawnRate = CustomOption.Create(60, cs(Jester.color, "Jester"), rates, null, true);
             jesterCanCallEmergency = CustomOption.Create(61, "Jester can call emergency meeting", true, jesterSpawnRate);
@@ -327,9 +344,9 @@ namespace TheOtherRoles {
 
             vultureSpawnRate = CustomOption.Create(340, cs(Vulture.color, "Vulture"), rates, null, true);
             vultureCooldown = CustomOption.Create(341, "Vulture Cooldown", 15f, 10f, 60f, 2.5f, vultureSpawnRate);
-            vultureNumberToWin = CustomOption.Create(342, "Number Of Corpses Needed To Be Eaten", 4f, 0f, 10f, 1f, vultureSpawnRate);
+            vultureNumberToWin = CustomOption.Create(342, "Number Of Corpses Needed To Be Eaten", 4f, 1f, 10f, 1f, vultureSpawnRate);
             vultureCanUseVents = CustomOption.Create(343, "Vulture Can Use Vents", true, vultureSpawnRate);
-            vultureShowArrows = CustomOption.Create(344, "Show Arrows Pointing Towards The Corpes", true, vultureSpawnRate);
+            vultureShowArrows = CustomOption.Create(344, "Show Arrows Pointing Towards The Corpses", true, vultureSpawnRate);
 
             lawyerSpawnRate = CustomOption.Create(350, cs(Lawyer.color, "Lawyer"), rates, null, true);
             lawyerTargetKnows = CustomOption.Create(351, "Lawyer Target Knows", true, lawyerSpawnRate);
@@ -338,7 +355,7 @@ namespace TheOtherRoles {
             lawyerVision = CustomOption.Create(354, "Lawyer Vision", 1f, 0.25f, 3f, 0.25f, lawyerSpawnRate);
             lawyerKnowsRole = CustomOption.Create(355, "Lawyer Knows Target Role", false, lawyerSpawnRate);
             pursuerCooldown = CustomOption.Create(356, "Pursuer Blank Cooldown", 30f, 5f, 60f, 2.5f, lawyerSpawnRate);
-            pursuerBlanksNumber = CustomOption.Create(357, "Pursuer Number Of Blanks", 5f, 0f, 20f, 1f, lawyerSpawnRate);
+            pursuerBlanksNumber = CustomOption.Create(357, "Pursuer Number Of Blanks", 5f, 1f, 20f, 1f, lawyerSpawnRate);
 
             shifterSpawnRate = CustomOption.Create(70, cs(Shifter.color, "Shifter"), rates, null, true);
             shifterShiftsModifiers = CustomOption.Create(71, "Shifter Shifts Modifiers", false, shifterSpawnRate);
@@ -346,13 +363,20 @@ namespace TheOtherRoles {
             mayorSpawnRate = CustomOption.Create(80, cs(Mayor.color, "Mayor"), rates, null, true);
 
             engineerSpawnRate = CustomOption.Create(90, cs(Engineer.color, "Engineer"), rates, null, true);
-            engineerNumberOfFixes = CustomOption.Create(91, "Number Of Sabotage Fixes", 1f, 0f, 3f, 1f, engineerSpawnRate);
+            engineerNumberOfFixes = CustomOption.Create(91, "Number Of Sabotage Fixes", 1f, 1f, 3f, 1f, engineerSpawnRate);
             engineerHighlightForImpostors = CustomOption.Create(92, "Impostors See Vents Highlighted", true, engineerSpawnRate);
             engineerHighlightForTeamJackal = CustomOption.Create(93, "Jackal and Sidekick See Vents Highlighted ", true, engineerSpawnRate);
 
             sheriffSpawnRate = CustomOption.Create(100, cs(Sheriff.color, "Sheriff"), rates, null, true);
             sheriffCooldown = CustomOption.Create(101, "Sheriff Cooldown", 30f, 10f, 60f, 2.5f, sheriffSpawnRate);
             sheriffCanKillNeutrals = CustomOption.Create(102, "Sheriff Can Kill Neutrals", false, sheriffSpawnRate);
+            deputySpawnRate = CustomOption.Create(103, "Sheriff Has A Deputy", rates, sheriffSpawnRate);
+            deputyNumberOfHandcuffs = CustomOption.Create(104, "Deputy Number Of Handcuffs", 3f, 1f, 10f, 1f, deputySpawnRate);
+            deputyHandcuffCooldown = CustomOption.Create(105, "Handcuff Cooldown", 30f, 10f, 60f, 2.5f, deputySpawnRate);
+            deputyHandcuffDuration = CustomOption.Create(106, "Handcuff Duration", 15f, 5f, 60f, 2.5f, deputySpawnRate);
+            deputyKnowsSheriff = CustomOption.Create(107, "Sheriff And Deputy Know Each Other ", true, deputySpawnRate);
+            deputyGetsPromoted = CustomOption.Create(108, "Deputy Gets Promoted To Sheriff", new string[] { "Off", "On (Immediately)", "On (After Meeting)" }, deputySpawnRate);
+            deputyKeepsHandcuffs = CustomOption.Create(109, "Deputy Keeps Handcuffs When Promoted", true, deputyGetsPromoted);
 
             lighterSpawnRate = CustomOption.Create(110, cs(Lighter.color, "Lighter"), rates, null, true);
             lighterModeLightsOnVision = CustomOption.Create(111, "Lighter Mode Vision On Lights On", 2f, 0.25f, 5f, 0.25f, lighterSpawnRate);
@@ -385,20 +409,21 @@ namespace TheOtherRoles {
             seerSpawnRate = CustomOption.Create(160, cs(Seer.color, "Seer"), rates, null, true);
             seerMode = CustomOption.Create(161, "Seer Mode", new string[]{ "Show Death Flash + Souls", "Show Death Flash", "Show Souls"}, seerSpawnRate);
             seerLimitSoulDuration = CustomOption.Create(163, "Seer Limit Soul Duration", false, seerSpawnRate);
-            seerSoulDuration = CustomOption.Create(162, "Seer Soul Duration", 15f, 0f, 60f, 5f, seerLimitSoulDuration);
+            seerSoulDuration = CustomOption.Create(162, "Seer Soul Duration", 15f, 0f, 120f, 5f, seerLimitSoulDuration);
         
             hackerSpawnRate = CustomOption.Create(170, cs(Hacker.color, "Hacker"), rates, null, true);
-            hackerCooldown = CustomOption.Create(171, "Hacker Cooldown", 30f, 0f, 60f, 5f, hackerSpawnRate);
+            hackerCooldown = CustomOption.Create(171, "Hacker Cooldown", 30f, 5f, 60f, 5f, hackerSpawnRate);
             hackerHackeringDuration = CustomOption.Create(172, "Hacker Duration", 10f, 2.5f, 60f, 2.5f, hackerSpawnRate);
             hackerOnlyColorType = CustomOption.Create(173, "Hacker Only Sees Color Type", false, hackerSpawnRate);
             hackerToolsNumber = CustomOption.Create(174, "Max Mobile Gadget Charges", 5f, 1f, 30f, 1f, hackerSpawnRate);
             hackerRechargeTasksNumber = CustomOption.Create(175, "Number Of Tasks Needed For Recharging", 2f, 1f, 5f, 1f, hackerSpawnRate);
+            hackerNoMove = CustomOption.Create(176, "Cant Move During Mobile Gadget Duration", true, hackerSpawnRate);
 
             trackerSpawnRate = CustomOption.Create(200, cs(Tracker.color, "Tracker"), rates, null, true);
             trackerUpdateIntervall = CustomOption.Create(201, "Tracker Update Intervall", 5f, 2.5f, 30f, 2.5f, trackerSpawnRate);
             trackerResetTargetAfterMeeting = CustomOption.Create(202, "Tracker Reset Target After Meeting", false, trackerSpawnRate);
             trackerCanTrackCorpses = CustomOption.Create(203, "Tracker Can Track Corpses", true, trackerSpawnRate);
-            trackerCorpsesTrackingCooldown = CustomOption.Create(204, "Corpses Tracking Cooldown", 30f, 0f, 120f, 5f, trackerCanTrackCorpses);
+            trackerCorpsesTrackingCooldown = CustomOption.Create(204, "Corpses Tracking Cooldown", 30f, 5f, 120f, 5f, trackerCanTrackCorpses);
             trackerCorpsesTrackingDuration = CustomOption.Create(205, "Corpses Tracking Duration", 5f, 2.5f, 30f, 2.5f, trackerCanTrackCorpses);
                            
             snitchSpawnRate = CustomOption.Create(210, cs(Snitch.color, "Snitch"), rates, null, true);
@@ -417,6 +442,10 @@ namespace TheOtherRoles {
             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);
+            securityGuardCamDuration = CustomOption.Create(285, "Security Guard Duration", 10f, 2.5f, 60f, 2.5f, securityGuardSpawnRate);
+            securityGuardCamMaxCharges = CustomOption.Create(286, "Gadged Max Charges", 5f, 1f, 30f, 1f, securityGuardSpawnRate);
+            securityGuardCamRechargeTasksNumber = CustomOption.Create(287, "Number Of Tasks Needed For Recharging", 3f, 1f, 10f, 1f, securityGuardSpawnRate);
+            securityGuardNoMove = CustomOption.Create(288, "Cant Move During Cam Duration", true, securityGuardSpawnRate);
 
             baitSpawnRate = CustomOption.Create(330, cs(Bait.color, "Bait"), rates, null, true);
             baitHighlightAllVents = CustomOption.Create(331, "Highlight All Vents If A Vent Is Occupied", false, baitSpawnRate);
@@ -443,4 +472,4 @@ namespace TheOtherRoles {
             
         }
     }
-}
\ No newline at end of file
+}
index 0f6dd9df62f24f332ae4f16f3cf9ed4a19ea1f6c..006b5a2b95a361eb7be6e2c049b20803620bef4c 100644 (file)
@@ -210,6 +210,7 @@ namespace TheOtherRoles {
             else if (source.Data.Role.IsImpostor && (target.Data.Role.IsImpostor || target == Spy.spy)) return false; // Members of team Impostors see the names of Impostors/Spies
             else if ((source == Lovers.lover1 || source == Lovers.lover2) && (target == Lovers.lover1 || target == Lovers.lover2)) return false; // Members of team Lovers see the names of each other
             else if ((source == Jackal.jackal || source == Sidekick.sidekick) && (target == Jackal.jackal || target == Sidekick.sidekick || target == Jackal.fakeSidekick)) return false; // Members of team Jackal see the names of each other
+            else if (Deputy.knowsSheriff && (source == Sheriff.sheriff || source == Deputy.deputy) && (target == Sheriff.sheriff || target == Deputy.deputy)) return false; // Sheriff & Deputy see the names of each other
             return true;
         }
 
index bf0f5f07251fcc99cf9c2b3ff94b62f7e4bba8b6..580f705188bc31c1ccc4cc59729b8cedd578b85e 100644 (file)
@@ -21,7 +21,8 @@ namespace TheOtherRoles
     public class TheOtherRolesPlugin : BasePlugin
     {
         public const string Id = "me.eisbison.theotherroles";
-        public const string VersionString = "3.3.3";
+        public const string VersionString = "3.4.0";
+
         public static System.Version Version = System.Version.Parse(VersionString);
         internal static BepInEx.Logging.ManualLogSource Logger;
 
@@ -40,6 +41,7 @@ namespace TheOtherRoles
         public static ConfigEntry<string> StreamerModeReplacementColor { get; set; }
         public static ConfigEntry<string> Ip { get; set; }
         public static ConfigEntry<ushort> Port { get; set; }
+        public static ConfigEntry<string> ShowPopUpVersion { get; set; }
 
         public static Sprite ModStamp;
 
@@ -62,6 +64,7 @@ namespace TheOtherRoles
             GhostsSeeRoles = Config.Bind("Custom", "Ghosts See Roles", true);
             GhostsSeeVotes = Config.Bind("Custom", "Ghosts See Votes", true);
             ShowRoleSummary = Config.Bind("Custom", "Show Role Summary", true);
+            ShowPopUpVersion = Config.Bind("Custom", "Show PopUp", "0");
             StreamerModeReplacementText = Config.Bind("Custom", "Streamer Mode Replacement Text", "\n\nThe Other Roles");
             StreamerModeReplacementColor = Config.Bind("Custom", "Streamer Mode Replacement Text Hex Color", "#87AAF5FF");
             
index 480c10fd4c5ad02bc4ec15a3b381f3a8c6b745df..6f34cdd9efc4dbe20aaa44cac8871de0cf1f4e9f 100644 (file)
@@ -12,6 +12,8 @@ using UnhollowerBaseLib;
 namespace TheOtherRoles.Modules {
     [HarmonyPatch]
     public static class ChatCommands {
+        public static bool isLover(this PlayerControl player) => !(player == null) && (player == Lovers.lover1 || player == Lovers.lover2);
+
         [HarmonyPatch(typeof(ChatController), nameof(ChatController.SendChat))]
         private static class SendChatPatch {
             static bool Prefix(ChatController __instance) {
@@ -77,9 +79,28 @@ namespace TheOtherRoles.Modules {
         [HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))]
         public static class EnableChat {
             public static void Postfix(HudManager __instance) {
-                if (!__instance.Chat.isActiveAndEnabled && AmongUsClient.Instance.GameMode == GameModes.FreePlay)
+                if (!__instance.Chat.isActiveAndEnabled && (AmongUsClient.Instance.GameMode == GameModes.FreePlay || (PlayerControl.LocalPlayer.isLover() && Lovers.enableChat)))
                     __instance.Chat.SetVisible(true);
             }
         }
+
+        [HarmonyPatch(typeof(ChatBubble), nameof(ChatBubble.SetName))]
+        public static class SetBubbleName { 
+            public static void Postfix(ChatBubble __instance, [HarmonyArgument(0)] string playerName) {
+                PlayerControl sourcePlayer = PlayerControl.AllPlayerControls.ToArray().ToList().FirstOrDefault(x => x.Data.PlayerName.Equals(playerName));
+                if (PlayerControl.LocalPlayer != null && PlayerControl.LocalPlayer.Data.Role.IsImpostor && Spy.spy != null && sourcePlayer.PlayerId == Spy.spy.PlayerId && __instance != null) __instance.NameText.color = Palette.ImpostorRed;
+            }
+        }
+
+        [HarmonyPatch(typeof(ChatController), nameof(ChatController.AddChat))]
+        public static class AddChat {
+            public static bool Prefix(ChatController __instance, [HarmonyArgument(0)] PlayerControl sourcePlayer) {
+                if (__instance != DestroyableSingleton<HudManager>.Instance.Chat)
+                    return true;
+                PlayerControl localPlayer = PlayerControl.LocalPlayer;
+                return localPlayer == null || (MeetingHud.Instance != null || LobbyBehaviour.Instance != null || (localPlayer.Data.IsDead || localPlayer.isLover() && Lovers.enableChat) || (int)sourcePlayer.PlayerId == (int)PlayerControl.LocalPlayer.PlayerId);
+
+            }
+        }
     }
 }
index 36f55541b399b9c73299ff26a314c959f6fa6a87..512a6c1c2b37b19d2b547c9e7913e7646f682b72 100644 (file)
@@ -59,18 +59,32 @@ namespace TheOtherRoles.Modules {
         }
     }
 
+    [HarmonyPatch(typeof(AnnouncementPopUp), nameof(AnnouncementPopUp.UpdateAnnounceText))]
+    public static class Announcement {
+        public static bool Prefix(AnnouncementPopUp __instance) {       
+            var text = __instance.AnnounceTextMeshPro;
+            text.text = ModUpdater.announcement;
+            return false;
+        }
+    }
+
     public class ModUpdater { 
         public static bool running = false;
         public static bool hasUpdate = false;
         public static string updateURI = null;
         private static Task updateTask = null;
+        public static string announcement = "";
         public static GenericPopup InfoPopup;
 
         public static void LaunchUpdater() {
             if (running) return;
             running = true;
             checkForUpdate().GetAwaiter().GetResult();
-            clearOldVersions();   
+            clearOldVersions();
+            if (hasUpdate || TheOtherRolesPlugin.ShowPopUpVersion.Value != TheOtherRolesPlugin.VersionString) {
+                DestroyableSingleton<MainMenuManager>.Instance.Announcement.gameObject.SetActive(true);
+                TheOtherRolesPlugin.ShowPopUpVersion.Value = TheOtherRolesPlugin.VersionString;
+            }
         }
 
         public static void ExecuteUpdate() {
@@ -116,11 +130,19 @@ namespace TheOtherRoles.Modules {
                 if (tagname == null) {
                     return false; // Something went wrong
                 }
+
+                string changeLog = data["body"]?.ToString();
+                if (changeLog != null) announcement = changeLog;
                 // check version
                 System.Version ver = System.Version.Parse(tagname.Replace("v", ""));
                 int diff = TheOtherRolesPlugin.Version.CompareTo(ver);
                 if (diff < 0) { // Update required
                     hasUpdate = true;
+                    announcement = $@"<size=150%>A new <color=#FC0303>THE OTHER ROLES</color>
+update to v{ver} is available</size>
+
+{announcement}";
+
                     JToken assets = data["assets"];
                     if (!assets.HasValues)
                         return false;
@@ -135,7 +157,11 @@ namespace TheOtherRoles.Modules {
                             }
                         }
                     }
-                }  
+                }  else {
+                    announcement = $@"<size=150%>Version {ver}:</size>
+
+{announcement}";
+                }
             } catch (System.Exception ex) {
                 TheOtherRolesPlugin.Instance.Log.LogError(ex.ToString());
                 System.Console.WriteLine(ex);
index 4cf49c62915d96f56b17d8107e996089d623edab..ba7347a17c1bd466408a6fadb971e03e4679cf70 100644 (file)
@@ -4,6 +4,7 @@ using System.IO;
 using System.Reflection;
 using UnityEngine;
 using UnityEngine.UI;
+using static TheOtherRoles.TheOtherRoles;
 
 namespace TheOtherRoles.Objects {
     public class CustomButton
@@ -13,25 +14,29 @@ namespace TheOtherRoles.Objects {
         public Vector3 PositionOffset;
         public float MaxTimer = float.MaxValue;
         public float Timer = 0f;
+        public float DeputyTimer = 0f;
         private Action OnClick;
+        private Action InitialOnClick;
         private Action OnMeetingEnds;
-        private Func<bool> HasButton;
-        private Func<bool> CouldUse;
+        public Func<bool> HasButton;
+        public Func<bool> CouldUse;
         private Action OnEffectEnds;
         public bool HasEffect;
         public bool isEffectActive = false;
         public bool showButtonText = false;
         public float EffectDuration;
         public Sprite Sprite;
-        private HudManager hudManager;
-        private bool mirror;
-        private KeyCode? hotkey;
+        public HudManager hudManager;
+        public bool mirror;
+        public KeyCode? hotkey;
         private string buttonText;
+        public bool isHandcuffed = false;
 
         public CustomButton(Action OnClick, Func<bool> HasButton, Func<bool> CouldUse, Action OnMeetingEnds, Sprite Sprite, Vector3 PositionOffset, HudManager hudManager, KeyCode? hotkey, bool HasEffect, float EffectDuration, Action OnEffectEnds, bool mirror = false, string buttonText = "")
         {
             this.hudManager = hudManager;
             this.OnClick = OnClick;
+            this.InitialOnClick = OnClick;
             this.HasButton = HasButton;
             this.CouldUse = CouldUse;
             this.PositionOffset = PositionOffset;
@@ -64,7 +69,11 @@ namespace TheOtherRoles.Objects {
                 actionButton.graphic.color = new Color(1f, 1f, 1f, 0.3f);
                 this.OnClick();
 
+                // Deputy skip onClickEvent if handcuffed
+                if (Deputy.handcuffedKnows.ContainsKey(PlayerControl.LocalPlayer.PlayerId) && Deputy.handcuffedKnows[PlayerControl.LocalPlayer.PlayerId] > 0f) return;
+
                 if (this.HasEffect && !this.isEffectActive) {
+                    this.DeputyTimer = this.EffectDuration;
                     this.Timer = this.EffectDuration;
                     actionButton.cooldownTimerText.color = new Color(0F, 0.8F, 0F);
                     this.isEffectActive = true;
@@ -111,6 +120,7 @@ namespace TheOtherRoles.Objects {
                 try
                 {
                     buttons[i].Timer = buttons[i].MaxTimer;
+                    buttons[i].DeputyTimer = buttons[i].MaxTimer;
                     buttons[i].Update();
                 }
                 catch (NullReferenceException)
@@ -130,7 +140,7 @@ namespace TheOtherRoles.Objects {
             }
         }
 
-        private void Update()
+        public void Update()
         {
             if (PlayerControl.LocalPlayer.Data == null || MeetingHud.Instance || ExileController.Instance || !HasButton()) {
                 setActive(false);
@@ -138,6 +148,24 @@ namespace TheOtherRoles.Objects {
             }
             setActive(hudManager.UseButton.isActiveAndEnabled);
 
+            if (DeputyTimer >= 0) { // This had to be reordered, so that the handcuffs do not stop the underlying timers from running
+                if (HasEffect && isEffectActive)
+                    DeputyTimer -= Time.deltaTime;
+                else if (!PlayerControl.LocalPlayer.inVent && PlayerControl.LocalPlayer.moveable)
+                    DeputyTimer -= Time.deltaTime;
+            }
+
+            if (DeputyTimer <= 0 && HasEffect && isEffectActive) {
+                isEffectActive = false;
+                actionButton.cooldownTimerText.color = Palette.EnabledColor;
+                OnEffectEnds();
+            }
+
+            if (isHandcuffed) {
+                setActive(false);
+                return;
+            }
+
             actionButton.graphic.sprite = Sprite;
             if (showButtonText && buttonText != ""){
                 actionButton.OverrideText(buttonText);
@@ -155,7 +183,7 @@ namespace TheOtherRoles.Objects {
                 actionButton.graphic.color = actionButton.buttonLabelText.color = Palette.DisabledClear;
                 actionButton.graphic.material.SetFloat("_Desat", 1f);
             }
-
+        
             if (Timer >= 0) {
                 if (HasEffect && isEffectActive)
                     Timer -= Time.deltaTime;
@@ -173,6 +201,16 @@ namespace TheOtherRoles.Objects {
 
             // Trigger OnClickEvent if the hotkey is being pressed down
             if (hotkey.HasValue && Input.GetKeyDown(hotkey.Value)) onClickEvent();
+
+            // Deputy disable the button and display Handcuffs instead...
+            if (Deputy.handcuffedPlayers.Contains(PlayerControl.LocalPlayer.PlayerId)) {
+                OnClick = () => {
+                    Deputy.setHandcuffedKnows();
+                };
+            } else // Reset.
+            {
+                OnClick = InitialOnClick;
+            }
         }
     }
 }
\ No newline at end of file
index ad477095f0f0746c3f83fb18f8290a070c4d9afc..d7d7edaa2cf8a4d027abc174d2d0de7075d435a6 100644 (file)
@@ -19,8 +19,7 @@ Button design by <color=#FCCE03FF>Bavari</color></size>";
 $@"Modded by <color=#FCCE03FF>Eisbison</color>, <color=#FCCE03FF>Thunderstorm584</color>, <color=#FCCE03FF>EndOfFile</color> & <color=#FCCE03FF>Mallöris</color>
 Design by <color=#FCCE03FF>Bavari</color>";
 
-
-        public static string contributorsCredentials = "<size=80%>GitHub Contributors: Alex2911, amsyarasyiq, gendelo3</size>";
+        public static string contributorsCredentials = "<size=60%>GitHub Contributors: Alex2911, amsyarasyiq, gendelo3, MaximeGillot</size>";
 
         [HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))]
         private static class VersionShowerPatch
@@ -60,7 +59,7 @@ Design by <color=#FCCE03FF>Bavari</color>";
                 __instance.text.alignment = TMPro.TextAlignmentOptions.TopRight;
                 if (AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Started) {
                     __instance.text.text = $"<size=130%><color=#ff351f>TheOtherRoles</color></size> v{TheOtherRolesPlugin.Version.ToString()}\n" + __instance.text.text;
-                    if (PlayerControl.LocalPlayer.Data.IsDead) {
+                    if (PlayerControl.LocalPlayer.Data.IsDead || (!(PlayerControl.LocalPlayer == null) && (PlayerControl.LocalPlayer == Lovers.lover1 || PlayerControl.LocalPlayer == Lovers.lover2))) {
                         __instance.transform.localPosition = new Vector3(3.45f, __instance.transform.localPosition.y, __instance.transform.localPosition.z);
                     } else {
                         __instance.transform.localPosition = new Vector3(4.2f, __instance.transform.localPosition.y, __instance.transform.localPosition.z);
@@ -85,7 +84,7 @@ Design by <color=#FCCE03FF>Bavari</color>";
                 var torLogo = new GameObject("bannerLogo_TOR");
                 torLogo.transform.position = Vector3.up;
                 var renderer = torLogo.AddComponent<SpriteRenderer>();
-                renderer.sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Banner.png", 300f);                                
+                renderer.sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Banner.png", 300f);
             }
         }
     }
index 6ec25badbf660d9d0ed962ff43fa8e627bafef39..37f4358ed041f8aa50a7695c98fcc636425e93eb 100644 (file)
@@ -169,6 +169,12 @@ namespace TheOtherRoles.Patches {
                 }
             }
 
+            // Deputy check Promotion, see if the sheriff still exists. The promotion will be after the meeting.
+            if (Deputy.deputy != null)
+            {
+                PlayerControlFixedUpdatePatch.deputyCheckPromotion(isMeeting: true);
+            }
+
             // Force Bounty Hunter Bounty Update
             if (BountyHunter.bountyHunter != null && BountyHunter.bountyHunter == PlayerControl.LocalPlayer)
                 BountyHunter.bountyUpdateTimer = 0f;
index c67303d88e4ad0bb010efb0ee72b3271f175f4d1..1ce87e8aa0f159c59cc87cfc902a7ae2b4368662 100644 (file)
@@ -108,7 +108,14 @@ namespace TheOtherRoles.Patches {
                 if (infos.Any(info => info.roleId == RoleId.Lover)) {
                     PlayerControl otherLover = PlayerControl.LocalPlayer == Lovers.lover1 ? Lovers.lover2 : Lovers.lover1;
                     __instance.RoleBlurbText.text += Helpers.cs(Lovers.color, $"\n♥ You are in love with {otherLover?.Data?.PlayerName ?? ""} ♥");
-                } 
+                }
+                if (Deputy.knowsSheriff && Deputy.deputy != null && Sheriff.sheriff != null) {
+                    if (infos.Any(info => info.roleId == RoleId.Sheriff)) 
+                        __instance.RoleBlurbText.text += Helpers.cs(Sheriff.color, $"\nYour Deputy is {Deputy.deputy?.Data?.PlayerName ?? ""}");
+                    else if (infos.Any(info => info.roleId == RoleId.Deputy))
+                        __instance.RoleBlurbText.text += Helpers.cs(Sheriff.color, $"\nYour Sheriff is {Sheriff.sheriff?.Data?.PlayerName ?? ""}");
+                }
+
             }
         }
 
index 2fc30cc33ae9c9b49c1243bf5296f6065dc229ef..39596f28646b1035090344cebdb58af28b8fd34f 100644 (file)
@@ -247,7 +247,7 @@ namespace TheOtherRoles.Patches {
             Transform exitButtonMask = UnityEngine.Object.Instantiate(maskTemplate, exitButtonParent);
             exitButton.gameObject.GetComponent<SpriteRenderer>().sprite = smallButtonTemplate.GetComponent<SpriteRenderer>().sprite;
             exitButtonParent.transform.localPosition = new Vector3(2.725f, 2.1f, -5);
-            exitButtonParent.transform.localScale = new Vector3(0.25f, 0.9f, 1);
+            exitButtonParent.transform.localScale = new Vector3(0.217f, 0.9f, 1);
             exitButton.GetComponent<PassiveButton>().OnClick.RemoveAllListeners();
             exitButton.GetComponent<PassiveButton>().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => {
                 __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true));
@@ -260,6 +260,13 @@ namespace TheOtherRoles.Patches {
             foreach (RoleInfo roleInfo in RoleInfo.allRoleInfos) {
                 RoleId guesserRole = (Guesser.niceGuesser != null && PlayerControl.LocalPlayer.PlayerId == Guesser.niceGuesser.PlayerId) ? RoleId.NiceGuesser :  RoleId.EvilGuesser;
                 if (roleInfo.roleId == RoleId.Lover || roleInfo.roleId == guesserRole || roleInfo == RoleInfo.niceMini || (!Guesser.evilGuesserCanGuessSpy && guesserRole == RoleId.EvilGuesser && roleInfo.roleId == RoleId.Spy)) continue; // Not guessable roles
+                
+                if (Guesser.guesserCantGuessSnitch && Snitch.snitch != null) {
+                    var (playerCompleted, playerTotal) = TasksHandler.taskInfo(Snitch.snitch.Data);
+                    int numberOfLeftTasks = playerTotal - playerCompleted;
+                    if (numberOfLeftTasks <= 0 && roleInfo.roleId == RoleId.Snitch) continue;
+                }
+
                 Transform buttonParent = (new GameObject()).transform;
                 buttonParent.SetParent(container);
                 Transform button = UnityEngine.Object.Instantiate(buttonTemplate, buttonParent);
@@ -346,7 +353,7 @@ namespace TheOtherRoles.Patches {
                     GameObject checkbox = UnityEngine.Object.Instantiate(template);
                     checkbox.transform.SetParent(playerVoteArea.transform);
                     checkbox.transform.position = template.transform.position;
-                    checkbox.transform.localPosition = new Vector3(-0.95f, 0.03f, -1f);
+                    checkbox.transform.localPosition = new Vector3(-0.95f, 0.03f, -1.3f);
                     SpriteRenderer renderer = checkbox.GetComponent<SpriteRenderer>();
                     renderer.sprite = Swapper.getCheckSprite();
                     renderer.color = Color.red;
@@ -375,7 +382,7 @@ namespace TheOtherRoles.Patches {
                         SpriteRenderer rend = (new GameObject()).AddComponent<SpriteRenderer>();
                         rend.transform.SetParent(pva.transform);
                         rend.gameObject.layer = pva.Megaphone.gameObject.layer;
-                        rend.transform.localPosition = new Vector3(-1.21f, -0.12f, -1f);
+                        rend.transform.localPosition = new Vector3(-0.5f, -0.03f, -1f);
                         rend.sprite = Witch.getSpelledOverlaySprite();
                     }
                 }
@@ -390,7 +397,7 @@ namespace TheOtherRoles.Patches {
                     GameObject template = playerVoteArea.Buttons.transform.Find("CancelButton").gameObject;
                     GameObject targetBox = UnityEngine.Object.Instantiate(template, playerVoteArea.transform);
                     targetBox.name = "ShootButton";
-                    targetBox.transform.localPosition = new Vector3(-0.95f, 0.03f, -1f);
+                    targetBox.transform.localPosition = new Vector3(-0.95f, 0.03f, -1.3f);
                     SpriteRenderer renderer = targetBox.GetComponent<SpriteRenderer>();
                     renderer.sprite = Guesser.getTargetSprite();
                     PassiveButton button = targetBox.GetComponent<PassiveButton>();
index 7398da854967e50f9a605831417e837268fcf92f..c9667ba29cd6a940e0ac8384e45ae6ef13053021 100644 (file)
@@ -142,6 +142,26 @@ namespace TheOtherRoles.Patches {
             setPlayerOutline(Sheriff.currentTarget, Sheriff.color);
         }
 
+        static void deputySetTarget()
+        {
+            if (Deputy.deputy == null || Deputy.deputy != PlayerControl.LocalPlayer) return;
+            Deputy.currentTarget = setTarget();
+            setPlayerOutline(Deputy.currentTarget, Deputy.color);
+        }
+
+        public static void deputyCheckPromotion(bool isMeeting=false)
+        {
+            // If LocalPlayer is Deputy, the Sheriff is disconnected and Deputy promotion is enabled, then trigger promotion
+            if (Deputy.deputy == null || Deputy.deputy != PlayerControl.LocalPlayer) return;
+            if (Deputy.promotesToSheriff == 0 || Deputy.deputy.Data.IsDead == true || Deputy.promotesToSheriff == 2 && !isMeeting) return;
+            if (Sheriff.sheriff == null || Sheriff.sheriff?.Data?.Disconnected == true || Sheriff.sheriff.Data.IsDead)
+            {
+                MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.DeputyPromotes, Hazel.SendOption.Reliable, -1);
+                AmongUsClient.Instance.FinishRpcImmediately(writer);
+                RPCProcedure.deputyPromotes();
+            }
+        }
+
         static void trackerSetTarget() {
             if (Tracker.tracker == null || Tracker.tracker != PlayerControl.LocalPlayer) return;
             Tracker.currentTarget = setTarget();
@@ -232,6 +252,19 @@ namespace TheOtherRoles.Patches {
             setPlayerOutline(Eraser.currentTarget, Eraser.color);
         }
 
+        static void deputyUpdate()
+        {
+            if (PlayerControl.LocalPlayer == null || !Deputy.handcuffedKnows.ContainsKey(PlayerControl.LocalPlayer.PlayerId)) return;
+            
+            if (Deputy.handcuffedKnows[PlayerControl.LocalPlayer.PlayerId] <= 0)
+            {
+                Deputy.handcuffedKnows.Remove(PlayerControl.LocalPlayer.PlayerId);
+                // Resets the buttons
+                Deputy.setHandcuffedKnows(false);
+            }
+            
+        }
+
         static void engineerUpdate() {
             bool jackalHighlight = Engineer.highlightForTeamJackal && (PlayerControl.LocalPlayer == Jackal.jackal || PlayerControl.LocalPlayer == Sidekick.sidekick);
             bool impostorHighlight = Engineer.highlightForImpostors && PlayerControl.LocalPlayer.Data.Role.IsImpostor;
@@ -450,6 +483,15 @@ namespace TheOtherRoles.Patches {
             SecurityGuard.ventTarget = target;
         }
 
+        public static void securityGuardUpdate() {
+            if (SecurityGuard.securityGuard == null || PlayerControl.LocalPlayer != SecurityGuard.securityGuard || SecurityGuard.securityGuard.Data.IsDead) return;
+            var (playerCompleted, _) = TasksHandler.taskInfo(SecurityGuard.securityGuard.Data);
+            if (playerCompleted == SecurityGuard.rechargedTasks) {
+                SecurityGuard.rechargedTasks += SecurityGuard.rechargeTasksNumber;
+                if (SecurityGuard.maxCharges > SecurityGuard.charges) SecurityGuard.charges++;
+            }
+        }
+
         public static void arsonistSetTarget() {
             if (Arsonist.arsonist == null || Arsonist.arsonist != PlayerControl.LocalPlayer) return;
             List<PlayerControl> untargetables;
@@ -523,8 +565,9 @@ namespace TheOtherRoles.Patches {
                 BountyHunter.arrowUpdateTimer = 0f; // Force arrow to update
                 BountyHunter.bountyUpdateTimer = BountyHunter.bountyDuration;
                 var possibleTargets = new List<PlayerControl>();
+                if (Lovers.getPartner(BountyHunter.bountyHunter) != null) 
                 foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
-                    if (!p.Data.IsDead && !p.Data.Disconnected && p != p.Data.Role.IsImpostor && p != Spy.spy && (p != Mini.mini || Mini.isGrownUp())) possibleTargets.Add(p);
+                    if (!p.Data.IsDead && !p.Data.Disconnected && p != p.Data.Role.IsImpostor && p != Spy.spy && (p != Mini.mini || Mini.isGrownUp()) && (Lovers.getPartner(BountyHunter.bountyHunter) == null || p != Lovers.getPartner(BountyHunter.bountyHunter))) possibleTargets.Add(p);
                 }
                 BountyHunter.bounty = possibleTargets[TheOtherRoles.rnd.Next(0, possibleTargets.Count)];
                 if (BountyHunter.bounty == null) return;
@@ -738,6 +781,9 @@ namespace TheOtherRoles.Patches {
                 shifterSetTarget();
                 // Sheriff
                 sheriffSetTarget();
+                // Deputy
+                deputySetTarget();
+                deputyUpdate();
                 // Detective
                 detectiveUpdateFootPrints();
                 // Tracker
@@ -759,10 +805,13 @@ namespace TheOtherRoles.Patches {
                 impostorSetTarget();
                 // Warlock
                 warlockSetTarget();
+                // Check for deputy promotion on Sheriff disconnect
+                deputyCheckPromotion();
                 // Check for sidekick promotion on Jackal disconnect
                 sidekickCheckPromotion();
                 // SecurityGuard
                 securityGuardSetTarget();
+                securityGuardUpdate();
                 // Arsonist
                 arsonistSetTarget();
                 // Snitch
@@ -888,7 +937,7 @@ namespace TheOtherRoles.Patches {
                     otherLover.MurderPlayer(otherLover);
                 }
             }
-            
+
             // Sidekick promotion trigger on murder
             if (Sidekick.promotesToJackal && Sidekick.sidekick != null && !Sidekick.sidekick.Data.IsDead && target == Jackal.jackal && Jackal.jackal == PlayerControl.LocalPlayer) {
                 MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SidekickPromotes, Hazel.SendOption.Reliable, -1);
index f396298f55f9e3ee4707809bca2f8326664bafad..cf672a533cc7b397065f2a9dd954706508cb40f4 100644 (file)
@@ -17,6 +17,8 @@ namespace TheOtherRoles.Patches {
 
     [HarmonyPatch(typeof(RoleManager), nameof(RoleManager.SelectRoles))]
     class RoleManagerSelectRolesPatch {
+        private static int crewValues;
+        private static int impValues;
         public static void Postfix() {
             MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ResetVaribles, Hazel.SendOption.Reliable, -1);
             AmongUsClient.Instance.FinishRpcImmediately(writer);
@@ -31,6 +33,7 @@ namespace TheOtherRoles.Patches {
             assignSpecialRoles(data); // Assign special roles like mafia and lovers first as they assign a role to multiple players and the chances are independent of the ticket system
             selectFactionForFactionIndependentRoles(data);
             assignEnsuredRoles(data); // Assign roles that should always be in the game next
+            assignDependentRoles(data); // Assign roles that may have a dependent role
             assignChanceRoles(data); // Assign roles that may or may not be in the game last
             assignRoleTargets(data);
         }
@@ -87,7 +90,6 @@ namespace TheOtherRoles.Patches {
 
             crewSettings.Add((byte)RoleId.Mayor, CustomOptionHolder.mayorSpawnRate.getSelection());
             crewSettings.Add((byte)RoleId.Engineer, CustomOptionHolder.engineerSpawnRate.getSelection());
-            crewSettings.Add((byte)RoleId.Sheriff, CustomOptionHolder.sheriffSpawnRate.getSelection());
             crewSettings.Add((byte)RoleId.Lighter, CustomOptionHolder.lighterSpawnRate.getSelection());
             crewSettings.Add((byte)RoleId.Detective, CustomOptionHolder.detectiveSpawnRate.getSelection());
             crewSettings.Add((byte)RoleId.TimeMaster, CustomOptionHolder.timeMasterSpawnRate.getSelection());
@@ -160,28 +162,25 @@ namespace TheOtherRoles.Patches {
             }
 
             // Assign Guesser (chance to be impostor based on setting)
-            bool isEvilGuesser = (rnd.Next(1, 101) <= CustomOptionHolder.guesserIsImpGuesserRate.getSelection() * 10);
-            if (CustomOptionHolder.guesserSpawnBothRate.getSelection() > 0) {
-                if (rnd.Next(1, 101) <= CustomOptionHolder.guesserSpawnRate.getSelection() * 10) {
-                    if (isEvilGuesser) {
-                        if (data.impostors.Count > 0 && data.maxImpostorRoles > 0) {
-                            byte evilGuesser = setRoleToRandomPlayer((byte)RoleId.EvilGuesser, data.impostors);
-                            data.impostors.ToList().RemoveAll(x => x.PlayerId == evilGuesser);
-                            data.maxImpostorRoles--;
-                            data.crewSettings.Add((byte)RoleId.NiceGuesser, CustomOptionHolder.guesserSpawnBothRate.getSelection());
-                        }
-                    }
-                    else if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) {                    
-                        byte niceGuesser = setRoleToRandomPlayer((byte)RoleId.NiceGuesser, data.crewmates);
-                        data.crewmates.ToList().RemoveAll(x => x.PlayerId == niceGuesser);
-                        data.maxCrewmateRoles--;
-                        data.impSettings.Add((byte)RoleId.EvilGuesser, CustomOptionHolder.guesserSpawnBothRate.getSelection());
-                    }
-                }
-            } else {
-                if (isEvilGuesser) data.impSettings.Add((byte)RoleId.EvilGuesser, CustomOptionHolder.guesserSpawnRate.getSelection()); 
+            if ((CustomOptionHolder.guesserSpawnBothRate.getSelection() > 0 && 
+                CustomOptionHolder.guesserSpawnRate.getSelection() == 10) || 
+                CustomOptionHolder.guesserSpawnBothRate.getSelection() == 0) {
+
+                if (rnd.Next(1, 101) <= CustomOptionHolder.guesserIsImpGuesserRate.getSelection() * 10) 
+                    data.impSettings.Add((byte)RoleId.EvilGuesser, CustomOptionHolder.guesserSpawnRate.getSelection());
                 else data.crewSettings.Add((byte)RoleId.NiceGuesser, CustomOptionHolder.guesserSpawnRate.getSelection());
+
             }
+
+            // Assign Sheriff
+            if ((CustomOptionHolder.deputySpawnRate.getSelection() > 0 &&
+                CustomOptionHolder.sheriffSpawnRate.getSelection() == 10) ||
+                CustomOptionHolder.deputySpawnRate.getSelection() == 0) 
+                    data.crewSettings.Add((byte)RoleId.Sheriff, CustomOptionHolder.sheriffSpawnRate.getSelection());
+
+
+            crewValues = data.crewSettings.Values.ToList().Sum();
+            impValues = data.impSettings.Values.ToList().Sum();
         }
 
         private static void assignEnsuredRoles(RoleAssignmentData data) {
@@ -210,7 +209,7 @@ namespace TheOtherRoles.Patches {
                 var players = roleType == RoleType.Crewmate || roleType == RoleType.Neutral ? data.crewmates : data.impostors;
                 var index = rnd.Next(0, rolesToAssign[roleType].Count);
                 var roleId = rolesToAssign[roleType][index];
-                setRoleToRandomPlayer(rolesToAssign[roleType][index], players); 
+                setRoleToRandomPlayer(rolesToAssign[roleType][index], players);
                 rolesToAssign[roleType].RemoveAt(index);
 
                 if (CustomOptionHolder.blockedRolePairings.ContainsKey(roleId)) {
@@ -228,14 +227,120 @@ namespace TheOtherRoles.Patches {
 
                 // Adjust the role limit
                 switch (roleType) {
-                    case RoleType.Crewmate: data.maxCrewmateRoles--; break;
-                    case RoleType.Neutral: data.maxNeutralRoles--;break;
-                    case RoleType.Impostor: data.maxImpostorRoles--;break;
+                    case RoleType.Crewmate: data.maxCrewmateRoles--; crewValues -= 10; break;
+                    case RoleType.Neutral: data.maxNeutralRoles--; break;
+                    case RoleType.Impostor: data.maxImpostorRoles--; impValues -= 10;  break;
                 }
             }
         }
 
-        
+        private static void assignDependentRoles(RoleAssignmentData data) {
+
+            int crew = data.crewmates.Count < data.maxCrewmateRoles ? data.crewmates.Count : data.maxCrewmateRoles;
+            int imp = data.impostors.Count < data.maxImpostorRoles ? data.impostors.Count : data.maxImpostorRoles;
+            int leftCrewRolesCount = crew - data.crewSettings.Where(x => x.Value != 0 && x.Value != 10).ToList().Count;
+            int leftImpRolesCount = imp - data.impSettings.Where(x => x.Value != 0 && x.Value != 10).ToList().Count;
+
+            // Other Guesser
+            if (CustomOptionHolder.guesserSpawnBothRate.getSelection() > 0) {
+                bool isEvilGuesser = (rnd.Next(1, 101) <= CustomOptionHolder.guesserIsImpGuesserRate.getSelection() * 10);
+                int guesserSpawnValues = isEvilGuesser ? impValues : crewValues;
+
+                if (Guesser.evilGuesser != null || Guesser.niceGuesser != null) {
+                    if (CustomOptionHolder.guesserSpawnBothRate.getSelection() == 10) { // Force both Guesser
+                        if (Guesser.niceGuesser == null && data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) { // Other Guesser = Nice Guesser
+                            byte niceGuesser = setRoleToRandomPlayer((byte)RoleId.NiceGuesser, data.crewmates);
+                            data.crewmates.ToList().RemoveAll(x => x.PlayerId == niceGuesser);
+                            data.maxCrewmateRoles--;
+                            leftCrewRolesCount--;
+                        }
+                        else if (Guesser.evilGuesser == null && data.impostors.Count > 0 && data.maxImpostorRoles > 0) { // Other Guesser = Evil Guesser
+                            byte evilGuesser = setRoleToRandomPlayer((byte)RoleId.EvilGuesser, data.impostors);
+                            data.impostors.ToList().RemoveAll(x => x.PlayerId == evilGuesser);
+                            data.maxImpostorRoles--;
+                            leftImpRolesCount--;
+                        }
+                    }
+                    else if (Guesser.niceGuesser == null && data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) {  // Dont force, add other guesser (nice) to pool
+                        data.crewSettings.Add((byte)RoleId.NiceGuesser, CustomOptionHolder.guesserSpawnBothRate.getSelection());
+                        crewValues += CustomOptionHolder.guesserSpawnBothRate.getSelection();
+                        leftCrewRolesCount--;
+                    }
+                    else if (Guesser.evilGuesser == null && data.impostors.Count > 0 && data.maxImpostorRoles > 0) { // Dont force, add other guesser (evil) to pool
+                        data.impSettings.Add((byte)RoleId.EvilGuesser, CustomOptionHolder.guesserSpawnBothRate.getSelection());
+                        impValues += CustomOptionHolder.guesserSpawnBothRate.getSelection();
+                        leftImpRolesCount--;
+                    }
+                }
+                else if ((CustomOptionHolder.guesserSpawnRate.getSelection() < 10 && rnd.Next(guesserSpawnValues + CustomOptionHolder.guesserSpawnRate.getSelection()) < CustomOptionHolder.guesserSpawnRate.getSelection()) 
+                    || (isEvilGuesser && leftImpRolesCount > 0) || 
+                    (!isEvilGuesser && leftCrewRolesCount > 0)) { // Guesser is < 100%, but won the lottery or there are roles left to assign
+                    if (isEvilGuesser && data.impostors.Count > 0 && data.maxImpostorRoles > 0) { // Add Evil Guesser
+                        byte evilGuesser = setRoleToRandomPlayer((byte)RoleId.EvilGuesser, data.impostors);
+                        data.impostors.ToList().RemoveAll(x => x.PlayerId == evilGuesser);
+                        data.maxImpostorRoles--;
+                        leftImpRolesCount--;
+                        if (CustomOptionHolder.guesserSpawnBothRate.getSelection() == 10 && data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) { // Force Other Guesser (nice)
+                            byte niceGuesser = setRoleToRandomPlayer((byte)RoleId.NiceGuesser, data.crewmates);
+                            data.crewmates.ToList().RemoveAll(x => x.PlayerId == niceGuesser);
+                            data.maxCrewmateRoles--;
+                            leftCrewRolesCount--;
+                        }
+                        else if (CustomOptionHolder.guesserSpawnBothRate.getSelection() < 10) { // Dont force, add other guesser (nice) to pool
+                            data.crewSettings.Add((byte)RoleId.NiceGuesser, CustomOptionHolder.guesserSpawnBothRate.getSelection());
+                            crewValues += CustomOptionHolder.guesserSpawnBothRate.getSelection();
+                            leftCrewRolesCount--;
+                        }
+                    }
+                    else if (!isEvilGuesser && data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) { // Add Nice Guesser
+                        byte niceGuesser = setRoleToRandomPlayer((byte)RoleId.NiceGuesser, data.crewmates);
+                        data.crewmates.ToList().RemoveAll(x => x.PlayerId == niceGuesser);
+                        data.maxCrewmateRoles--;
+                        leftCrewRolesCount--;
+                        if (CustomOptionHolder.guesserSpawnBothRate.getSelection() == 10 && data.impostors.Count > 0 && data.maxImpostorRoles > 0) { // Force Other Guesser (evil)
+                            byte evilGuesser = setRoleToRandomPlayer((byte)RoleId.EvilGuesser, data.impostors);
+                            data.impostors.ToList().RemoveAll(x => x.PlayerId == evilGuesser);
+                            data.maxImpostorRoles--;
+                            leftImpRolesCount--;
+                        }
+                        else if (CustomOptionHolder.guesserSpawnBothRate.getSelection() < 10) { // Dont force, add other guesser (evil) to pool
+                            data.impSettings.Add((byte)RoleId.EvilGuesser, CustomOptionHolder.guesserSpawnBothRate.getSelection());
+                            impValues += CustomOptionHolder.guesserSpawnBothRate.getSelection();
+                            leftImpRolesCount--;
+                        }
+                    }
+                }
+            }
+
+            // Deputy
+            if (CustomOptionHolder.deputySpawnRate.getSelection() > 0 && data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) {
+                if (Sheriff.sheriff != null) {
+                    if (CustomOptionHolder.deputySpawnRate.getSelection() == 10) { // Force Deputy
+                        byte deputy = setRoleToRandomPlayer((byte)RoleId.Deputy, data.crewmates);
+                        data.crewmates.ToList().RemoveAll(x => x.PlayerId == deputy);
+                        data.maxCrewmateRoles--;
+                    } else if (CustomOptionHolder.deputySpawnRate.getSelection() < 10) { // Dont force, add Deputy to pool
+                        data.crewSettings.Add((byte)RoleId.Deputy, CustomOptionHolder.deputySpawnRate.getSelection()); 
+                    }
+                } else if ((CustomOptionHolder.sheriffSpawnRate.getSelection() < 10 && rnd.Next(crewValues + CustomOptionHolder.sheriffSpawnRate.getSelection()) < CustomOptionHolder.sheriffSpawnRate.getSelection())
+                    || leftCrewRolesCount > 0) { // Sheriff is < 100%, but won the lottery or there are roles left to assign
+                    byte sheriff = setRoleToRandomPlayer((byte)RoleId.Sheriff, data.crewmates);
+                    data.crewmates.ToList().RemoveAll(x => x.PlayerId == sheriff);
+                    data.maxCrewmateRoles--;
+                    leftCrewRolesCount--;
+                    if (CustomOptionHolder.deputySpawnRate.getSelection() == 10 && data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) { // Force Deputy
+                        byte deputy = setRoleToRandomPlayer((byte)RoleId.Deputy, data.crewmates);
+                        data.crewmates.ToList().RemoveAll(x => x.PlayerId == deputy);
+                        data.maxCrewmateRoles--;
+                        leftCrewRolesCount--;
+                    } else { // Dont force, add Deputy to pool
+                        data.crewSettings.Add((byte)RoleId.Deputy, CustomOptionHolder.deputySpawnRate.getSelection());
+                        crewValues += CustomOptionHolder.deputySpawnRate.getSelection();
+                        leftCrewRolesCount--;
+                    }
+                }
+            }
+        }
         private static void assignChanceRoles(RoleAssignmentData data) {
             // Get all roles where the chance to occur is set grater than 0% but not 100% and build a ticket pool based on their weight
             List<byte> crewmateTickets = data.crewSettings.Where(x => x.Value > 0 && x.Value < 10).Select(x => Enumerable.Repeat(x.Key, x.Value)).SelectMany(x => x).ToList();
@@ -262,7 +367,7 @@ namespace TheOtherRoles.Patches {
                 var players = roleType == RoleType.Crewmate || roleType == RoleType.Neutral ? data.crewmates : data.impostors;
                 var index = rnd.Next(0, rolesToAssign[roleType].Count);
                 var roleId = rolesToAssign[roleType][index];
-                setRoleToRandomPlayer(rolesToAssign[roleType][index], players);
+                setRoleToRandomPlayer(roleId, players);
                 rolesToAssign[roleType].RemoveAll(x => x == roleId);
 
                 if (CustomOptionHolder.blockedRolePairings.ContainsKey(roleId)) {
index 1f51e518de6d7dc17f87355ef2723f89c27d735f..34243014b63edafb38bedd9e0c959f76ff2b8814 100644 (file)
@@ -69,11 +69,21 @@ namespace TheOtherRoles.Patches {
                 setPlayerNameColor(Mayor.mayor, Mayor.color);
             else if (Engineer.engineer != null && Engineer.engineer == PlayerControl.LocalPlayer)
                 setPlayerNameColor(Engineer.engineer, Engineer.color);
-            else if (Sheriff.sheriff != null && Sheriff.sheriff == PlayerControl.LocalPlayer) 
+            else if (Sheriff.sheriff != null && Sheriff.sheriff == PlayerControl.LocalPlayer) {
                 setPlayerNameColor(Sheriff.sheriff, Sheriff.color);
-            else if (Lighter.lighter != null && Lighter.lighter == PlayerControl.LocalPlayer) 
+                if (Deputy.deputy != null && Deputy.knowsSheriff) {
+                    setPlayerNameColor(Deputy.deputy, Deputy.color);
+                }
+            }
+            else if (Deputy.deputy != null && Deputy.deputy == PlayerControl.LocalPlayer) {
+                setPlayerNameColor(Deputy.deputy, Deputy.color);
+                if (Sheriff.sheriff != null && Deputy.knowsSheriff) {
+                    setPlayerNameColor(Sheriff.sheriff, Sheriff.color);
+                }
+            }
+            else if (Lighter.lighter != null && Lighter.lighter == PlayerControl.LocalPlayer)
                 setPlayerNameColor(Lighter.lighter, Lighter.color);
-            else if (Detective.detective != null && Detective.detective == PlayerControl.LocalPlayer) 
+            else if (Detective.detective != null && Detective.detective == PlayerControl.LocalPlayer)
                 setPlayerNameColor(Detective.detective, Detective.color);
             else if (TimeMaster.timeMaster != null && TimeMaster.timeMaster == PlayerControl.LocalPlayer)
                 setPlayerNameColor(TimeMaster.timeMaster, TimeMaster.color);
@@ -84,12 +94,12 @@ namespace TheOtherRoles.Patches {
             else if (Swapper.swapper != null && Swapper.swapper == PlayerControl.LocalPlayer)
                 setPlayerNameColor(Swapper.swapper, Swapper.color);
             else if (Seer.seer != null && Seer.seer == PlayerControl.LocalPlayer)
-                setPlayerNameColor(Seer.seer, Seer.color);  
-            else if (Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer) 
+                setPlayerNameColor(Seer.seer, Seer.color);
+            else if (Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer)
                 setPlayerNameColor(Hacker.hacker, Hacker.color);
-            else if (Tracker.tracker != null && Tracker.tracker == PlayerControl.LocalPlayer) 
+            else if (Tracker.tracker != null && Tracker.tracker == PlayerControl.LocalPlayer)
                 setPlayerNameColor(Tracker.tracker, Tracker.color);
-            else if (Snitch.snitch != null && Snitch.snitch == PlayerControl.LocalPlayer) 
+            else if (Snitch.snitch != null && Snitch.snitch == PlayerControl.LocalPlayer)
                 setPlayerNameColor(Snitch.snitch, Snitch.color);
             else if (Jackal.jackal != null && Jackal.jackal == PlayerControl.LocalPlayer) {
                 // Jackal can see his sidekick
@@ -210,6 +220,8 @@ namespace TheOtherRoles.Patches {
             Lighter.lighterTimer -= Time.deltaTime;
             Trickster.lightsOutTimer -= Time.deltaTime;
             Tracker.corpsesTrackingTimer -= Time.deltaTime;
+            foreach (byte key in Deputy.handcuffedKnows.Keys.ToList())
+                Deputy.handcuffedKnows[key] -= Time.deltaTime;
         }
 
         public static void miniUpdate() {
@@ -244,6 +256,20 @@ namespace TheOtherRoles.Patches {
             
             if (enabled) __instance.KillButton.Show();
             else __instance.KillButton.Hide();
+
+            if (Deputy.handcuffedKnows.ContainsKey(PlayerControl.LocalPlayer.PlayerId) && Deputy.handcuffedKnows[PlayerControl.LocalPlayer.PlayerId] > 0) __instance.KillButton.Hide();
+        }
+
+        static void updateReportButton(HudManager __instance) {
+            if (Deputy.handcuffedKnows.ContainsKey(PlayerControl.LocalPlayer.PlayerId) && Deputy.handcuffedKnows[PlayerControl.LocalPlayer.PlayerId] > 0 || MeetingHud.Instance) __instance.ReportButton.Hide();
+            else if (!__instance.ReportButton.isActiveAndEnabled) __instance.ReportButton.Show();
+        }
+         
+        static void updateVentButton(HudManager __instance)
+        {
+            if (Deputy.handcuffedKnows.ContainsKey(PlayerControl.LocalPlayer.PlayerId) && Deputy.handcuffedKnows[PlayerControl.LocalPlayer.PlayerId] > 0 || MeetingHud.Instance) __instance.ImpostorVentButton.Hide();
+            else if (PlayerControl.LocalPlayer.roleCanUseVents() && !__instance.ImpostorVentButton.isActiveAndEnabled) __instance.ImpostorVentButton.Show();
+
         }
 
         static void Postfix(HudManager __instance)
@@ -262,6 +288,11 @@ namespace TheOtherRoles.Patches {
             timerUpdate();
             // Mini
             miniUpdate();
+
+            // Deputy Sabotage, Use and Vent Button Disabling
+            updateReportButton(__instance);
+            updateVentButton(__instance);
+
         }
     }
 }
index 6cfa5ab5005ecbcf4ec6177b12e2376d73334caf..f811fcc28910b32e40e012d6ce4d3dd4191ab612 100644 (file)
@@ -7,6 +7,7 @@ using static TheOtherRoles.TheOtherRoles;
 using static TheOtherRoles.GameHistory;
 using static TheOtherRoles.MapOptions;
 using System.Collections.Generic;
+using TheOtherRoles.Objects;
 
 
 namespace TheOtherRoles.Patches {
@@ -14,8 +15,7 @@ namespace TheOtherRoles.Patches {
     [HarmonyPatch(typeof(Vent), nameof(Vent.CanUse))]
     public static class VentCanUsePatch
     {
-        public static bool Prefix(Vent __instance, ref float __result, [HarmonyArgument(0)] GameData.PlayerInfo pc, [HarmonyArgument(1)] out bool canUse, [HarmonyArgument(2)] out bool couldUse)
-        {
+        public static bool Prefix(Vent __instance, ref float __result, [HarmonyArgument(0)] GameData.PlayerInfo pc, [HarmonyArgument(1)] out bool canUse, [HarmonyArgument(2)] out bool couldUse) {
             float num = float.MaxValue;
             PlayerControl @object = pc.Object;
 
@@ -58,7 +58,7 @@ namespace TheOtherRoles.Patches {
     class VentButtonDoClickPatch {
         static  bool Prefix(VentButton __instance) {
             // Manually modifying the VentButton to use Vent.Use again in order to trigger the Vent.Use prefix patch
-                   if (__instance.currentTarget != null) __instance.currentTarget.Use();
+                   if (__instance.currentTarget != null && !Deputy.handcuffedKnows.ContainsKey(PlayerControl.LocalPlayer.PlayerId)) __instance.currentTarget.Use();
             return false;
         }
     }
@@ -66,6 +66,12 @@ namespace TheOtherRoles.Patches {
     [HarmonyPatch(typeof(Vent), nameof(Vent.Use))]
     public static class VentUsePatch {
         public static bool Prefix(Vent __instance) {
+            // Deputy handcuff disables the vents
+            if (Deputy.handcuffedPlayers.Contains(PlayerControl.LocalPlayer.PlayerId)) {
+                Deputy.setHandcuffedKnows();
+                return false;
+            }
+
             bool canUse;
             bool couldUse;
             __instance.CanUse(PlayerControl.LocalPlayer.Data, out canUse, out couldUse);
@@ -122,6 +128,12 @@ namespace TheOtherRoles.Patches {
     class KillButtonDoClickPatch {
         public static bool Prefix(KillButton __instance) {
             if (__instance.isActiveAndEnabled && __instance.currentTarget && !__instance.isCoolingDown && !PlayerControl.LocalPlayer.Data.IsDead && PlayerControl.LocalPlayer.CanMove) {
+                // Deputy handcuff update.
+                if (Deputy.handcuffedPlayers.Contains(PlayerControl.LocalPlayer.PlayerId)) {
+                    Deputy.setHandcuffedKnows();
+                    return false;
+                }
+                
                 // Use an unchecked kill command, to allow shorter kill cooldowns etc. without getting kicked
                 MurderAttemptResult res = Helpers.checkMuderAttemptAndKill(PlayerControl.LocalPlayer, __instance.currentTarget);
                 // Handle blank kill
@@ -142,7 +154,6 @@ namespace TheOtherRoles.Patches {
         }
     }
 
-
     [HarmonyPatch(typeof(SabotageButton), nameof(SabotageButton.Refresh))]
     class SabotageButtonRefreshPatch {
         static void Postfix() {
@@ -155,6 +166,14 @@ namespace TheOtherRoles.Patches {
         }
     }
 
+    [HarmonyPatch(typeof(ReportButton), nameof(ReportButton.DoClick))]
+    class ReportButtonDoClickPatch {
+        public static bool Prefix(ReportButton __instance) {
+            if (__instance.isActiveAndEnabled && Deputy.handcuffedPlayers.Contains(PlayerControl.LocalPlayer.PlayerId)) Deputy.setHandcuffedKnows();
+            return !Deputy.handcuffedKnows.ContainsKey(PlayerControl.LocalPlayer.PlayerId);
+        }
+    }
+
     [HarmonyPatch(typeof(EmergencyMinigame), nameof(EmergencyMinigame.Update))]
     class EmergencyMinigameUpdatePatch {
         static void Postfix(EmergencyMinigame __instance) {
index fef63561e57b1233bb726f312fccf0e8b3afa958..207a4ffa285e29b74bce33e857bf9690cfc1dfb0 100644 (file)
@@ -18,6 +18,7 @@ namespace TheOtherRoles
         Mayor,
         Engineer,
         Sheriff,
+        Deputy,
         Lighter,
         Godfather,
         Mafioso,
@@ -88,6 +89,8 @@ namespace TheOtherRoles
         TrackerUsedTracker,
         VampireSetBitten,
         PlaceGarlic,
+        DeputyUsedHandcuffs,
+        DeputyPromotes,
         JackalCreatesSidekick,
         SidekickPromotes,
         ErasePlayerRoles,
@@ -162,6 +165,9 @@ namespace TheOtherRoles
                     case RoleId.Sheriff:
                         Sheriff.sheriff = player;
                         break;
+                    case RoleId.Deputy:
+                        Deputy.deputy = player;
+                        break;
                     case RoleId.Lighter:
                         Lighter.lighter = player;
                         break;
@@ -431,8 +437,12 @@ namespace TheOtherRoles
                 Mayor.mayor = oldShifter;
             if (Engineer.engineer != null && Engineer.engineer == player)
                 Engineer.engineer = oldShifter;
-            if (Sheriff.sheriff != null && Sheriff.sheriff == player)
+            if (Sheriff.sheriff != null && Sheriff.sheriff == player) {
+                if (Sheriff.formerDeputy != null && Sheriff.formerDeputy == Sheriff.sheriff) Sheriff.formerDeputy = oldShifter;  // Shifter also shifts info on promoted deputy (to get handcuffs)
                 Sheriff.sheriff = oldShifter;
+            }
+            if (Deputy.deputy != null && Deputy.deputy == player)
+                Deputy.deputy = oldShifter;
             if (Lighter.lighter != null && Lighter.lighter == player)
                 Lighter.lighter = oldShifter;
             if (Detective.detective != null && Detective.detective == player)
@@ -525,6 +535,22 @@ namespace TheOtherRoles
                     Tracker.tracked = player;
         }
 
+        public static void deputyUsedHandcuffs(byte targetId)
+        {
+            Deputy.remainingHandcuffs--;
+            Deputy.handcuffedPlayers.Add(targetId);
+        }
+
+        public static void deputyPromotes()
+        {
+            if (Deputy.deputy != null) {  // Deputy should never be null here, but there appeared to be a race condition during testing, which was removed.
+                Sheriff.replaceCurrentSheriff(Deputy.deputy);
+                Sheriff.formerDeputy = Deputy.deputy;
+                Deputy.deputy = null;
+                // No clear and reload, as we need to keep the number of handcuffs left etc
+            }
+        }
+
         public static void jackalCreatesSidekick(byte targetId) {
             PlayerControl player = Helpers.playerById(targetId);
             if (player == null) return;
@@ -555,6 +581,7 @@ namespace TheOtherRoles
             if (player == Mayor.mayor) Mayor.clearAndReload();
             if (player == Engineer.engineer) Engineer.clearAndReload();
             if (player == Sheriff.sheriff) Sheriff.clearAndReload();
+            if (player == Deputy.deputy) Deputy.clearAndReload();
             if (player == Lighter.lighter) Lighter.clearAndReload();
             if (player == Detective.detective) Detective.clearAndReload();
             if (player == TimeMaster.timeMaster) TimeMaster.clearAndReload();
@@ -738,6 +765,14 @@ namespace TheOtherRoles
                         pva.SetDead(pva.DidReport, true);
                         pva.Overlay.gameObject.SetActive(true);
                     }
+
+                    //Give players back their vote if target is shot dead
+                    if (pva.VotedFor != dyingTargetId || pva.VotedFor != partnerId) continue;
+                    pva.UnsetVote();
+                    var voteAreaPlayer = Helpers.playerById(pva.TargetPlayerId);
+                    if (!voteAreaPlayer.AmOwner) continue;
+                    MeetingHud.Instance.ClearVote();
+
                 }
                 if (AmongUsClient.Instance.AmHost) 
                     MeetingHud.Instance.CheckForEndVoting();
@@ -879,6 +914,12 @@ namespace TheOtherRoles
                     break;
                 case (byte)CustomRPC.TrackerUsedTracker:
                     RPCProcedure.trackerUsedTracker(reader.ReadByte());
+                    break;               
+                case (byte)CustomRPC.DeputyUsedHandcuffs:
+                    RPCProcedure.deputyUsedHandcuffs(reader.ReadByte());
+                    break;
+                case (byte)CustomRPC.DeputyPromotes:
+                    RPCProcedure.deputyPromotes();
                     break;
                 case (byte)CustomRPC.JackalCreatesSidekick:
                     RPCProcedure.jackalCreatesSidekick(reader.ReadByte());
diff --git a/TheOtherRoles/Resources/DeputizeButton.png b/TheOtherRoles/Resources/DeputizeButton.png
new file mode 100644 (file)
index 0000000..c915e05
Binary files /dev/null and b/TheOtherRoles/Resources/DeputizeButton.png differ
diff --git a/TheOtherRoles/Resources/DeputyHandcuffButton.png b/TheOtherRoles/Resources/DeputyHandcuffButton.png
new file mode 100644 (file)
index 0000000..b12991d
Binary files /dev/null and b/TheOtherRoles/Resources/DeputyHandcuffButton.png differ
diff --git a/TheOtherRoles/Resources/DeputyHandcuffed.png b/TheOtherRoles/Resources/DeputyHandcuffed.png
new file mode 100644 (file)
index 0000000..38e00ba
Binary files /dev/null and b/TheOtherRoles/Resources/DeputyHandcuffed.png differ
index cc2ee3e4ea7b62d39bb0ab8183573b05f998a469..f3be91277d57c059960d93c9a2e8d1066f3fdd82 100644 (file)
Binary files a/TheOtherRoles/Resources/RepairButton.png and b/TheOtherRoles/Resources/RepairButton.png differ
index 04bbdf6ca81dbb0072daba19f08963d765482634..b787ccbbf49f3adba23345770b052c4dfaa3c1f4 100644 (file)
@@ -28,6 +28,7 @@ namespace TheOtherRoles
         public static RoleInfo mayor = new RoleInfo("Mayor", Mayor.color, "Your vote counts twice", "Your vote counts twice", RoleId.Mayor);
         public static RoleInfo engineer = new RoleInfo("Engineer",  Engineer.color, "Maintain important systems on the ship", "Repair the ship", RoleId.Engineer);
         public static RoleInfo sheriff = new RoleInfo("Sheriff", Sheriff.color, "Shoot the <color=#FF1919FF>Impostors</color>", "Shoot the Impostors", RoleId.Sheriff);
+        public static RoleInfo deputy = new RoleInfo("Deputy", Sheriff.color, "Handcuff the <color=#FF1919FF>Impostors</color>", "Handcuff the Impostors", RoleId.Deputy);
         public static RoleInfo lighter = new RoleInfo("Lighter", Lighter.color, "Your light never goes out", "Your light never goes out", RoleId.Lighter);
         public static RoleInfo godfather = new RoleInfo("Godfather", Godfather.color, "Kill all Crewmates", "Kill all Crewmates", RoleId.Godfather);
         public static RoleInfo mafioso = new RoleInfo("Mafioso", Mafioso.color, "Work with the <color=#FF1919FF>Mafia</color> to kill the Crewmates", "Kill all Crewmates", RoleId.Mafioso);
@@ -99,6 +100,7 @@ namespace TheOtherRoles
             mayor,
             engineer,
             sheriff,
+            deputy,
             lighter,
             detective,
             timeMaster,
@@ -122,7 +124,8 @@ namespace TheOtherRoles
             if (p == Jester.jester) infos.Add(jester);
             if (p == Mayor.mayor) infos.Add(mayor);
             if (p == Engineer.engineer) infos.Add(engineer);
-            if (p == Sheriff.sheriff) infos.Add(sheriff);
+            if (p == Sheriff.sheriff || p == Sheriff.formerSheriff) infos.Add(sheriff);
+            if (p == Deputy.deputy) infos.Add(deputy);
             if (p == Lighter.lighter) infos.Add(lighter);
             if (p == Godfather.godfather) infos.Add(godfather);
             if (p == Mafioso.mafioso) infos.Add(mafioso);
index 4243d1c9b949c684e45df9e552373e724a6cbaac..da9785a8f38cf102f80a447e751570b6a7702843 100644 (file)
@@ -24,6 +24,7 @@ namespace TheOtherRoles
             Mayor.clearAndReload();
             Engineer.clearAndReload();
             Sheriff.clearAndReload();
+            Deputy.clearAndReload();
             Lighter.clearAndReload();
             Godfather.clearAndReload();
             Mafioso.clearAndReload();
@@ -155,15 +156,88 @@ namespace TheOtherRoles
 
             public static PlayerControl currentTarget;
 
+            public static PlayerControl formerDeputy;  // Needed for keeping handcuffs + shifting
+            public static PlayerControl formerSheriff;  // When deputy gets promoted...
+
+            public static void replaceCurrentSheriff(PlayerControl deputy)
+            {
+                if (!formerSheriff) formerSheriff = sheriff;
+                sheriff = deputy;
+                currentTarget = null;
+                cooldown = CustomOptionHolder.jackalKillCooldown.getFloat();
+            }
+
             public static void clearAndReload() {
                 sheriff = null;
                 currentTarget = null;
+                formerDeputy = null;
+                formerSheriff = null;
                 cooldown = CustomOptionHolder.sheriffCooldown.getFloat();
                 canKillNeutrals = CustomOptionHolder.sheriffCanKillNeutrals.getBool();
                 spyCanDieToSheriff = CustomOptionHolder.spyCanDieToSheriff.getBool();
             }
         }
 
+        public static class Deputy
+        {
+            public static PlayerControl deputy;
+            public static Color color = Sheriff.color;
+
+            public static PlayerControl currentTarget;
+            public static List<byte> handcuffedPlayers = new List<byte>();
+            public static int promotesToSheriff; // No: 0, Immediately: 1, After Meeting: 2
+            public static bool keepsHandcuffsOnPromotion;
+            public static float handcuffDuration;
+            public static float remainingHandcuffs;
+            public static float handcuffCooldown;
+            public static bool knowsSheriff;
+            public static Dictionary<byte, float> handcuffedKnows = new Dictionary<byte, float>();
+
+            private static Sprite buttonSprite;
+            private static Sprite handcuffedSprite;
+            
+            public static Sprite getButtonSprite()
+            {
+                if (buttonSprite) return buttonSprite;
+                buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.DeputyHandcuffButton.png", 115f);
+                return buttonSprite;
+            }
+
+            public static Sprite getHandcuffedButtonSprite()
+            {
+                if (handcuffedSprite) return handcuffedSprite;
+                handcuffedSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.DeputyHandcuffed.png", 115f);
+                return handcuffedSprite;
+            }
+
+            // Can be used to enable / disable the handcuff effect on the target's buttons
+            public static void setHandcuffedKnows(bool active = true)
+            {
+                if (active) {
+                    byte localPlayerId = PlayerControl.LocalPlayer.PlayerId;
+                    handcuffedKnows.Add(localPlayerId, handcuffDuration);
+                    handcuffedPlayers.RemoveAll(x => x == localPlayerId);
+                }
+
+                HudManagerStartPatch.setAllButtonsHandcuffedStatus(active);
+            }
+
+            public static void clearAndReload()
+            {
+                deputy = null;
+                currentTarget = null;
+                handcuffedPlayers = new List<byte>();
+                handcuffedKnows = new Dictionary<byte, float>();
+                HudManagerStartPatch.setAllButtonsHandcuffedStatus(false, true);
+                promotesToSheriff = CustomOptionHolder.deputyGetsPromoted.getSelection();
+                remainingHandcuffs = CustomOptionHolder.deputyNumberOfHandcuffs.getFloat();
+                handcuffCooldown = CustomOptionHolder.deputyHandcuffCooldown.getFloat();
+                keepsHandcuffsOnPromotion = CustomOptionHolder.deputyKeepsHandcuffs.getBool();
+                handcuffDuration = CustomOptionHolder.deputyHandcuffDuration.getFloat();
+                knowsSheriff = CustomOptionHolder.deputyKnowsSheriff.getBool();
+            }
+        }
+
         public static class Lighter {
             public static PlayerControl lighter;
             public static Color color = new Color32(238, 229, 190, byte.MaxValue);
@@ -335,6 +409,7 @@ namespace TheOtherRoles
         public static Color color = new Color32(232, 57, 185, byte.MaxValue);
 
         public static bool bothDie = true;
+        public static bool enableChat = true;
         // Lovers save if next to be exiled is a lover, because RPC of ending game comes before RPC of exiled
         public static bool notAckedExiledIsLover = false;
 
@@ -363,6 +438,7 @@ namespace TheOtherRoles
             lover2 = null;
             notAckedExiledIsLover = false;
             bothDie = CustomOptionHolder.loversBothDie.getBool();
+            enableChat = CustomOptionHolder.loversEnableChat.getBool();
         }
 
         public static PlayerControl getPartner(this PlayerControl player) {
@@ -491,6 +567,7 @@ namespace TheOtherRoles
         public static int rechargedTasks = 2;
         public static int chargesVitals = 1;
         public static int chargesAdminTable = 1;
+        public static bool cantMove = true;
 
         private static Sprite buttonSprite;
         private static Sprite vitalsSprite;
@@ -539,6 +616,7 @@ namespace TheOtherRoles
             rechargedTasks = Mathf.RoundToInt(CustomOptionHolder.hackerRechargeTasksNumber.getFloat());
             chargesVitals = Mathf.RoundToInt(CustomOptionHolder.hackerToolsNumber.getFloat()) / 2;
             chargesAdminTable = Mathf.RoundToInt(CustomOptionHolder.hackerToolsNumber.getFloat()) / 2;
+            cantMove = CustomOptionHolder.hackerNoMove.getBool();
         }
     }
 
@@ -924,7 +1002,14 @@ namespace TheOtherRoles
         public static int ventPrice = 1;
         public static int camPrice = 2;
         public static int placedCameras = 0;
+        public static float duration = 10f;
+        public static int maxCharges = 5;
+        public static int rechargeTasksNumber = 3;
+        public static int rechargedTasks = 3;
+        public static int charges = 1;
+        public static bool cantMove = true;
         public static Vent ventTarget = null;
+        public static Minigame minigame = null;
 
         private static Sprite closeVentButtonSprite;
         public static Sprite getCloseVentButtonSprite() {
@@ -954,14 +1039,35 @@ namespace TheOtherRoles
             return staticVentSealedSprite;
         }
 
+        private static Sprite camSprite;
+        public static Sprite getCamSprite() {
+            if (camSprite) return camSprite;
+            camSprite = HudManager.Instance.UseButton.fastUseSettings[ImageNames.CamsButton].Image;
+            return camSprite;
+        }
+
+        private static Sprite logSprite;
+        public static Sprite getLogSprite() {
+            if (logSprite) return logSprite;
+            logSprite = HudManager.Instance.UseButton.fastUseSettings[ImageNames.DoorLogsButton].Image;
+            return logSprite;
+        }
+
         public static void clearAndReload() {
             securityGuard = null;
             ventTarget = null;
+            minigame = null;
+            duration = CustomOptionHolder.securityGuardCamDuration.getFloat();
+            maxCharges = Mathf.RoundToInt(CustomOptionHolder.securityGuardCamMaxCharges.getFloat());
+            rechargeTasksNumber = Mathf.RoundToInt(CustomOptionHolder.securityGuardCamRechargeTasksNumber.getFloat());
+            rechargedTasks = Mathf.RoundToInt(CustomOptionHolder.securityGuardCamRechargeTasksNumber.getFloat());
+            charges = Mathf.RoundToInt(CustomOptionHolder.securityGuardCamMaxCharges.getFloat()) /2;
             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());
+            cantMove = CustomOptionHolder.securityGuardNoMove.getBool();
         }
     }
 
@@ -1021,7 +1127,7 @@ namespace TheOtherRoles
         public static bool showInfoInGhostChat = true;
         public static bool killsThroughShield = true;
         public static bool evilGuesserCanGuessSpy = true;
-
+        public static bool guesserCantGuessSnitch = false;
         public static Sprite getTargetSprite() {
             if (targetSprite) return targetSprite;
             targetSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TargetIcon.png", 150f);
@@ -1052,7 +1158,7 @@ namespace TheOtherRoles
         public static void clearAndReload() {
             niceGuesser = null;
             evilGuesser = null;
-
+            guesserCantGuessSnitch = CustomOptionHolder.guesserCantGuessSnitchIfTaksDone.getBool();
             remainingShotsEvilGuesser = Mathf.RoundToInt(CustomOptionHolder.guesserNumberOfShots.getFloat());
             remainingShotsNiceGuesser = Mathf.RoundToInt(CustomOptionHolder.guesserNumberOfShots.getFloat());
             hasMultipleShotsPerMeeting = CustomOptionHolder.guesserHasMultipleShotsPerMeeting.getBool();
index ee0f6e63eb3b05c9358f2ef61628bf6911093a62..aebc258d80b617700072926912a142f1c4cd247f 100644 (file)
@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
     <PropertyGroup>
         <TargetFramework>netstandard2.1</TargetFramework>
-        <Version>3.3.3</Version>
+        <Version>3.4.0</Version>
         <Description>TheOtherRoles</Description>
         <Authors>Eisbison</Authors>
     </PropertyGroup>