From: dwd0tcom Date: Sat, 22 Jan 2022 16:31:19 +0000 (+0100) Subject: Version 3.4.0 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=8e0544e0c99460de36dfe15471bb8bf76a0f3baa;p=rhonda%2Ftheotherroles.git Version 3.4.0 --- diff --git a/Images/TOR_roles.jpg b/Images/TOR_roles.jpg index c854825..fcbe686 100644 Binary files a/Images/TOR_roles.jpg and b/Images/TOR_roles.jpg differ diff --git a/README.md b/README.md index cc27277..1eba5e9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ ![eisbison banner](./Images/TOR_logo.png) +

+

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.

@@ -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
Click to show the Changelog + +**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. diff --git a/TheOtherRoles/Buttons.cs b/TheOtherRoles/Buttons.cs index de050d2..2687284 100644 --- a/TheOtherRoles/Buttons.cs +++ b/TheOtherRoles/Buttons.cs @@ -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> 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 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 { 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>(); + 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.Instance.ShowMap((System.Action)(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().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().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 diff --git a/TheOtherRoles/CustomOptionHolder.cs b/TheOtherRoles/CustomOptionHolder.cs index 5f1a2fd..c9c80f6 100644 --- a/TheOtherRoles/CustomOptionHolder.cs +++ b/TheOtherRoles/CustomOptionHolder.cs @@ -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 +} diff --git a/TheOtherRoles/Helpers.cs b/TheOtherRoles/Helpers.cs index 0f6dd9d..006b5a2 100644 --- a/TheOtherRoles/Helpers.cs +++ b/TheOtherRoles/Helpers.cs @@ -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; } diff --git a/TheOtherRoles/Main.cs b/TheOtherRoles/Main.cs index bf0f5f0..580f705 100644 --- a/TheOtherRoles/Main.cs +++ b/TheOtherRoles/Main.cs @@ -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 StreamerModeReplacementColor { get; set; } public static ConfigEntry Ip { get; set; } public static ConfigEntry Port { get; set; } + public static ConfigEntry 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"); diff --git a/TheOtherRoles/Modules/ChatCommands.cs b/TheOtherRoles/Modules/ChatCommands.cs index 480c10f..6f34cdd 100644 --- a/TheOtherRoles/Modules/ChatCommands.cs +++ b/TheOtherRoles/Modules/ChatCommands.cs @@ -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.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); + + } + } } } diff --git a/TheOtherRoles/Modules/ModUpdater.cs b/TheOtherRoles/Modules/ModUpdater.cs index 36f5554..512a6c1 100644 --- a/TheOtherRoles/Modules/ModUpdater.cs +++ b/TheOtherRoles/Modules/ModUpdater.cs @@ -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.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 = $@"A new THE OTHER ROLES +update to v{ver} is available + +{announcement}"; + JToken assets = data["assets"]; if (!assets.HasValues) return false; @@ -135,7 +157,11 @@ namespace TheOtherRoles.Modules { } } } - } + } else { + announcement = $@"Version {ver}: + +{announcement}"; + } } catch (System.Exception ex) { TheOtherRolesPlugin.Instance.Log.LogError(ex.ToString()); System.Console.WriteLine(ex); diff --git a/TheOtherRoles/Objects/CustomButton.cs b/TheOtherRoles/Objects/CustomButton.cs index 4cf49c6..ba7347a 100644 --- a/TheOtherRoles/Objects/CustomButton.cs +++ b/TheOtherRoles/Objects/CustomButton.cs @@ -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 HasButton; - private Func CouldUse; + public Func HasButton; + public Func 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 HasButton, Func 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 diff --git a/TheOtherRoles/Patches/CredentialsPatch.cs b/TheOtherRoles/Patches/CredentialsPatch.cs index ad47709..d7d7eda 100644 --- a/TheOtherRoles/Patches/CredentialsPatch.cs +++ b/TheOtherRoles/Patches/CredentialsPatch.cs @@ -19,8 +19,7 @@ Button design by Bavari"; $@"Modded by Eisbison, Thunderstorm584, EndOfFile & Mallöris Design by Bavari"; - - public static string contributorsCredentials = "GitHub Contributors: Alex2911, amsyarasyiq, gendelo3"; + public static string contributorsCredentials = "GitHub Contributors: Alex2911, amsyarasyiq, gendelo3, MaximeGillot"; [HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))] private static class VersionShowerPatch @@ -60,7 +59,7 @@ Design by Bavari"; __instance.text.alignment = TMPro.TextAlignmentOptions.TopRight; if (AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Started) { __instance.text.text = $"TheOtherRoles 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 Bavari"; var torLogo = new GameObject("bannerLogo_TOR"); torLogo.transform.position = Vector3.up; var renderer = torLogo.AddComponent(); - renderer.sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Banner.png", 300f); + renderer.sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Banner.png", 300f); } } } diff --git a/TheOtherRoles/Patches/ExileControllerPatch.cs b/TheOtherRoles/Patches/ExileControllerPatch.cs index 6ec25ba..37f4358 100644 --- a/TheOtherRoles/Patches/ExileControllerPatch.cs +++ b/TheOtherRoles/Patches/ExileControllerPatch.cs @@ -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; diff --git a/TheOtherRoles/Patches/IntroPatch.cs b/TheOtherRoles/Patches/IntroPatch.cs index c67303d..1ce87e8 100644 --- a/TheOtherRoles/Patches/IntroPatch.cs +++ b/TheOtherRoles/Patches/IntroPatch.cs @@ -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 ?? ""}"); + } + } } diff --git a/TheOtherRoles/Patches/MeetingPatch.cs b/TheOtherRoles/Patches/MeetingPatch.cs index 2fc30cc..39596f2 100644 --- a/TheOtherRoles/Patches/MeetingPatch.cs +++ b/TheOtherRoles/Patches/MeetingPatch.cs @@ -247,7 +247,7 @@ namespace TheOtherRoles.Patches { Transform exitButtonMask = UnityEngine.Object.Instantiate(maskTemplate, exitButtonParent); exitButton.gameObject.GetComponent().sprite = smallButtonTemplate.GetComponent().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().OnClick.RemoveAllListeners(); exitButton.GetComponent().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(); renderer.sprite = Swapper.getCheckSprite(); renderer.color = Color.red; @@ -375,7 +382,7 @@ namespace TheOtherRoles.Patches { SpriteRenderer rend = (new GameObject()).AddComponent(); 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(); renderer.sprite = Guesser.getTargetSprite(); PassiveButton button = targetBox.GetComponent(); diff --git a/TheOtherRoles/Patches/PlayerControlPatch.cs b/TheOtherRoles/Patches/PlayerControlPatch.cs index 7398da8..c9667ba 100644 --- a/TheOtherRoles/Patches/PlayerControlPatch.cs +++ b/TheOtherRoles/Patches/PlayerControlPatch.cs @@ -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 untargetables; @@ -523,8 +565,9 @@ namespace TheOtherRoles.Patches { BountyHunter.arrowUpdateTimer = 0f; // Force arrow to update BountyHunter.bountyUpdateTimer = BountyHunter.bountyDuration; var possibleTargets = new List(); + 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); diff --git a/TheOtherRoles/Patches/RoleAssignmentPatch.cs b/TheOtherRoles/Patches/RoleAssignmentPatch.cs index f396298..cf672a5 100644 --- a/TheOtherRoles/Patches/RoleAssignmentPatch.cs +++ b/TheOtherRoles/Patches/RoleAssignmentPatch.cs @@ -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 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)) { diff --git a/TheOtherRoles/Patches/UpdatePatch.cs b/TheOtherRoles/Patches/UpdatePatch.cs index 1f51e51..3424301 100644 --- a/TheOtherRoles/Patches/UpdatePatch.cs +++ b/TheOtherRoles/Patches/UpdatePatch.cs @@ -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); + } } } diff --git a/TheOtherRoles/Patches/UsablesPatch.cs b/TheOtherRoles/Patches/UsablesPatch.cs index 6cfa5ab..f811fcc 100644 --- a/TheOtherRoles/Patches/UsablesPatch.cs +++ b/TheOtherRoles/Patches/UsablesPatch.cs @@ -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) { diff --git a/TheOtherRoles/RPC.cs b/TheOtherRoles/RPC.cs index fef6356..207a4ff 100644 --- a/TheOtherRoles/RPC.cs +++ b/TheOtherRoles/RPC.cs @@ -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 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 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 index 0000000..38e00ba Binary files /dev/null and b/TheOtherRoles/Resources/DeputyHandcuffed.png differ diff --git a/TheOtherRoles/Resources/RepairButton.png b/TheOtherRoles/Resources/RepairButton.png index cc2ee3e..f3be912 100644 Binary files a/TheOtherRoles/Resources/RepairButton.png and b/TheOtherRoles/Resources/RepairButton.png differ diff --git a/TheOtherRoles/RoleInfo.cs b/TheOtherRoles/RoleInfo.cs index 04bbdf6..b787ccb 100644 --- a/TheOtherRoles/RoleInfo.cs +++ b/TheOtherRoles/RoleInfo.cs @@ -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 Impostors", "Shoot the Impostors", RoleId.Sheriff); + public static RoleInfo deputy = new RoleInfo("Deputy", Sheriff.color, "Handcuff the Impostors", "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 Mafia 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); diff --git a/TheOtherRoles/TheOtherRoles.cs b/TheOtherRoles/TheOtherRoles.cs index 4243d1c..da9785a 100644 --- a/TheOtherRoles/TheOtherRoles.cs +++ b/TheOtherRoles/TheOtherRoles.cs @@ -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 handcuffedPlayers = new List(); + 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 handcuffedKnows = new Dictionary(); + + 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(); + handcuffedKnows = new Dictionary(); + 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(); diff --git a/TheOtherRoles/TheOtherRoles.csproj b/TheOtherRoles/TheOtherRoles.csproj index ee0f6e6..aebc258 100644 --- a/TheOtherRoles/TheOtherRoles.csproj +++ b/TheOtherRoles/TheOtherRoles.csproj @@ -1,7 +1,7 @@ netstandard2.1 - 3.3.3 + 3.4.0 TheOtherRoles Eisbison