
+<p align="center"><a href="https://github.com/Eisbison/TheOtherRoles/releases/"><img src="https://badgen.net/github/release/eisbison/theotherroles"></a></p>
+
<p align="center">
This mod is not affiliated with Among Us or Innersloth LLC, and the content contained therein is not endorsed or otherwise sponsored by Innersloth LLC. Portions of the materials contained herein are property of Innersloth LLC. © Innersloth LLC.</p>
| [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) | | |
# 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)
# Changelog
<details>
<summary>Click to show the Changelog</summary>
+
+**Version 3.4.0**
+- Added new Role [Deputy](#deputy) thanks [gendelo3](https://github.com/gendelo3)
+- Added Hacker option "Cant Move During Mobile Gadget Duration"
+- Added Security Guard mobile cams after placing all screws
+- Added Lover option "Enable Lover Chat"
+- Added return votes in meetings: You'll now get your votes back if your target got shot by the Guesser
+- Added New Option for Guesser: Guesser can't guess Snitch if they has done all tasks (created by Mr_Fluff)
+- Added The Other Roles changelog announcement popup
+- Changed that the Bounty Hunter exclude their Lover
+- Changed the position of the Witch icon in meetings for better visibility
+- Fixed a bug where the spy had a white name for Impostors in chat
+- Fixed a bug where the Guesser and Swapper UI in meetings was behind the visor cosmetics
+
**Version 3.3.3**
- Fixed a bug where a guessed Guesser could guess
- 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
## 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
**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 |
|----------|:-------------:|
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
|----------|:-------------:|
| 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 | -
-----------------------
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.
| 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 | -
-----------------------
|----------|:-------------:|
| 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
**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
| 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 | -
-----------------------
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
|----------|:-------------:|
| 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
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
| 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).
## 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.
## 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.
using static TheOtherRoles.TheOtherRoles;
using TheOtherRoles.Objects;
using System.Linq;
+using System.Collections.Generic;
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;
public static CustomButton cleanerCleanButton;
public static CustomButton warlockCurseButton;
public static CustomButton securityGuardButton;
+ public static CustomButton securityGuardCamButton;
public static CustomButton arsonistButton;
public static CustomButton vultureEatButton;
public static CustomButton mediumButton;
public static CustomButton pursuerButton;
public static CustomButton witchSpellButton;
+ public static Dictionary<byte, List<CustomButton>> deputyHandcuffedButtons = null;
+
public static TMPro.TMP_Text securityGuardButtonScrewsText;
+ public static TMPro.TMP_Text securityGuardChargesText;
+ public static TMPro.TMP_Text deputyButtonHandcuffsText;
public static TMPro.TMP_Text pursuerButtonBlanksText;
public static TMPro.TMP_Text hackerAdminTableChargesText;
public static TMPro.TMP_Text hackerVitalsChargesText;
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;
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;
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;
}
timeMasterShieldButton.actionButton.cooldownTimerText.color = Palette.EnabledColor;
}
+ private static void addReplacementHandcuffedButton(CustomButton button, Vector3? positionOffset = null, Func<bool> couldUse = null)
+ {
+ Vector3 positionOffsetValue = positionOffset ?? button.PositionOffset; // For non custom buttons, we can set these manually.
+ positionOffsetValue.z = -0.1f;
+ couldUse = couldUse ?? button.CouldUse;
+ CustomButton replacementHandcuffedButton = new CustomButton(() => { }, () => { return true; }, couldUse, () => { }, Deputy.getHandcuffedButtonSprite(), positionOffsetValue, button.hudManager, button.hotkey,
+ true, Deputy.handcuffDuration, () => { }, button.mirror);
+ replacementHandcuffedButton.Timer = replacementHandcuffedButton.EffectDuration;
+ replacementHandcuffedButton.actionButton.cooldownTimerText.color = new Color(0F, 0.8F, 0F);
+ replacementHandcuffedButton.isEffectActive = true;
+ if (deputyHandcuffedButtons.ContainsKey(PlayerControl.LocalPlayer.PlayerId))
+ deputyHandcuffedButtons[PlayerControl.LocalPlayer.PlayerId].Add(replacementHandcuffedButton);
+ else
+ deputyHandcuffedButtons.Add(PlayerControl.LocalPlayer.PlayerId, new List<CustomButton> { replacementHandcuffedButton });
+ }
+
+ // Disables / Enables all Buttons (except the ones disabled in the Deputy class), and replaces them with new buttons.
+ public static void setAllButtonsHandcuffedStatus(bool handcuffed, bool reset = false)
+ {
+ if (reset) {
+ deputyHandcuffedButtons = new Dictionary<byte, List<CustomButton>>();
+ return;
+ }
+ if (handcuffed && !deputyHandcuffedButtons.ContainsKey(PlayerControl.LocalPlayer.PlayerId))
+ {
+ int maxI = CustomButton.buttons.Count;
+ for (int i = 0; i < maxI; i++)
+ {
+ try
+ {
+ if (CustomButton.buttons[i].HasButton()) // For each custombutton the player has
+ {
+ addReplacementHandcuffedButton(CustomButton.buttons[i]); // The new buttons are the only non-handcuffed buttons now!
+ }
+ CustomButton.buttons[i].isHandcuffed = true;
+ }
+ catch (NullReferenceException)
+ {
+ System.Console.WriteLine("[WARNING] NullReferenceException from MeetingEndedUpdate().HasButton(), if theres only one warning its fine"); // Note: idk what this is good for, but i copied it from above /gendelo
+ }
+ }
+
+ // Non Custom (Vanilla) Buttons. The Originals are disabled / hidden in UpdatePatch.cs already, just need to replace them. Can use any button, as we replace onclick etc anyways.
+ // Kill Button if enabled for the Role
+ if (HudManager.Instance.KillButton.isActiveAndEnabled) addReplacementHandcuffedButton(arsonistButton, new Vector3(0, 1f, 0), couldUse: () => { return HudManager.Instance.KillButton.currentTarget != null; });
+ // Vent Button if enabled
+ if (PlayerControl.LocalPlayer.roleCanUseVents()) addReplacementHandcuffedButton(arsonistButton, new Vector3(-1.8f, 1f, 0), couldUse: () => { return HudManager.Instance.ImpostorVentButton.currentTarget != null; });
+ // Report Button
+ addReplacementHandcuffedButton(arsonistButton, new Vector3(-0.9f, -0.06f, 0), () => { return HudManager.Instance.ReportButton.enabled; });
+ }
+ else if (!handcuffed && deputyHandcuffedButtons.ContainsKey(PlayerControl.LocalPlayer.PlayerId)) // Reset to original. Disables the replacements, enables the original buttons.
+ {
+ foreach (CustomButton replacementButton in deputyHandcuffedButtons[PlayerControl.LocalPlayer.PlayerId])
+ {
+ replacementButton.HasButton = () => { return false; };
+ replacementButton.Update(); // To make it disappear properly.
+ CustomButton.buttons.Remove(replacementButton);
+ }
+ deputyHandcuffedButtons.Remove(PlayerControl.LocalPlayer.PlayerId);
+
+ foreach (CustomButton button in CustomButton.buttons)
+ {
+ button.isHandcuffed = false;
+ }
+ }
+ }
+
public static void Postfix(HudManager __instance)
{
// Engineer Repair
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);
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);
}
},
() => { 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),
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(
() => {
if (!MapBehaviour.Instance || !MapBehaviour.Instance.isActiveAndEnabled)
DestroyableSingleton<HudManager>.Instance.ShowMap((System.Action<MapBehaviour>)(m => m.ShowCountOverlay()));
+ if (Hacker.cantMove) PlayerControl.LocalPlayer.moveable = false;
+ PlayerControl.LocalPlayer.NetTransform.Halt(); // Stop current movement
+
Hacker.chargesAdminTable--;
},
() => { return Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead;},
() => {
if (hackerAdminTableChargesText != null) hackerAdminTableChargesText.text = $"{Hacker.chargesAdminTable} / {Hacker.toolsNumber}";
- return PlayerControl.LocalPlayer.CanMove && Hacker.chargesAdminTable > 0;
+ return Hacker.chargesAdminTable > 0;
},
() => {
hackerAdminTableButton.Timer = hackerAdminTableButton.MaxTimer;
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,
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; },
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;
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();
securityGuardButtonScrewsText.transform.localScale = Vector3.one * 0.5f;
securityGuardButtonScrewsText.transform.localPosition += new Vector3(-0.05f, 0.7f, 0);
+ securityGuardCamButton = new CustomButton(
+ () => {
+ if (PlayerControl.GameOptions.MapId != 1) {
+ if (SecurityGuard.minigame == null) {
+ var e = UnityEngine.Object.FindObjectsOfType<SystemConsole>().FirstOrDefault(x => x.gameObject.name.Contains("Surv_Panel"));
+ if (e == null || Camera.main == null) return;
+ SecurityGuard.minigame = UnityEngine.Object.Instantiate(e.MinigamePrefab, Camera.main.transform, false);
+ }
+ SecurityGuard.minigame.transform.SetParent(Camera.main.transform, false);
+ SecurityGuard.minigame.transform.localPosition = new Vector3(0.0f, 0.0f, -50f);
+ SecurityGuard.minigame.Begin(null);
+ } else {
+ if (SecurityGuard.minigame == null) {
+ var e = UnityEngine.Object.FindObjectsOfType<SystemConsole>().FirstOrDefault(x => x.gameObject.name.Contains("SurvLogConsole"));
+ if (e == null || Camera.main == null) return;
+ SecurityGuard.minigame = UnityEngine.Object.Instantiate(e.MinigamePrefab, Camera.main.transform, false);
+ }
+ SecurityGuard.minigame.transform.SetParent(Camera.main.transform, false);
+ SecurityGuard.minigame.transform.localPosition = new Vector3(0.0f, 0.0f, -50f);
+ SecurityGuard.minigame.Begin(null);
+ }
+ SecurityGuard.charges--;
+
+ if (SecurityGuard.cantMove) PlayerControl.LocalPlayer.moveable = false;
+ PlayerControl.LocalPlayer.NetTransform.Halt(); // Stop current movement
+ },
+ () => { return SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead && SecurityGuard.remainingScrews < Mathf.Min(SecurityGuard.ventPrice, SecurityGuard.camPrice); },
+ () => {
+ if (securityGuardChargesText != null) securityGuardChargesText.text = $"{SecurityGuard.charges} / {SecurityGuard.maxCharges}";
+ securityGuardCamButton.actionButton.graphic.sprite = PlayerControl.GameOptions.MapId == 1 ? SecurityGuard.getLogSprite() : SecurityGuard.getCamSprite();
+ securityGuardCamButton.actionButton.OverrideText(PlayerControl.GameOptions.MapId == 1 ? "DOORLOG" : "SECURITY");
+ return PlayerControl.LocalPlayer.CanMove && SecurityGuard.charges > 0;
+ },
+ () => {
+ securityGuardCamButton.Timer = securityGuardCamButton.MaxTimer;
+ securityGuardCamButton.isEffectActive = false;
+ securityGuardCamButton.actionButton.cooldownTimerText.color = Palette.EnabledColor;
+ },
+ SecurityGuard.getCamSprite(),
+ new Vector3(-1.8f, -0.06f, 0),
+ __instance,
+ KeyCode.Q,
+ true,
+ 0f,
+ () => {
+ securityGuardCamButton.Timer = securityGuardCamButton.MaxTimer;
+ if (Minigame.Instance) {
+ SecurityGuard.minigame.ForceClose();
+ }
+ PlayerControl.LocalPlayer.moveable = true;
+ },
+ false,
+ PlayerControl.GameOptions.MapId == 1 ? "DOORLOG" : "SECURITY"
+ );
+
+ // Security Guard cam button charges
+ securityGuardChargesText = GameObject.Instantiate(securityGuardCamButton.actionButton.cooldownTimerText, securityGuardCamButton.actionButton.cooldownTimerText.transform.parent);
+ securityGuardChargesText.text = "";
+ securityGuardChargesText.enableWordWrapping = false;
+ securityGuardChargesText.transform.localScale = Vector3.one * 0.5f;
+ securityGuardChargesText.transform.localPosition += new Vector3(-0.05f, 0.7f, 0);
+
// Arsonist button
arsonistButton = new CustomButton(
() => {
// Set the default (or settings from the previous game) timers/durations when spawning the buttons
setCustomButtonCooldowns();
+ deputyHandcuffedButtons = null;
}
}
}
\ No newline at end of file
public static CustomOption loversImpLoverRate;
public static CustomOption loversBothDie;
public static CustomOption loversCanHaveAnotherRole;
+ public static CustomOption loversEnableChat;
public static CustomOption guesserSpawnRate;
public static CustomOption guesserIsImpGuesserRate;
public static CustomOption guesserKillsThroughShield;
public static CustomOption guesserEvilCanKillSpy;
public static CustomOption guesserSpawnBothRate;
+ public static CustomOption guesserCantGuessSnitchIfTaksDone;
public static CustomOption jesterSpawnRate;
public static CustomOption jesterCanCallEmergency;
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;
public static CustomOption hackerOnlyColorType;
public static CustomOption hackerToolsNumber;
public static CustomOption hackerRechargeTasksNumber;
+ public static CustomOption hackerNoMove;
public static CustomOption trackerSpawnRate;
public static CustomOption trackerUpdateIntervall;
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;
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);
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);
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);
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);
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);
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);
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);
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);
}
}
-}
\ No newline at end of file
+}
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;
}
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;
public static ConfigEntry<string> StreamerModeReplacementColor { get; set; }
public static ConfigEntry<string> Ip { get; set; }
public static ConfigEntry<ushort> Port { get; set; }
+ public static ConfigEntry<string> ShowPopUpVersion { get; set; }
public static Sprite ModStamp;
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");
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) {
[HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))]
public static class EnableChat {
public static void Postfix(HudManager __instance) {
- if (!__instance.Chat.isActiveAndEnabled && AmongUsClient.Instance.GameMode == GameModes.FreePlay)
+ if (!__instance.Chat.isActiveAndEnabled && (AmongUsClient.Instance.GameMode == GameModes.FreePlay || (PlayerControl.LocalPlayer.isLover() && Lovers.enableChat)))
__instance.Chat.SetVisible(true);
}
}
+
+ [HarmonyPatch(typeof(ChatBubble), nameof(ChatBubble.SetName))]
+ public static class SetBubbleName {
+ public static void Postfix(ChatBubble __instance, [HarmonyArgument(0)] string playerName) {
+ PlayerControl sourcePlayer = PlayerControl.AllPlayerControls.ToArray().ToList().FirstOrDefault(x => x.Data.PlayerName.Equals(playerName));
+ if (PlayerControl.LocalPlayer != null && PlayerControl.LocalPlayer.Data.Role.IsImpostor && Spy.spy != null && sourcePlayer.PlayerId == Spy.spy.PlayerId && __instance != null) __instance.NameText.color = Palette.ImpostorRed;
+ }
+ }
+
+ [HarmonyPatch(typeof(ChatController), nameof(ChatController.AddChat))]
+ public static class AddChat {
+ public static bool Prefix(ChatController __instance, [HarmonyArgument(0)] PlayerControl sourcePlayer) {
+ if (__instance != DestroyableSingleton<HudManager>.Instance.Chat)
+ return true;
+ PlayerControl localPlayer = PlayerControl.LocalPlayer;
+ return localPlayer == null || (MeetingHud.Instance != null || LobbyBehaviour.Instance != null || (localPlayer.Data.IsDead || localPlayer.isLover() && Lovers.enableChat) || (int)sourcePlayer.PlayerId == (int)PlayerControl.LocalPlayer.PlayerId);
+
+ }
+ }
}
}
}
}
+ [HarmonyPatch(typeof(AnnouncementPopUp), nameof(AnnouncementPopUp.UpdateAnnounceText))]
+ public static class Announcement {
+ public static bool Prefix(AnnouncementPopUp __instance) {
+ var text = __instance.AnnounceTextMeshPro;
+ text.text = ModUpdater.announcement;
+ return false;
+ }
+ }
+
public class ModUpdater {
public static bool running = false;
public static bool hasUpdate = false;
public static string updateURI = null;
private static Task updateTask = null;
+ public static string announcement = "";
public static GenericPopup InfoPopup;
public static void LaunchUpdater() {
if (running) return;
running = true;
checkForUpdate().GetAwaiter().GetResult();
- clearOldVersions();
+ clearOldVersions();
+ if (hasUpdate || TheOtherRolesPlugin.ShowPopUpVersion.Value != TheOtherRolesPlugin.VersionString) {
+ DestroyableSingleton<MainMenuManager>.Instance.Announcement.gameObject.SetActive(true);
+ TheOtherRolesPlugin.ShowPopUpVersion.Value = TheOtherRolesPlugin.VersionString;
+ }
}
public static void ExecuteUpdate() {
if (tagname == null) {
return false; // Something went wrong
}
+
+ string changeLog = data["body"]?.ToString();
+ if (changeLog != null) announcement = changeLog;
// check version
System.Version ver = System.Version.Parse(tagname.Replace("v", ""));
int diff = TheOtherRolesPlugin.Version.CompareTo(ver);
if (diff < 0) { // Update required
hasUpdate = true;
+ announcement = $@"<size=150%>A new <color=#FC0303>THE OTHER ROLES</color>
+update to v{ver} is available</size>
+
+{announcement}";
+
JToken assets = data["assets"];
if (!assets.HasValues)
return false;
}
}
}
- }
+ } else {
+ announcement = $@"<size=150%>Version {ver}:</size>
+
+{announcement}";
+ }
} catch (System.Exception ex) {
TheOtherRolesPlugin.Instance.Log.LogError(ex.ToString());
System.Console.WriteLine(ex);
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
+using static TheOtherRoles.TheOtherRoles;
namespace TheOtherRoles.Objects {
public class CustomButton
public Vector3 PositionOffset;
public float MaxTimer = float.MaxValue;
public float Timer = 0f;
+ public float DeputyTimer = 0f;
private Action OnClick;
+ private Action InitialOnClick;
private Action OnMeetingEnds;
- private Func<bool> HasButton;
- private Func<bool> CouldUse;
+ public Func<bool> HasButton;
+ public Func<bool> CouldUse;
private Action OnEffectEnds;
public bool HasEffect;
public bool isEffectActive = false;
public bool showButtonText = false;
public float EffectDuration;
public Sprite Sprite;
- private HudManager hudManager;
- private bool mirror;
- private KeyCode? hotkey;
+ public HudManager hudManager;
+ public bool mirror;
+ public KeyCode? hotkey;
private string buttonText;
+ public bool isHandcuffed = false;
public CustomButton(Action OnClick, Func<bool> HasButton, Func<bool> CouldUse, Action OnMeetingEnds, Sprite Sprite, Vector3 PositionOffset, HudManager hudManager, KeyCode? hotkey, bool HasEffect, float EffectDuration, Action OnEffectEnds, bool mirror = false, string buttonText = "")
{
this.hudManager = hudManager;
this.OnClick = OnClick;
+ this.InitialOnClick = OnClick;
this.HasButton = HasButton;
this.CouldUse = CouldUse;
this.PositionOffset = PositionOffset;
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;
try
{
buttons[i].Timer = buttons[i].MaxTimer;
+ buttons[i].DeputyTimer = buttons[i].MaxTimer;
buttons[i].Update();
}
catch (NullReferenceException)
}
}
- private void Update()
+ public void Update()
{
if (PlayerControl.LocalPlayer.Data == null || MeetingHud.Instance || ExileController.Instance || !HasButton()) {
setActive(false);
}
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);
actionButton.graphic.color = actionButton.buttonLabelText.color = Palette.DisabledClear;
actionButton.graphic.material.SetFloat("_Desat", 1f);
}
-
+
if (Timer >= 0) {
if (HasEffect && isEffectActive)
Timer -= Time.deltaTime;
// 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
$@"Modded by <color=#FCCE03FF>Eisbison</color>, <color=#FCCE03FF>Thunderstorm584</color>, <color=#FCCE03FF>EndOfFile</color> & <color=#FCCE03FF>Mallöris</color>
Design by <color=#FCCE03FF>Bavari</color>";
-
- public static string contributorsCredentials = "<size=80%>GitHub Contributors: Alex2911, amsyarasyiq, gendelo3</size>";
+ public static string contributorsCredentials = "<size=60%>GitHub Contributors: Alex2911, amsyarasyiq, gendelo3, MaximeGillot</size>";
[HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))]
private static class VersionShowerPatch
__instance.text.alignment = TMPro.TextAlignmentOptions.TopRight;
if (AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Started) {
__instance.text.text = $"<size=130%><color=#ff351f>TheOtherRoles</color></size> v{TheOtherRolesPlugin.Version.ToString()}\n" + __instance.text.text;
- if (PlayerControl.LocalPlayer.Data.IsDead) {
+ if (PlayerControl.LocalPlayer.Data.IsDead || (!(PlayerControl.LocalPlayer == null) && (PlayerControl.LocalPlayer == Lovers.lover1 || PlayerControl.LocalPlayer == Lovers.lover2))) {
__instance.transform.localPosition = new Vector3(3.45f, __instance.transform.localPosition.y, __instance.transform.localPosition.z);
} else {
__instance.transform.localPosition = new Vector3(4.2f, __instance.transform.localPosition.y, __instance.transform.localPosition.z);
var torLogo = new GameObject("bannerLogo_TOR");
torLogo.transform.position = Vector3.up;
var renderer = torLogo.AddComponent<SpriteRenderer>();
- renderer.sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Banner.png", 300f);
+ renderer.sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Banner.png", 300f);
}
}
}
}
}
+ // 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;
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 ?? ""}");
+ }
+
}
}
Transform exitButtonMask = UnityEngine.Object.Instantiate(maskTemplate, exitButtonParent);
exitButton.gameObject.GetComponent<SpriteRenderer>().sprite = smallButtonTemplate.GetComponent<SpriteRenderer>().sprite;
exitButtonParent.transform.localPosition = new Vector3(2.725f, 2.1f, -5);
- exitButtonParent.transform.localScale = new Vector3(0.25f, 0.9f, 1);
+ exitButtonParent.transform.localScale = new Vector3(0.217f, 0.9f, 1);
exitButton.GetComponent<PassiveButton>().OnClick.RemoveAllListeners();
exitButton.GetComponent<PassiveButton>().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => {
__instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true));
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);
GameObject checkbox = UnityEngine.Object.Instantiate(template);
checkbox.transform.SetParent(playerVoteArea.transform);
checkbox.transform.position = template.transform.position;
- checkbox.transform.localPosition = new Vector3(-0.95f, 0.03f, -1f);
+ checkbox.transform.localPosition = new Vector3(-0.95f, 0.03f, -1.3f);
SpriteRenderer renderer = checkbox.GetComponent<SpriteRenderer>();
renderer.sprite = Swapper.getCheckSprite();
renderer.color = Color.red;
SpriteRenderer rend = (new GameObject()).AddComponent<SpriteRenderer>();
rend.transform.SetParent(pva.transform);
rend.gameObject.layer = pva.Megaphone.gameObject.layer;
- rend.transform.localPosition = new Vector3(-1.21f, -0.12f, -1f);
+ rend.transform.localPosition = new Vector3(-0.5f, -0.03f, -1f);
rend.sprite = Witch.getSpelledOverlaySprite();
}
}
GameObject template = playerVoteArea.Buttons.transform.Find("CancelButton").gameObject;
GameObject targetBox = UnityEngine.Object.Instantiate(template, playerVoteArea.transform);
targetBox.name = "ShootButton";
- targetBox.transform.localPosition = new Vector3(-0.95f, 0.03f, -1f);
+ targetBox.transform.localPosition = new Vector3(-0.95f, 0.03f, -1.3f);
SpriteRenderer renderer = targetBox.GetComponent<SpriteRenderer>();
renderer.sprite = Guesser.getTargetSprite();
PassiveButton button = targetBox.GetComponent<PassiveButton>();
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();
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;
SecurityGuard.ventTarget = target;
}
+ public static void securityGuardUpdate() {
+ if (SecurityGuard.securityGuard == null || PlayerControl.LocalPlayer != SecurityGuard.securityGuard || SecurityGuard.securityGuard.Data.IsDead) return;
+ var (playerCompleted, _) = TasksHandler.taskInfo(SecurityGuard.securityGuard.Data);
+ if (playerCompleted == SecurityGuard.rechargedTasks) {
+ SecurityGuard.rechargedTasks += SecurityGuard.rechargeTasksNumber;
+ if (SecurityGuard.maxCharges > SecurityGuard.charges) SecurityGuard.charges++;
+ }
+ }
+
public static void arsonistSetTarget() {
if (Arsonist.arsonist == null || Arsonist.arsonist != PlayerControl.LocalPlayer) return;
List<PlayerControl> untargetables;
BountyHunter.arrowUpdateTimer = 0f; // Force arrow to update
BountyHunter.bountyUpdateTimer = BountyHunter.bountyDuration;
var possibleTargets = new List<PlayerControl>();
+ if (Lovers.getPartner(BountyHunter.bountyHunter) != null)
foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
- if (!p.Data.IsDead && !p.Data.Disconnected && p != p.Data.Role.IsImpostor && p != Spy.spy && (p != Mini.mini || Mini.isGrownUp())) possibleTargets.Add(p);
+ if (!p.Data.IsDead && !p.Data.Disconnected && p != p.Data.Role.IsImpostor && p != Spy.spy && (p != Mini.mini || Mini.isGrownUp()) && (Lovers.getPartner(BountyHunter.bountyHunter) == null || p != Lovers.getPartner(BountyHunter.bountyHunter))) possibleTargets.Add(p);
}
BountyHunter.bounty = possibleTargets[TheOtherRoles.rnd.Next(0, possibleTargets.Count)];
if (BountyHunter.bounty == null) return;
shifterSetTarget();
// Sheriff
sheriffSetTarget();
+ // Deputy
+ deputySetTarget();
+ deputyUpdate();
// Detective
detectiveUpdateFootPrints();
// Tracker
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
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);
[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);
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);
}
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());
}
// 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) {
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)) {
// Adjust the role limit
switch (roleType) {
- case RoleType.Crewmate: data.maxCrewmateRoles--; break;
- case RoleType.Neutral: data.maxNeutralRoles--;break;
- case RoleType.Impostor: data.maxImpostorRoles--;break;
+ case RoleType.Crewmate: data.maxCrewmateRoles--; crewValues -= 10; break;
+ case RoleType.Neutral: data.maxNeutralRoles--; break;
+ case RoleType.Impostor: data.maxImpostorRoles--; impValues -= 10; break;
}
}
}
-
+ private static void assignDependentRoles(RoleAssignmentData data) {
+
+ int crew = data.crewmates.Count < data.maxCrewmateRoles ? data.crewmates.Count : data.maxCrewmateRoles;
+ int imp = data.impostors.Count < data.maxImpostorRoles ? data.impostors.Count : data.maxImpostorRoles;
+ int leftCrewRolesCount = crew - data.crewSettings.Where(x => x.Value != 0 && x.Value != 10).ToList().Count;
+ int leftImpRolesCount = imp - data.impSettings.Where(x => x.Value != 0 && x.Value != 10).ToList().Count;
+
+ // Other Guesser
+ if (CustomOptionHolder.guesserSpawnBothRate.getSelection() > 0) {
+ bool isEvilGuesser = (rnd.Next(1, 101) <= CustomOptionHolder.guesserIsImpGuesserRate.getSelection() * 10);
+ int guesserSpawnValues = isEvilGuesser ? impValues : crewValues;
+
+ if (Guesser.evilGuesser != null || Guesser.niceGuesser != null) {
+ if (CustomOptionHolder.guesserSpawnBothRate.getSelection() == 10) { // Force both Guesser
+ if (Guesser.niceGuesser == null && data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) { // Other Guesser = Nice Guesser
+ byte niceGuesser = setRoleToRandomPlayer((byte)RoleId.NiceGuesser, data.crewmates);
+ data.crewmates.ToList().RemoveAll(x => x.PlayerId == niceGuesser);
+ data.maxCrewmateRoles--;
+ leftCrewRolesCount--;
+ }
+ else if (Guesser.evilGuesser == null && data.impostors.Count > 0 && data.maxImpostorRoles > 0) { // Other Guesser = Evil Guesser
+ byte evilGuesser = setRoleToRandomPlayer((byte)RoleId.EvilGuesser, data.impostors);
+ data.impostors.ToList().RemoveAll(x => x.PlayerId == evilGuesser);
+ data.maxImpostorRoles--;
+ leftImpRolesCount--;
+ }
+ }
+ else if (Guesser.niceGuesser == null && data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) { // Dont force, add other guesser (nice) to pool
+ data.crewSettings.Add((byte)RoleId.NiceGuesser, CustomOptionHolder.guesserSpawnBothRate.getSelection());
+ crewValues += CustomOptionHolder.guesserSpawnBothRate.getSelection();
+ leftCrewRolesCount--;
+ }
+ else if (Guesser.evilGuesser == null && data.impostors.Count > 0 && data.maxImpostorRoles > 0) { // Dont force, add other guesser (evil) to pool
+ data.impSettings.Add((byte)RoleId.EvilGuesser, CustomOptionHolder.guesserSpawnBothRate.getSelection());
+ impValues += CustomOptionHolder.guesserSpawnBothRate.getSelection();
+ leftImpRolesCount--;
+ }
+ }
+ else if ((CustomOptionHolder.guesserSpawnRate.getSelection() < 10 && rnd.Next(guesserSpawnValues + CustomOptionHolder.guesserSpawnRate.getSelection()) < CustomOptionHolder.guesserSpawnRate.getSelection())
+ || (isEvilGuesser && leftImpRolesCount > 0) ||
+ (!isEvilGuesser && leftCrewRolesCount > 0)) { // Guesser is < 100%, but won the lottery or there are roles left to assign
+ if (isEvilGuesser && data.impostors.Count > 0 && data.maxImpostorRoles > 0) { // Add Evil Guesser
+ byte evilGuesser = setRoleToRandomPlayer((byte)RoleId.EvilGuesser, data.impostors);
+ data.impostors.ToList().RemoveAll(x => x.PlayerId == evilGuesser);
+ data.maxImpostorRoles--;
+ leftImpRolesCount--;
+ if (CustomOptionHolder.guesserSpawnBothRate.getSelection() == 10 && data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) { // Force Other Guesser (nice)
+ byte niceGuesser = setRoleToRandomPlayer((byte)RoleId.NiceGuesser, data.crewmates);
+ data.crewmates.ToList().RemoveAll(x => x.PlayerId == niceGuesser);
+ data.maxCrewmateRoles--;
+ leftCrewRolesCount--;
+ }
+ else if (CustomOptionHolder.guesserSpawnBothRate.getSelection() < 10) { // Dont force, add other guesser (nice) to pool
+ data.crewSettings.Add((byte)RoleId.NiceGuesser, CustomOptionHolder.guesserSpawnBothRate.getSelection());
+ crewValues += CustomOptionHolder.guesserSpawnBothRate.getSelection();
+ leftCrewRolesCount--;
+ }
+ }
+ else if (!isEvilGuesser && data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) { // Add Nice Guesser
+ byte niceGuesser = setRoleToRandomPlayer((byte)RoleId.NiceGuesser, data.crewmates);
+ data.crewmates.ToList().RemoveAll(x => x.PlayerId == niceGuesser);
+ data.maxCrewmateRoles--;
+ leftCrewRolesCount--;
+ if (CustomOptionHolder.guesserSpawnBothRate.getSelection() == 10 && data.impostors.Count > 0 && data.maxImpostorRoles > 0) { // Force Other Guesser (evil)
+ byte evilGuesser = setRoleToRandomPlayer((byte)RoleId.EvilGuesser, data.impostors);
+ data.impostors.ToList().RemoveAll(x => x.PlayerId == evilGuesser);
+ data.maxImpostorRoles--;
+ leftImpRolesCount--;
+ }
+ else if (CustomOptionHolder.guesserSpawnBothRate.getSelection() < 10) { // Dont force, add other guesser (evil) to pool
+ data.impSettings.Add((byte)RoleId.EvilGuesser, CustomOptionHolder.guesserSpawnBothRate.getSelection());
+ impValues += CustomOptionHolder.guesserSpawnBothRate.getSelection();
+ leftImpRolesCount--;
+ }
+ }
+ }
+ }
+
+ // Deputy
+ if (CustomOptionHolder.deputySpawnRate.getSelection() > 0 && data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) {
+ if (Sheriff.sheriff != null) {
+ if (CustomOptionHolder.deputySpawnRate.getSelection() == 10) { // Force Deputy
+ byte deputy = setRoleToRandomPlayer((byte)RoleId.Deputy, data.crewmates);
+ data.crewmates.ToList().RemoveAll(x => x.PlayerId == deputy);
+ data.maxCrewmateRoles--;
+ } else if (CustomOptionHolder.deputySpawnRate.getSelection() < 10) { // Dont force, add Deputy to pool
+ data.crewSettings.Add((byte)RoleId.Deputy, CustomOptionHolder.deputySpawnRate.getSelection());
+ }
+ } else if ((CustomOptionHolder.sheriffSpawnRate.getSelection() < 10 && rnd.Next(crewValues + CustomOptionHolder.sheriffSpawnRate.getSelection()) < CustomOptionHolder.sheriffSpawnRate.getSelection())
+ || leftCrewRolesCount > 0) { // Sheriff is < 100%, but won the lottery or there are roles left to assign
+ byte sheriff = setRoleToRandomPlayer((byte)RoleId.Sheriff, data.crewmates);
+ data.crewmates.ToList().RemoveAll(x => x.PlayerId == sheriff);
+ data.maxCrewmateRoles--;
+ leftCrewRolesCount--;
+ if (CustomOptionHolder.deputySpawnRate.getSelection() == 10 && data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) { // Force Deputy
+ byte deputy = setRoleToRandomPlayer((byte)RoleId.Deputy, data.crewmates);
+ data.crewmates.ToList().RemoveAll(x => x.PlayerId == deputy);
+ data.maxCrewmateRoles--;
+ leftCrewRolesCount--;
+ } else { // Dont force, add Deputy to pool
+ data.crewSettings.Add((byte)RoleId.Deputy, CustomOptionHolder.deputySpawnRate.getSelection());
+ crewValues += CustomOptionHolder.deputySpawnRate.getSelection();
+ leftCrewRolesCount--;
+ }
+ }
+ }
+ }
private static void assignChanceRoles(RoleAssignmentData data) {
// Get all roles where the chance to occur is set grater than 0% but not 100% and build a ticket pool based on their weight
List<byte> crewmateTickets = data.crewSettings.Where(x => x.Value > 0 && x.Value < 10).Select(x => Enumerable.Repeat(x.Key, x.Value)).SelectMany(x => x).ToList();
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)) {
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);
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
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() {
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)
timerUpdate();
// Mini
miniUpdate();
+
+ // Deputy Sabotage, Use and Vent Button Disabling
+ updateReportButton(__instance);
+ updateVentButton(__instance);
+
}
}
}
using static TheOtherRoles.GameHistory;
using static TheOtherRoles.MapOptions;
using System.Collections.Generic;
+using TheOtherRoles.Objects;
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;
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;
}
}
[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);
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
}
}
-
[HarmonyPatch(typeof(SabotageButton), nameof(SabotageButton.Refresh))]
class SabotageButtonRefreshPatch {
static void Postfix() {
}
}
+ [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) {
Mayor,
Engineer,
Sheriff,
+ Deputy,
Lighter,
Godfather,
Mafioso,
TrackerUsedTracker,
VampireSetBitten,
PlaceGarlic,
+ DeputyUsedHandcuffs,
+ DeputyPromotes,
JackalCreatesSidekick,
SidekickPromotes,
ErasePlayerRoles,
case RoleId.Sheriff:
Sheriff.sheriff = player;
break;
+ case RoleId.Deputy:
+ Deputy.deputy = player;
+ break;
case RoleId.Lighter:
Lighter.lighter = player;
break;
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)
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;
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();
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();
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());
public static RoleInfo mayor = new RoleInfo("Mayor", Mayor.color, "Your vote counts twice", "Your vote counts twice", RoleId.Mayor);
public static RoleInfo engineer = new RoleInfo("Engineer", Engineer.color, "Maintain important systems on the ship", "Repair the ship", RoleId.Engineer);
public static RoleInfo sheriff = new RoleInfo("Sheriff", Sheriff.color, "Shoot the <color=#FF1919FF>Impostors</color>", "Shoot the Impostors", RoleId.Sheriff);
+ public static RoleInfo deputy = new RoleInfo("Deputy", Sheriff.color, "Handcuff the <color=#FF1919FF>Impostors</color>", "Handcuff the Impostors", RoleId.Deputy);
public static RoleInfo lighter = new RoleInfo("Lighter", Lighter.color, "Your light never goes out", "Your light never goes out", RoleId.Lighter);
public static RoleInfo godfather = new RoleInfo("Godfather", Godfather.color, "Kill all Crewmates", "Kill all Crewmates", RoleId.Godfather);
public static RoleInfo mafioso = new RoleInfo("Mafioso", Mafioso.color, "Work with the <color=#FF1919FF>Mafia</color> to kill the Crewmates", "Kill all Crewmates", RoleId.Mafioso);
mayor,
engineer,
sheriff,
+ deputy,
lighter,
detective,
timeMaster,
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);
Mayor.clearAndReload();
Engineer.clearAndReload();
Sheriff.clearAndReload();
+ Deputy.clearAndReload();
Lighter.clearAndReload();
Godfather.clearAndReload();
Mafioso.clearAndReload();
public static PlayerControl currentTarget;
+ public static PlayerControl formerDeputy; // Needed for keeping handcuffs + shifting
+ public static PlayerControl formerSheriff; // When deputy gets promoted...
+
+ public static void replaceCurrentSheriff(PlayerControl deputy)
+ {
+ if (!formerSheriff) formerSheriff = sheriff;
+ sheriff = deputy;
+ currentTarget = null;
+ cooldown = CustomOptionHolder.jackalKillCooldown.getFloat();
+ }
+
public static void clearAndReload() {
sheriff = null;
currentTarget = null;
+ formerDeputy = null;
+ formerSheriff = null;
cooldown = CustomOptionHolder.sheriffCooldown.getFloat();
canKillNeutrals = CustomOptionHolder.sheriffCanKillNeutrals.getBool();
spyCanDieToSheriff = CustomOptionHolder.spyCanDieToSheriff.getBool();
}
}
+ public static class Deputy
+ {
+ public static PlayerControl deputy;
+ public static Color color = Sheriff.color;
+
+ public static PlayerControl currentTarget;
+ public static List<byte> handcuffedPlayers = new List<byte>();
+ public static int promotesToSheriff; // No: 0, Immediately: 1, After Meeting: 2
+ public static bool keepsHandcuffsOnPromotion;
+ public static float handcuffDuration;
+ public static float remainingHandcuffs;
+ public static float handcuffCooldown;
+ public static bool knowsSheriff;
+ public static Dictionary<byte, float> handcuffedKnows = new Dictionary<byte, float>();
+
+ private static Sprite buttonSprite;
+ private static Sprite handcuffedSprite;
+
+ public static Sprite getButtonSprite()
+ {
+ if (buttonSprite) return buttonSprite;
+ buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.DeputyHandcuffButton.png", 115f);
+ return buttonSprite;
+ }
+
+ public static Sprite getHandcuffedButtonSprite()
+ {
+ if (handcuffedSprite) return handcuffedSprite;
+ handcuffedSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.DeputyHandcuffed.png", 115f);
+ return handcuffedSprite;
+ }
+
+ // Can be used to enable / disable the handcuff effect on the target's buttons
+ public static void setHandcuffedKnows(bool active = true)
+ {
+ if (active) {
+ byte localPlayerId = PlayerControl.LocalPlayer.PlayerId;
+ handcuffedKnows.Add(localPlayerId, handcuffDuration);
+ handcuffedPlayers.RemoveAll(x => x == localPlayerId);
+ }
+
+ HudManagerStartPatch.setAllButtonsHandcuffedStatus(active);
+ }
+
+ public static void clearAndReload()
+ {
+ deputy = null;
+ currentTarget = null;
+ handcuffedPlayers = new List<byte>();
+ handcuffedKnows = new Dictionary<byte, float>();
+ HudManagerStartPatch.setAllButtonsHandcuffedStatus(false, true);
+ promotesToSheriff = CustomOptionHolder.deputyGetsPromoted.getSelection();
+ remainingHandcuffs = CustomOptionHolder.deputyNumberOfHandcuffs.getFloat();
+ handcuffCooldown = CustomOptionHolder.deputyHandcuffCooldown.getFloat();
+ keepsHandcuffsOnPromotion = CustomOptionHolder.deputyKeepsHandcuffs.getBool();
+ handcuffDuration = CustomOptionHolder.deputyHandcuffDuration.getFloat();
+ knowsSheriff = CustomOptionHolder.deputyKnowsSheriff.getBool();
+ }
+ }
+
public static class Lighter {
public static PlayerControl lighter;
public static Color color = new Color32(238, 229, 190, byte.MaxValue);
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;
lover2 = null;
notAckedExiledIsLover = false;
bothDie = CustomOptionHolder.loversBothDie.getBool();
+ enableChat = CustomOptionHolder.loversEnableChat.getBool();
}
public static PlayerControl getPartner(this PlayerControl player) {
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;
rechargedTasks = Mathf.RoundToInt(CustomOptionHolder.hackerRechargeTasksNumber.getFloat());
chargesVitals = Mathf.RoundToInt(CustomOptionHolder.hackerToolsNumber.getFloat()) / 2;
chargesAdminTable = Mathf.RoundToInt(CustomOptionHolder.hackerToolsNumber.getFloat()) / 2;
+ cantMove = CustomOptionHolder.hackerNoMove.getBool();
}
}
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() {
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();
}
}
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);
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();
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
- <Version>3.3.3</Version>
+ <Version>3.4.0</Version>
<Description>TheOtherRoles</Description>
<Authors>Eisbison</Authors>
</PropertyGroup>