]> git.deb.at Git - rhonda/theotherroles.git/commitdiff
Add version v1.5 changes
authorEisbison <eisbison.creator@gmail.com>
Sun, 14 Mar 2021 17:49:44 +0000 (18:49 +0100)
committerEisbison <eisbison.creator@gmail.com>
Sun, 14 Mar 2021 17:49:44 +0000 (18:49 +0100)
18 files changed:
README.md
Source Code/.gitignore [new file with mode: 0644]
Source Code/BonusRoles.cs
Source Code/BonusRoles.csproj
Source Code/Buttons.cs
Source Code/CredentialsPatch.cs
Source Code/CustomButton.cs
Source Code/EndGamePatch.cs
Source Code/GameHistory.cs
Source Code/IntroPatch.cs
Source Code/Main.cs
Source Code/MeetingPatch.cs
Source Code/PlayerControlPatch.cs
Source Code/RPC.cs
Source Code/Resources/SpyButton.png [new file with mode: 0644]
Source Code/RoleAssignmentPatch.cs
Source Code/UpdatePatch.cs
Source Code/UsablesPatch.cs

index ce49c1c4633b0cab46938b494b9836ebed8a5ddc..baa1cbc54814ef5f6b91787bc4fab296b2504ff0 100644 (file)
--- a/README.md
+++ b/README.md
@@ -35,12 +35,20 @@ Even more roles are coming soon :)
 # Releases :\r
 | Among Us - Version| Mod Version | Link |\r
 |----------|-------------|-----------------|\r
-| **2021.3.5s** | v1.4 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.4/TheOtherRoles.zip)\r
-| **2021.3.5s** | v1.3 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.3/TheOtherRoles.zip)\r
+| 2021.3.5s | v1.5 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.5/TheOtherRoles.zip)\r
+| 2021.3.5s | v1.4 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.4/TheOtherRoles.zip)\r
+| 2021.3.5s | v1.3 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.3/TheOtherRoles.zip)\r
 | 2020.12.19s | v1.1 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.1/TheOtherRoles.zip)\r
 | 2020.12.19s | v1.0 | [Download](https://github.com/Eisbison/TheOtherRoles/files/6097191/TheOtherRoles.zip) |\r
 \r
 \\r
+**Changes in v1.5:**\r
+- Time Master - Buff: He is not affected by his rewind anymore, which gives him more utility. Players will now be rewinded out of vents.\r
+- Child - Nerf: The child now grows up (see [child](#child)) and becomes a normal crewmate at some point. A growing child is not killable anymore. Some tasks are still not doable for the small child, we are working on that. But eventually when growing up it can do all the tasks as it's size increases.\r
+- Seer - Nerf: Added an option that sets how often the Seer mistakes the player for another.\r
+- Spy - Nerf: The Spy now only sees the additional information when he activates his "Spy mode". That should stops the spy from camping the admin table/vitals.\r
+- Other: Camouflager/Morphling cooldowns were fixed. Custom regions code was removed to enable 3rd party tools. Some minor bugfixes.\r
+\r
 **Changes in v1.4:**\r
 - Fixing a Camoflager/Morphling animation bug\r
 - Fixing a bug where the Swapper could swap votes even if they was dead\r
@@ -59,7 +67,7 @@ Even more roles are coming soon :)
 - A bug was removed where the game of the Lovers crashed if they were the last players killed by the host of the lobby.\r
 \r
 # Installation \r
-1. Download and unzip the newest [release](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.4/TheOtherRoles.zip)\r
+1. Download and unzip the newest [release](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.5/TheOtherRoles.zip)\r
 2. Find the folder of your game, for steams players you can right click in steam, on the game, a menu will appear proposing you to go to the folders.3. Make a copy of your game, it's not obligatory but advise, put it where you want.\r
 4. Drag or extract the files from the zip into your game, at the .exe level.\r
 6. Run the game (the first launch might take a while)\r
@@ -68,6 +76,10 @@ Not working? You might want to install the dependency [vc_redist](https://aka.ms
 \r
 ![Install](https://i.imgur.com/pvBAyZN.png)\r
 \r
+# Custom Servers\r
+We highly recommend you to play on custom servers rather than on the official ones. A guide on how to set up a server will be added soon. In order to make your client able to connect to a custom server we recommend using [Unify](https://github.com/moltenmods/unify). Just add\r
+the *.dll* file into your Among Us/BepInEx/plugins folder and you're good to go.\r
+\r
 # Credits & Resources\r
 [Reactor](https://github.com/NuclearPowered/Reactor) - The framework used\\r
 [BepInEx](https://github.com/BepInEx) - Used to hook game functions\\r
@@ -190,6 +202,8 @@ The Seer has the ability to find out about people's intentions. The information
 Depending on the selected option they either directly see the role of the player (appended to their name) or if the player is \r
 a good (name text changes to yellow) or a bad (name text changes to a darker color) player.\\r
 Depending on the selected option the player will get a blue flash on their screen if they are being revealed by the Seer.\r
+The Seer can make mistakes (how many depends on the selected option). If a mistake happens, the information of another player (not the target, not the Seer) gets revealed.\r
+\r
 \\r
 **NOTE**\r
 - **Good players:** Crewmates with all their special roles\\r
@@ -204,6 +218,7 @@ Depending on the selected option the player will get a blue flash on their scree
 |----------|:-------------:|:-------------:|\r
 | Seer Spawn Chance | - | -\r
 | Seer Cooldown | Cooldown for his special ability. This cooldown will **not** be reset after a meeting | -\r
+| Seer Chance Of Seeing Right | Sets the chance of how likely the Seer makes a mistake | -\r
 | Info That Seer Reveals | Description above | "Role", "Good/Bad"\r
 | Players That See When They Are Being Revealed | Selects the group of players that get notified when they are being revealed | "Everyone", "The Good", "The Bad", "Nobody"\r
 -----------------------\r
@@ -212,6 +227,8 @@ Depending on the selected option the player will get a blue flash on their scree
 ### **Team: Crewmates**\r
 The Engineer (if alive) can fix one sabotage per game from anywhere on the map.\\r
 The Engineer can use vents.\r
+Because of the vents the Engineer might not be able to start some tasks using the "Use" button,\r
+you can double click on the tasks instead.\r
 \\r
 **NOTE**\r
 - The kill button of Impostors activates if they stand next to a vent where the Engineer is. They can also kill them there. No other action (e.g. Morphling sample, Shifter shift, ...) can affect players inside vents.\r
@@ -253,16 +270,20 @@ Depending on the options the Lighter can have a bigger or smaller vision than ot
 \r
 ## Child\r
 ### **Team: Crewmates**\r
-The Child won't be harmed by anyone, since if it dies everyone besides the child will lose the game.\\r
+The Child won't be harmed by anyone until it turns 18 years old, then it becomes a regular crewmate.\\r
 The Child's character is smaller and hence visible to everyone in the game.\\r
-The child works with the crewmate and aims to play out the strength of its "invincibility".\\r
-It does **not** want to win the game as a dead child and the impostors **don't want** to deny\r
-a crewmate win by killing a child (changes will come to prevent this behaviour).\r
+The child works with the crewmates and aims to play out the strength of its "invincibility" in the early game.\\r
+If the child is voted out before it turns 18, everyone in the lobby loses.\r
+\\r
+**NOTE**\r
+- Impostors can't kill the child (the button does not work) until it turns 18\r
+- The Sheriff always suicides if he tries to kill the child\r
 \r
 ### Game Options\r
 | Name | Description |\r
 |----------|:-------------:|\r
 | Child Spawn Chance | -\r
+| Child  | Child Growing Up Duration\r
 -----------------------\r
 \r
 ## Medic\r
@@ -303,7 +324,8 @@ The Mayor leades the crewmates by having a vote that counts twice.
 \r
 ## Spy\r
 ### **Team: Crewmates**\r
-The Spy gets more information than others from the admin table and vitals. \\r
+If the Spy activates the "Spy mode", the Spy gets more information than others from the admin table and vitals for a set duration.\\r
+Otherwise he sees the same information as everyone else.\r
 **Admin table:** The spy can see the colors of the players on the table.\\r
 **Vitals**: The spy can see how long dead players have been dead for.\\r
 \\r
@@ -313,6 +335,8 @@ The Spy gets more information than others from the admin table and vitals. \
 | Name | Description |\r
 |----------|:-------------:|\r
 | Spy Spawn Chance | -\r
+| Spy Cooldown| -\r
+| Spy Duration | Sets how long the "Spy mode" remains active\r
 -----------------------\r
 \r
 \r
@@ -338,7 +362,7 @@ can only use them, if the player they exchanged roles with have not used them be
 \r
 ## Time Master\r
 ### **Team: Crewmates**\r
-The Time Master can rewind the time.\\r
+The Time Master can rewind the time. The Time Master won't be affected by the rewind.\\r
 If set in the options, players will be revived during the time rewind.\\r
 Only movement and kills are affected (not cooldowns, meetings, tasks, ...)\r
 \r
diff --git a/Source Code/.gitignore b/Source Code/.gitignore
new file mode 100644 (file)
index 0000000..8d4a6c0
--- /dev/null
@@ -0,0 +1,2 @@
+bin
+obj
\ No newline at end of file
index fe6f1d706a79f1dcd7ef66e1889eb90a9d425290..86840175af76589e9b4f7ce872092c0c433cf30a 100644 (file)
@@ -19,7 +19,7 @@ namespace BonusRoles
     [HarmonyPatch]
     public static class BonusRoles
     {
-        public static System.Random rnd = new System.Random();
+        public static System.Random rnd = new System.Random((int)DateTime.Now.Ticks);
 
         public static void clearAndReloadRoles() {
             Jester.clearAndReload();
@@ -297,11 +297,12 @@ namespace BonusRoles
     public static class Seer {
         public static PlayerControl seer;
         public static Color color = new Color(60f / 255f, 181f / 255f, 100f / 255f, 1);
-        public static List<PlayerControl> revealedPlayers = new List<PlayerControl>();
+        public static Dictionary<PlayerControl, PlayerControl> revealedPlayers = new Dictionary<PlayerControl, PlayerControl>();
 
         public static float cooldown = float.MaxValue;
         public static int kindOfInfo = 0;
         public static int playersWithNotification = 0;
+        public static float chanceOfSeeingRight = 100;
 
         public static PlayerControl currentTarget;
         
@@ -314,10 +315,11 @@ namespace BonusRoles
 
         public static void clearAndReload() {
             seer = null;
-            revealedPlayers = new List<PlayerControl>();
+            revealedPlayers = new Dictionary<PlayerControl, PlayerControl>();
             cooldown = BonusRolesPlugin.seerCooldown.GetValue();
             kindOfInfo = BonusRolesPlugin.seerKindOfInfo.GetValue();
             playersWithNotification = BonusRolesPlugin.seerPlayersWithNotification.GetValue();
+            chanceOfSeeingRight = BonusRolesPlugin.seerChanceOfSeeingRight.GetValue();
         }
     }
 
@@ -383,23 +385,54 @@ namespace BonusRoles
         private static Sprite adminTableIcon;
         public static Color color = new Color(252f / 255f, 90f / 255f, 30f / 255f, 1);
 
+        public static float cooldown = float.MaxValue;
+        public static float duration = 10f;
+
+        public static float spyTimer = 0f;
+
         public static Sprite getAdminTableIconSprite() {
             if (adminTableIcon) return adminTableIcon;
             adminTableIcon = Helpers.loadSpriteFromResources("BonusRoles.Resources.AdminTableIcon.png", 350f);
             return adminTableIcon;
         }
 
+        private static Sprite buttonSprite;
+        public static Sprite getButtonSprite() {
+            if (buttonSprite) return buttonSprite;
+            buttonSprite = Helpers.loadSpriteFromResources("BonusRoles.Resources.SpyButton.png", 100f);
+            return buttonSprite;
+        }
+
         public static void clearAndReload() {
             spy = null;
+            spyTimer = 0f;
+            cooldown = BonusRolesPlugin.spyCooldown.GetValue();
+            duration = BonusRolesPlugin.spySpyingDuration.GetValue();
         }
     }
 
     public static class Child {
         public static PlayerControl child;
         public static Color color = Color.white;
+        
+        public static float growingUpDuration = float.MaxValue;
+        public static DateTime timeOfGrowthStart = DateTime.UtcNow;
 
         public static void clearAndReload() {
             child = null;
+            growingUpDuration = BonusRolesPlugin.childGrowingUpDuration.GetValue();
+            timeOfGrowthStart = DateTime.UtcNow;
+        }
+
+        public static float growingProgress() {
+            if (timeOfGrowthStart == null) return 0f;
+
+            float timeSinceStart = (float)(DateTime.UtcNow - timeOfGrowthStart).TotalMilliseconds;
+            return Mathf.Clamp(timeSinceStart/(growingUpDuration*1000), 0f, 1f);
+        }
+
+        public static bool isGrownUp() {
+            return growingProgress() == 1f;
         }
     }
 }
\ No newline at end of file
index 3df26ef0580ded563fea443ace0c7cc0dff1ec5a..803c6752c332661384b630a391fd1506e746350c 100644 (file)
@@ -4,7 +4,7 @@
         <Version>1.0.0</Version>
         <Mappings>NuclearPowered/Mappings:0.2.0</Mappings>
 
-        <Description>BonusRoles</Description>
+        <Description>TheOtherRoles</Description>
         <Authors>Eisbison</Authors>
     </PropertyGroup>
 
@@ -40,6 +40,7 @@
         <EmbeddedResource Include="Resources\MorphButton.png"/>
         <EmbeddedResource Include="Resources\SampleButton.png"/>
         <EmbeddedResource Include="Resources\CamoButton.png"/>
+        <EmbeddedResource Include="Resources\SpyButton.png"/>
         <EmbeddedResource Include="Resources\Footprint.png"/>
         <EmbeddedResource Include="Resources\AdminTableIcon.png"/>
         <EmbeddedResource Include="Resources\SwapperCheck.png"/>
index 2fe81fdad02d045a05e3e71bc9a643307d1d7311..55d2620bc8cfca153704544128dcceaea43f43d7 100644 (file)
@@ -7,6 +7,8 @@ using UnityEngine;
 using static BonusRoles.BonusRoles;
 using Reactor.Extensions;
 using System.Collections.Generic;
+using System.Linq;
+using UnhollowerBaseLib;
 
 namespace BonusRoles
 {
@@ -22,6 +24,7 @@ namespace BonusRoles
         private static CustomButton seerRevealButton;
         private static CustomButton morphlingButton;
         private static CustomButton camouflagerButton;
+        private static CustomButton spyButton;
 
         public static void setCustomButtonCooldowns() {
             engineerRepairButton.MaxTimer = 0f;
@@ -33,6 +36,9 @@ namespace BonusRoles
             seerRevealButton.MaxTimer = Seer.cooldown;
             morphlingButton.MaxTimer = Morphling.cooldown;
             camouflagerButton.MaxTimer = Camouflager.cooldown;
+            spyButton.MaxTimer = Spy.cooldown;
+
+            spyButton.EffectDuration = Spy.duration;
         }
 
         public static void Postfix(HudManager __instance)
@@ -64,7 +70,7 @@ namespace BonusRoles
                         }
                     }
                 },
-                () => { return Engineer.engineer != null && Engineer.engineer == PlayerControl.LocalPlayer && __instance.UseButton.isActiveAndEnabled && !PlayerControl.LocalPlayer.Data.IsDead; },
+                () => { return Engineer.engineer != null && Engineer.engineer == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
                 () => {
                     bool sabotageActive = false;
                     foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks)
@@ -72,7 +78,7 @@ namespace BonusRoles
                             sabotageActive = true;
                     return sabotageActive && !Engineer.usedRepair && PlayerControl.LocalPlayer.CanMove;
                 },
-                false,
+                () => {},
                 Engineer.getButtonSprite(),
                 Vector3.zero,
                 __instance
@@ -105,9 +111,9 @@ namespace BonusRoles
                         }
                     }
                 },
-                () => { return Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer && __instance.UseButton.isActiveAndEnabled && !PlayerControl.LocalPlayer.Data.IsDead; },
+                () => { return Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
                 () => { return __instance.ReportButton.renderer.color == Palette.EnabledColor  && PlayerControl.LocalPlayer.CanMove; },
-                true,
+                () => { janitorCleanButton.Timer = janitorCleanButton.MaxTimer;},
                 Janitor.getButtonSprite(),
                 Vector3.zero,
                 __instance
@@ -138,9 +144,9 @@ namespace BonusRoles
                     sheriffKillButton.Timer = sheriffKillButton.MaxTimer; 
                     Sheriff.currentTarget = null;
                 },
-                () => { return Sheriff.sheriff != null && Sheriff.sheriff == PlayerControl.LocalPlayer && __instance.UseButton.isActiveAndEnabled && !PlayerControl.LocalPlayer.Data.IsDead; },
+                () => { return Sheriff.sheriff != null && Sheriff.sheriff == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
                 () => { return Sheriff.currentTarget && PlayerControl.LocalPlayer.CanMove; },
-                true,
+                () => { sheriffKillButton.Timer = sheriffKillButton.MaxTimer;},
                 __instance.KillButton.renderer.sprite,
                 Vector3.zero,
                 __instance
@@ -154,9 +160,9 @@ namespace BonusRoles
                     RPCProcedure.timeMasterRewindTime();
                     timeMasterRewindTimeButton.Timer = timeMasterRewindTimeButton.MaxTimer;
                 },
-                () => { return TimeMaster.timeMaster != null && TimeMaster.timeMaster == PlayerControl.LocalPlayer && __instance.UseButton.isActiveAndEnabled && !PlayerControl.LocalPlayer.Data.IsDead; },
+                () => { return TimeMaster.timeMaster != null && TimeMaster.timeMaster == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
                 () => { return PlayerControl.LocalPlayer.CanMove; },
-                true,
+                () => { timeMasterRewindTimeButton.Timer = timeMasterRewindTimeButton.MaxTimer;},
                 TimeMaster.getButtonSprite(),
                 Vector3.zero,
                 __instance
@@ -172,9 +178,9 @@ namespace BonusRoles
                     AmongUsClient.Instance.FinishRpcImmediately(writer);
                     RPCProcedure.medicSetShielded(Medic.currentTarget.PlayerId);
                 },
-                () => { return Medic.medic != null && Medic.medic == PlayerControl.LocalPlayer && __instance.UseButton.isActiveAndEnabled && !PlayerControl.LocalPlayer.Data.IsDead; },
+                () => { return Medic.medic != null && Medic.medic == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
                 () => { return !Medic.usedShield && Medic.currentTarget && PlayerControl.LocalPlayer.CanMove; },
-                false,
+                () => {},
                 Medic.getButtonSprite(),
                 Vector3.zero,
                 __instance
@@ -192,9 +198,9 @@ namespace BonusRoles
                     
                     RPCProcedure.shifterShift(Shifter.currentTarget.PlayerId);
                 },
-                () => { return Shifter.shifter != null && Shifter.shifter == PlayerControl.LocalPlayer && __instance.UseButton.isActiveAndEnabled && !PlayerControl.LocalPlayer.Data.IsDead; },
+                () => { return Shifter.shifter != null && Shifter.shifter == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
                 () => { return Shifter.currentTarget && PlayerControl.LocalPlayer.CanMove; },
-                true,
+                () => { shifterShiftButton.Timer = shifterShiftButton.MaxTimer; },
                 Shifter.getButtonSprite(),
                 Vector3.zero,
                 __instance
@@ -205,15 +211,26 @@ namespace BonusRoles
                 () => {
                     seerRevealButton.Timer = seerRevealButton.MaxTimer;
 
+                    PlayerControl targetOrMistake = Seer.currentTarget;
+                    if (rnd.Next(1, 101) > Seer.chanceOfSeeingRight) {
+                        var players = PlayerControl.AllPlayerControls.ToArray().ToList();
+                        players.RemoveAll(p => p != null && (p.PlayerId == Seer.seer.PlayerId || p.PlayerId == Seer.currentTarget.PlayerId));
+                        int index = rnd.Next(0, players.Count);
+
+                        if (players.Count != 0 && players[index] != null)
+                            targetOrMistake = players[index];
+                    }
+
                     MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SeerReveal, Hazel.SendOption.None, -1);
                     writer.Write(Seer.currentTarget.PlayerId);
+                    writer.Write(targetOrMistake.PlayerId);
                     AmongUsClient.Instance.FinishRpcImmediately(writer);
                     
-                    RPCProcedure.seerReveal(Seer.currentTarget.PlayerId);
+                    RPCProcedure.seerReveal(Seer.currentTarget.PlayerId, targetOrMistake.PlayerId);
                 },
-                () => { return Seer.seer != null && Seer.seer == PlayerControl.LocalPlayer && __instance.UseButton.isActiveAndEnabled && !PlayerControl.LocalPlayer.Data.IsDead; },
+                () => { return Seer.seer != null && Seer.seer == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
                 () => { return Seer.currentTarget && PlayerControl.LocalPlayer.CanMove; },
-                false,
+                () => {},
                 Seer.getButtonSprite(),
                 Vector3.zero,
                 __instance
@@ -235,9 +252,15 @@ namespace BonusRoles
                         morphlingButton.HasEffect = false; // Block effect on this click
                     }
                 },
-                () => { return Morphling.morphling != null && Morphling.morphling == PlayerControl.LocalPlayer && __instance.UseButton.isActiveAndEnabled && !PlayerControl.LocalPlayer.Data.IsDead; },
+                () => { return Morphling.morphling != null && Morphling.morphling == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
                 () => { return (Morphling.currentTarget || Morphling.sampledTarget) && PlayerControl.LocalPlayer.CanMove; },
-                true,
+                () => { 
+                    morphlingButton.Timer = morphlingButton.MaxTimer;
+                    morphlingButton.Sprite = Morphling.getSampleSprite();
+                    morphlingButton.isEffectActive = false;
+                    morphlingButton.killButtonManager.TimerText.Color = Palette.EnabledColor;
+                    Morphling.sampledTarget = null;
+                },
                 Morphling.getSampleSprite(),
                 new Vector3(0f, 1.3f, 0f),
                 __instance,
@@ -256,9 +279,13 @@ namespace BonusRoles
                     AmongUsClient.Instance.FinishRpcImmediately(writer);
                     RPCProcedure.camouflagerCamouflage();
                 },
-                () => { return Camouflager.camouflager != null && Camouflager.camouflager == PlayerControl.LocalPlayer && __instance.UseButton.isActiveAndEnabled && !PlayerControl.LocalPlayer.Data.IsDead; },
+                () => { return Camouflager.camouflager != null && Camouflager.camouflager == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
                 () => { return PlayerControl.LocalPlayer.CanMove; },
-                true,
+                () => {
+                    camouflagerButton.Timer = camouflagerButton.MaxTimer;
+                    camouflagerButton.isEffectActive = false;
+                    camouflagerButton.killButtonManager.TimerText.Color = Palette.EnabledColor;
+                },
                 Camouflager.getButtonSprite(),
                 new Vector3(0f, 1.3f, 0f),
                 __instance,
@@ -266,6 +293,28 @@ namespace BonusRoles
                 10f,
                 () => { camouflagerButton.Timer = camouflagerButton.MaxTimer; }
             );
+
+            // Spy button
+            spyButton = new CustomButton(
+                () => {
+                    Spy.spyTimer = Spy.duration;
+                },
+                () => { return Spy.spy != null && Spy.spy == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+                () => { return PlayerControl.LocalPlayer.CanMove; },
+                () => {
+                    spyButton.Timer = spyButton.MaxTimer;
+                    spyButton.isEffectActive = false;
+                    spyButton.killButtonManager.TimerText.Color = Palette.EnabledColor;
+                },
+                Spy.getButtonSprite(),
+                Vector3.zero,
+                __instance,
+                true,
+                0f,
+                () => {
+                    spyButton.Timer = spyButton.MaxTimer;
+                }
+            );
         }
     }
 }
\ No newline at end of file
index c2a8c0aca5f055d8d5651fa988a7a5ad5260db66..e104d9efcb25ef21be36a52c3879b9862acb3478 100644 (file)
@@ -13,9 +13,9 @@ namespace BonusRoles
         static void Postfix(VersionShower __instance) {
             string spacer = new String('\n', 15);
             if (__instance.text.Text.Contains(spacer))
-                __instance.text.Text += "\n[FFFFFFFF]- Loaded [FCCE03FF]TheOtherRoles[FFFFFFFF] v1.4\n  by [FCCE03FF]Eisbison";
+                __instance.text.Text += "\n[FFFFFFFF]- Loaded [FCCE03FF]TheOtherRoles[FFFFFFFF] v1.5\n  by [FCCE03FF]Eisbison";
             else
-                __instance.text.Text += spacer + "[FFFFFFFF]- Loaded [FCCE03FF]TheOtherRoles[FFFFFFFF] v1.4\n  by [FCCE03FF]Eisbison";
+                __instance.text.Text += spacer + "[FFFFFFFF]- Loaded [FCCE03FF]TheOtherRoles[FFFFFFFF] v1.5\n  by [FCCE03FF]Eisbison";
         }
     }
 
index e89bf316114d424192d15d0cc54c5b88e69295c4..0f3aae100f156d1f34bf12c0bdcae51dc35a9161 100644 (file)
@@ -13,25 +13,25 @@ public class CustomButton
     public Vector3 PositionOffset;
     public float MaxTimer = float.MaxValue;
     public float Timer = 0f;
-    public bool ResetTimeAfterMeeting;
     private Action OnClick;
+    private Action OnMeetingEnds;
     private Func<bool> HasButton;
     private Func<bool> CouldUse;
     private Action OnEffectEnds;
     public bool HasEffect;
-    private bool isEffectActive = false;
-    private float EffectDuration;
+    public bool isEffectActive = false;
+    public float EffectDuration;
     public Sprite Sprite;
     private HudManager hudManager;
 
-    public CustomButton(Action OnClick, Func<bool> HasButton, Func<bool> CouldUse, bool ResetTimeAfterMeeting, Sprite Sprite, Vector3 PositionOffset, HudManager hudManager, bool HasEffect, float EffectDuration, Action OnEffectEnds)
+    public CustomButton(Action OnClick, Func<bool> HasButton, Func<bool> CouldUse, Action OnMeetingEnds, Sprite Sprite, Vector3 PositionOffset, HudManager hudManager, bool HasEffect, float EffectDuration, Action OnEffectEnds)
     {
         this.hudManager = hudManager;
         this.OnClick = OnClick;
         this.HasButton = HasButton;
         this.CouldUse = CouldUse;
         this.PositionOffset = PositionOffset;
-        this.ResetTimeAfterMeeting = ResetTimeAfterMeeting;
+        this.OnMeetingEnds = OnMeetingEnds;
         this.HasEffect = HasEffect;
         this.EffectDuration = EffectDuration;
         this.OnEffectEnds = OnEffectEnds;
@@ -60,13 +60,13 @@ public class CustomButton
         setActive(false);
     }
 
-    public CustomButton(Action OnClick, Func<bool> HasButton, Func<bool> CouldUse, bool ResetTimeAfterMeeting, Sprite Sprite, Vector3 PositionOffset, HudManager hudManager)
-    : this(OnClick, HasButton, CouldUse, ResetTimeAfterMeeting, Sprite, PositionOffset, hudManager, false, 0f, () => {}) { }
-
+    public CustomButton(Action OnClick, Func<bool> HasButton, Func<bool> CouldUse, Action OnMeetingEnds, Sprite Sprite, Vector3 PositionOffset, HudManager hudManager)
+    : this(OnClick, HasButton, CouldUse, OnMeetingEnds, Sprite, PositionOffset, hudManager, false, 0f, () => {}) { }
 
     public static void HudUpdate()
     {
         buttons.RemoveAll(item => item.killButtonManager == null);
+
         if (MeetingHud.Instance || ExileController.Instance) return;
     
         for (int i = 0; i < buttons.Count; i++)
@@ -88,10 +88,8 @@ public class CustomButton
         {
             try
             {
-                if (buttons[i].ResetTimeAfterMeeting) {
-                    buttons[i].Timer = buttons[i].MaxTimer;
-                    buttons[i].Update();
-                }
+                buttons[i].OnMeetingEnds();
+                buttons[i].Update();
             }
             catch (NullReferenceException)
             {
@@ -131,7 +129,7 @@ public class CustomButton
             setActive(false);
             return;
         }
-        setActive(true);
+        setActive(hudManager.UseButton.isActiveAndEnabled);
 
         killButtonManager.renderer.sprite = Sprite;
 
index 499d028f995808a698bfdac823736feac2b954cd..0ad301783fa9cb2e485524e3d86bec91942f1015 100644 (file)
@@ -17,7 +17,7 @@ namespace BonusRoles {
         LoversTeamWin,
         LoversSoloWin,
         JesterWin,
-        ChildWin
+        ChildDied
     }
 
     static class AdditionalTempData {
@@ -58,10 +58,11 @@ namespace BonusRoles {
 
             // Child win condition (should be implemented using a proper GameOverReason in the future)
             if (Child.child != null && Child.child.Data.IsImpostor) {
-                AdditionalTempData.winCondition = WinCondition.ChildWin;
+                AdditionalTempData.winCondition = WinCondition.ChildDied;
                 TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
                 WinningPlayerData wpd = new WinningPlayerData(Child.child.Data);
-                wpd.IsImpostor = false; 
+                wpd.IsImpostor = false;
+                wpd.IsYou = false;
                 TempData.winners.Add(wpd);
             }
 
@@ -105,8 +106,8 @@ namespace BonusRoles {
             TextRenderer textRenderer = bonusText.GetComponent<TextRenderer>();
             textRenderer.Text = "";
 
-            if (AdditionalTempData.winCondition == WinCondition.ChildWin) {
-                textRenderer.Text = "Child Wins";
+            if (AdditionalTempData.winCondition == WinCondition.ChildDied) {
+                textRenderer.Text = "Child Died";
                 textRenderer.Color = Child.color;
             }
             else if (AdditionalTempData.winCondition == WinCondition.JesterWin) {
index 20c7552a508c011433899aedfa8a8641c4bc8b3d..933e19548174e724d9fa054b16c224b592037988 100644 (file)
@@ -23,10 +23,12 @@ namespace BonusRoles{
     static class GameHistory {
         public static List<Tuple<Vector3, DateTime>> localPlayerPositions = new List<Tuple<Vector3, DateTime>>();
         public static List<DeadPlayer> deadPlayers = new List<DeadPlayer>();
+        public static DateTime localVentEnterTimePoint = DateTime.MinValue;
 
         public static void clearGameHistory() {
             localPlayerPositions = new List<Tuple<Vector3, DateTime>>();
             deadPlayers = new List<DeadPlayer>();
+            localVentEnterTimePoint = DateTime.MinValue;
         }
     }
 }
\ No newline at end of file
index 653cae05d6fdefaa2db01b6c30f18b02adf5b64c..451b71aafe46695407b44124bf7f7918c3d6a328 100644 (file)
@@ -164,7 +164,7 @@ namespace BonusRoles
             {
                 __instance.__this.Title.Text = "Child";
                 __instance.__this.Title.Color = Child.color;
-                __instance.__this.ImpostorText.Text = "No one will harm you";
+                __instance.__this.ImpostorText.Text = "No one will harm you until you grow up";
                 __instance.__this.BackgroundBar.material.color = Child.color;
             }
         }
index 01e17105562e8793b98ed355e8556516605501aa..158d00afa58a638eead4e422377788f37575640f 100644 (file)
@@ -26,9 +26,9 @@ namespace BonusRoles
 
         // Role spawn chances
         public static CustomNumberOption mafiaSpawnChance = CustomOption.AddNumber("Mafia Spawn Chance", 100, 0, 100, 10);
+        public static CustomNumberOption loversSpawnChance = CustomOption.AddNumber("Lovers Spawn Chance", 100, 0, 100, 10);
         public static CustomNumberOption morphlingSpawnChance = CustomOption.AddNumber("Morphling Spawn Chance", 100, 0, 100, 10);
         public static CustomNumberOption camouflagerSpawnChance = CustomOption.AddNumber("Camouflager Spawn Chance", 100, 0, 100, 10);
-        public static CustomNumberOption loversSpawnChance = CustomOption.AddNumber("Lovers Spawn Chance", 100, 0, 100, 10);
         public static CustomNumberOption jesterSpawnChance = CustomOption.AddNumber("Jester Spawn Chance", 100, 0, 100, 10);
         public static CustomNumberOption mayorSpawnChance = CustomOption.AddNumber("Mayor Spawn Chance", 100, 0, 100, 10);
         public static CustomNumberOption engineerSpawnChance = CustomOption.AddNumber("Engineer Spawn Chance", 100, 0, 100, 10);
@@ -63,64 +63,26 @@ namespace BonusRoles
         public static CustomToggleOption medicShowAttemptToShielded = CustomOption.AddToggle("Shielded Player Sees Murder Attempt", false);
         public static CustomNumberOption shifterCooldown = CustomOption.AddNumber("Shifter Cooldown", 30f, 10f, 60f, 2.5f);
         public static CustomNumberOption seerCooldown = CustomOption.AddNumber("Seer Cooldown (No Reset After Meeting)", 15f, 30f, 180f, 15f);
+        public static CustomNumberOption seerChanceOfSeeingRight = CustomOption.AddNumber("Seer Chance Of Seeing Right", 100, 0, 100, 5);
         public static CustomStringOption seerKindOfInfo = CustomOption.AddString("Info That Seer Reveals", new string[] {"Role", "Good/Bad"});
-        public static CustomStringOption seerPlayersWithNotification = CustomOption.AddString("Players That See When They Are Being Revealed", new string[] {"Everyone", "The Good", "The Bad", "Nobody"}); 
+        public static CustomStringOption seerPlayersWithNotification = CustomOption.AddString("Players That See When They Are Being Revealed", new string[] {"Everyone", "The Good", "The Bad", "Nobody"});
+        public static CustomNumberOption spyCooldown = CustomOption.AddNumber("Spy Cooldown", 30f, 10f, 120f, 5f);
+        public static CustomNumberOption spySpyingDuration = CustomOption.AddNumber("Spy Duration", 10f, 2.5f, 60f, 2.5f);
+        public static CustomNumberOption childGrowingUpDuration = CustomOption.AddNumber("Child Growing Up Duration", 400f, 100f, 1500f, 100f);
+
+        public static ConfigEntry<bool> DebugMode { get; private set; }
+
 
-        public static ConfigEntry<string> Ip { get; set; }
-        public static ConfigEntry<ushort> Port { get; set; }
-        public static ConfigEntry<string> Name { get; private set; }
 
         public override void Load()
         {
-            Ip = Config.Bind("Custom", "Ipv4 or Hostname", "127.0.0.1");
-            Port = Config.Bind("Custom", "Port", (ushort)22023);
-            Name = Config.Bind("Custom", "Custom", "Custom");
-
-            var defaultRegions = ServerManager.DefaultRegions.ToList();
-            var ip = Ip.Value;
-            if (Uri.CheckHostName(Ip.Value).ToString() == "Dns")
-            {
-                try
-                {
-                    foreach (IPAddress address in Dns.GetHostAddresses(Ip.Value))
-                    {
-                        if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
-                        {
-                            ip = address.ToString(); break;
-                        }
-                    }
-                }
-                catch { }
-            }
-
-            var port = Port.Value;
-            defaultRegions.Insert(0, new StaticRegionInfo(
-                Name.Value, (StringNames)3000, ip, new[]
-                {
-                    new ServerInfo($"CustomServer", ip, Port.Value)
-                }).Duplicate()
-            ) ;
-            ServerManager.DefaultRegions = defaultRegions.ToArray();
+            DebugMode  = Config.Bind("Custom", "Enable Debug Mode", false);
 
             CustomOption.ShamelessPlug = false;
             Harmony.PatchAll();
         }
     }
 
-    // Patch server name
-    [HarmonyPatch(typeof(LanguageUnit),"HOGGPANBLGP")]
-    public class ServerNamePatch {
-        public static bool Prefix(StringNames LENBLADGELB, string NNLLKLDPBAI, Il2CppReferenceArray<Il2CppSystem.Object> DKBJCINDDCD, ref string __result)
-        {
-            if (LENBLADGELB == (StringNames)3000)
-            {
-                __result = BonusRolesPlugin.Name.Value;
-                return false;
-            }         
-            return true;
-        }
-    }
-
     // Deactivate bans, since I always leave my local testing game and ban myself
     [HarmonyPatch(typeof(StatsManager), nameof(StatsManager.AmBanned), MethodType.Getter)]
     public static class AmBannedPatch
@@ -130,4 +92,52 @@ namespace BonusRoles
             __result = false;
         }
     }
+
+    // Debugging tools
+    [HarmonyPatch(typeof(KeyboardJoystick), nameof(KeyboardJoystick.Update))]
+    public static class DebugManager
+    {
+        private static readonly System.Random random = new System.Random((int)DateTime.Now.Ticks);
+        private static List<PlayerControl> bots = new List<PlayerControl>();
+
+        public static void Postfix(KeyboardJoystick __instance)
+        {
+            if (!BonusRolesPlugin.DebugMode.Value) return;
+
+            // Spawn dummys
+            if (Input.GetKeyDown(KeyCode.F)) {
+                var playerControl = UnityEngine.Object.Instantiate(AmongUsClient.Instance.PlayerPrefab);
+                var i = playerControl.PlayerId = (byte) GameData.Instance.GetAvailableId();
+
+                bots.Add(playerControl);
+                GameData.Instance.AddPlayer(playerControl);
+                AmongUsClient.Instance.Spawn(playerControl, -2, SpawnFlags.None);
+                
+                playerControl.transform.position = PlayerControl.LocalPlayer.transform.position;
+                playerControl.GetComponent<DummyBehaviour>().enabled = true;
+                playerControl.NetTransform.enabled = false;
+                playerControl.SetName(RandomString(10));
+                playerControl.SetColor((byte) random.Next(Palette.PlayerColors.Length));
+                playerControl.SetHat((uint) random.Next(HatManager.Instance.AllHats.Count),playerControl.Data.ColorId);
+                playerControl.SetPet((uint) random.Next(HatManager.Instance.AllPets.Count));
+                playerControl.SetSkin((uint) random.Next(HatManager.Instance.AllSkins.Count));
+                GameData.Instance.RpcSetTasks(playerControl.PlayerId, new byte[0]);
+            }
+
+            // Terminate round
+            if(Input.GetKeyDown(KeyCode.L)) {
+                MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ForceEnd, Hazel.SendOption.None, -1);
+                AmongUsClient.Instance.FinishRpcImmediately(writer);
+                RPCProcedure.forceEnd();
+            }
+        }
+
+        public static string RandomString(int length)
+        {
+            const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+            return new string(Enumerable.Repeat(chars, length)
+                .Select(s => s[random.Next(s.Length)]).ToArray());
+        }
+    }
+
 }
index f03e6766f412f077b67c1de0acbad6a13c29ec91..aa6f565ce2928aa6a3d59bfe22dbf6dafe905746 100644 (file)
@@ -278,7 +278,7 @@ namespace BonusRoles
                 }
 
                 // Child win condition
-                if (Child.child != null && ExileController.Instance.exiled != null && ExileController.Instance.exiled.PlayerId == Child.child.PlayerId) {
+                if (Child.child != null && ExileController.Instance.exiled != null && ExileController.Instance.exiled.PlayerId == Child.child.PlayerId && !Child.isGrownUp()) {
                     MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ChildDied, Hazel.SendOption.None, -1);
                     AmongUsClient.Instance.FinishRpcImmediately(writer);
                     RPCProcedure.childDied();
index 5382fcf9341ef6e9ab7007071f1fc15877b705a8..010d59113d60beb01069b964a7a589b3869bf8a1 100644 (file)
@@ -18,7 +18,8 @@ namespace BonusRoles {
                 if (localPlayerPositions.Count > 0) {
                     // Set position
                     var next = localPlayerPositions[0];
-                    PlayerControl.LocalPlayer.transform.position = next.Item1;
+                    if (!PlayerControl.LocalPlayer.inVent)
+                        PlayerControl.LocalPlayer.transform.position = next.Item1;
                     localPlayerPositions.RemoveAt(0);
                     if (localPlayerPositions.Count > 0) localPlayerPositions.RemoveAt(0); // Skip every second position to rewinde in half the time
                 
@@ -95,7 +96,7 @@ namespace BonusRoles {
         static void seerSetTarget() {
             if (Seer.seer == null || Seer.seer != PlayerControl.LocalPlayer) return;
             Seer.currentTarget = setAnyRoleTarget();
-            if (Seer.currentTarget != null && Seer.revealedPlayers.Any(p => p.Data.PlayerId == Seer.currentTarget.Data.PlayerId)) Seer.currentTarget = null; // Remove target if already revealed
+            if (Seer.currentTarget != null && Seer.revealedPlayers.Keys.Any(p => p.Data.PlayerId == Seer.currentTarget.Data.PlayerId)) Seer.currentTarget = null; // Remove target if already revealed
 
         }
 
@@ -140,14 +141,16 @@ namespace BonusRoles {
     class PerformKillPatch
     {
         public static bool Prefix(KillButtonManager __instance) {
-            // Block imposter shielded kill
-                   if (__instance.isActiveAndEnabled && __instance.CurrentTarget && !__instance.isCoolingDown && !PlayerControl.LocalPlayer.Data.IsDead && PlayerControl.LocalPlayer.CanMove) {
-                if (Medic.shielded != null && Medic.shielded == __instance.CurrentTarget) {
-                    MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShieldedMurderAttempt, Hazel.SendOption.None, -1);
-                    AmongUsClient.Instance.FinishRpcImmediately(writer);
-                    RPCProcedure.shieldedMurderAttempt();
-                    return false;
-                }
+            // Block impostor shielded kill
+            if (Medic.shielded != null && Medic.shielded == __instance.CurrentTarget) {
+                MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShieldedMurderAttempt, Hazel.SendOption.None, -1);
+                AmongUsClient.Instance.FinishRpcImmediately(writer);
+                RPCProcedure.shieldedMurderAttempt();
+                return false;
+            }
+            // Block impostor not fully grown child kill
+            else if (Child.child != null && __instance.CurrentTarget == Child.child && !Child.isGrownUp()) {
+                return false;
             }
             return true;
         }
@@ -252,13 +255,6 @@ namespace BonusRoles {
                     RPCProcedure.loverSuicide(otherLover.PlayerId);
                 }
             }
-
-            // Child died
-            if (Child.child != null && PAIBDFDMIGK == Child.child) {
-                MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ChildDied, Hazel.SendOption.None, -1);
-                AmongUsClient.Instance.FinishRpcImmediately(writer);
-                RPCProcedure.childDied();
-            }
         }
     }
 
index ae51dad57d752435d46550adcb1acfdbb955620e..0672ceabe35c09c911a5aec36f543c5ec3705b86 100644 (file)
@@ -7,6 +7,7 @@ using System.Collections.Generic;
 using System.Collections;
 using System.Linq;
 using UnityEngine;
+using System;
 
 namespace BonusRoles
 {
@@ -179,10 +180,10 @@ namespace BonusRoles
         }
 
         public static void janitorClean(byte playerId) {
-            DeadBody[] array = Object.FindObjectsOfType<DeadBody>();
+            DeadBody[] array = UnityEngine.Object.FindObjectsOfType<DeadBody>();
             for (int i = 0; i < array.Length; i++) {
                 if (GameData.Instance.GetPlayerById(array[i].ParentId).PlayerId == playerId)
-                    Object.Destroy(array[i].gameObject);
+                    UnityEngine.Object.Destroy(array[i].gameObject);
             }
         }
 
@@ -198,7 +199,27 @@ namespace BonusRoles
         }
 
         public static void timeMasterRewindTime() {
+            if (TimeMaster.timeMaster == null) return;
+
+            PlayerControl lp = PlayerControl.LocalPlayer;
+            if (lp?.Data != null && !lp.Data.IsDead && lp.inVent) {
+                if ((float)(DateTime.UtcNow -localVentEnterTimePoint).TotalMilliseconds < 1000 * TimeMaster.rewindTime) {
+                    foreach (Vent vent in ShipStatus.Instance.AllVents) {
+                        bool canUse;
+                        bool couldUse;
+                        vent.CanUse(PlayerControl.LocalPlayer.Data, out canUse, out couldUse);
+                        if (canUse) {
+                            PlayerControl.LocalPlayer.MyPhysics.RpcExitVent(vent.Id);
+                                       vent.SetButtons(false);
+                        }
+                    }
+                }
+            }
+
+            if (PlayerControl.LocalPlayer == TimeMaster.timeMaster) return; // Time Master himself does not rewind
+
             TimeMaster.isRewinding = true;
+
             if (MapBehaviour.Instance)
                 MapBehaviour.Instance.Close();
             if (Minigame.Instance)
@@ -313,15 +334,17 @@ namespace BonusRoles
             }
         }
 
-        public static void seerReveal(byte playerId) {
+        public static void seerReveal(byte targetId, byte targetOrMistakeId) {
             if (Seer.seer == null) return;
+            
+            PlayerControl target = Helpers.playerById(targetId);
+            PlayerControl targetOrMistake = Helpers.playerById(targetOrMistakeId);
 
-            PlayerControl player = Helpers.playerById(playerId);
-            if (player != null && !Seer.revealedPlayers.Any(p => p.Data.PlayerId == playerId)) {
-                Seer.revealedPlayers.Add(player);
+            if (target != null && targetOrMistake != null && !Seer.revealedPlayers.Keys.Any(p => p.Data.PlayerId == targetId)) {
+                Seer.revealedPlayers.Add(target, targetOrMistake);
 
-                if (PlayerControl.LocalPlayer == player && HudManager.Instance?.FullScreen != null) {
-                    SeerInfo si = SeerInfo.getSeerInfoForPlayer(player);
+                if (PlayerControl.LocalPlayer == target && HudManager.Instance?.FullScreen != null) {
+                    SeerInfo si = SeerInfo.getSeerInfoForPlayer(target); // Use SeerInfo for target here, because we need the isGood of the targets role
                     bool showNotification = false;
                     if (Seer.playersWithNotification == 0 ) showNotification = true;
                     else if (Seer.playersWithNotification == 1 && si.isGood) showNotification = true;
@@ -442,7 +465,9 @@ namespace BonusRoles
                     RPCProcedure.swapperSwap(playerId1, playerId2);
                     break;
                 case (byte)CustomRPC.SeerReveal:
-                    RPCProcedure.seerReveal(HFPCBBHJIPJ.ReadByte());
+                    byte targetId = HFPCBBHJIPJ.ReadByte();
+                    byte targetOrMistakeId = HFPCBBHJIPJ.ReadByte();
+                    RPCProcedure.seerReveal(targetId, targetOrMistakeId);
                     break;
                 case (byte)CustomRPC.MorphlingMorph:
                     RPCProcedure.morphlingMorph(HFPCBBHJIPJ.ReadByte());
diff --git a/Source Code/Resources/SpyButton.png b/Source Code/Resources/SpyButton.png
new file mode 100644 (file)
index 0000000..1d5dd66
Binary files /dev/null and b/Source Code/Resources/SpyButton.png differ
index 77df16d179cf339e5b64c992358feebd1fac6a4e..2a82376b5efbe78032b5d389ef7a045f3849a2e5 100644 (file)
@@ -29,9 +29,9 @@ namespace BonusRoles
             AmongUsClient.Instance.FinishRpcImmediately(writer);
             RPCProcedure.resetVariables();
 
-            List<PlayerControl> crewmates = PlayerControl.AllPlayerControls.ToArray().ToList();
+            List<PlayerControl> crewmates = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList();
             crewmates.RemoveAll(x => x.Data.IsImpostor);
-            List<PlayerControl> impostors = PlayerControl.AllPlayerControls.ToArray().ToList();
+            List<PlayerControl> impostors = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList();
             impostors.RemoveAll(x => !x.Data.IsImpostor);
 
             // Special roles impostors can be converted to
@@ -41,12 +41,6 @@ namespace BonusRoles
                 setRoleToRandomPlayer((byte)RoleId.Mafioso, impostors);
             }
 
-            if (impostors.Count >= 1 && (rnd.Next(1, 101) <= BonusRolesPlugin.morphlingSpawnChance.GetValue()))
-                setRoleToRandomPlayer((byte)RoleId.Morphling, impostors);
-
-            if (impostors.Count >= 1 && (rnd.Next(1, 101) <= BonusRolesPlugin.camouflagerSpawnChance.GetValue()))
-                setRoleToRandomPlayer((byte)RoleId.Camouflager, impostors);
-            
             // Special roles that involve crewmates and impostors
             if (rnd.Next(1, 101) <= BonusRolesPlugin.loversSpawnChance.GetValue())
             {
@@ -59,6 +53,13 @@ namespace BonusRoles
                 }
             }
 
+            // Special roles impostors can be converted to
+            if (impostors.Count >= 1 && (rnd.Next(1, 101) <= BonusRolesPlugin.morphlingSpawnChance.GetValue()))
+                setRoleToRandomPlayer((byte)RoleId.Morphling, impostors);
+
+            if (impostors.Count >= 1 && (rnd.Next(1, 101) <= BonusRolesPlugin.camouflagerSpawnChance.GetValue()))
+                setRoleToRandomPlayer((byte)RoleId.Camouflager, impostors);
+            
             // Special roles crewmates can be converted to
             if (crewmates.Count > 0 && (rnd.Next(1, 101) <= BonusRolesPlugin.jesterSpawnChance.GetValue()))
                 setRoleToRandomPlayer((byte)RoleId.Jester, crewmates);
index 23f8274048d19994f4d7074fadf329f0db8c55f5..d5dc2e64dac08e8b36bd9886edaca221a2680f18 100644 (file)
@@ -191,22 +191,27 @@ namespace BonusRoles
             if (Seer.seer == null || Seer.seer != PlayerControl.LocalPlayer) return;
 
             // Update revealed players
-            foreach (PlayerControl p in Seer.revealedPlayers) {
+            foreach (KeyValuePair<PlayerControl, PlayerControl> entry in Seer.revealedPlayers) {
+                PlayerControl target = entry.Key;
+                PlayerControl targetOrMistake = entry.Value;
+
+                if (target == null || targetOrMistake == null) continue;
+
                 // Update color and name regarding settings and given info
-                string result = p.Data.PlayerName;
-                SeerInfo si = SeerInfo.getSeerInfoForPlayer(p);
+                string result = target.Data.PlayerName;
+                SeerInfo si = SeerInfo.getSeerInfoForPlayer(targetOrMistake);
                 if (Seer.kindOfInfo == 0)
-                    result = p.Data.PlayerName + " (" + si.roleName + ")";
+                    result = target.Data.PlayerName + " (" + si.roleName + ")";
                 else if (Seer.kindOfInfo == 1) {
                     si.color = si.isGood ? new Color(250f / 255f, 217f / 255f, 52f / 255f, 1) : new Color (51f / 255f, 61f / 255f, 54f / 255f, 1); 
                 }
 
                 // Set color and name
-                p.nameText.Color = si.color;
-                p.nameText.Text = result;
+                target.nameText.Color = si.color;
+                target.nameText.Text = result;
                 if (MeetingHud.Instance != null) {
                     foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates) {
-                        if (p.PlayerId == player.TargetPlayerId) {
+                        if (target.PlayerId == player.TargetPlayerId) {
                             player.NameText.Text = result;
                             player.NameText.Color = si.color;
                             break;
@@ -216,6 +221,10 @@ namespace BonusRoles
             }
         }
 
+        static void spyUpdate() {
+            Spy.spyTimer -= Time.deltaTime;
+        }
+
         static void camouflageAndMorphActions() {
             float oldCamouflageTimer = Camouflager.camouflageTimer;
             float oldMorphTimer = Morphling.morphTimer;
@@ -292,14 +301,33 @@ namespace BonusRoles
             }
         }
 
-        public static void updatePlayerSizes() {
+        public static void childUpdate() {
             foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
                 if (p == null) continue;
 
-                if (Child.child != null && Child.child == p)
-                    p.transform.localScale = new Vector3(0.35f, 0.35f, 1f);
-                else if (Morphling.morphling != null && Morphling.morphling == p && Morphling.morphTarget != null && Morphling.morphTarget == Child.child && Morphling.morphTimer > 0f)
-                    p.transform.localScale = new Vector3(0.35f, 0.35f, 1f);
+                if (Child.child == null) {
+                    p.transform.localScale = new Vector3(0.7f, 0.7f, 1f);
+                    return;
+                }                 
+                
+                float growingProgress = Child.growingProgress();
+                float scale = growingProgress * 0.35f + 0.35f;
+                string suffix = "";
+                if (growingProgress != 1f)
+                    suffix = " [FAD934FF](" + Mathf.FloorToInt(growingProgress * 18) + ")"; 
+
+                if (Child.child == p) {
+                    p.transform.localScale = new Vector3(scale, scale, 1f);
+                    p.nameText.Text += suffix;
+                    if (MeetingHud.Instance != null)
+                        foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates)
+                            if (player.NameText != null && p.PlayerId == player.TargetPlayerId)
+                                player.NameText.Text += suffix;
+                }
+                else if (Morphling.morphling != null && Morphling.morphling == p && Morphling.morphTarget != null && Morphling.morphTarget == Child.child && Morphling.morphTimer > 0f) {
+                    p.transform.localScale = new Vector3(scale, scale, 1f);
+                    p.nameText.Text += suffix;
+                }
                 else
                     p.transform.localScale = new Vector3(0.7f, 0.7f, 1f);
             }
@@ -326,9 +354,11 @@ namespace BonusRoles
             shifterClearTasks();
             // Seer update
             seerUpdate();
+            // Spy update();
+            spyUpdate();
 
             camouflageAndMorphActions();
-            updatePlayerSizes();
+            childUpdate();
         }
     }
 }
\ No newline at end of file
index 8dea16b6ae2247eebea4d7401c1895ca5ac9e8ee..5146a4975797f2da140278b6c5833873180d3b7c 100644 (file)
@@ -13,7 +13,7 @@ namespace BonusRoles
 {
 
     [HarmonyPatch(typeof(Vent), "CanUse")]
-    public static class VentPatch
+    public static class VentCanUsePatch
     {
         public static bool Prefix(Vent __instance, ref float __result, [HarmonyArgument(0)] GameData.PlayerInfo pc, [HarmonyArgument(1)] out bool canUse, [HarmonyArgument(2)] out bool couldUse)
         {
@@ -47,6 +47,17 @@ namespace BonusRoles
         }
     }
 
+    [HarmonyPatch(typeof(Vent), "Use")]
+    public static class VentUsePatch {
+        public static void Prefix(Vent __instance) {
+            bool flag;
+            bool flag2;
+            __instance.CanUse(PlayerControl.LocalPlayer.Data, out flag, out flag2);
+            if (flag && !PlayerControl.LocalPlayer.inVent)
+                localVentEnterTimePoint = DateTime.UtcNow;
+        }
+    }
+
     [HarmonyPatch(typeof(UseButtonManager), nameof(UseButtonManager.SetTarget))]
     class UseButtonSetTargetPatch {
         static void Postfix(UseButtonManager __instance) {
@@ -115,7 +126,7 @@ namespace BonusRoles
     class VitalsMinigamePatch {
         static void Postfix(VitalsMinigame __instance) {
             // Spy show time since death
-            if (Spy.spy == null || Spy.spy != PlayerControl.LocalPlayer) return;
+            bool showSpyInfo = Spy.spy != null && Spy.spy == PlayerControl.LocalPlayer && Spy.spyTimer > 0;
             for (int k = 0; k < __instance.vitals.Length; k++)
             {
                 VitalsPanel vitalsPanel = __instance.vitals[k];
@@ -125,7 +136,11 @@ namespace BonusRoles
                     DeadPlayer deadPlayer = deadPlayers?.Where(x => x.player?.PlayerId == playerInfo?.PlayerId)?.FirstOrDefault();
                     if (deadPlayer != null && deadPlayer.timeOfDeath != null) {
                         float timeSinceDeath = ((float)(DateTime.UtcNow - deadPlayer.timeOfDeath).TotalMilliseconds);
-                        vitalsPanel.Text.Text = Math.Round(timeSinceDeath / 1000) + "s";
+
+                        if (showSpyInfo)
+                            vitalsPanel.Text.Text = Math.Round(timeSinceDeath / 1000) + "s";
+                        else
+                            vitalsPanel.Text.Text = DestroyableSingleton<TranslationController>.Instance.GetString(Palette.ShortColorNames[(int)playerInfo.ColorId], new UnhollowerBaseLib.Il2CppReferenceArray<Il2CppSystem.Object>(0));
                     }
                 }
                }
@@ -139,9 +154,7 @@ namespace BonusRoles
         [HarmonyPatch(typeof(MapCountOverlay), nameof(MapCountOverlay.Update))]
         class MapCountOverlayUpdatePatch {
             static bool Prefix(MapCountOverlay __instance) {
-                // Spy save colors for each room on the admin panel
-                if (Spy.spy == null || Spy.spy != PlayerControl.LocalPlayer) return true;
-
+                // Save colors for the Spy
                 __instance.timer += Time.deltaTime;
                 if (__instance.timer < 0.1f)
                 {
@@ -221,10 +234,11 @@ namespace BonusRoles
 
         [HarmonyPatch(typeof(CounterArea), nameof(CounterArea.UpdateCount))]
         class CounterAreaUpdateCountPatch {
+            private static Sprite defaultIcon;
+
             static void Postfix(CounterArea __instance) {
                 // Spy display saved colors on the admin panel
-                if (Spy.spy == null || Spy.spy != PlayerControl.LocalPlayer) return;
-
+                bool showSpyInfo = Spy.spy != null && Spy.spy == PlayerControl.LocalPlayer && Spy.spyTimer > 0;
                 if (players.ContainsKey(__instance.RoomType)) {
                     List<Color> colors = players[__instance.RoomType];
 
@@ -232,9 +246,15 @@ namespace BonusRoles
                         PoolableBehavior icon = __instance.myIcons[i];
                         SpriteRenderer renderer = icon.GetComponent<SpriteRenderer>();
 
-                        if (renderer != null && colors.Count > i && Spy.getAdminTableIconSprite() != null) {
-                            renderer.sprite = Spy.getAdminTableIconSprite();
-                            renderer.color = colors[i];
+                        if (renderer != null) {
+                            if (defaultIcon == null) defaultIcon = renderer.sprite;
+                            if (showSpyInfo && colors.Count > i && Spy.getAdminTableIconSprite() != null) {
+                                renderer.sprite = Spy.getAdminTableIconSprite();
+                                renderer.color = colors[i];
+                            } else {
+                                renderer.sprite = defaultIcon;
+                                renderer.color = Color.white;
+                            }
                         } 
                     }
                 }