From: Eisbison Date: Wed, 16 Jun 2021 15:53:46 +0000 (+0200) Subject: Version 2.7.0 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=9ceaa35398e4044f65b0c76effdfe7fca18c25bc;p=rhonda%2Ftheotherroles.git Version 2.7.0 --- diff --git a/Images/TOR_colors.jpg b/Images/TOR_colors.jpg new file mode 100644 index 0000000..59c0d09 Binary files /dev/null and b/Images/TOR_colors.jpg differ diff --git a/Images/TOR_colors.png b/Images/TOR_colors.png deleted file mode 100644 index 78ee632..0000000 Binary files a/Images/TOR_colors.png and /dev/null differ diff --git a/README.md b/README.md index bcf831d..b62eaac 100644 --- a/README.md +++ b/README.md @@ -11,32 +11,32 @@ This mod is not affiliated with Among Us or Innersloth LLC, and the content cont The **The Other Roles**, is a mod for [Among Us](https://store.steampowered.com/app/945360/Among_Us) which adds many new roles, new [Settings](#settings), new [Custom Hats](#custom-hats) and support for [10+ Player Lobbies](#Custom-Servers-and-10+-Players) to the game. Even more roles are coming soon :) -| Impostors | Crewmates | Neutral | -|----------|-------------|-----------------| -| [Evil Mini](#mini) | [Nice Mini](#mini) | [Arsonist](#arsonist) | -| [Camouflager](#camouflager) | [Detective](#detective) | [Jester](#jester) | -| [Cleaner](#cleaner) | [Engineer](#engineer) | [Jackal](#jackal) | -| [Eraser](#eraser) | [Hacker](#hacker) | [Sidekick](#sidekick) | -| [ImpLover](#lovers) | [Lover](#lovers) | | -| [Godfather (Mafia)](#mafia) | [Lighter](#lighter) | | -| [Mafioso (Mafia)](#mafia) | [Mayor](#mayor) | | -| [Janitor (Mafia)](#mafia) | [Medic](#medic) | | -| [Morphling](#morphling) | [Security Guard](#security-guard) | | -| [Trickster](#trickster) | [Seer](#seer) | | -| [Vampire](#vampire) | [Sheriff](#sheriff) | | -| [Warlock](#warlock) | [Shifter](#shifter) | | -| [Evil Guesser](#guesser) | [Nice Guesser](#guesser) | | -| | [Snitch](#snitch) | | -| | [Spy](#spy) | | -| | [Swapper](#swapper) | | -| | [Time Master](#time-master) | | -| | [Tracker](#tracker) | | +| Impostors | Crewmates | Neutral | Secondary | +|----------|-------------|-----------------|----------------| +| [Evil Mini](#mini) | [Nice Mini](#mini) | [Arsonist](#arsonist) | [Lover](#lovers) | +| [Evil Guesser](#guesser) | [Nice Guesser](#guesser) | [Jester](#jester) | | +| [Bounty Hunter](#bounty-hunter) | [Detective](#detective) | [Jackal](#jackal) | | +| [Camouflager](#camouflager) | [Engineer](#engineer) | [Sidekick](#sidekick) | | +| [Cleaner](#cleaner) | [Hacker](#hacker) | | | +| [Eraser](#eraser) | [Lighter](#lighter) | | | +| [Godfather (Mafia)](#mafia) | [Mayor](#mayor) | | | +| [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) | | | +| | [Spy](#spy) | | | +| | [Swapper](#swapper) | | | +| | [Time Master](#time-master) | | | +| | [Tracker](#tracker) | | | The [Role Assignment](#role-assignment) sections explains how the roles are being distributed among the players. # Releases | Among Us - Version| Mod Version | Link | |----------|-------------|-----------------| +| **2021.6.15**| v2.7.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.7.0/TheOtherRoles.zip) | 2021.5.25.2s| v2.6.7| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.7/TheOtherRoles.zip) | 2021.5.10s| v2.6.6| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.6/TheOtherRoles.zip) | 2021.5.10s| v2.6.5| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.5/TheOtherRoles.zip) @@ -70,14 +70,23 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein
Click to show the Changelog +**Version 2.7.0** +- **New Role:** [Bounty Hunter](#bounty-hunter) created by [Mallöris](https://github.com/Mallaris) +- Added more new [colors](#colors) (Thanks to [Drakoni](https://twitter.com/Drakoni13) for sorting them) +- Added a setting to the [Shifter](#shifter), that will prevent [Medic Shield](#medic) & [Lover](#lovers) Roles to be shifted +- Changed [Jackal](#jackal) & [Sidekick](#sidekick) to always be killable by [Sheriff](#sheriff) +- Changed [Jackal](#jackal) & [Sidekick](#sidekick) to not be [erasable](#eraser) anymore +- Changed [Role Assignment](#role-assignment) slightly to make chances more consistent +- Fixed a bug where votes would still count after the [Guesser](#guesser) or it's target died +- Fixed a bug where a [lover partner](#lovers) would not be shown as dead when killed by the [Guesser](#guesser) +- Fixed a bug on the Airship, where the [Jester](#jester) win was not triggered in some cases + **Version 2.6.7** - **New Role:** [Guesser](#guesser) -- Changed the colors of some roles -- Changed name of Child to [Mini](#mini) -- Changed Version check to allow to check for modified versions of the mod. (Forks don't need to use different versioning now) -- Changed Role Assignment to make [Spy](#spy) incompatible with [Mini](#mini) (former child) -- Fixed a bug where a [Jester](#jester) win was triggered, when the partner of a [Jester Lover](#lovers) was voted out -- Fixed a bug where a [Mini](#mini) lose was triggered, when the partner of a Crew [Mini Lover](#lovers) was voted out +- We changed the colors of some of our roles +- We renamed the Child to Mini +- Fixed a bug where a Jester win was triggered, when the partner of a Jester Lover was voted out +- Fixed a bug where a Mini lose was triggered, when the partner of a Crew Mini Lover was voted out **Version 2.6.6** - Fixed a bug introduced in v2.6.5 that caused all player to be able to use vents when the new option for spy was enabled @@ -435,7 +444,7 @@ Here are a few instructions, on how to create a custom hat: - **Submission:** If you got a hat design, you can submit it on our [Discord server](https://discord.gg/77RkMJHWsM). We'll look at all the hats and add all the good ones to the game. # Colors -![TOR Colors](./Images/TOR_colors.png) +![TOR Colors](./Images/TOR_colors.jpg) # Roles @@ -613,6 +622,35 @@ The Warlock can still perform normal kills, but the two buttons share the same c | Warlock Root Time | Time the Warlock is rooted in place after killing using the curse ----------------------- + +## Bounty Hunter +### **Team: Impostors** +\ +Created by [Mallöris](https://github.com/Mallaris)\ +\ +The Bounty Hunter is an Impostor, that continuously get bountys (the targeted player doesn't get notified).\ +The target of the Bounty Hunter swaps after every meeting and after a configuarble amount of time.\ +If the Bounty Hunter kills his target, his kill cooldown will be a lot less than usual.\ +Killing a player that's not his 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 +- Killing the target resets the timer and a new target will be selected. + +### Game Options +| Name | Description | +|----------|:-------------:| +| Bounty Hunter Spawn Chance | - +| Duration After Which Bounty Changes | - +| Cooldown After Killing Bounty | - +| Additional Cooldown After Killing Others | Time will be added to the normal impostor cooldown if the Bounty Hunter kills a not-bounty player +| Show Arrow Pointing Towards The Bounty | If set to true an arrow will appear (only visiable for the Bounty Hunter) +| Bounty Hunter Arrow Update Intervall | Sets how often the position is being updated +----------------------- + + ## Guesser ### **Team: Crewmates or Impostors** The Guesser can be a Crewmate or an Impostor (depending on the settings).\ @@ -873,6 +911,7 @@ can only use them, if the previous player did not used them before) | Name | Description |----------|:-------------:| | Shifter Spawn Chance | - +| Shifter Shifts Modifiers | Sets if Lovers and/or Medic Shield will be shifted ----------------------- ## Time Master diff --git a/Source Code/.gitignore b/Source Code/.gitignore index 8d4a6c0..80a85c7 100644 --- a/Source Code/.gitignore +++ b/Source Code/.gitignore @@ -1,2 +1,4 @@ bin -obj \ No newline at end of file +obj +.vscode/tasks.json +.gitignore \ No newline at end of file diff --git a/Source Code/AUFixes.cs b/Source Code/AUFixes.cs deleted file mode 100644 index 89548c4..0000000 --- a/Source Code/AUFixes.cs +++ /dev/null @@ -1,28 +0,0 @@ -using HarmonyLib; -using Hazel; -using UnityEngine; -using TMPro; -using System; - -namespace TheOtherRoles { - - [HarmonyPatch(typeof(OpenDoorConsole), nameof(OpenDoorConsole.Use))] - class ToiletDoorFix { // Synchronize opening toilet doors among clients - public static void Prefix(OpenDoorConsole __instance) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.OpenToiletDoor, SendOption.None, -1); - writer.Write(__instance.MyDoor.Id); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - } - - [HarmonyPatch(typeof(NotificationPopper), nameof(NotificationPopper.Update))] - public static class NotificationPopperUpdatePatch { // Fix position of notifications (e.g. player disconnected) - public static void Postfix(NotificationPopper __instance) { - if (__instance.alphaTimer > 0f) { - var pos = __instance.transform.localPosition; - __instance.transform.localPosition += new Vector3(0.5f, 0f, 0f); - } - } - } - -} \ No newline at end of file diff --git a/Source Code/Buttons.cs b/Source Code/Buttons.cs index 1174c86..4d03eb9 100644 --- a/Source Code/Buttons.cs +++ b/Source Code/Buttons.cs @@ -174,7 +174,8 @@ namespace TheOtherRoles byte targetId = 0; if ((Sheriff.currentTarget.Data.IsImpostor && (Sheriff.currentTarget != Mini.mini || Mini.isGrownUp())) || (Sheriff.spyCanDieToSheriff && Spy.spy == Sheriff.currentTarget) || - (Sheriff.canKillNeutrals && (Arsonist.arsonist == Sheriff.currentTarget || Jester.jester == Sheriff.currentTarget || Jackal.jackal == Sheriff.currentTarget || Sidekick.sidekick == Sheriff.currentTarget))) { + (Sheriff.canKillNeutrals && (Arsonist.arsonist == Sheriff.currentTarget || Jester.jester == Sheriff.currentTarget)) || + (Jackal.jackal == Sheriff.currentTarget || Sidekick.sidekick == Sheriff.currentTarget)) { targetId = Sheriff.currentTarget.PlayerId; } else { @@ -460,8 +461,8 @@ namespace TheOtherRoles AmongUsClient.Instance.FinishRpcImmediately(writer); RPCProcedure.jackalCreatesSidekick(Jackal.currentTarget.PlayerId); }, - () => { return Jackal.canCreateSidekick && Sidekick.sidekick == null && Jackal.fakeSidekick == null && Jackal.jackal != null && Jackal.jackal == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; }, - () => { return Sidekick.sidekick == null && Jackal.fakeSidekick == null && Jackal.currentTarget != null && PlayerControl.LocalPlayer.CanMove; }, + () => { return Jackal.canCreateSidekick && Jackal.jackal != null && Jackal.jackal == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; }, + () => { return Jackal.canCreateSidekick && Jackal.currentTarget != null && PlayerControl.LocalPlayer.CanMove; }, () => { jackalSidekickButton.Timer = jackalSidekickButton.MaxTimer;}, Jackal.getSidekickButtonSprite(), new Vector3(-1.3f, 1.3f, 0f), @@ -769,8 +770,8 @@ namespace TheOtherRoles arsonistButton.Timer = Arsonist.dousedEveryoneAlive() ? 0 : arsonistButton.MaxTimer; foreach (PlayerControl p in Arsonist.dousedPlayers) { - if (Arsonist.dousedIcons.ContainsKey(p.PlayerId)) { - Arsonist.dousedIcons[p.PlayerId].setSemiTransparent(false); + if (MapOptions.playerIcons.ContainsKey(p.PlayerId)) { + MapOptions.playerIcons[p.PlayerId].setSemiTransparent(false); } } } diff --git a/Source Code/ChatCommands.cs b/Source Code/ChatCommands.cs index 98823d3..2c91f41 100644 --- a/Source Code/ChatCommands.cs +++ b/Source Code/ChatCommands.cs @@ -18,45 +18,9 @@ namespace TheOtherRoles { string text = __instance.TextArea.text; bool handled = false; if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) { - using(MD5 md5 = MD5.Create()) { - string hash = System.BitConverter.ToString(md5.ComputeHash(Encoding.UTF8.GetBytes("tor@" + text.ToLower() + "§eof"))).Replace("-", "").ToLowerInvariant(); - if (hash.Equals("a4eb05314008537d2832e32fa1f33b2e")) { // i am a cheater - handled = true; - byte colorId = (byte)CustomColors.pickableColors; - SaveManager.BodyColor = (byte)colorId; - if (PlayerControl.LocalPlayer) - PlayerControl.LocalPlayer.CmdCheckColor(colorId); - } else if (hash.Equals("80cc70dc5f21bc321b84ce984abd511b")) { // i dont understand hashes - handled = true; - byte colorId = (byte)(CustomColors.pickableColors + 1); - SaveManager.BodyColor = (byte)colorId; - if (PlayerControl.LocalPlayer) - PlayerControl.LocalPlayer.CmdCheckColor(colorId); - } else if (hash.Equals("3359ffcd0b14ffa39d476a5c96632032")) { // Batch 2 - handled = true; - byte colorId = (byte)(CustomColors.pickableColors + 2); - SaveManager.BodyColor = (byte)colorId; - if (PlayerControl.LocalPlayer) - PlayerControl.LocalPlayer.CmdCheckColor(colorId); - } else if (hash.Equals("14056e0b9e53bc91f0c6a8b1fd5ce8b5")) { - handled = true; - byte colorId = (byte)(CustomColors.pickableColors + 3); - SaveManager.BodyColor = (byte)colorId; - if (PlayerControl.LocalPlayer) - PlayerControl.LocalPlayer.CmdCheckColor(colorId); - } else if (hash.Equals("fb00fb81b0be5177af908576e144d788")) { - handled = true; - byte colorId = (byte)(CustomColors.pickableColors + 4); - SaveManager.BodyColor = (byte)colorId; - if (PlayerControl.LocalPlayer) - PlayerControl.LocalPlayer.CmdCheckColor(colorId); - } else if (hash.Equals("a79e2bd7c9cdc723924bd4d7734ae5da")) { // Eisbison Color - handled = true; - byte colorId = (byte)(CustomColors.pickableColors + 5); - SaveManager.BodyColor = (byte)colorId; - if (PlayerControl.LocalPlayer) - PlayerControl.LocalPlayer.CmdCheckColor(colorId); - } else if (text.ToLower().StartsWith("/kick ")) { + //using(MD5 md5 = MD5.Create()) { + // string hash = System.BitConverter.ToString(md5.ComputeHash(Encoding.UTF8.GetBytes("tor@" + text.ToLower() + "§eof"))).Replace("-", "").ToLowerInvariant(); + if (text.ToLower().StartsWith("/kick ")) { string playerName = text.Substring(6); PlayerControl target = PlayerControl.AllPlayerControls.ToArray().ToList().FirstOrDefault(x => x.Data.PlayerName.Equals(playerName)); if (target != null && AmongUsClient.Instance != null && AmongUsClient.Instance.CanBan()) { @@ -66,9 +30,19 @@ namespace TheOtherRoles { handled = true; } } + } else if (text.ToLower().StartsWith("/ban ")) { + string playerName = text.Substring(6); + PlayerControl target = PlayerControl.AllPlayerControls.ToArray().ToList().FirstOrDefault(x => x.Data.PlayerName.Equals(playerName)); + if (target != null && AmongUsClient.Instance != null && AmongUsClient.Instance.CanBan()) { + var client = AmongUsClient.Instance.GetClient(target.OwnerId); + if (client != null) { + AmongUsClient.Instance.KickPlayer(client.Id, true); + handled = true; + } + } } } - } + //} if (handled) { __instance.TextArea.Clear(); __instance.quickChatMenu.ResetGlyphs(); diff --git a/Source Code/CredentialsPatch.cs b/Source Code/CredentialsPatch.cs index a7e9e68..c5b28b5 100644 --- a/Source Code/CredentialsPatch.cs +++ b/Source Code/CredentialsPatch.cs @@ -46,18 +46,32 @@ Balanced with Dhalucard Design by (); + rend.sprite = TheOtherRolesPlugin.GetModStamp(); + rend.color = new Color(1, 1, 1, 0.5f); + modStamp.transform.parent = __instance.transform.parent; + modStamp.transform.localScale *= 0.6f; + } + float offset = (AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Started) ? 0.75f : 0f; + modStamp.transform.position = HudManager.Instance.MapButton.transform.position + Vector3.down * offset; + } + static void Postfix(PingTracker __instance){ __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) { - __instance.transform.localPosition = new Vector3(3.45f, 2.675f, __instance.transform.localPosition.z); + __instance.transform.localPosition = new Vector3(3.45f, __instance.transform.localPosition.y, __instance.transform.localPosition.z); } else { - __instance.transform.localPosition = new Vector3(4.2f, 2.675f, __instance.transform.localPosition.z); + __instance.transform.localPosition = new Vector3(4.2f, __instance.transform.localPosition.y, __instance.transform.localPosition.z); } } else { __instance.text.text = $"{fullCredentials}\n{__instance.text.text}"; - __instance.transform.localPosition = new Vector3(3.5f, 2.675f, __instance.transform.localPosition.z); + __instance.transform.localPosition = new Vector3(3.5f, __instance.transform.localPosition.y, __instance.transform.localPosition.z); } } } diff --git a/Source Code/CrowdedPatches.cs b/Source Code/CrowdedPatches.cs deleted file mode 100644 index a6eb2ad..0000000 --- a/Source Code/CrowdedPatches.cs +++ /dev/null @@ -1,186 +0,0 @@ -// Adapted from https://github.com/CrowdedMods/CrowdedMod -/* -MIT License - -Copyright (c) 2020 andry08 & CrowdedMods - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -using System.Collections.Generic; -using System.Linq; -using HarmonyLib; -using UnityEngine; -using Hazel; - -namespace TheOtherRoles { - [HarmonyPatch] - static class GameOptionsPatch { - public static List additionalButtons = new List(); - - [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.Start))] - public static class CreateOptionsPickerStartPatch { - public static void Postfix(CreateOptionsPicker __instance) { - List maxPlayerButtons = __instance.MaxPlayerButtons.ToList(); - if (maxPlayerButtons == null || maxPlayerButtons.Count <= 2) return; - additionalButtons = new List(); - - for (int i = 1; i < 6; i++) { - SpriteRenderer nextButton = Object.Instantiate(maxPlayerButtons.Last(), maxPlayerButtons.Last().transform.parent); - additionalButtons.Add(nextButton); - nextButton.enabled = false; - nextButton.gameObject.name = "1" + i; - TMPro.TMP_Text text = nextButton.gameObject.GetComponentInChildren(); - text.text = "1" + i; - text.color = Helpers.isCustomServer() ? Color.white : Palette.DisabledGrey; - - nextButton.transform.position = nextButton.transform.position + new Vector3(i * (maxPlayerButtons[1].transform.position.x - maxPlayerButtons[0].transform.position.x), 0, 0); - var passiveButton = nextButton.GetComponent(); - passiveButton.OnClick.RemoveAllListeners(); - - void onClick() { - bool isCustom = Helpers.isCustomServer(); - foreach (SpriteRenderer renderer in additionalButtons) { - if (renderer != null && renderer.gameObject != null) { - renderer.enabled = false; - renderer.gameObject.GetComponentInChildren().color = isCustom ? Color.white : Palette.DisabledGrey; - } - } - - if (!isCustom) return; - - nextButton.enabled = true; - - byte value = byte.Parse(nextButton.name); - var targetOptions = __instance.GetTargetOptions(); - if (value <= targetOptions.NumImpostors) { - targetOptions.NumImpostors = value - 1; - __instance.SetImpostorButtons(targetOptions.NumImpostors); - } - __instance.SetMaxPlayersButtons(value); - } - passiveButton.OnClick.AddListener((UnityEngine.Events.UnityAction)onClick); - } - } - } - - public static void setLegalSettings() { - GameOptionsData hostOptions = SaveManager.GameHostOptions; - GameOptionsData searchOptions = SaveManager.GameSearchOptions; - if (searchOptions.MapId == 0) searchOptions.ToggleMapFilter(0); // ToggleMapFilter - - hostOptions.NumImpostors = searchOptions.NumImpostors = 1; // NumImpostors - hostOptions.MaxPlayers = searchOptions.MaxPlayers = 4; // MaxPlayers - - SaveManager.GameHostOptions = hostOptions; - SaveManager.GameSearchOptions = searchOptions; - } - - [HarmonyPatch(typeof(FindGameButton), nameof(FindGameButton.OnClick))] - class FindGameButtonOnClickPatch { - public static void Prefix() { - // Set legal settings - setLegalSettings(); - } - } - - [HarmonyPatch(typeof(JoinGameButton), nameof(JoinGameButton.OnClick))] - class JoinGameButtonOnClickPatch { - public static void Prefix() { - // Set legal settings - setLegalSettings(); - } - } - } - - [HarmonyPatch(typeof(SaveManager), nameof(SaveManager.GameHostOptions), MethodType.Getter)] - public static class SaveManagerGetHostOptions - { - public static bool Prefix(out GameOptionsData __result) - { - SaveManager.hostOptionsData ??= SaveManager.LoadGameOptions("gameHostOptions"); - - SaveManager.hostOptionsData.NumImpostors = Mathf.Clamp(SaveManager.hostOptionsData.NumImpostors, 1, SaveManager.hostOptionsData.MaxPlayers - 1); - SaveManager.hostOptionsData.KillDistance = Mathf.Clamp(SaveManager.hostOptionsData.KillDistance, 0, 2); - - __result = SaveManager.hostOptionsData; - return false; - } - } - - [HarmonyPatch(typeof(GameData), nameof(GameData.GetAvailableId))] - public static class GameDataGetAvailableIdPatch { - public static bool Prefix(ref GameData __instance, out sbyte __result) { - for (sbyte i = 0; i <= 15; i++) - if (!__instance.AllPlayers.ToArray().Any(p => p.PlayerId == i)) { - __result = i; - return false; - } - __result = -1; - return false; - } - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckColor))] - public static class PlayerControlCmdCheckColorPatch { - public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)]byte bodyColor) { - if (!Helpers.isCustomServer() || Palette.PlayerColors.Count >= PlayerControl.AllPlayerControls.Count) - return true; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetUncheckedColor, Hazel.SendOption.Reliable, -1); - writer.Write(bodyColor); - writer.Write(__instance.PlayerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.setUncheckedColor(bodyColor, __instance.PlayerId); - return false; - } - } - - [HarmonyPatch(typeof(PlayerTab), nameof(PlayerTab.UpdateAvailableColors))] - public static class PlayerTabUpdateAvailableColorsPatch { - public static bool Prefix(PlayerTab __instance) { - if (!Helpers.isCustomServer() || Palette.PlayerColors.Count >= PlayerControl.AllPlayerControls.Count) - return true; - - PlayerControl.SetPlayerMaterialColors(PlayerControl.LocalPlayer.Data.ColorId, __instance.DemoImage); - for (int i = 0; i < Palette.PlayerColors.Length; i++) - __instance.AvailableColors.Add(i); - return false; - } - } - - [HarmonyPatch(typeof(SecurityLogger), nameof(SecurityLogger.Awake))] - public static class SecurityLoggerAwakePatch - { - public static void Postfix(SecurityLogger __instance) { - __instance.Timers = new float[15]; - } - } - - [HarmonyPatch(typeof(KeyMinigame),nameof(KeyMinigame.Start))] - public static class KeyMinigameStartPatch - { - public static bool Prefix(KeyMinigame __instance) - { - __instance.targetSlotId = (PlayerControl.LocalPlayer != null) ? PlayerControl.LocalPlayer.PlayerId % 10 : 0; - __instance.Slots[__instance.targetSlotId].Image.sprite = __instance.Slots[__instance.targetSlotId].Highlit; - return false; - } - } -} diff --git a/Source Code/CustomColors.cs b/Source Code/CustomColors.cs index 42b279d..bc80f39 100644 --- a/Source Code/CustomColors.cs +++ b/Source Code/CustomColors.cs @@ -12,118 +12,125 @@ namespace TheOtherRoles { public class CustomColors { protected static Dictionary ColorStrings = new Dictionary(); public static List lighterColors = new List(){ 3, 4, 5, 7, 10, 11}; - public static uint pickableColors = 12; + public static uint pickableColors = (uint)Palette.ColorNames.Length; + /* version 1 + private static readonly List ORDER = new List() { 7, 17, 5, 33, 4, + 30, 0, 19, 27, 3, + 13, 25, 18, 15, 23, + 8, 32, 1, 21, 31, + 10, 34, 12, 14, 28, + 22, 29, 11, 26, 2, + 20, 24, 9, 16, 6 }; */ + private static readonly List ORDER = new List() { 7, 14, 5, 33, 4, + 30, 0, 19, 27, 3, + 17, 25, 18, 13, 23, + 8, 32, 1, 21, 31, + 10, 34, 15, 28, 22, + 29, 11, 2, 26, 16, + 20, 24, 9, 12, 6 }; public static void Load() { List longlist = Enumerable.ToList(Palette.ColorNames); - List shortlist = Enumerable.ToList(Palette.ShortColorNames); List colorlist = Enumerable.ToList(Palette.PlayerColors); List shadowlist = Enumerable.ToList(Palette.ShadowColors); List colors = new List(); - colors.Add(new CustomColor { longname = "Salmon", shortname = "SALMN", - color = new Color32(239, 191, 192, byte.MaxValue), - shadow = new Color32(182, 119, 114, byte.MaxValue), + /* Custom Colors */ + colors.Add(new CustomColor { longname = "Salmon", + color = new Color32(239, 191, 192, byte.MaxValue), // color = new Color32(0xD8, 0x82, 0x83, byte.MaxValue), + shadow = new Color32(182, 119, 114, byte.MaxValue), // shadow = new Color32(0xA5, 0x63, 0x65, byte.MaxValue), isLighterColor = true }); - colors.Add(new CustomColor { longname = "Bordeaux", shortname = "BRDX", + colors.Add(new CustomColor { longname = "Bordeaux", color = new Color32(109, 7, 26, byte.MaxValue), shadow = new Color32(54, 2, 11, byte.MaxValue), isLighterColor = false }); - colors.Add(new CustomColor { longname = "Olive", shortname = "OLIVE", + colors.Add(new CustomColor { longname = "Olive", color = new Color32(154, 140, 61, byte.MaxValue), shadow = new Color32(104, 95, 40, byte.MaxValue), isLighterColor = false }); - colors.Add(new CustomColor { longname = "Turqoise", shortname = "TURQ", + colors.Add(new CustomColor { longname = "Turqoise", color = new Color32(22, 132, 176, byte.MaxValue), shadow = new Color32(15, 89, 117, byte.MaxValue), isLighterColor = false }); - colors.Add(new CustomColor { longname = "Mint", shortname = "MINT", + colors.Add(new CustomColor { longname = "Mint", color = new Color32(111, 192, 156, byte.MaxValue), shadow = new Color32(65, 148, 111, byte.MaxValue), isLighterColor = true }); - colors.Add(new CustomColor { longname = "Lavender", shortname = "LVNDR", + colors.Add(new CustomColor { longname = "Lavender", color = new Color32(173, 126, 201, byte.MaxValue), shadow = new Color32(131, 58, 203, byte.MaxValue), isLighterColor = true }); - colors.Add(new CustomColor { longname = "Nougat", shortname = "NOUGT", + colors.Add(new CustomColor { longname = "Nougat", color = new Color32(160, 101, 56, byte.MaxValue), shadow = new Color32(115, 15, 78, byte.MaxValue), isLighterColor = false }); - colors.Add(new CustomColor { longname = "Peach", shortname = "PEACH", + colors.Add(new CustomColor { longname = "Peach", color = new Color32(255, 164, 119, byte.MaxValue), shadow = new Color32(238, 128, 100, byte.MaxValue), isLighterColor = true }); - colors.Add(new CustomColor { longname = "Wasabi", shortname = "WSBI", + colors.Add(new CustomColor { longname = "Wasabi", color = new Color32(112, 143, 46, byte.MaxValue), shadow = new Color32(72, 92, 29, byte.MaxValue), - isLighterColor = true }); - colors.Add(new CustomColor { longname = "Hot Pink", shortname = "HTPNK", + isLighterColor = false }); + colors.Add(new CustomColor { longname = "Hot Pink", color = new Color32(255, 51, 102, byte.MaxValue), shadow = new Color32(232, 0, 58, byte.MaxValue), isLighterColor = true }); - colors.Add(new CustomColor { longname = "Gray", shortname = "GRAY", - color = new Color32(147, 147, 147, byte.MaxValue), - shadow = new Color32(120, 120, 120, byte.MaxValue), - isLighterColor = false }); - colors.Add(new CustomColor { longname = "Petrol", shortname = "PTRL", + colors.Add(new CustomColor { longname = "Petrol", color = new Color32(0, 99, 105, byte.MaxValue), shadow = new Color32(0, 61, 54, byte.MaxValue), isLighterColor = false }); - - pickableColors += (uint)colors.Count; // Colors to show in Tab - /** Hidden Colors **/ - colors.Add(new CustomColor { longname = "Panda", shortname = "PANDA", - color = new Color32(255, 255, 255, 0), - shadow = new Color32(12, 12, 12, 0), + colors.Add(new CustomColor { longname = "Lemon", + color = new Color32(0xDB, 0xFD, 0x2F, byte.MaxValue), + shadow = new Color32(0x74, 0xE5, 0x10, byte.MaxValue), isLighterColor = true }); - colors.Add(new CustomColor { longname = "Midnight", shortname = "MDNT", - color = new Color32(64, 8, 71, 0), - shadow = new Color32(24, 32, 116, 0), - isLighterColor = false }); + colors.Add(new CustomColor { longname = "Signal Orange", + color = new Color32(0xF7, 0x44, 0x17, byte.MaxValue), + shadow = new Color32(0x9B, 0x2E, 0x0F, byte.MaxValue), + isLighterColor = true }); + + colors.Add(new CustomColor { longname = "Teal", + color = new Color32(0x25, 0xB8, 0xBF, byte.MaxValue), + shadow = new Color32(0x12, 0x89, 0x86, byte.MaxValue), + isLighterColor = false }); - /** Batch 2 **/ - colors.Add(new CustomColor { longname = "Electric", shortname = "ELEC", - color = new Color32(0xDB, 0xFD, 0x2F, 0), - shadow = new Color32(0x89, 0x9E, 0x1E, 0), + colors.Add(new CustomColor { longname = "Blurple", + color = new Color32(0x59, 0x3C, 0xD6, byte.MaxValue), + shadow = new Color32(0x29, 0x17, 0x96, byte.MaxValue), + isLighterColor = false }); + + colors.Add(new CustomColor { longname = "Sunrise", + color = new Color32(0xFF, 0xCA, 0x19, byte.MaxValue), + shadow = new Color32(0xDB, 0x44, 0x42, byte.MaxValue), isLighterColor = true }); - colors.Add(new CustomColor { longname = "Signal Orange", shortname = "SIGN", - color = new Color32(0xF7, 0x44, 0x17, 0), - shadow = new Color32(0x9B, 0x2E, 0x0F, 0), - isLighterColor = true }); - colors.Add(new CustomColor { longname = "Navy Blue", shortname = "NAVY", - color = new Color32(0x35, 0x7B, 0x9E, 0), - shadow = new Color32(0x16, 0x2B, 0x62, 0), - isLighterColor = false }); - colors.Add(new CustomColor { longname = "Eisbison", shortname = "EIS", - color = new Color32(0xA8, 0xDF, 0xFF, 0), - shadow = new Color32(0x59, 0x9F, 0xC8, 0), - isLighterColor = true }); + colors.Add(new CustomColor { longname = "Ice", + color = new Color32(0xA8, 0xDF, 0xFF, byte.MaxValue), + shadow = new Color32(0x59, 0x9F, 0xC8, byte.MaxValue), + isLighterColor = true }); + + pickableColors += (uint)colors.Count; // Colors to show in Tab + /** Hidden Colors **/ + /** Add Colors **/ int id = 50000; foreach (CustomColor cc in colors) { longlist.Add((StringNames)id); CustomColors.ColorStrings[id++] = cc.longname; - shortlist.Add((StringNames)id); - CustomColors.ColorStrings[id++] = cc.shortname; colorlist.Add(cc.color); shadowlist.Add(cc.shadow); if (cc.isLighterColor) lighterColors.Add(colorlist.Count - 1); } - Palette.ShortColorNames = shortlist.ToArray(); Palette.ColorNames = longlist.ToArray(); Palette.PlayerColors = colorlist.ToArray(); Palette.ShadowColors = shadowlist.ToArray(); - MedScanMinigame.ColorNames = Palette.ColorNames; - Telemetry.ColorNames = Palette.ColorNames; } protected internal struct CustomColor { public string longname; - public string shortname; public Color32 color; public Color32 shadow; public bool isLighterColor; @@ -151,19 +158,23 @@ namespace TheOtherRoles { private static class PlayerTabEnablePatch { public static void Postfix(PlayerTab __instance) { // Replace instead Il2CppArrayBase chips = __instance.ColorChips.ToArray(); - int cols = 4; // TODO: Design an algorithm to dynamically position chips to optimally fill space - for (int i = 0; i < chips.Length; i++) { - ColorChip chip = chips[i]; - int row = i / cols, col = i % cols; // Dynamically do the positioning - chip.transform.localPosition = new Vector3(1.46f + (col * 0.6f), -0.43f - (row * 0.55f), chip.transform.localPosition.z); - chip.transform.localScale *= 0.9f; - if (i >= pickableColors) { - chip.transform.localScale *= 0f; // Needs to exist for PlayerTab - chip.enabled = false; - chip.Button.enabled = false; - chip.Button.OnClick.RemoveAllListeners(); - } + int cols = 5; // TODO: Design an algorithm to dynamically position chips to optimally fill space + for (int i = 0; i < ORDER.Count; i++) { + int pos = ORDER[i]; + if (pos < 0 || pos > chips.Length) + continue; + ColorChip chip = chips[pos]; + int row = i / cols, col = i % cols; // Dynamically do the positioning + chip.transform.localPosition = new Vector3(-0.975f + (col * 0.485f), 1.475f - (row * 0.49f), chip.transform.localPosition.z); + chip.transform.localScale *= 0.78f; + } + for (int j = ORDER.Count; j < chips.Length; j++) { // If number isn't in order, hide it + ColorChip chip = chips[j]; + chip.transform.localScale *= 0f; + chip.enabled = false; + chip.Button.enabled = false; + chip.Button.OnClick.RemoveAllListeners(); } } } diff --git a/Source Code/CustomHats.cs b/Source Code/CustomHats.cs index 32563e8..f9c8ee5 100644 --- a/Source Code/CustomHats.cs +++ b/Source Code/CustomHats.cs @@ -123,7 +123,7 @@ namespace TheOtherRoles { Texture2D texture = fromDisk ? Helpers.loadTextureFromDisk(path) : Helpers.loadTextureFromResources(path); if (texture == null) return null; - Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.8f), texture.width * 0.75f); + Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.53f, 0.575f), texture.width * 0.375f); if (sprite == null) return null; texture.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontUnloadUnusedAsset; diff --git a/Source Code/CustomOptions.cs b/Source Code/CustomOptions.cs index 864497e..58116e9 100644 --- a/Source Code/CustomOptions.cs +++ b/Source Code/CustomOptions.cs @@ -74,7 +74,15 @@ namespace TheOtherRoles { public static CustomOption jackalCanCreateSidekickFromImpostor; public static CustomOption jackalAndSidekickHaveImpostorVision; + public static CustomOption bountyHunterSpawnRate; + public static CustomOption bountyHunterBountyDuration; + public static CustomOption bountyHunterReducedCooldown; + public static CustomOption bountyHunterPunishmentTime; + public static CustomOption bountyHunterShowArrow; + public static CustomOption bountyHunterArrowUpdateIntervall; + public static CustomOption shifterSpawnRate; + public static CustomOption shifterShiftsModifiers; public static CustomOption mayorSpawnRate; @@ -210,7 +218,15 @@ namespace TheOtherRoles { warlockSpawnRate = CustomOption.Create(270, cs(Cleaner.color, "Warlock"), rates, null, true); warlockCooldown = CustomOption.Create(271, "Warlock Cooldown", 30f, 10f, 60f, 2.5f, warlockSpawnRate); warlockRootTime = CustomOption.Create(272, "Warlock Root Time", 5f, 0f, 15f, 1f, warlockSpawnRate); - + + bountyHunterSpawnRate = CustomOption.Create(320, cs(BountyHunter.color, "Bounty Hunter"), rates, null, true); + bountyHunterBountyDuration = CustomOption.Create(321, "Duration After Which Bounty Changes", 60f, 10f, 180f, 10f, bountyHunterSpawnRate); + bountyHunterReducedCooldown = CustomOption.Create(322, "Cooldown After Killing Bounty", 2.5f, 0f, 30f, 2.5f, bountyHunterSpawnRate); + bountyHunterPunishmentTime = CustomOption.Create(323, "Additional Cooldown After Killing Others", 20f, 0f, 60f, 2.5f, bountyHunterSpawnRate); + bountyHunterShowArrow = CustomOption.Create(324, "Show Arrow Pointing Towards The Bounty", true, bountyHunterSpawnRate); + bountyHunterArrowUpdateIntervall = CustomOption.Create(325, "Arrow Update Intervall", 15f, 2.5f, 60f, 2.5f, bountyHunterShowArrow); + + miniSpawnRate = CustomOption.Create(180, cs(Mini.color, "Mini"), rates, null, true); miniGrowingUpDuration = CustomOption.Create(181, "Mini Growing Up Duration", 400f, 100f, 1500f, 100f, miniSpawnRate); @@ -244,6 +260,7 @@ namespace TheOtherRoles { jackalAndSidekickHaveImpostorVision = CustomOption.Create(430, "Jackal And Sidekick Have Impostor Vision", false, jackalSpawnRate); 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); @@ -316,6 +333,8 @@ namespace TheOtherRoles { blockedRolePairings.Add((byte)RoleId.Vampire, new [] { (byte)RoleId.Warlock}); blockedRolePairings.Add((byte)RoleId.Warlock, new [] { (byte)RoleId.Vampire}); + blockedRolePairings.Add((byte)RoleId.Spy, new [] { (byte)RoleId.Mini}); + blockedRolePairings.Add((byte)RoleId.Mini, new [] { (byte)RoleId.Spy}); } } @@ -624,9 +643,9 @@ namespace TheOtherRoles { var hudString = sb.ToString(); int defaultSettingsLines = 19; - int roleSettingsLines = defaultSettingsLines + 33; - int detailedSettingsP1 = roleSettingsLines + 34; - int detailedSettingsP2 = detailedSettingsP1 + 35; + int roleSettingsLines = defaultSettingsLines + 34; + int detailedSettingsP1 = roleSettingsLines + 37; + int detailedSettingsP2 = detailedSettingsP1 + 38; int end1 = hudString.TakeWhile(c => (defaultSettingsLines -= (c == '\n' ? 1 : 0)) > 0).Count(); int end2 = hudString.TakeWhile(c => (roleSettingsLines -= (c == '\n' ? 1 : 0)) > 0).Count(); int end3 = hudString.TakeWhile(c => (detailedSettingsP1 -= (c == '\n' ? 1 : 0)) > 0).Count(); @@ -643,10 +662,10 @@ namespace TheOtherRoles { gap = 5; index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count(); hudString = hudString.Insert(index, "\n"); - gap = 17; + gap = 18; index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count(); hudString = hudString.Insert(index + 1, "\n"); - gap = 21; + gap = 22; index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count(); hudString = hudString.Insert(index + 1, "\n"); } else if (counter == 2) { diff --git a/Source Code/EndGamePatch.cs b/Source Code/EndGamePatch.cs index f0cfea9..b6fc905 100644 --- a/Source Code/EndGamePatch.cs +++ b/Source Code/EndGamePatch.cs @@ -196,7 +196,7 @@ namespace TheOtherRoles { textRenderer.color = Mini.color; } - if(MapOptions.showRoleSummary) { + if (MapOptions.showRoleSummary) { var position = Camera.main.ViewportToWorldPoint(new Vector3(0f, 1f, Camera.main.nearClipPlane)); GameObject roleSummary = UnityEngine.Object.Instantiate(__instance.WinText.gameObject); roleSummary.transform.position = new Vector3(__instance.ExitButton.transform.position.x + 0.1f, position.y - 0.1f, -14f); diff --git a/Source Code/ExileControllerPatch.cs b/Source Code/ExileControllerPatch.cs index 4f3d139..7c36c41 100644 --- a/Source Code/ExileControllerPatch.cs +++ b/Source Code/ExileControllerPatch.cs @@ -27,7 +27,7 @@ namespace TheOtherRoles { // Eraser erase if (Eraser.eraser != null && AmongUsClient.Instance.AmHost && Eraser.futureErased != null) { // We need to send the RPC from the host here, to make sure that the order of shifting and erasing is correct (for that reason the futureShifted and futureErased are being synced) foreach (PlayerControl target in Eraser.futureErased) { - if (target != null) { + if (target != null && target.canBeErased()) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ErasePlayerRoles, Hazel.SendOption.Reliable, -1); writer.Write(target.PlayerId); AmongUsClient.Instance.FinishRpcImmediately(writer); @@ -64,12 +64,24 @@ namespace TheOtherRoles { } } - [HarmonyPatch(typeof(UnityEngine.Object), nameof(UnityEngine.Object.Destroy), new Type[] { typeof(UnityEngine.Object) })] - class ExileControllerDestroyPatch { - static void Prefix(UnityEngine.Object obj) { - if (ExileController.Instance == null || obj != ExileController.Instance.gameObject) return; - var exiled = ExileController.Instance.exiled; + [HarmonyPatch] + class ExileControllerWrapUpPatch { + [HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))] + class BaseExileControllerPatch { + public static void Postfix(ExileController __instance) { + WrapUpPostfix(__instance.exiled); + } + } + + [HarmonyPatch(typeof(AirshipExileController), nameof(AirshipExileController.WrapUpAndSpawn))] + class AirshipExileControllerPatch { + public static void Postfix(AirshipExileController __instance) { + WrapUpPostfix(__instance.exiled); + } + } + + static void WrapUpPostfix(GameData.PlayerInfo exiled) { // Mini exile lose condition if (exiled != null && Mini.mini != null && Mini.mini.PlayerId == exiled.PlayerId && !Mini.isGrownUp() && !Mini.mini.Data.IsImpostor) { Mini.triggerMiniLose = true; @@ -117,15 +129,19 @@ namespace TheOtherRoles { Vector3 bottomLeft = new Vector3(-HudManager.Instance.UseButton.transform.localPosition.x, HudManager.Instance.UseButton.transform.localPosition.y, HudManager.Instance.UseButton.transform.localPosition.z); bottomLeft += new Vector3(-0.25f, -0.25f, 0); foreach (PlayerControl p in PlayerControl.AllPlayerControls) { - if (!Arsonist.dousedIcons.ContainsKey(p.PlayerId)) continue; + if (!MapOptions.playerIcons.ContainsKey(p.PlayerId)) continue; if (p.Data.IsDead || p.Data.Disconnected) { - Arsonist.dousedIcons[p.PlayerId].gameObject.SetActive(false); + MapOptions.playerIcons[p.PlayerId].gameObject.SetActive(false); } else { - Arsonist.dousedIcons[p.PlayerId].transform.localPosition = bottomLeft + Vector3.right * visibleCounter * 0.35f; + MapOptions.playerIcons[p.PlayerId].transform.localPosition = bottomLeft + Vector3.right * visibleCounter * 0.35f; visibleCounter++; } } } + + // Force Bounty Hunter Bounty Update + if (BountyHunter.bountyHunter != null && BountyHunter.bountyHunter == PlayerControl.LocalPlayer) + BountyHunter.bountyUpdateTimer = 0f; } } diff --git a/Source Code/Helpers.cs b/Source Code/Helpers.cs index a2727f0..a930739 100644 --- a/Source Code/Helpers.cs +++ b/Source Code/Helpers.cs @@ -176,7 +176,11 @@ namespace TheOtherRoles { } public static bool hasFakeTasks(this PlayerControl player) { - return (player == Jester.jester || player == Jackal.jackal || player == Sidekick.sidekick || player == Arsonist.arsonist); + return (player == Jester.jester || player == Jackal.jackal || player == Sidekick.sidekick || player == Arsonist.arsonist || Jackal.formerJackals.Contains(player)); + } + + public static bool canBeErased(this PlayerControl player) { + return (player != Jackal.jackal && player != Sidekick.sidekick && !Jackal.formerJackals.Contains(player)); } public static void clearAllTasks(this PlayerControl player) { @@ -207,5 +211,23 @@ namespace TheOtherRoles { f = Mathf.Clamp01(f); return (byte)(f * 255); } + + public static KeyValuePair MaxPair(this Dictionary self, out bool tie) { + tie = true; + KeyValuePair result = new KeyValuePair(byte.MaxValue, int.MinValue); + foreach (KeyValuePair keyValuePair in self) + { + if (keyValuePair.Value > result.Value) + { + result = keyValuePair; + tie = false; + } + else if (keyValuePair.Value == result.Value) + { + tie = true; + } + } + return result; + } } } diff --git a/Source Code/IntroPatch.cs b/Source Code/IntroPatch.cs index d16851a..05ce908 100644 --- a/Source Code/IntroPatch.cs +++ b/Source Code/IntroPatch.cs @@ -8,31 +8,50 @@ using System.Linq; namespace TheOtherRoles { [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.OnDestroy))] - class IntroCutsceneOnDestroyPatch { + class IntroCutsceneOnDestroyPatch + { public static void Prefix(IntroCutscene __instance) { - // Arsonist generate player icons - if (PlayerControl.LocalPlayer != null && PlayerControl.LocalPlayer == Arsonist.arsonist && HudManager.Instance != null) { - int playerCounter = 0; + // Generate and initialize player icons + int playerCounter = 0; + if (PlayerControl.LocalPlayer != null && HudManager.Instance != null) { Vector3 bottomLeft = new Vector3(-HudManager.Instance.UseButton.transform.localPosition.x, HudManager.Instance.UseButton.transform.localPosition.y, HudManager.Instance.UseButton.transform.localPosition.z); - bottomLeft += new Vector3(-0.25f, -0.25f, 0); - foreach (PlayerControl player in PlayerControl.AllPlayerControls) { - if (player != PlayerControl.LocalPlayer) { - GameData.PlayerInfo data = player.Data; - PoolablePlayer poolablePlayer = UnityEngine.Object.Instantiate(__instance.PlayerPrefab, HudManager.Instance.transform); - poolablePlayer.transform.localPosition = bottomLeft + Vector3.right * playerCounter * 0.35f; - poolablePlayer.transform.localScale = Vector3.one * 0.3f; - PlayerControl.SetPlayerMaterialColors(data.ColorId, poolablePlayer.Body); - DestroyableSingleton.Instance.SetSkin(poolablePlayer.SkinSlot, data.SkinId); - poolablePlayer.HatSlot.SetHat(data.HatId, data.ColorId); - PlayerControl.SetPetImage(data.PetId, data.ColorId, poolablePlayer.PetSlot); - poolablePlayer.NameText.text = data.PlayerName; - poolablePlayer.SetFlipX(true); - poolablePlayer.setSemiTransparent(true); - Arsonist.dousedIcons[player.PlayerId] = poolablePlayer; - playerCounter++; + foreach (PlayerControl p in PlayerControl.AllPlayerControls) { + GameData.PlayerInfo data = p.Data; + PoolablePlayer player = UnityEngine.Object.Instantiate(__instance.PlayerPrefab, HudManager.Instance.transform); + PlayerControl.SetPlayerMaterialColors(data.ColorId, player.Body); + DestroyableSingleton.Instance.SetSkin(player.SkinSlot, data.SkinId); + player.HatSlot.SetHat(data.HatId, data.ColorId); + PlayerControl.SetPetImage(data.PetId, data.ColorId, player.PetSlot); + player.NameText.text = data.PlayerName; + player.SetFlipX(true); + MapOptions.playerIcons[p.PlayerId] = player; + + if (PlayerControl.LocalPlayer == Arsonist.arsonist && p != Arsonist.arsonist) { + player.transform.localPosition = bottomLeft + new Vector3(-0.25f, -0.25f, 0) + Vector3.right * playerCounter++ * 0.35f; + player.transform.localScale = Vector3.one * 0.2f; + player.setSemiTransparent(true); + player.gameObject.SetActive(true); + } else if (PlayerControl.LocalPlayer == BountyHunter.bountyHunter) { + player.transform.localPosition = bottomLeft + new Vector3(-0.25f, 0f, 0); + player.transform.localScale = Vector3.one * 0.4f; + player.gameObject.SetActive(false); + } else { + player.gameObject.SetActive(false); } } } + + // Force Bounty Hunter to load a new Bounty when the Intro is over + if (BountyHunter.bounty != null && PlayerControl.LocalPlayer == BountyHunter.bountyHunter) { + BountyHunter.bountyUpdateTimer = 0f; + if (HudManager.Instance != null) { + Vector3 bottomLeft = new Vector3(-HudManager.Instance.UseButton.transform.localPosition.x, HudManager.Instance.UseButton.transform.localPosition.y, HudManager.Instance.UseButton.transform.localPosition.z) + new Vector3(-0.25f, 1f, 0); + BountyHunter.cooldownText = UnityEngine.Object.Instantiate(HudManager.Instance.KillButton.TimerText, HudManager.Instance.transform); + BountyHunter.cooldownText.alignment = TMPro.TextAlignmentOptions.Center; + BountyHunter.cooldownText.transform.localPosition = bottomLeft + new Vector3(0f, -1f, -1f); + BountyHunter.cooldownText.gameObject.SetActive(true); + } + } } } @@ -104,3 +123,4 @@ namespace TheOtherRoles } } } + diff --git a/Source Code/JackInTheBox.cs b/Source Code/JackInTheBox.cs index 72b2cfa..38901fe 100644 --- a/Source Code/JackInTheBox.cs +++ b/Source Code/JackInTheBox.cs @@ -4,8 +4,8 @@ using System.Collections; using UnityEngine; using System.Linq; -namespace TheOtherRoles{ - +namespace TheOtherRoles { + public class JackInTheBox { public static System.Collections.Generic.List AllJackInTheBoxes = new System.Collections.Generic.List(); public static int JackInTheBoxLimit = 3; @@ -38,7 +38,7 @@ namespace TheOtherRoles{ public JackInTheBox(Vector2 p) { gameObject = new GameObject("JackInTheBox"); - Vector3 position = new Vector3(p.x, p.y, PlayerControl.LocalPlayer.transform.position.z + 1f); + Vector3 position = new Vector3(p.x, p.y, PlayerControl.LocalPlayer.transform.position.z + 1f); position += (Vector3)PlayerControl.LocalPlayer.Collider.offset; // Add collider offset that DoMove moves the player up at a valid position // Create the marker gameObject.transform.position = position; @@ -46,7 +46,7 @@ namespace TheOtherRoles{ boxRenderer.sprite = getBoxAnimationSprite(0); // Create the vent - var referenceVent = UnityEngine.Object.FindObjectOfType(); + var referenceVent = UnityEngine.Object.FindObjectOfType(); vent = UnityEngine.Object.Instantiate(referenceVent); vent.transform.position = gameObject.transform.position; vent.Left = null; @@ -75,7 +75,7 @@ namespace TheOtherRoles{ public static void UpdateStates() { if (boxesConvertedToVents == true) return; - foreach(var box in AllJackInTheBoxes) { + foreach (var box in AllJackInTheBoxes) { var playerIsTrickster = PlayerControl.LocalPlayer == Trickster.trickster; box.gameObject.SetActive(playerIsTrickster); } @@ -88,7 +88,7 @@ namespace TheOtherRoles{ } public static void convertToVents() { - foreach(var box in AllJackInTheBoxes) { + foreach (var box in AllJackInTheBoxes) { box.convertToVent(); } connectVents(); @@ -101,7 +101,7 @@ namespace TheOtherRoles{ } private static void connectVents() { - for(var i = 0;i < AllJackInTheBoxes.Count - 1;i++) { + for (var i = 0; i < AllJackInTheBoxes.Count - 1; i++) { var a = AllJackInTheBoxes[i]; var b = AllJackInTheBoxes[i + 1]; a.vent.Right = b.vent; @@ -116,7 +116,7 @@ namespace TheOtherRoles{ boxesConvertedToVents = false; AllJackInTheBoxes = new List(); } - + } } \ No newline at end of file diff --git a/Source Code/Main.cs b/Source Code/Main.cs index 65fa6ce..7accc2e 100644 --- a/Source Code/Main.cs +++ b/Source Code/Main.cs @@ -20,7 +20,7 @@ namespace TheOtherRoles public class TheOtherRolesPlugin : BasePlugin { public const string Id = "me.eisbison.theotherroles"; - public const string VersionString = "2.6.7"; + public const string VersionString = "2.7.0"; public static System.Version Version = System.Version.Parse(VersionString); public Harmony Harmony { get; } = new Harmony(Id); @@ -39,6 +39,8 @@ namespace TheOtherRoles public static ConfigEntry Ip { get; set; } public static ConfigEntry Port { get; set; } + public static Sprite ModStamp; + public static IRegionInfo[] defaultRegions; public static void UpdateRegions() { ServerManager serverManager = DestroyableSingleton.Instance; @@ -78,6 +80,10 @@ namespace TheOtherRoles Harmony.PatchAll(); } + public static Sprite GetModStamp() { + if (ModStamp) return ModStamp; + return ModStamp = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.ModStamp.png", 150f); + } } // Deactivate bans, since I always leave my local testing game and ban myself diff --git a/Source Code/MapOptions.cs b/Source Code/MapOptions.cs index 4a165de..488f9cd 100644 --- a/Source Code/MapOptions.cs +++ b/Source Code/MapOptions.cs @@ -20,11 +20,13 @@ namespace TheOtherRoles{ public static int meetingsCount = 0; public static List camerasToAdd = new List(); public static List ventsToSeal = new List(); + public static Dictionary playerIcons = new Dictionary(); - public static void clearAndReloadMapOptions() { +public static void clearAndReloadMapOptions() { meetingsCount = 0; camerasToAdd = new List(); ventsToSeal = new List(); + playerIcons = new Dictionary(); ; maxNumberOfMeetings = Mathf.RoundToInt(CustomOptionHolder.maxNumberOfMeetings.getSelection()); blockSkippingInEmergencyMeetings = CustomOptionHolder.blockSkippingInEmergencyMeetings.getBool(); diff --git a/Source Code/MeetingPatch.cs b/Source Code/MeetingPatch.cs index b64458f..fa36996 100644 --- a/Source Code/MeetingPatch.cs +++ b/Source Code/MeetingPatch.cs @@ -22,190 +22,138 @@ namespace TheOtherRoles [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.CheckForEndVoting))] class MeetingCalculateVotesPatch { - private static byte[] calculateVotes(MeetingHud __instance) { - byte[] array = new byte[16]; - for (int i = 0; i < __instance.playerStates.Length; i++) - { + private static Dictionary CalculateVotes(MeetingHud __instance) { + Dictionary dictionary = new Dictionary(); + for (int i = 0; i < __instance.playerStates.Length; i++) { PlayerVoteArea playerVoteArea = __instance.playerStates[i]; - if (playerVoteArea.didVote) - { - int num = (int)(playerVoteArea.votedFor + 1); - if (num >= 0 && num < array.Length) - { - byte[] array2 = array; - int num2 = num; - // Mayor count vote twice - if (Mayor.mayor != null && playerVoteArea.TargetPlayerId == (sbyte)Mayor.mayor.PlayerId) - array2[num2] += 2; - else - array2[num2] += 1; - } + if (playerVoteArea.VotedFor != 252 && playerVoteArea.VotedFor != 255 && playerVoteArea.VotedFor != 254) { + PlayerControl player = Helpers.playerById((byte)playerVoteArea.TargetPlayerId); + if (player == null || player.Data == null || player.Data.IsDead || player.Data.Disconnected) continue; + + int currentVotes; + int additionalVotes = (Mayor.mayor != null && Mayor.mayor.PlayerId == playerVoteArea.TargetPlayerId) ? 2 : 1; // Mayor vote + if (dictionary.TryGetValue(playerVoteArea.VotedFor, out currentVotes)) + dictionary[playerVoteArea.VotedFor] = currentVotes + additionalVotes; + else + dictionary[playerVoteArea.VotedFor] = additionalVotes; } } - // Swapper swap votes PlayerVoteArea swapped1 = null; PlayerVoteArea swapped2 = null; - foreach (PlayerVoteArea playerVoteArea in __instance.playerStates) { if (playerVoteArea.TargetPlayerId == Swapper.playerId1) swapped1 = playerVoteArea; if (playerVoteArea.TargetPlayerId == Swapper.playerId2) swapped2 = playerVoteArea; } - if (swapped1 != null && swapped2 != null && swapped1.TargetPlayerId + 1 >= 0 && swapped1.TargetPlayerId + 1 < array.Length && swapped2.TargetPlayerId + 1 >= 0 && swapped2.TargetPlayerId + 1 < array.Length) { - byte tmp = array[swapped1.TargetPlayerId + 1]; - array[swapped1.TargetPlayerId + 1] = array[swapped2.TargetPlayerId + 1]; - array[swapped2.TargetPlayerId + 1] = tmp; + if (swapped1 != null && swapped2 != null && dictionary.ContainsKey(swapped1.TargetPlayerId) && dictionary.ContainsKey(swapped2.TargetPlayerId)) { + int tmp = dictionary[swapped1.TargetPlayerId]; + dictionary[swapped1.TargetPlayerId] = dictionary[swapped2.TargetPlayerId]; + dictionary[swapped2.TargetPlayerId] = tmp; } - return array; - } - private static int IndexOfMax(byte[] self, Func comparer, out bool tie) { - tie = false; - int num = int.MinValue; - int result = -1; - for (int i = 0; i < self.Length; i++) - { - int num2 = comparer(self[i]); - if (num2 > num) - { - result = i; - num = num2; - tie = false; - } - else if (num2 == num) - { - tie = true; - result = -1; - } - } - return result; + return dictionary; } + static bool Prefix(MeetingHud __instance) { - if (__instance.playerStates.All((PlayerVoteArea ps) => ps.isDead || ps.didVote)) { + if (__instance.playerStates.All((PlayerVoteArea ps) => ps.AmDead || ps.DidVote)) { // If skipping is disabled, replace skipps/no-votes with self vote if (target == null && blockSkippingInEmergencyMeetings && noVoteIsSelfVote) { foreach (PlayerVoteArea playerVoteArea in __instance.playerStates) { - if (playerVoteArea.votedFor < 0) playerVoteArea.votedFor = playerVoteArea.TargetPlayerId; // TargetPlayerId + if (playerVoteArea.VotedFor < 0) playerVoteArea.VotedFor = playerVoteArea.TargetPlayerId; // TargetPlayerId } } - byte[] self = calculateVotes(__instance); + Dictionary self = CalculateVotes(__instance); bool tie; - int maxIdx = IndexOfMax(self, (byte p) => (int)p, out tie) - 1; - GameData.PlayerInfo exiled = null; - foreach (GameData.PlayerInfo pi in GameData.Instance.AllPlayers) { - if (pi.PlayerId == maxIdx && !pi.IsDead) { - exiled = pi; - break; - } - } - byte[] array = new byte[15]; - for (int i = 0; i < __instance.playerStates.Length; i++) { + KeyValuePair max = self.MaxPair(out tie); + GameData.PlayerInfo exiled = GameData.Instance.AllPlayers.ToArray().FirstOrDefault(v => !tie && v.PlayerId == max.Key && !v.IsDead); + + MeetingHud.VoterState[] array = new MeetingHud.VoterState[__instance.playerStates.Length]; + for (int i = 0; i < __instance.playerStates.Length; i++) + { PlayerVoteArea playerVoteArea = __instance.playerStates[i]; - array[(int)playerVoteArea.TargetPlayerId] = playerVoteArea.GetState(); + array[i] = new MeetingHud.VoterState { + VoterId = playerVoteArea.TargetPlayerId, + VotedForId = playerVoteArea.VotedFor + }; } // RPCVotingComplete - if (AmongUsClient.Instance.AmClient) - __instance.VotingComplete(array, exiled, tie); - MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(__instance.NetId, 23, Hazel.SendOption.Reliable); - messageWriter.WriteBytesAndSize(array); - messageWriter.Write((exiled != null) ? exiled.PlayerId : byte.MaxValue); - messageWriter.Write(tie); - messageWriter.EndMessage(); + __instance.RpcVotingComplete(array, exiled, tie); } return false; } } + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.BloopAVoteIcon))] + class MeetingHudBloopAVoteIconPatch { + public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)]GameData.PlayerInfo voterPlayer, [HarmonyArgument(1)]int index, [HarmonyArgument(2)]Transform parent) { + SpriteRenderer spriteRenderer = UnityEngine.Object.Instantiate(__instance.PlayerVotePrefab); + if (!PlayerControl.GameOptions.AnonymousVotes || (PlayerControl.LocalPlayer.Data.IsDead && MapOptions.ghostsSeeVotes)) + PlayerControl.SetPlayerMaterialColors(voterPlayer.ColorId, spriteRenderer); + else + PlayerControl.SetPlayerMaterialColors(Palette.DisabledGrey, spriteRenderer); + spriteRenderer.transform.SetParent(parent); + spriteRenderer.transform.localScale = Vector3.zero; + __instance.StartCoroutine(Effects.Bloop((float)index * 0.3f, spriteRenderer.transform, 1f, 0.5f)); + parent.GetComponent().AddVote(spriteRenderer); + return false; + } + } + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.PopulateResults))] - class MeetingPopulateVotesPatch { - static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)]Il2CppStructArray states) - { - // Swapper swap votes + class MeetingHudPopulateVotesPatch { + + static bool Prefix(MeetingHud __instance, Il2CppStructArray states) { + // Swapper swap PlayerVoteArea swapped1 = null; PlayerVoteArea swapped2 = null; - foreach (PlayerVoteArea playerVoteArea in __instance.playerStates) { if (playerVoteArea.TargetPlayerId == Swapper.playerId1) swapped1 = playerVoteArea; if (playerVoteArea.TargetPlayerId == Swapper.playerId2) swapped2 = playerVoteArea; } - bool doSwap = swapped1 != null && swapped2 != null; - float delay = 0f; if (doSwap) { - delay = 2f; - __instance.StartCoroutine(Effects.Slide3D(swapped1.transform, swapped1.transform.localPosition, swapped2.transform.localPosition, 2f)); - __instance.StartCoroutine(Effects.Slide3D(swapped2.transform, swapped2.transform.localPosition, swapped1.transform.localPosition, 2f)); + __instance.StartCoroutine(Effects.Slide3D(swapped1.transform, swapped1.transform.localPosition, swapped2.transform.localPosition, 1.5f)); + __instance.StartCoroutine(Effects.Slide3D(swapped2.transform, swapped2.transform.localPosition, swapped1.transform.localPosition, 1.5f)); } - float votesXOffset = 0f; - float votesFinalSize = 1f; - if (__instance.playerStates.Length > 10) { - votesXOffset = 0.1f; - votesFinalSize = scale; - } - // Mayor display vote twice __instance.TitleText.text = DestroyableSingleton.Instance.GetString(StringNames.MeetingVotingResults, new Il2CppReferenceArray(0)); int num = 0; - for (int i = 0; i < __instance.playerStates.Length; i++) - { + for (int i = 0; i < __instance.playerStates.Length; i++) { PlayerVoteArea playerVoteArea = __instance.playerStates[i]; + byte targetPlayerId = playerVoteArea.TargetPlayerId; + // Swapper change playerVoteArea that gets the votes + if (doSwap && playerVoteArea.TargetPlayerId == swapped1.TargetPlayerId) playerVoteArea = swapped2; + else if (doSwap && playerVoteArea.TargetPlayerId == swapped2.TargetPlayerId) playerVoteArea = swapped1; + playerVoteArea.ClearForResults(); int num2 = 0; bool mayorFirstVoteDisplayed = false; + for (int j = 0; j < states.Length; j++) { + MeetingHud.VoterState voterState = states[j]; + GameData.PlayerInfo playerById = GameData.Instance.GetPlayerById(voterState.VoterId); + if (playerById == null) { + Debug.LogError(string.Format("Couldn't find player info for voter: {0}", voterState.VoterId)); + } else if (i == 0 && voterState.SkippedVote && !playerById.IsDead) { + __instance.BloopAVoteIcon(playerById, num, __instance.SkippedVoting.transform); + num++; + } + else if (voterState.VotedForId == targetPlayerId && !playerById.IsDead) { + __instance.BloopAVoteIcon(playerById, num2, playerVoteArea.transform); + num2++; + } - for (int j = 0; j < __instance.playerStates.Length; j++) - { - PlayerVoteArea playerVoteArea2 = __instance.playerStates[j]; - byte self = states[(int)playerVoteArea2.TargetPlayerId]; - - if (!((self & 128) > 0)) - { - GameData.PlayerInfo playerById = GameData.Instance.GetPlayerById((byte)playerVoteArea2.TargetPlayerId); - int votedFor = (int)PlayerVoteArea.GetVotedFor(self); - if (votedFor == (int)playerVoteArea.TargetPlayerId) - { - SpriteRenderer spriteRenderer = UnityEngine.Object.Instantiate(__instance.PlayerVotePrefab); - if (!PlayerControl.GameOptions.AnonymousVotes || (PlayerControl.LocalPlayer.Data.IsDead && MapOptions.ghostsSeeVotes)) - PlayerControl.SetPlayerMaterialColors((int)playerById.ColorId, spriteRenderer); - else - PlayerControl.SetPlayerMaterialColors(Palette.DisabledGrey, spriteRenderer); - - spriteRenderer.transform.SetParent(playerVoteArea.transform); - spriteRenderer.transform.localPosition = __instance.CounterOrigin + new Vector3(votesXOffset + __instance.CounterOffsets.x * (float)(num2), 0f, 0f); - spriteRenderer.transform.localScale = Vector3.zero; - spriteRenderer.transform.SetParent(playerVoteArea.transform.parent); // Reparent votes so they don't move with their playerVoteArea - __instance.StartCoroutine(Effects.Bloop((float)(num2) * 0.5f + delay, spriteRenderer.transform, votesFinalSize, 0.5f)); - num2++; - } - else if (i == 0 && votedFor == -1) - { - SpriteRenderer spriteRenderer2 = UnityEngine.Object.Instantiate(__instance.PlayerVotePrefab); - if (!PlayerControl.GameOptions.AnonymousVotes || (PlayerControl.LocalPlayer.Data.IsDead && MapOptions.ghostsSeeVotes)) - PlayerControl.SetPlayerMaterialColors((int)playerById.ColorId, spriteRenderer2); - else - PlayerControl.SetPlayerMaterialColors(Palette.DisabledGrey, spriteRenderer2); - spriteRenderer2.transform.SetParent(__instance.SkippedVoting.transform); - spriteRenderer2.transform.localPosition = __instance.CounterOrigin + new Vector3(votesXOffset + __instance.CounterOffsets.x * (float)(num), 0f, 0f); - spriteRenderer2.transform.localScale = Vector3.zero; - spriteRenderer2.transform.SetParent(playerVoteArea.transform.parent); // Reparent votes so they don't move with their playerVoteArea - __instance.StartCoroutine(Effects.Bloop((float)num * 0.5f + delay, spriteRenderer2.transform, votesFinalSize, 0.5f)); - num++; - } - - // Major vote, redo this iteration to place a second vote - if (Mayor.mayor != null && playerVoteArea2.TargetPlayerId == (sbyte)Mayor.mayor.PlayerId && !mayorFirstVoteDisplayed) { - mayorFirstVoteDisplayed = true; - j--; - } + // Major vote, redo this iteration to place a second vote + if (Mayor.mayor != null && voterState.VoterId == (sbyte)Mayor.mayor.PlayerId && !mayorFirstVoteDisplayed) { + mayorFirstVoteDisplayed = true; + j--; } } } - return false; } } @@ -228,7 +176,7 @@ namespace TheOtherRoles static void swapperOnClick(int i, MeetingHud __instance) { if (__instance.state == MeetingHud.VoteStates.Results) return; - if (__instance.playerStates[i].isDead) return; + if (__instance.playerStates[i].AmDead) return; int selectedCount = selections.Where(b => b).Count(); SpriteRenderer renderer = renderers[i]; @@ -282,61 +230,53 @@ namespace TheOtherRoles var smallButtonTemplate = __instance.playerStates[0].Buttons.transform.Find("CancelButton"); var textTemplate = __instance.playerStates[0].NameText; - Transform exitButton = UnityEngine.Object.Instantiate(smallButtonTemplate.transform, container); - exitButton.transform.localPosition = new Vector3(2.5f, 2.25f, -5); + exitButton.transform.localPosition = new Vector3(-2.725f, 2.1f, -10f); exitButton.GetComponent().OnClick.RemoveAllListeners(); exitButton.GetComponent().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { UnityEngine.Object.Destroy(container.gameObject); })); - List confirmButtons = new List(); + List buttons = new List(); + Transform selectedButton = null; foreach (RoleInfo roleInfo in RoleInfo.allRoleInfos) { if (roleInfo.roleId == RoleId.Lover || roleInfo.roleId == RoleId.Guesser || roleInfo == RoleInfo.niceMini) continue; // Not guessable roles Transform button = UnityEngine.Object.Instantiate(buttonTemplate.transform, container); - Transform confirm = UnityEngine.Object.Instantiate(smallButtonTemplate.transform, button); - confirmButtons.Add(confirm); + buttons.Add(button); TMPro.TextMeshPro label = UnityEngine.Object.Instantiate(textTemplate, button); int row = i/4, col = i%4; - button.localPosition = new Vector3(-3 + 1.83f * col, 1.5f - 0.4f * row, -5); - button.localScale = new Vector3(0.4f, 0.4f, 1f); - confirm.localScale = new Vector3(1.5f, 1.5f, 1f); - confirm.localPosition = new Vector3(0, 0, confirm.localPosition.z); - confirm.GetComponent().sprite = Guesser.getTargetSprite(); - confirm.GetComponent().color = Color.black; - confirm.gameObject.SetActive(false); + button.localPosition = new Vector3(-2.725f + 1.83f * col, 1.5f - 0.45f * row, -5); + button.localScale = new Vector3(0.55f, 0.55f, 1f); label.text = Helpers.cs(roleInfo.color, roleInfo.name); label.alignment = TMPro.TextAlignmentOptions.Center; label.transform.localPosition = new Vector3(0, 0, label.transform.localPosition.z); - label.transform.localScale *= 2; + label.transform.localScale *= 1.7f; int copiedIndex = i; button.GetComponent().OnClick.RemoveAllListeners(); button.GetComponent().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { - confirmButtons.ForEach(x => x.gameObject.SetActive(false)); - confirm.gameObject.SetActive(true); - })); - - - confirm.GetComponent().OnClick.RemoveAllListeners(); - confirm.GetComponent().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { - PlayerControl target = Helpers.playerById((byte)__instance.playerStates[buttonTarget].TargetPlayerId); - if (!(__instance.state == MeetingHud.VoteStates.Voted || __instance.state == MeetingHud.VoteStates.NotVoted) || target == null || Guesser.remainingShots <= 0 ) return; + if (selectedButton != button) { + selectedButton = button; + buttons.ForEach(x => x.GetComponent().color = x == selectedButton ? Color.red : Color.white); + } else { + PlayerControl target = Helpers.playerById((byte)__instance.playerStates[buttonTarget].TargetPlayerId); + if (!(__instance.state == MeetingHud.VoteStates.Voted || __instance.state == MeetingHud.VoteStates.NotVoted) || target == null || Guesser.remainingShots <= 0 ) return; - var mainRoleInfo = RoleInfo.getRoleInfoForPlayer(target).FirstOrDefault(); - if (mainRoleInfo == null) return; + var mainRoleInfo = RoleInfo.getRoleInfoForPlayer(target).FirstOrDefault(); + if (mainRoleInfo == null) return; - target = (mainRoleInfo == roleInfo) ? target : PlayerControl.LocalPlayer; + target = (mainRoleInfo == roleInfo) ? target : PlayerControl.LocalPlayer; - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.GuesserShoot, Hazel.SendOption.Reliable, -1); - writer.Write(target.PlayerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.guesserShoot(target.PlayerId); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.GuesserShoot, Hazel.SendOption.Reliable, -1); + writer.Write(target.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.guesserShoot(target.PlayerId); - UnityEngine.Object.Destroy(container.gameObject); - __instance.playerStates.ToList().ForEach(x => { if (x.transform.FindChild("ShootButton") != null) UnityEngine.Object.Destroy(x.transform.FindChild("ShootButton").gameObject); }); + UnityEngine.Object.Destroy(container.gameObject); + __instance.playerStates.ToList().ForEach(x => { if (x.transform.FindChild("ShootButton") != null) UnityEngine.Object.Destroy(x.transform.FindChild("ShootButton").gameObject); }); + } })); i++; @@ -360,13 +300,13 @@ namespace TheOtherRoles for (int i = 0; i < __instance.playerStates.Length; i++) { PlayerVoteArea playerVoteArea = __instance.playerStates[i]; - if (playerVoteArea.isDead || (playerVoteArea.TargetPlayerId == Swapper.swapper.PlayerId && Swapper.canOnlySwapOthers)) continue; + if (playerVoteArea.AmDead || (playerVoteArea.TargetPlayerId == Swapper.swapper.PlayerId && Swapper.canOnlySwapOthers)) continue; GameObject template = playerVoteArea.Buttons.transform.Find("CancelButton").gameObject; GameObject checkbox = UnityEngine.Object.Instantiate(template); checkbox.transform.SetParent(playerVoteArea.transform); checkbox.transform.position = template.transform.position; - checkbox.transform.localPosition = new Vector3(0f, 0.03f, template.transform.localPosition.z); + checkbox.transform.localPosition = new Vector3(-0.95f, 0.03f, -1f); SpriteRenderer renderer = checkbox.GetComponent(); renderer.sprite = Swapper.getCheckSprite(); renderer.color = Color.red; @@ -385,12 +325,12 @@ namespace TheOtherRoles if (Guesser.guesser != null && PlayerControl.LocalPlayer == Guesser.guesser && !Guesser.guesser.Data.IsDead && Guesser.remainingShots >= 0) { for (int i = 0; i < __instance.playerStates.Length; i++) { PlayerVoteArea playerVoteArea = __instance.playerStates[i]; - if (playerVoteArea.isDead || playerVoteArea.TargetPlayerId == Guesser.guesser.PlayerId) continue; + if (playerVoteArea.AmDead || playerVoteArea.TargetPlayerId == Guesser.guesser.PlayerId) continue; GameObject template = playerVoteArea.Buttons.transform.Find("CancelButton").gameObject; GameObject targetBox = UnityEngine.Object.Instantiate(template, playerVoteArea.transform); targetBox.name = "ShootButton"; - targetBox.transform.localPosition = new Vector3(0f, 0.03f, template.transform.localPosition.z); + targetBox.transform.localPosition = new Vector3(-0.95f, 0.03f, -1f); SpriteRenderer renderer = targetBox.GetComponent(); renderer.sprite = Guesser.getTargetSprite(); PassiveButton button = targetBox.GetComponent(); @@ -399,32 +339,6 @@ namespace TheOtherRoles button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => guesserOnClick(copiedIndex, __instance))); } } - - // Change buttons if there are more than 10 players - if (__instance.playerStates != null && __instance.playerStates.Length > 10) { - PlayerVoteArea[] playerStates = __instance.playerStates.OrderBy((PlayerVoteArea p) => p.isDead ? 50 : 0) - .ThenBy((PlayerVoteArea p) => p.TargetPlayerId) - .ToArray(); - for (int i = 0; i < playerStates.Length; i++) { - PlayerVoteArea area = playerStates[i]; - - int row = i/3, col = i%3; - - // Update scalings - area.Overlay.transform.localScale = area.PlayerButton.transform.localScale = new Vector3(1, 1/scale, 1); - area.NameText.transform.localScale = new Vector3(1/scale, 1/scale, 1); - area.gameObject.transform.localScale = new Vector3(scale, scale, 1); - GameObject megaphoneWrapper = new GameObject(); - megaphoneWrapper.transform.SetParent(area.transform); - megaphoneWrapper.transform.localScale = Vector3.one * 1/scale; - area.Megaphone.transform.SetParent(megaphoneWrapper.transform); - - // Update positions - area.Megaphone.transform.localPosition += Vector3.left * 0.1f; - area.NameText.transform.localPosition += new Vector3(0.25f, 0.043f, 0f); - area.transform.localPosition = new Vector3(-3.63f + 2.43f * col, 1.5f - 0.76f * row, -0.9f - 0.01f * row); - } - } } [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.ServerStart))] diff --git a/Source Code/ModUpdater.cs b/Source Code/ModUpdater.cs index 5ddda3a..2cbd6f3 100644 --- a/Source Code/ModUpdater.cs +++ b/Source Code/ModUpdater.cs @@ -182,9 +182,6 @@ namespace TheOtherRoles { public static void setPopupText(string message) { if (InfoPopup == null) return; - if (InfoPopup.TextArea != null) { - InfoPopup.TextArea.Text = message; - } if (InfoPopup.TextAreaTMP != null) { InfoPopup.TextAreaTMP.text = message; } diff --git a/Source Code/PlayerControlPatch.cs b/Source Code/PlayerControlPatch.cs index 55a6888..34b807e 100644 --- a/Source Code/PlayerControlPatch.cs +++ b/Source Code/PlayerControlPatch.cs @@ -342,21 +342,21 @@ namespace TheOtherRoles { foreach (PlayerControl p in PlayerControl.AllPlayerControls) { if (p != PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead) continue; - Transform playerInfoTransform = p.transform.FindChild("Info"); + Transform playerInfoTransform = p.nameText.transform.parent.FindChild("Info"); TMPro.TextMeshPro playerInfo = playerInfoTransform != null ? playerInfoTransform.GetComponent() : null; if (playerInfo == null) { playerInfo = UnityEngine.Object.Instantiate(p.nameText, p.nameText.transform.parent); - playerInfo.transform.localPosition += Vector3.up * 0.25f; + playerInfo.transform.localPosition += Vector3.up * 0.5f; playerInfo.fontSize *= 0.75f; playerInfo.gameObject.name = "Info"; } PlayerVoteArea playerVoteArea = MeetingHud.Instance?.playerStates?.FirstOrDefault(x => x.TargetPlayerId == p.PlayerId); - Transform meetingInfoTransform = playerVoteArea != null ? playerVoteArea.transform.FindChild("Info") : null; + Transform meetingInfoTransform = playerVoteArea != null ? playerVoteArea.NameText.transform.parent.FindChild("Info") : null; TMPro.TextMeshPro meetingInfo = meetingInfoTransform != null ? meetingInfoTransform.GetComponent() : null; if (meetingInfo == null && playerVoteArea != null) { meetingInfo = UnityEngine.Object.Instantiate(playerVoteArea.NameText, playerVoteArea.NameText.transform.parent); - meetingInfo.transform.localPosition += Vector3.down * (MeetingHud.Instance.playerStates.Length > 10 ? 0.4f : 0.25f); + meetingInfo.transform.localPosition += Vector3.down * 0.20f; meetingInfo.fontSize *= 0.75f; meetingInfo.gameObject.name = "Info"; } @@ -423,6 +423,99 @@ namespace TheOtherRoles { if (Arsonist.currentTarget != null) setPlayerOutline(Arsonist.currentTarget, Arsonist.color); } + static void snitchUpdate() + { + if (Snitch.localArrows == null) return; + + foreach (Arrow arrow in Snitch.localArrows) arrow.arrow.SetActive(false); + + if (Snitch.snitch == null || Snitch.snitch.Data.IsDead) return; + + var (playerCompleted, playerTotal) = TasksHandler.taskInfo(Snitch.snitch.Data); + int numberOfTasks = playerTotal - playerCompleted; + + if (PlayerControl.LocalPlayer.Data.IsImpostor && numberOfTasks <= Snitch.taskCountForImpostors) + { + if (Snitch.localArrows.Count == 0) Snitch.localArrows.Add(new Arrow(Color.blue)); + if (Snitch.localArrows.Count != 0 && Snitch.localArrows[0] != null) + { + Snitch.localArrows[0].arrow.SetActive(true); + Snitch.localArrows[0].Update(Snitch.snitch.transform.position); + } + } + else if (PlayerControl.LocalPlayer == Snitch.snitch && numberOfTasks == 0) + { + int arrowIndex = 0; + foreach (PlayerControl p in PlayerControl.AllPlayerControls) + { + if (p.Data.IsImpostor && !p.Data.IsDead) + { + if (arrowIndex >= Snitch.localArrows.Count) Snitch.localArrows.Add(new Arrow(Color.blue)); + if (arrowIndex < Snitch.localArrows.Count && Snitch.localArrows[arrowIndex] != null) + { + Snitch.localArrows[arrowIndex].arrow.SetActive(true); + Snitch.localArrows[arrowIndex].Update(p.transform.position); + } + arrowIndex++; + } + } + } + } + + static void bountyHunterUpdate() { + if (BountyHunter.bountyHunter == null || PlayerControl.LocalPlayer != BountyHunter.bountyHunter) return; + + if (BountyHunter.bountyHunter.Data.IsDead) { + if (BountyHunter.arrow != null || BountyHunter.arrow.arrow != null) UnityEngine.Object.Destroy(BountyHunter.arrow.arrow); + BountyHunter.arrow = null; + if (BountyHunter.cooldownText != null && BountyHunter.cooldownText.gameObject != null) UnityEngine.Object.Destroy(BountyHunter.cooldownText.gameObject); + BountyHunter.cooldownText = null; + BountyHunter.bounty = null; + foreach (PoolablePlayer p in MapOptions.playerIcons.Values) { + if (p != null && p.gameObject != null) p.gameObject.SetActive(false); + } + return; + } + + BountyHunter.arrowUpdateTimer -= Time.fixedDeltaTime; + BountyHunter.bountyUpdateTimer -= Time.fixedDeltaTime; + + if (BountyHunter.bounty == null || BountyHunter.bountyUpdateTimer <= 0f) { + // Set new bounty + BountyHunter.bounty = null; + BountyHunter.arrowUpdateTimer = 0f; // Force arrow to update + BountyHunter.bountyUpdateTimer = BountyHunter.bountyDuration; + var possibleTargets = new List(); + foreach (PlayerControl p in PlayerControl.AllPlayerControls) { + if (!p.Data.IsDead && !p.Data.Disconnected && p != p.Data.IsImpostor && p != Spy.spy && (p != Mini.mini || Mini.isGrownUp())) possibleTargets.Add(p); + } + BountyHunter.bounty = possibleTargets[TheOtherRoles.rnd.Next(0, possibleTargets.Count)]; + if (BountyHunter.bounty == null) return; + + // Show poolable player + if (HudManager.Instance != null && HudManager.Instance.UseButton != null) { + foreach (PoolablePlayer pp in MapOptions.playerIcons.Values) pp.gameObject.SetActive(false); + if (MapOptions.playerIcons.ContainsKey(BountyHunter.bounty.PlayerId) && MapOptions.playerIcons[BountyHunter.bounty.PlayerId].gameObject != null) + MapOptions.playerIcons[BountyHunter.bounty.PlayerId].gameObject.SetActive(true); + } + } + + // Update Cooldown Text + if (BountyHunter.cooldownText != null) { + BountyHunter.cooldownText.text = Mathf.CeilToInt(Mathf.Clamp(BountyHunter.bountyUpdateTimer, 0, BountyHunter.bountyDuration)).ToString(); + } + + // Update Arrow + if (BountyHunter.showArrow && BountyHunter.bounty != null) { + if (BountyHunter.arrow == null) BountyHunter.arrow = new Arrow(Color.red); + if (BountyHunter.arrowUpdateTimer <= 0f) { + BountyHunter.arrow.Update(BountyHunter.bounty.transform.position); + BountyHunter.arrowUpdateTimer = BountyHunter.arrowUpdateIntervall; + } + BountyHunter.arrow.Update(); + } + } + public static void Postfix(PlayerControl __instance) { if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return; @@ -476,6 +569,10 @@ namespace TheOtherRoles { securityGuardSetTarget(); // Arsonist arsonistSetTarget(); + // Snitch + snitchUpdate(); + // BountyHunter + bountyHunterUpdate(); } } } @@ -513,7 +610,7 @@ namespace TheOtherRoles { class RpcMurderPlayer { public static bool Prefix([HarmonyArgument(0)]PlayerControl target) { if (Helpers.handleMurderAttempt(target)) { // Custom checks - if (Mini.mini != null && PlayerControl.LocalPlayer == Mini.mini) { // Not checked by official servers + if (Mini.mini != null && PlayerControl.LocalPlayer == Mini.mini || BountyHunter.bountyHunter != null && PlayerControl.LocalPlayer == BountyHunter.bountyHunter) { // Not checked by official servers MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1); writer.Write(PlayerControl.LocalPlayer.PlayerId); writer.Write(target.PlayerId); @@ -649,6 +746,16 @@ namespace TheOtherRoles { var multiplier = Mini.isGrownUp() ? 0.66f : 2f; Mini.mini.SetKillTimer(PlayerControl.GameOptions.KillCooldown * multiplier); } + + // Set bountyHunter cooldown + if (BountyHunter.bountyHunter != null && PlayerControl.LocalPlayer == BountyHunter.bountyHunter && __instance == BountyHunter.bountyHunter) { + if (target == BountyHunter.bounty) { + BountyHunter.bountyHunter.SetKillTimer(BountyHunter.bountyKillCooldown); + BountyHunter.bountyUpdateTimer = 0f; // Force bounty update + } + else + BountyHunter.bountyHunter.SetKillTimer(PlayerControl.GameOptions.KillCooldown + BountyHunter.punishmentTime); + } } } @@ -657,10 +764,12 @@ namespace TheOtherRoles { public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)]float time) { if (PlayerControl.GameOptions.KillCooldown <= 0f) return false; float multiplier = 1f; + float addition = 0f; if (Mini.mini != null && PlayerControl.LocalPlayer == Mini.mini && Mini.mini.Data.IsImpostor) multiplier = Mini.isGrownUp() ? 0.66f : 2f; + if (BountyHunter.bountyHunter != null && PlayerControl.LocalPlayer == BountyHunter.bountyHunter) addition = BountyHunter.punishmentTime; - __instance.killTimer = Mathf.Clamp(time, 0f, PlayerControl.GameOptions.KillCooldown * multiplier); - DestroyableSingleton.Instance.KillButton.SetCoolDown(__instance.killTimer, PlayerControl.GameOptions.KillCooldown * multiplier); + __instance.killTimer = Mathf.Clamp(time, 0f, PlayerControl.GameOptions.KillCooldown * multiplier + addition); + DestroyableSingleton.Instance.KillButton.SetCoolDown(__instance.killTimer, PlayerControl.GameOptions.KillCooldown * multiplier + addition); return false; } } diff --git a/Source Code/RPC.cs b/Source Code/RPC.cs index da98b40..d48e473 100644 --- a/Source Code/RPC.cs +++ b/Source Code/RPC.cs @@ -44,6 +44,7 @@ namespace TheOtherRoles SecurityGuard, Arsonist, Guesser, + BountyHunter, Crewmate, Impostor } @@ -56,11 +57,9 @@ namespace TheOtherRoles ShareOptionSelection, ForceEnd, SetRole, - SetUncheckedColor, VersionHandshake, UseUncheckedVent, UncheckedMurderPlayer, - OpenToiletDoor, // Role functionality EngineerFixLights = 81, @@ -226,15 +225,13 @@ namespace TheOtherRoles case RoleId.Guesser: Guesser.guesser = player; break; + case RoleId.BountyHunter: + BountyHunter.bountyHunter = player; + break; } } } - public static void setUncheckedColor(byte colorId, byte playerId) { - var player = Helpers.playerById(playerId); - if (player != null) player.SetColor(colorId); - } - public static void versionHandshake(int major, int minor, int build, int revision, Guid guid, int clientId) { System.Version ver; if (revision < 0) @@ -364,19 +361,20 @@ namespace TheOtherRoles return; } - // Switch shield - if (Medic.shielded != null && Medic.shielded == player) { - Medic.shielded = oldShifter; - } else if (Medic.shielded != null && Medic.shielded == oldShifter) { - Medic.shielded = player; - } - - // Shift Lovers Role - if (Lovers.lover1 != null && oldShifter == Lovers.lover1) Lovers.lover1 = player; - else if (Lovers.lover1 != null && player == Lovers.lover1) Lovers.lover1 = oldShifter; + if (Shifter.shiftModifiers) { + // Switch shield + if (Medic.shielded != null && Medic.shielded == player) { + Medic.shielded = oldShifter; + } else if (Medic.shielded != null && Medic.shielded == oldShifter) { + Medic.shielded = player; + } + // Shift Lovers Role + if (Lovers.lover1 != null && oldShifter == Lovers.lover1) Lovers.lover1 = player; + else if (Lovers.lover1 != null && player == Lovers.lover1) Lovers.lover1 = oldShifter; - if (Lovers.lover2 != null && oldShifter == Lovers.lover2) Lovers.lover2 = player; - else if (Lovers.lover2 != null && player == Lovers.lover2) Lovers.lover2 = oldShifter; + if (Lovers.lover2 != null && oldShifter == Lovers.lover2) Lovers.lover2 = player; + else if (Lovers.lover2 != null && player == Lovers.lover2) Lovers.lover2 = oldShifter; + } // Shift role if (Mayor.mayor != null && Mayor.mayor == player) @@ -500,26 +498,23 @@ namespace TheOtherRoles { if (player.PlayerId == targetId) { - if(!Jackal.canCreateSidekickFromImpostor && player.Data.IsImpostor) { + if (!Jackal.canCreateSidekickFromImpostor && player.Data.IsImpostor) { Jackal.fakeSidekick = player; - return; + } else { + player.RemoveInfected(); + erasePlayerRoles(player.PlayerId, true); + Sidekick.sidekick = player; } - player.RemoveInfected(); - erasePlayerRoles(player.PlayerId, true); - - Sidekick.sidekick = player; + Jackal.canCreateSidekick = false; return; } } } public static void sidekickPromotes() { - var player = Sidekick.sidekick; Jackal.removeCurrentJackal(); - Jackal.jackal = player; - if (Jackal.jackalPromotedFromSidekickCanCreateSidekick == false) { - Jackal.canCreateSidekick = false; - } + Jackal.jackal = Sidekick.sidekick; + Jackal.canCreateSidekick = Jackal.jackalPromotedFromSidekickCanCreateSidekick; Sidekick.clearAndReload(); return; } @@ -573,12 +568,16 @@ namespace TheOtherRoles } } if (player == Sidekick.sidekick) Sidekick.clearAndReload(); + if (player == BountyHunter.bountyHunter) BountyHunter.clearAndReload(); } public static void setFutureErased(byte playerId) { PlayerControl player = Helpers.playerById(playerId); - if (Eraser.futureErased == null) Eraser.futureErased = new List(); - if (player != null) Eraser.futureErased.Add(player); + if (Eraser.futureErased == null) + Eraser.futureErased = new List(); + if (player != null) { + Eraser.futureErased.Add(player); + } } public static void setFutureShifted(byte playerId) { @@ -661,22 +660,27 @@ namespace TheOtherRoles PlayerControl target = Helpers.playerById(playerId); if (target == null) return; target.Exiled(); + PlayerControl partner = target.getPartner(); // Lover check + byte partnerId = partner != null ? partner.PlayerId : playerId; Guesser.remainingShots = Mathf.Max(0, Guesser.remainingShots - 1); if (Constants.ShouldPlaySfx()) SoundManager.Instance.PlaySound(target.KillSfx, false, 0.8f); if (MeetingHud.Instance) { foreach (PlayerVoteArea pva in MeetingHud.Instance.playerStates) { - if (pva.TargetPlayerId == playerId) { - pva.SetDead(playerId == PlayerControl.LocalPlayer.PlayerId, pva.didReport, true); + if (pva.TargetPlayerId == playerId || pva.TargetPlayerId == partnerId) { + pva.SetDead(pva.DidReport, true); pva.Overlay.gameObject.SetActive(true); - pva.Overlay.transform.GetChild(0).gameObject.SetActive(true); } } - if (AmongUsClient.Instance.AmHost) MeetingHud.Instance.CheckForEndVoting(); + if (AmongUsClient.Instance.AmHost) + MeetingHud.Instance.CheckForEndVoting(); } - if (HudManager.Instance != null && Guesser.guesser != null && PlayerControl.LocalPlayer == target) - HudManager.Instance.KillOverlay.ShowOne(Guesser.guesser.Data, target.Data); + if (HudManager.Instance != null && Guesser.guesser != null) + if (PlayerControl.LocalPlayer == target) + HudManager.Instance.KillOverlay.ShowKillAnimation(Guesser.guesser.Data, target.Data); + else if (partner != null && PlayerControl.LocalPlayer == partner) + HudManager.Instance.KillOverlay.ShowKillAnimation(partner.Data, partner.Data); } - } + } [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.HandleRpc))] class RPCHandlerPatch @@ -705,11 +709,6 @@ namespace TheOtherRoles byte flag = reader.ReadByte(); RPCProcedure.setRole(roleId, playerId, flag); break; - case (byte)CustomRPC.SetUncheckedColor: - byte c = reader.ReadByte(); - byte p = reader.ReadByte(); - RPCProcedure.setUncheckedColor(c, p); - break; case (byte)CustomRPC.VersionHandshake: byte major = reader.ReadByte(); byte minor = reader.ReadByte(); @@ -717,7 +716,7 @@ namespace TheOtherRoles int versionOwnerId = reader.ReadPackedInt32(); byte revision = 0xFF; Guid guid; - if (reader.Length >= 24) { + if (reader.Length - reader.Position >= 17) { // enough bytes left to read revision = reader.ReadByte(); // GUID byte[] gbytes = reader.ReadBytes(16); @@ -739,14 +738,6 @@ namespace TheOtherRoles RPCProcedure.uncheckedMurderPlayer(source, target); break; - // Fixes - - case (byte)CustomRPC.OpenToiletDoor: - int doorId = reader.ReadInt32(); - PlainDoor door = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(door => door.Id == doorId); - door.SetDoorway(true); - break; - // Role functionality case (byte)CustomRPC.EngineerFixLights: diff --git a/Source Code/Resources/CamoButton.png b/Source Code/Resources/CamoButton.png index 5ab0c7e..91fec8e 100644 Binary files a/Source Code/Resources/CamoButton.png and b/Source Code/Resources/CamoButton.png differ diff --git a/Source Code/Resources/CleanButton.png b/Source Code/Resources/CleanButton.png index ccbea86..69a8c5c 100644 Binary files a/Source Code/Resources/CleanButton.png and b/Source Code/Resources/CleanButton.png differ diff --git a/Source Code/Resources/CurseButton.png b/Source Code/Resources/CurseButton.png index fd49cc2..0b29330 100644 Binary files a/Source Code/Resources/CurseButton.png and b/Source Code/Resources/CurseButton.png differ diff --git a/Source Code/Resources/CurseKillButton.png b/Source Code/Resources/CurseKillButton.png index 670f792..140b7b9 100644 Binary files a/Source Code/Resources/CurseKillButton.png and b/Source Code/Resources/CurseKillButton.png differ diff --git a/Source Code/Resources/DouseButton.png b/Source Code/Resources/DouseButton.png index 3d894b5..c5531a3 100644 Binary files a/Source Code/Resources/DouseButton.png and b/Source Code/Resources/DouseButton.png differ diff --git a/Source Code/Resources/EraserButton.png b/Source Code/Resources/EraserButton.png index 0566e8d..7cf31dd 100644 Binary files a/Source Code/Resources/EraserButton.png and b/Source Code/Resources/EraserButton.png differ diff --git a/Source Code/Resources/Garlic.png b/Source Code/Resources/Garlic.png index c078e9a..fcca3b4 100644 Binary files a/Source Code/Resources/Garlic.png and b/Source Code/Resources/Garlic.png differ diff --git a/Source Code/Resources/HackerButton.png b/Source Code/Resources/HackerButton.png index 3ceb9c2..934acf3 100644 Binary files a/Source Code/Resources/HackerButton.png and b/Source Code/Resources/HackerButton.png differ diff --git a/Source Code/Resources/IgniteButton.png b/Source Code/Resources/IgniteButton.png index 7426095..a30a380 100644 Binary files a/Source Code/Resources/IgniteButton.png and b/Source Code/Resources/IgniteButton.png differ diff --git a/Source Code/Resources/LighterButton.png b/Source Code/Resources/LighterButton.png index 2614c40..bfd53b1 100644 Binary files a/Source Code/Resources/LighterButton.png and b/Source Code/Resources/LighterButton.png differ diff --git a/Source Code/Resources/LightsOutButton.png b/Source Code/Resources/LightsOutButton.png index 5bd97e1..9fce067 100644 Binary files a/Source Code/Resources/LightsOutButton.png and b/Source Code/Resources/LightsOutButton.png differ diff --git a/Source Code/Resources/ModStamp.png b/Source Code/Resources/ModStamp.png new file mode 100644 index 0000000..4e5cfda Binary files /dev/null and b/Source Code/Resources/ModStamp.png differ diff --git a/Source Code/Resources/MorphButton.png b/Source Code/Resources/MorphButton.png index 2b2404f..e44ddc2 100644 Binary files a/Source Code/Resources/MorphButton.png and b/Source Code/Resources/MorphButton.png differ diff --git a/Source Code/Resources/PlaceCameraButton.png b/Source Code/Resources/PlaceCameraButton.png index 313226d..2e37148 100644 Binary files a/Source Code/Resources/PlaceCameraButton.png and b/Source Code/Resources/PlaceCameraButton.png differ diff --git a/Source Code/Resources/PlaceJackInTheBoxButton.png b/Source Code/Resources/PlaceJackInTheBoxButton.png index 5222c7f..8b7e358 100644 Binary files a/Source Code/Resources/PlaceJackInTheBoxButton.png and b/Source Code/Resources/PlaceJackInTheBoxButton.png differ diff --git a/Source Code/Resources/SampleButton.png b/Source Code/Resources/SampleButton.png index 8e81022..0f51d67 100644 Binary files a/Source Code/Resources/SampleButton.png and b/Source Code/Resources/SampleButton.png differ diff --git a/Source Code/Resources/ShieldButton.png b/Source Code/Resources/ShieldButton.png index 1bca178..6315ab8 100644 Binary files a/Source Code/Resources/ShieldButton.png and b/Source Code/Resources/ShieldButton.png differ diff --git a/Source Code/Resources/ShiftButton.png b/Source Code/Resources/ShiftButton.png index a34f7aa..47d83d0 100644 Binary files a/Source Code/Resources/ShiftButton.png and b/Source Code/Resources/ShiftButton.png differ diff --git a/Source Code/Resources/SidekickButton.png b/Source Code/Resources/SidekickButton.png index 7008b2a..296ce9d 100644 Binary files a/Source Code/Resources/SidekickButton.png and b/Source Code/Resources/SidekickButton.png differ diff --git a/Source Code/Resources/SwapperCheck.png b/Source Code/Resources/SwapperCheck.png index 2feabac..32ff666 100644 Binary files a/Source Code/Resources/SwapperCheck.png and b/Source Code/Resources/SwapperCheck.png differ diff --git a/Source Code/Resources/TargetIcon.png b/Source Code/Resources/TargetIcon.png new file mode 100644 index 0000000..760ca78 Binary files /dev/null and b/Source Code/Resources/TargetIcon.png differ diff --git a/Source Code/Resources/TimeShieldButton.png b/Source Code/Resources/TimeShieldButton.png index cf977f7..1a3f896 100644 Binary files a/Source Code/Resources/TimeShieldButton.png and b/Source Code/Resources/TimeShieldButton.png differ diff --git a/Source Code/Resources/TrackerButton.png b/Source Code/Resources/TrackerButton.png index 9842f91..2cd82d5 100644 Binary files a/Source Code/Resources/TrackerButton.png and b/Source Code/Resources/TrackerButton.png differ diff --git a/Source Code/Resources/TricksterVentButton.png b/Source Code/Resources/TricksterVentButton.png index 645e4ae..f23870a 100644 Binary files a/Source Code/Resources/TricksterVentButton.png and b/Source Code/Resources/TricksterVentButton.png differ diff --git a/Source Code/Resources/VampireButton.png b/Source Code/Resources/VampireButton.png index 36be184..f2a2822 100644 Binary files a/Source Code/Resources/VampireButton.png and b/Source Code/Resources/VampireButton.png differ diff --git a/Source Code/RoleAssignmentPatch.cs b/Source Code/RoleAssignmentPatch.cs index 4d37597..56349ad 100644 --- a/Source Code/RoleAssignmentPatch.cs +++ b/Source Code/RoleAssignmentPatch.cs @@ -26,8 +26,7 @@ namespace TheOtherRoles private static void assignRoles() { var data = getRoleAssignmentData(); 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 - if (Mini.mini != null) // Remove Spy from assigns, if Mini exists - data.crewSettings.Remove((byte)RoleId.Spy); + selectFactionForFactionIndependentRoles(data); assignEnsuredRoles(data); // Assign roles that should always be in the game next assignChanceRoles(data); // Assign roles that may or may not be in the game last } @@ -73,6 +72,7 @@ namespace TheOtherRoles impSettings.Add((byte)RoleId.Trickster, CustomOptionHolder.tricksterSpawnRate.getSelection()); impSettings.Add((byte)RoleId.Cleaner, CustomOptionHolder.cleanerSpawnRate.getSelection()); impSettings.Add((byte)RoleId.Warlock, CustomOptionHolder.warlockSpawnRate.getSelection()); + impSettings.Add((byte)RoleId.BountyHunter, CustomOptionHolder.bountyHunterSpawnRate.getSelection()); neutralSettings.Add((byte)RoleId.Jester, CustomOptionHolder.jesterSpawnRate.getSelection()); neutralSettings.Add((byte)RoleId.Arsonist, CustomOptionHolder.arsonistSpawnRate.getSelection()); @@ -140,27 +140,21 @@ namespace TheOtherRoles setRoleToRandomPlayer((byte)RoleId.Mafioso, data.impostors); data.maxImpostorRoles -= 3; } + } - // Assign Mini - if (rnd.Next(1, 101) <= CustomOptionHolder.miniSpawnRate.getSelection() * 10) { - if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && rnd.Next(1, 101) <= 33) { - setRoleToRandomPlayer((byte)RoleId.Mini, data.impostors); - data.maxImpostorRoles--; - } else if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) { - setRoleToRandomPlayer((byte)RoleId.Mini, data.crewmates); - data.maxCrewmateRoles--; - } + private static void selectFactionForFactionIndependentRoles(RoleAssignmentData data) { + // Assign Mini (33% chance impostor / 67% chance crewmate) + if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && rnd.Next(1, 101) <= 33) { + data.impSettings.Add((byte)RoleId.Mini, CustomOptionHolder.miniSpawnRate.getSelection()); + } else if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) { + data.crewSettings.Add((byte)RoleId.Mini, CustomOptionHolder.miniSpawnRate.getSelection()); } - // Assign Guesser - if (rnd.Next(1, 101) <= CustomOptionHolder.guesserSpawnRate.getSelection() * 10) { - if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && rnd.Next(1, 101) <= CustomOptionHolder.guesserIsImpGuesserRate.getSelection() * 10) { - setRoleToRandomPlayer((byte)RoleId.Guesser, data.impostors); - data.maxImpostorRoles--; - } else if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) { - setRoleToRandomPlayer((byte)RoleId.Guesser, data.crewmates); - data.maxCrewmateRoles--; - } + // Assign Guesser (chance to be impostor based on setting) + if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && rnd.Next(1, 101) <= CustomOptionHolder.guesserIsImpGuesserRate.getSelection() * 10) { + data.impSettings.Add((byte)RoleId.Guesser, CustomOptionHolder.guesserSpawnRate.getSelection()); + } else if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) { + data.crewSettings.Add((byte)RoleId.Guesser, CustomOptionHolder.guesserSpawnRate.getSelection()); } } diff --git a/Source Code/RoleInfo.cs b/Source Code/RoleInfo.cs index 9db07c9..01a15c1 100644 --- a/Source Code/RoleInfo.cs +++ b/Source Code/RoleInfo.cs @@ -37,6 +37,7 @@ namespace TheOtherRoles public static RoleInfo trickster = new RoleInfo("Trickster", Trickster.color, "Use your jack-in-the-boxes to surprise others", "Surprise your enemies", RoleId.Trickster); public static RoleInfo cleaner = new RoleInfo("Cleaner", Cleaner.color, "Kill everyone and leave no traces", "Clean up dead bodies", RoleId.Cleaner); public static RoleInfo warlock = new RoleInfo("Warlock", Warlock.color, "Curse other players and kill everyone", "Curse and kill everyone", RoleId.Warlock); + public static RoleInfo bountyHunter = new RoleInfo("Bounty Hunter", BountyHunter.color, "Hunt your Bounty down", "Hunt your Bounty down", RoleId.BountyHunter); public static RoleInfo detective = new RoleInfo("Detective", Detective.color, "Find the Impostors by examining footprints", "Examine footprints", RoleId.Detective); public static RoleInfo timeMaster = new RoleInfo("Time Master", TimeMaster.color, "Save yourself with your time shield", "Use your time shield", RoleId.TimeMaster); public static RoleInfo medic = new RoleInfo("Medic", Medic.color, "Protect someone with your shield", "Protect other players", RoleId.Medic); @@ -71,6 +72,7 @@ namespace TheOtherRoles trickster, cleaner, warlock, + bountyHunter, niceMini, evilMini, goodGuesser, @@ -95,7 +97,8 @@ namespace TheOtherRoles tracker, snitch, spy, - securityGuard + securityGuard, + bountyHunter }; public static List getRoleInfoForPlayer(PlayerControl p) { @@ -134,6 +137,7 @@ namespace TheOtherRoles if (p == SecurityGuard.securityGuard) infos.Add(securityGuard); if (p == Arsonist.arsonist) infos.Add(arsonist); if (p == Guesser.guesser) infos.Add(p.Data.IsImpostor ? badGuesser : goodGuesser); + if (p == BountyHunter.bountyHunter) infos.Add(bountyHunter); // Default roles if (infos.Count == 0 && p.Data.IsImpostor) infos.Add(impostor); // Just Impostor diff --git a/Source Code/TheOtherRoles.cs b/Source Code/TheOtherRoles.cs index 74e0d47..4f472ed 100644 --- a/Source Code/TheOtherRoles.cs +++ b/Source Code/TheOtherRoles.cs @@ -51,6 +51,7 @@ namespace TheOtherRoles SecurityGuard.clearAndReload(); Arsonist.clearAndReload(); Guesser.clearAndReload(); + BountyHunter.clearAndReload(); } public static class Jester { @@ -269,6 +270,7 @@ namespace TheOtherRoles public static PlayerControl futureShift; public static PlayerControl currentTarget; + public static bool shiftModifiers = false; private static Sprite buttonSprite; public static Sprite getButtonSprite() { @@ -281,6 +283,7 @@ namespace TheOtherRoles shifter = null; currentTarget = null; futureShift = null; + shiftModifiers = CustomOptionHolder.shifterShiftsModifiers.getBool(); } } @@ -344,6 +347,16 @@ namespace TheOtherRoles notAckedExiledIsLover = false; bothDie = CustomOptionHolder.loversBothDie.getBool(); } + + public static PlayerControl getPartner(this PlayerControl player) { + if (player == null) + return null; + if (lover1 == player) + return lover2; + if (lover2 == player) + return lover1; + return null; + } } public static class Seer { @@ -610,7 +623,6 @@ namespace TheOtherRoles public static PlayerControl jackal; public static Color color = new Color32(0, 180, 235, byte.MaxValue); public static PlayerControl fakeSidekick; - public static PlayerControl currentTarget; public static List formerJackals = new List(); @@ -889,7 +901,6 @@ namespace TheOtherRoles public static PlayerControl currentTarget; public static PlayerControl douseTarget; public static List dousedPlayers = new List(); - public static Dictionary dousedIcons = new Dictionary(); private static Sprite douseSprite; public static Sprite getDouseSprite() { @@ -915,12 +926,9 @@ namespace TheOtherRoles douseTarget = null; triggerArsonistWin = false; dousedPlayers = new List(); - foreach (PoolablePlayer p in dousedIcons.Values) { - if (p != null && p.gameObject != null) { - UnityEngine.Object.Destroy(p.gameObject); - } + foreach (PoolablePlayer p in MapOptions.playerIcons.Values) { + if (p != null && p.gameObject != null) p.gameObject.SetActive(false); } - dousedIcons = new Dictionary(); cooldown = CustomOptionHolder.arsonistCooldown.getFloat(); duration = CustomOptionHolder.arsonistDuration.getFloat(); } @@ -945,4 +953,43 @@ namespace TheOtherRoles remainingShots = Mathf.RoundToInt(CustomOptionHolder.guesserNumberOfShots.getFloat()); } } + + public static class BountyHunter { + public static PlayerControl bountyHunter; + public static Color color = Palette.ImpostorRed; + + public static Arrow arrow; + public static float bountyDuration = 30f; + public static bool showArrow = true; + public static float bountyKillCooldown = 0f; + public static float punishmentTime = 15f; + public static float arrowUpdateIntervall = 10f; + + public static float arrowUpdateTimer = 0f; + public static float bountyUpdateTimer = 0f; + public static PlayerControl bounty; + public static TMPro.TextMeshPro cooldownText; + + public static void clearAndReload() { + arrow = new Arrow(color); + bountyHunter = null; + bounty = null; + arrowUpdateTimer = 0f; + bountyUpdateTimer = 0f; + if (arrow != null && arrow.arrow != null) UnityEngine.Object.Destroy(arrow.arrow); + arrow = null; + if (cooldownText != null && cooldownText.gameObject != null) UnityEngine.Object.Destroy(cooldownText.gameObject); + cooldownText = null; + foreach (PoolablePlayer p in MapOptions.playerIcons.Values) { + if (p != null && p.gameObject != null) p.gameObject.SetActive(false); + } + + + bountyDuration = CustomOptionHolder.bountyHunterBountyDuration.getFloat(); + bountyKillCooldown = CustomOptionHolder.bountyHunterReducedCooldown.getFloat(); + punishmentTime = CustomOptionHolder.bountyHunterPunishmentTime.getFloat(); + showArrow = CustomOptionHolder.bountyHunterShowArrow.getBool(); + arrowUpdateIntervall = CustomOptionHolder.bountyHunterArrowUpdateIntervall.getFloat(); + } + } } \ No newline at end of file diff --git a/Source Code/TheOtherRoles.csproj b/Source Code/TheOtherRoles.csproj index d9ed497..5d06ee7 100644 --- a/Source Code/TheOtherRoles.csproj +++ b/Source Code/TheOtherRoles.csproj @@ -1,13 +1,13 @@ netstandard2.1 - 2.6.7 + 2.7.0 TheOtherRoles Eisbison - 2021.5.10s + 2021.6.15 $(DefineConstants);STEAM true diff --git a/Source Code/UpdatePatch.cs b/Source Code/UpdatePatch.cs index 57c2608..6ecb63c 100644 --- a/Source Code/UpdatePatch.cs +++ b/Source Code/UpdatePatch.cs @@ -117,7 +117,7 @@ namespace TheOtherRoles } else if (Guesser.guesser != null && Guesser.guesser == PlayerControl.LocalPlayer) { setPlayerNameColor(Guesser.guesser, Guesser.guesser.Data.IsImpostor ? Palette.ImpostorRed : Guesser.color); } - + // No else if here, as a Lover of team Jackal needs the colors if (Sidekick.sidekick != null && Sidekick.sidekick == PlayerControl.LocalPlayer) { // Sidekick can see the jackal @@ -133,7 +133,7 @@ namespace TheOtherRoles } // Crewmate roles with no changes: Mini - // Impostor roles with no changes: Morphling, Camouflager, Vampire, Godfather, Eraser, Janitor, Cleaner, Warlock and Mafioso + // Impostor roles with no changes: Morphling, Camouflager, Vampire, Godfather, Eraser, Janitor, Cleaner, Warlock, BountyHunter and Mafioso } static void setNameTags() { @@ -288,37 +288,6 @@ namespace TheOtherRoles __instance.KillButton.enabled = enabled; } - static void snitchUpdate() { - if (Snitch.localArrows == null) return; - - foreach (Arrow arrow in Snitch.localArrows) arrow.arrow.SetActive(false); - - if (Snitch.snitch == null || Snitch.snitch.Data.IsDead) return; - - var (playerCompleted, playerTotal) = TasksHandler.taskInfo(Snitch.snitch.Data); - int numberOfTasks = playerTotal - playerCompleted; - - if (PlayerControl.LocalPlayer.Data.IsImpostor && numberOfTasks <= Snitch.taskCountForImpostors) { - if (Snitch.localArrows.Count == 0) Snitch.localArrows.Add(new Arrow(Color.blue)); - if (Snitch.localArrows.Count != 0 && Snitch.localArrows[0] != null) { - Snitch.localArrows[0].arrow.SetActive(true); - Snitch.localArrows[0].Update(Snitch.snitch.transform.position); - } - } else if (PlayerControl.LocalPlayer == Snitch.snitch && numberOfTasks == 0) { - int arrowIndex = 0; - foreach (PlayerControl p in PlayerControl.AllPlayerControls) { - if (p.Data.IsImpostor && !p.Data.IsDead) { - if (arrowIndex >= Snitch.localArrows.Count) Snitch.localArrows.Add(new Arrow(Color.blue)); - if (arrowIndex < Snitch.localArrows.Count && Snitch.localArrows[arrowIndex] != null) { - Snitch.localArrows[arrowIndex].arrow.SetActive(true); - Snitch.localArrows[arrowIndex].Update(p.transform.position); - } - arrowIndex++; - } - } - } - } - static void Postfix(HudManager __instance) { if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return; @@ -337,8 +306,6 @@ namespace TheOtherRoles camouflageAndMorphActions(); // Mini miniUpdate(); - // Snitch - snitchUpdate(); } } } diff --git a/Source Code/UsablesPatch.cs b/Source Code/UsablesPatch.cs index cc065f6..8cba747 100644 --- a/Source Code/UsablesPatch.cs +++ b/Source Code/UsablesPatch.cs @@ -232,54 +232,57 @@ namespace TheOtherRoles } } - [HarmonyPatch(typeof(VitalsMinigame), nameof(VitalsMinigame.Begin))] - class VitalsMinigameBeginPatch { - static void Postfix(VitalsMinigame __instance) { - - if (__instance.vitals.Length > 10) { - for (int i = 0; i < __instance.vitals.Length; i++) { - var vitalsPanel = __instance.vitals[i]; - var player = GameData.Instance.AllPlayers[i]; - vitalsPanel.Text.text = player.PlayerName.Length >= 4 ? player.PlayerName.Substring(0, 4).ToUpper() : player.PlayerName.ToUpper(); + [HarmonyPatch] + class VitalsMinigamePatch { + private static List hackerTexts = new List(); + + [HarmonyPatch(typeof(VitalsMinigame), nameof(VitalsMinigame.Begin))] + class VitalsMinigameStartPatch { + static void Postfix(VitalsMinigame __instance) { + if (Hacker.hacker != null && PlayerControl.LocalPlayer == Hacker.hacker) { + hackerTexts = new List(); + foreach (VitalsPanel panel in __instance.vitals) { + TMPro.TextMeshPro text = UnityEngine.Object.Instantiate(__instance.SabText, panel.transform); + hackerTexts.Add(text); + UnityEngine.Object.DestroyImmediate(text.GetComponent()); + text.gameObject.SetActive(false); + text.transform.localScale = Vector3.one * 0.75f; + text.transform.localPosition = new Vector3(-0.75f, -0.23f, 0f); + + } } } } - } - - [HarmonyPatch(typeof(VitalsMinigame), nameof(VitalsMinigame.Update))] - class VitalsMinigameUpdatePatch { + [HarmonyPatch(typeof(VitalsMinigame), nameof(VitalsMinigame.Update))] + class VitalsMinigameUpdatePatch { - static void Postfix(VitalsMinigame __instance) { - // Hacker show time since death - bool showHackerInfo = Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer && Hacker.hackerTimer > 0; - for (int k = 0; k < __instance.vitals.Length; k++) - { - VitalsPanel vitalsPanel = __instance.vitals[k]; - GameData.PlayerInfo player = GameData.Instance.AllPlayers[k]; - - // Crowded scaling - float scale = 10f / Mathf.Max(10, __instance.vitals.Length); - vitalsPanel.transform.localPosition = new Vector3((float)k * 0.6f * scale + -2.7f, 0.2f, -1f); - vitalsPanel.transform.localScale = new Vector3(scale, scale, vitalsPanel.transform.localScale.z); - - // Hacker update - if (vitalsPanel.IsDead) { - DeadPlayer deadPlayer = deadPlayers?.Where(x => x.player?.PlayerId == player?.PlayerId)?.FirstOrDefault(); - if (deadPlayer != null && deadPlayer.timeOfDeath != null) { - float timeSinceDeath = ((float)(DateTime.UtcNow - deadPlayer.timeOfDeath).TotalMilliseconds); - - if (showHackerInfo) - vitalsPanel.Text.text = Math.Round(timeSinceDeath / 1000) + "s"; - else if (__instance.vitals.Length > 10) - vitalsPanel.Text.text = player.PlayerName.Length >= 4 ? player.PlayerName.Substring(0, 4).ToUpper() : player.PlayerName.ToUpper(); - else - vitalsPanel.Text.text = DestroyableSingleton.Instance.GetString(Palette.ShortColorNames[(int)player.ColorId], new UnhollowerBaseLib.Il2CppReferenceArray(0)); + static void Postfix(VitalsMinigame __instance) { + // Hacker show time since death + + if (Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer && Hacker.hackerTimer > 0) { + for (int k = 0; k < __instance.vitals.Length; k++) { + VitalsPanel vitalsPanel = __instance.vitals[k]; + GameData.PlayerInfo player = GameData.Instance.AllPlayers[k]; + + // Hacker update + if (vitalsPanel.IsDead) { + DeadPlayer deadPlayer = deadPlayers?.Where(x => x.player?.PlayerId == player?.PlayerId)?.FirstOrDefault(); + if (deadPlayer != null && deadPlayer.timeOfDeath != null && k < hackerTexts.Count && hackerTexts[k] != null) { + float timeSinceDeath = ((float)(DateTime.UtcNow - deadPlayer.timeOfDeath).TotalMilliseconds); + hackerTexts[k].gameObject.SetActive(true); + hackerTexts[k].text = Math.Round(timeSinceDeath / 1000) + "s"; + } + } } + } else { + foreach (TMPro.TextMeshPro text in hackerTexts) + if (text != null && text.gameObject != null) + text.gameObject.SetActive(false); } - } + } } - } + } [HarmonyPatch] class AdminPanelPatch {