]> git.deb.at Git - rhonda/theotherroles.git/commitdiff
Version 2.7.0
authorEisbison <eisbison.creator@gmail.com>
Wed, 16 Jun 2021 15:53:46 +0000 (17:53 +0200)
committerEisbison <eisbison.creator@gmail.com>
Wed, 16 Jun 2021 15:53:46 +0000 (17:53 +0200)
54 files changed:
Images/TOR_colors.jpg [new file with mode: 0644]
Images/TOR_colors.png [deleted file]
README.md
Source Code/.gitignore
Source Code/AUFixes.cs [deleted file]
Source Code/Buttons.cs
Source Code/ChatCommands.cs
Source Code/CredentialsPatch.cs
Source Code/CrowdedPatches.cs [deleted file]
Source Code/CustomColors.cs
Source Code/CustomHats.cs
Source Code/CustomOptions.cs
Source Code/EndGamePatch.cs
Source Code/ExileControllerPatch.cs
Source Code/Helpers.cs
Source Code/IntroPatch.cs
Source Code/JackInTheBox.cs
Source Code/Main.cs
Source Code/MapOptions.cs
Source Code/MeetingPatch.cs
Source Code/ModUpdater.cs
Source Code/PlayerControlPatch.cs
Source Code/RPC.cs
Source Code/Resources/CamoButton.png
Source Code/Resources/CleanButton.png
Source Code/Resources/CurseButton.png
Source Code/Resources/CurseKillButton.png
Source Code/Resources/DouseButton.png
Source Code/Resources/EraserButton.png
Source Code/Resources/Garlic.png
Source Code/Resources/HackerButton.png
Source Code/Resources/IgniteButton.png
Source Code/Resources/LighterButton.png
Source Code/Resources/LightsOutButton.png
Source Code/Resources/ModStamp.png [new file with mode: 0644]
Source Code/Resources/MorphButton.png
Source Code/Resources/PlaceCameraButton.png
Source Code/Resources/PlaceJackInTheBoxButton.png
Source Code/Resources/SampleButton.png
Source Code/Resources/ShieldButton.png
Source Code/Resources/ShiftButton.png
Source Code/Resources/SidekickButton.png
Source Code/Resources/SwapperCheck.png
Source Code/Resources/TargetIcon.png [new file with mode: 0644]
Source Code/Resources/TimeShieldButton.png
Source Code/Resources/TrackerButton.png
Source Code/Resources/TricksterVentButton.png
Source Code/Resources/VampireButton.png
Source Code/RoleAssignmentPatch.cs
Source Code/RoleInfo.cs
Source Code/TheOtherRoles.cs
Source Code/TheOtherRoles.csproj
Source Code/UpdatePatch.cs
Source Code/UsablesPatch.cs

diff --git a/Images/TOR_colors.jpg b/Images/TOR_colors.jpg
new file mode 100644 (file)
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 (file)
index 78ee632..0000000
Binary files a/Images/TOR_colors.png and /dev/null differ
index bcf831defd9d228cd7c3be8866b0e5b697f52b19..b62eaacd32836b04b31948c0b58fac8fe847f1b4 100644 (file)
--- 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
 <details>
   <summary>Click to show the Changelog</summary>
 
+**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
index 8d4a6c08a83aaa5470f3c556faf2988be0ed1288..80a85c7fb40e5573aebbaecb55479c7d0aeb5453 100644 (file)
@@ -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 (file)
index 89548c4..0000000
+++ /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
index 1174c868e1688bf8673e5ccdb1e47472d5747407..4d03eb93ff2d628da0af306fb8383319206ceb2d 100644 (file)
@@ -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);
                         }
                     }
                 }
index 98823d3cadb1c7dee8899630c0a45881739122b6..2c91f417a310d99b641ec8e4c8b80d84415c8fbd 100644 (file)
@@ -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();
index a7e9e687f78aa898bf6a5621503e07c3db89e162..c5b28b5c2b6756b6f38277e9b122f65cb239905b 100644 (file)
@@ -46,18 +46,32 @@ Balanced with <color=#FCCE03FF>Dhalucard</color>      Design by <color=#FCCE03FF
         [HarmonyPatch(typeof(PingTracker), nameof(PingTracker.Update))]
         private static class PingTrackerPatch
         {
+            private static GameObject modStamp;
+            static void Prefix(PingTracker __instance) {
+                if (modStamp == null) {
+                    modStamp = new GameObject("ModStamp");
+                    var rend = modStamp.AddComponent<SpriteRenderer>();
+                    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 = $"<size=130%><color=#ff351f>TheOtherRoles</color></size> 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 (file)
index a6eb2ad..0000000
+++ /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<SpriteRenderer> additionalButtons = new List<SpriteRenderer>();
-
-        [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.Start))]
-        public static class CreateOptionsPickerStartPatch {
-            public static void Postfix(CreateOptionsPicker __instance) {
-                List<SpriteRenderer> maxPlayerButtons = __instance.MaxPlayerButtons.ToList();
-                if (maxPlayerButtons == null || maxPlayerButtons.Count <= 2) return;
-                additionalButtons = new List<SpriteRenderer>();
-
-                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<TMPro.TMP_Text>();
-                    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>();
-                    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<TMPro.TMP_Text>().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;
-        }
-    }
-}
index 42b279da2fd11a5e7bb7d4f8908a6e2a4f83a101..bc80f393aed7e082d215fc69777a396a92d8a646 100644 (file)
@@ -12,118 +12,125 @@ namespace TheOtherRoles {
     public class CustomColors {
         protected static Dictionary<int, string> ColorStrings = new Dictionary<int, string>();
         public static List<int> lighterColors = new List<int>(){ 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<int> ORDER = new List<int>() { 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<int> ORDER = new List<int>() { 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<StringNames> longlist = Enumerable.ToList<StringNames>(Palette.ColorNames);
-            List<StringNames> shortlist = Enumerable.ToList<StringNames>(Palette.ShortColorNames);
             List<Color32> colorlist = Enumerable.ToList<Color32>(Palette.PlayerColors);
             List<Color32> shadowlist = Enumerable.ToList<Color32>(Palette.ShadowColors);
 
             List<CustomColor> colors = new List<CustomColor>();
 
-            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<ColorChip> 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();
                     }
                 }
             }
index 32563e85df2dabb8266ddb014d5a1f0091fa116f..f9c8ee55028797a7f1babe4ff220f9642a2d1254 100644 (file)
@@ -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;
index 864497eae3912a299ff3f40e8bcf16b2558dffce..58116e9e0b1971c1bd5ba08f684361f43031ceb3 100644 (file)
@@ -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) {
index f0cfea937f98973a6211a827140055d3d4d6e6c5..b6fc905edeb9031f7faf627b7330d05f95b50cb4 100644 (file)
@@ -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); 
index 4f3d139b7d7c594de340387420097ec7c1a11b25..7c36c417c73bbad58e467d4f14602495cf0b6b8d 100644 (file)
@@ -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;
         }
     }
 
index a2727f0790f8559dbacc0090130fb0357aef4af1..a930739b68ae34391d32e58d199231acf7c9b651 100644 (file)
@@ -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<byte, int> MaxPair(this Dictionary<byte, int> self, out bool tie) {
+            tie = true;
+            KeyValuePair<byte, int> result = new KeyValuePair<byte, int>(byte.MaxValue, int.MinValue);
+            foreach (KeyValuePair<byte, int> keyValuePair in self)
+            {
+                if (keyValuePair.Value > result.Value)
+                {
+                    result = keyValuePair;
+                    tie = false;
+                }
+                else if (keyValuePair.Value == result.Value)
+                {
+                    tie = true;
+                }
+            }
+            return result;
+        }
     }
 }
index d16851a7e0f4505974c343faec913200b6e52b0a..05ce908d3df1dbf9958372d3d3d89bf337444d91 100644 (file)
@@ -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<PoolablePlayer>(__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<HatManager>.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<PoolablePlayer>(__instance.PlayerPrefab, HudManager.Instance.transform);
+                    PlayerControl.SetPlayerMaterialColors(data.ColorId, player.Body);
+                    DestroyableSingleton<HatManager>.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<TMPro.TextMeshPro>(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
         }
     }
 }
+
index 72b2cfac69ecaf3ed5950a1dae3fd7b96ff337a8..38901fed4f7cd992ad371ca3b634105079407aa3 100644 (file)
@@ -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<JackInTheBox> AllJackInTheBoxes = new System.Collections.Generic.List<JackInTheBox>();
         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<Vent>(); 
+            var referenceVent = UnityEngine.Object.FindObjectOfType<Vent>();
             vent = UnityEngine.Object.Instantiate<Vent>(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<JackInTheBox>();
         }
-        
+
     }
 
 }
\ No newline at end of file
index 65fa6ce800e92c6a5550fb0d52ac053c30e783ae..7accc2e589c0176a5ffd9e39b6116a167595cfe6 100644 (file)
@@ -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<string> Ip { get; set; }
         public static ConfigEntry<ushort> Port { get; set; }
 
+        public static Sprite ModStamp;
+
         public static IRegionInfo[] defaultRegions;
         public static void UpdateRegions() {
             ServerManager serverManager = DestroyableSingleton<ServerManager>.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
index 4a165debc7f6ff265b304979059253a39953f5e4..488f9cdf20463400b145e06f74b24bc5d137c55e 100644 (file)
@@ -20,11 +20,13 @@ namespace TheOtherRoles{
         public static int meetingsCount = 0;
         public static List<SurvCamera> camerasToAdd = new List<SurvCamera>();
         public static List<Vent> ventsToSeal = new List<Vent>();
+        public static Dictionary<byte, PoolablePlayer> playerIcons = new Dictionary<byte, PoolablePlayer>();
 
-        public static void clearAndReloadMapOptions() {
+public static void clearAndReloadMapOptions() {
             meetingsCount = 0;
             camerasToAdd = new List<SurvCamera>();
             ventsToSeal = new List<Vent>();
+            playerIcons = new Dictionary<byte, PoolablePlayer>(); ;
 
             maxNumberOfMeetings = Mathf.RoundToInt(CustomOptionHolder.maxNumberOfMeetings.getSelection());
             blockSkippingInEmergencyMeetings = CustomOptionHolder.blockSkippingInEmergencyMeetings.getBool();
index b64458f647349fa38462c3f580dafbab98733ba5..fa369968c82a8ce4c18567838f5dcfece1171c9e 100644 (file)
@@ -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<byte, int> CalculateVotes(MeetingHud __instance) {
+                Dictionary<byte, int> dictionary = new Dictionary<byte, int>();
+                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<byte, int> 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<byte, int> 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<byte, int> 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<SpriteRenderer>(__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<VoteSpreader>().AddVote(spriteRenderer);
+                return false;
+            }
+        } 
+
         [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.PopulateResults))]
-        class MeetingPopulateVotesPatch {
-            static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)]Il2CppStructArray<byte> states)
-            {
-                // Swapper swap votes
+        class MeetingHudPopulateVotesPatch {
+            
+            static bool Prefix(MeetingHud __instance, Il2CppStructArray<MeetingHud.VoterState> 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<TranslationController>.Instance.GetString(StringNames.MeetingVotingResults, new Il2CppReferenceArray<Il2CppSystem.Object>(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<SpriteRenderer>(__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<SpriteRenderer>(__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<PassiveButton>().OnClick.RemoveAllListeners();
             exitButton.GetComponent<PassiveButton>().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => {
                 UnityEngine.Object.Destroy(container.gameObject);
             }));
 
-            List<Transform> confirmButtons = new List<Transform>();
+            List<Transform> buttons = new List<Transform>();
+            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<SpriteRenderer>().sprite = Guesser.getTargetSprite();
-                confirm.GetComponent<SpriteRenderer>().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<PassiveButton>().OnClick.RemoveAllListeners();
                 button.GetComponent<PassiveButton>().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => {
-                    confirmButtons.ForEach(x => x.gameObject.SetActive(false));
-                    confirm.gameObject.SetActive(true);
-                }));
-
-
-                confirm.GetComponent<PassiveButton>().OnClick.RemoveAllListeners();
-                confirm.GetComponent<PassiveButton>().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<SpriteRenderer>().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<SpriteRenderer>();
                     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<SpriteRenderer>();
                     renderer.sprite = Guesser.getTargetSprite();
                     PassiveButton button = targetBox.GetComponent<PassiveButton>();
@@ -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<PlayerVoteArea>();
-                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))]
index 5ddda3a405dac8d7995bdd871fe55c9b2e7d670c..2cbd6f3a29dfb317a9f98043666bfad0e8e9ae9a 100644 (file)
@@ -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;
             }
index 55a68888f57a11a0b3bc20533f28c5d18b134b5b..34b807e95289fae9dec23d90fb997edefb7a7af9 100644 (file)
@@ -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<TMPro.TextMeshPro>() : 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<TMPro.TextMeshPro>() : 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<PlayerControl>();
+                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<HudManager>.Instance.KillButton.SetCoolDown(__instance.killTimer, PlayerControl.GameOptions.KillCooldown * multiplier);
+            __instance.killTimer = Mathf.Clamp(time, 0f, PlayerControl.GameOptions.KillCooldown * multiplier + addition);
+            DestroyableSingleton<HudManager>.Instance.KillButton.SetCoolDown(__instance.killTimer, PlayerControl.GameOptions.KillCooldown * multiplier + addition);
             return false;
         }
     }
index da98b40021884f3ed034b286e1ac3984f942dffe..d48e473bcc5b36af05dd90bdbb64eaf0b7682555 100644 (file)
@@ -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<PlayerControl>();
-            if (player != null) Eraser.futureErased.Add(player);
+            if (Eraser.futureErased == null) 
+                Eraser.futureErased = new List<PlayerControl>();
+            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<PlainDoor>().FirstOrDefault(door => door.Id == doorId);
-                    door.SetDoorway(true);
-                    break;
-
                 // Role functionality
 
                 case (byte)CustomRPC.EngineerFixLights:
index 5ab0c7eb2f51ca2d8c2bd6a4617decb42e47e8b8..91fec8ea56a63b327fb4d6f96282cc3ac6f247d2 100644 (file)
Binary files a/Source Code/Resources/CamoButton.png and b/Source Code/Resources/CamoButton.png differ
index ccbea8613fbc786954996b3a1e9f7006615eec86..69a8c5c2e501269e23783989cd07615ba93d2d76 100644 (file)
Binary files a/Source Code/Resources/CleanButton.png and b/Source Code/Resources/CleanButton.png differ
index fd49cc203c310349089af282846a6cf19ad7ff0d..0b2933079f0ccf7af2bc534ed7710a7c5ed585a3 100644 (file)
Binary files a/Source Code/Resources/CurseButton.png and b/Source Code/Resources/CurseButton.png differ
index 670f792db883816db5f027313e1e5164ccf23b22..140b7b9ddac9662372c42bbfbc48c80619946739 100644 (file)
Binary files a/Source Code/Resources/CurseKillButton.png and b/Source Code/Resources/CurseKillButton.png differ
index 3d894b558e31a2502c4c4b28313fe51363cee904..c5531a37c359875581cb9e8f1913bafe697a7f58 100644 (file)
Binary files a/Source Code/Resources/DouseButton.png and b/Source Code/Resources/DouseButton.png differ
index 0566e8ddd8f6628e90670f669d39da3087ccb791..7cf31dd8c971b4b0f9cefaefc60418fcda12f389 100644 (file)
Binary files a/Source Code/Resources/EraserButton.png and b/Source Code/Resources/EraserButton.png differ
index c078e9a113255815682ac6f35526968a70af4188..fcca3b4a20eaf2f5db1dda6db7ed7a9891e40870 100644 (file)
Binary files a/Source Code/Resources/Garlic.png and b/Source Code/Resources/Garlic.png differ
index 3ceb9c26dea2fa7579063ac01204ab62f3edcf6d..934acf3eace468db55958d5c6a58b789355a915f 100644 (file)
Binary files a/Source Code/Resources/HackerButton.png and b/Source Code/Resources/HackerButton.png differ
index 7426095ae95096758a23bec126c55b9adcea7254..a30a380a1a9f7e16d954498fcc7e1ddfa657cd8b 100644 (file)
Binary files a/Source Code/Resources/IgniteButton.png and b/Source Code/Resources/IgniteButton.png differ
index 2614c40219a6cbe9c9a15da4777150efd61b5a22..bfd53b10cc7d8c6f7406b698a328ebbf51892bb1 100644 (file)
Binary files a/Source Code/Resources/LighterButton.png and b/Source Code/Resources/LighterButton.png differ
index 5bd97e15c2e972f851069a687900838da3e0d00e..9fce067d9fef398477cb9dd409ff73565b74741d 100644 (file)
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 (file)
index 0000000..4e5cfda
Binary files /dev/null and b/Source Code/Resources/ModStamp.png differ
index 2b2404f10d19a853f126b0b08b650bbd667cbd13..e44ddc2aad0def979e6fbd7729881bd9016a0bfe 100644 (file)
Binary files a/Source Code/Resources/MorphButton.png and b/Source Code/Resources/MorphButton.png differ
index 313226dcbdf222c69e94b46fe80b85196c470aa8..2e37148030cf5061fb293ef8993384127d4ff082 100644 (file)
Binary files a/Source Code/Resources/PlaceCameraButton.png and b/Source Code/Resources/PlaceCameraButton.png differ
index 5222c7fd8d24ea69f52660112229855363e2cf29..8b7e358cad6e1ccf6e8e1eefcede5d47f04c0c36 100644 (file)
Binary files a/Source Code/Resources/PlaceJackInTheBoxButton.png and b/Source Code/Resources/PlaceJackInTheBoxButton.png differ
index 8e81022fa227948c8b84e63bf3d6451fdf9731b9..0f51d67e75a72b002b8e2d77eeb6d4ea3173b1ba 100644 (file)
Binary files a/Source Code/Resources/SampleButton.png and b/Source Code/Resources/SampleButton.png differ
index 1bca17854609b8a10cb4b9f1bfac81094b936b9a..6315ab8e8b79917c739b2c8e6d9b79f66ed59e08 100644 (file)
Binary files a/Source Code/Resources/ShieldButton.png and b/Source Code/Resources/ShieldButton.png differ
index a34f7aaad2aee5a90831203b65cd309444cbb31c..47d83d0c1d95ba9416a51afd4b61e7c2c11a8940 100644 (file)
Binary files a/Source Code/Resources/ShiftButton.png and b/Source Code/Resources/ShiftButton.png differ
index 7008b2a05d9e28fa351556c52c0757fc99ab6c1d..296ce9d12fee3b77bc1a76d0a13dde77018b8f8a 100644 (file)
Binary files a/Source Code/Resources/SidekickButton.png and b/Source Code/Resources/SidekickButton.png differ
index 2feabac5985961f9b2993a20d22ffbc6f15f900a..32ff6663fccda5e5a78355c46dbe03c3b2542360 100644 (file)
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 (file)
index 0000000..760ca78
Binary files /dev/null and b/Source Code/Resources/TargetIcon.png differ
index cf977f7340fd943ddb1e5ac1c73765b825172aa7..1a3f896a44e1c7104c66127a7b243e5ca52daff3 100644 (file)
Binary files a/Source Code/Resources/TimeShieldButton.png and b/Source Code/Resources/TimeShieldButton.png differ
index 9842f91f4a15defedfca604c56f230c34345b3a4..2cd82d528df942c79e713dadcf54b62ccdadd09a 100644 (file)
Binary files a/Source Code/Resources/TrackerButton.png and b/Source Code/Resources/TrackerButton.png differ
index 645e4ae92ff3c63b3c8003a4e878af2395bc6a00..f23870a74cdbc00d5ef7d7c383c59ef41f4446b7 100644 (file)
Binary files a/Source Code/Resources/TricksterVentButton.png and b/Source Code/Resources/TricksterVentButton.png differ
index 36be18467e93e54c6c34a4843b745325c85bb6f3..f2a2822ea67471f12222f9688f3270b7fabfaafb 100644 (file)
Binary files a/Source Code/Resources/VampireButton.png and b/Source Code/Resources/VampireButton.png differ
index 4d37597047f3519615b00169b0b9b9096e6dbd01..56349ad056eb78dd0d02018cc010dbe893ca7d6c 100644 (file)
@@ -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());
             }
         }
 
index 9db07c984ca22f5018ca629ae45944c3a86e1984..01a15c184959fb350001e1ceeda3202a8a590514 100644 (file)
@@ -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 <color=#FF1919FF>Impostors</color> 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<RoleInfo> 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
index 74e0d472aef374cd459009a0038fbd740ae9f7a5..4f472ed1007a75c4b835bf6429e6cb31a162bd75 100644 (file)
@@ -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<PlayerControl> formerJackals = new List<PlayerControl>();
         
@@ -889,7 +901,6 @@ namespace TheOtherRoles
         public static PlayerControl currentTarget;
         public static PlayerControl douseTarget;
         public static List<PlayerControl> dousedPlayers = new List<PlayerControl>();
-        public static Dictionary<byte, PoolablePlayer> dousedIcons = new Dictionary<byte, PoolablePlayer>();
 
         private static Sprite douseSprite;
         public static Sprite getDouseSprite() {
@@ -915,12 +926,9 @@ namespace TheOtherRoles
             douseTarget = null; 
             triggerArsonistWin = false;
             dousedPlayers = new List<PlayerControl>();
-            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<byte, PoolablePlayer>();
             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
index d9ed49784693d82c331905624b6702dc77ae46ae..5d06ee7f81e990d99916f851bade252154dde92b 100644 (file)
@@ -1,13 +1,13 @@
 <Project Sdk="Microsoft.NET.Sdk">
     <PropertyGroup>
         <TargetFramework>netstandard2.1</TargetFramework>
-        <Version>2.6.7</Version>
+        <Version>2.7.0</Version>
         <Description>TheOtherRoles</Description>
         <Authors>Eisbison</Authors>
     </PropertyGroup>
 
     <PropertyGroup>
-        <GameVersion>2021.5.10s</GameVersion>
+        <GameVersion>2021.6.15</GameVersion>
         <DefineConstants>$(DefineConstants);STEAM</DefineConstants>
         <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     </PropertyGroup>
index 57c2608edca14d40594ba70b0dfc146793cb866d..6ecb63c8e7776947d93809c8aeb893264c6813d7 100644 (file)
@@ -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();
         }
     }
 }
index cc065f685510d82131ad3b276395eadbbfb5250d..8cba74729c01b8a0783117f047421750db263513 100644 (file)
@@ -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<TMPro.TextMeshPro> hackerTexts = new List<TMPro.TextMeshPro>();
+
+        [HarmonyPatch(typeof(VitalsMinigame), nameof(VitalsMinigame.Begin))]
+        class VitalsMinigameStartPatch {
+            static void Postfix(VitalsMinigame __instance) {
+                if (Hacker.hacker != null && PlayerControl.LocalPlayer == Hacker.hacker) {
+                    hackerTexts = new List<TMPro.TextMeshPro>();
+                    foreach (VitalsPanel panel in __instance.vitals) {
+                        TMPro.TextMeshPro text = UnityEngine.Object.Instantiate(__instance.SabText, panel.transform);
+                        hackerTexts.Add(text);
+                        UnityEngine.Object.DestroyImmediate(text.GetComponent<AlphaBlink>());
+                        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<TranslationController>.Instance.GetString(Palette.ShortColorNames[(int)player.ColorId], new UnhollowerBaseLib.Il2CppReferenceArray<Il2CppSystem.Object>(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 {