]> git.deb.at Git - rhonda/theotherroles.git/commitdiff
Update source code
authorEisbison <eisbison.creator@gmail.com>
Sat, 27 Mar 2021 14:57:38 +0000 (15:57 +0100)
committerEisbison <eisbison.creator@gmail.com>
Sat, 27 Mar 2021 14:57:38 +0000 (15:57 +0100)
25 files changed:
Source Code/Buttons.cs
Source Code/CredentialsPatch.cs
Source Code/CustomButton.cs
Source Code/EndGamePatch.cs
Source Code/GameOptionsPatch.cs
Source Code/Helpers.cs
Source Code/IntroPatch.cs
Source Code/Main.cs
Source Code/MeetingPatch.cs
Source Code/PlayerControlPatch.cs
Source Code/RPC.cs
Source Code/Resources/Arrow.png [new file with mode: 0644]
Source Code/Resources/Garlic.png [new file with mode: 0644]
Source Code/Resources/GarlicBackground.png [new file with mode: 0644]
Source Code/Resources/GarlicButton.png [new file with mode: 0644]
Source Code/Resources/SidekickButton.png [new file with mode: 0644]
Source Code/Resources/TrackerButton.png [new file with mode: 0644]
Source Code/Resources/VampireButton.png [new file with mode: 0644]
Source Code/RoleAssignmentPatch.cs
Source Code/RoleInfo.cs [new file with mode: 0644]
Source Code/ShipStatusPatch.cs
Source Code/TheOtherRoles.cs
Source Code/TheOtherRoles.csproj
Source Code/UpdatePatch.cs
Source Code/UsablesPatch.cs

index fc16fecf1e5ca7f143ac80e1321c540142e9c595..3f59c9bebc28b4c3ef44c403118bd7c4f5e230f6 100644 (file)
@@ -28,6 +28,9 @@ namespace TheOtherRoles
         private static CustomButton trackerButton;
         private static CustomButton vampireKillButton;
         private static CustomButton garlicButton;
+        private static CustomButton jackalKillButton;
+        private static CustomButton sidekickKillButton;
+        private static CustomButton jackalSidekickButton;
 
         public static void setCustomButtonCooldowns() {
             engineerRepairButton.MaxTimer = 0f;
@@ -40,12 +43,16 @@ namespace TheOtherRoles
             morphlingButton.MaxTimer = Morphling.cooldown;
             camouflagerButton.MaxTimer = Camouflager.cooldown;
             spyButton.MaxTimer = Spy.cooldown;
-            vampireKillButton.MaxTimer = PlayerControl.GameOptions.KillCooldown;
+            vampireKillButton.MaxTimer = Vampire.cooldown;
             trackerButton.MaxTimer = 0f;
             garlicButton.MaxTimer = 0f;
 
             spyButton.EffectDuration = Spy.duration;
             vampireKillButton.EffectDuration= Vampire.delay;
+
+            jackalKillButton.MaxTimer = Jackal.cooldown;
+            sidekickKillButton.MaxTimer = Sidekick.cooldown;
+            jackalSidekickButton.MaxTimer = Jackal.createSidekickCooldown;
         }
 
         public static void Postfix(HudManager __instance)
@@ -133,16 +140,19 @@ namespace TheOtherRoles
                         MessageWriter attemptWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShieldedMurderAttempt, Hazel.SendOption.None, -1);
                         AmongUsClient.Instance.FinishRpcImmediately(attemptWriter);
                         RPCProcedure.shieldedMurderAttempt();
-                        
                         return;    
                     }
 
                     byte targetId = 0;
-                    if (Sheriff.currentTarget.Data.IsImpostor || (Sheriff.jesterCanDieToSheriff && Jester.jester != null && Jester.jester == Sheriff.currentTarget))
+                    if (Sheriff.currentTarget.Data.IsImpostor || 
+                        Sheriff.currentTarget == Jackal.jackal || 
+                        Sheriff.currentTarget == Sidekick.sidekick || 
+                        (Sheriff.jesterCanDieToSheriff && Jester.jester != null && Jester.jester == Sheriff.currentTarget)) {
                         targetId = Sheriff.currentTarget.PlayerId;
-                    else
+                    }
+                    else {
                         targetId = PlayerControl.LocalPlayer.PlayerId;
-
+                    }
                     MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SheriffKill, Hazel.SendOption.None, -1);
                     killWriter.Write(targetId);
                     AmongUsClient.Instance.FinishRpcImmediately(killWriter);
@@ -361,11 +371,11 @@ namespace TheOtherRoles
                 },
                 () => { return Vampire.vampire != null && Vampire.vampire == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
                 () => {
-                    if (Vampire.targetNearGarlic)
+                    if (Vampire.targetNearGarlic && Vampire.canKillNearGarlics)
                         vampireKillButton.killButtonManager.renderer.sprite = __instance.KillButton.renderer.sprite;
                     else
                         vampireKillButton.killButtonManager.renderer.sprite = Vampire.getButtonSprite();
-                    return Vampire.currentTarget != null && PlayerControl.LocalPlayer.CanMove;
+                    return Vampire.currentTarget != null && PlayerControl.LocalPlayer.CanMove && (!Vampire.targetNearGarlic || Vampire.canKillNearGarlics);
                 },
                 () => {
                     vampireKillButton.Timer = vampireKillButton.MaxTimer;
@@ -403,6 +413,64 @@ namespace TheOtherRoles
                 __instance,
                 true
             );
+
+            
+            // Jackal Sidekick Button
+            jackalSidekickButton = new CustomButton(
+                () => {
+                    MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.JackalCreatesSidekick, Hazel.SendOption.None, -1);
+                    writer.Write(Jackal.currentTarget.PlayerId);
+                    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; },
+                () => { jackalSidekickButton.Timer = jackalSidekickButton.MaxTimer;},
+                Jackal.getSidekickButtonSprite(),
+                new Vector3(-1.3f, 1.3f, 0f),
+                __instance
+            );
+
+            // Jackal Kill
+            jackalKillButton = new CustomButton(
+                () => {
+                    if (!Helpers.handleMurderAttempt(Jackal.currentTarget)) return;
+                    byte targetId = Jackal.currentTarget.PlayerId;
+                    MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.JackalKill, Hazel.SendOption.None, -1);
+                    killWriter.Write(targetId);
+                    AmongUsClient.Instance.FinishRpcImmediately(killWriter);
+                    RPCProcedure.jackalKill(targetId);
+                    jackalKillButton.Timer = jackalKillButton.MaxTimer; 
+                    Jackal.currentTarget = null;
+                },
+                () => { return Jackal.jackal != null && Jackal.jackal == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+                () => { return Jackal.currentTarget && PlayerControl.LocalPlayer.CanMove; },
+                () => { jackalKillButton.Timer = jackalKillButton.MaxTimer;},
+                __instance.KillButton.renderer.sprite,
+                new Vector3(-1.3f, 0, 0),
+                __instance
+            );
+            
+            // Sidekick Kill
+            sidekickKillButton = new CustomButton(
+                () => {
+                    if (!Helpers.handleMurderAttempt(Sidekick.currentTarget)) return;
+                    byte targetId = Sidekick.currentTarget.PlayerId;
+                    MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SidekickKill, Hazel.SendOption.None, -1);
+                    killWriter.Write(targetId);
+                    AmongUsClient.Instance.FinishRpcImmediately(killWriter);
+                    RPCProcedure.sidekickKill(targetId);
+
+                    sidekickKillButton.Timer = sidekickKillButton.MaxTimer; 
+                    Sidekick.currentTarget = null;
+                },
+                () => { return Sidekick.canKill && Sidekick.sidekick != null && Sidekick.sidekick == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+                () => { return Sidekick.currentTarget && PlayerControl.LocalPlayer.CanMove; },
+                () => { sidekickKillButton.Timer = sidekickKillButton.MaxTimer;},
+                __instance.KillButton.renderer.sprite,
+                new Vector3(-1.3f, 0, 0),
+                __instance
+            );
         }
     }
 }
\ No newline at end of file
index 9b4394facc8b3add278172e289314b68358929ab..28edb415af2aa2f8071a2b83915acfd01ed80ab7 100644 (file)
@@ -12,10 +12,11 @@ namespace TheOtherRoles
     {
         static void Postfix(VersionShower __instance) {
             string spacer = new String('\n', 15);
+            string text = "[FCCE03FF]TheOtherRoles[] v1.8.1:\n- Modded by [FCCE03FF]Eisbison[] and [FFEB91FF]Thunderstorm584[]\n- Balanced with [FFEB91FF]Dhalucard";
             if (__instance.text.Text.Contains(spacer))
-                __instance.text.Text += "\n[FFFFFFFF]- Loaded [FCCE03FF]TheOtherRoles[FFFFFFFF] v1.7\n  by [FCCE03FF]Eisbison";
+                __instance.text.Text += "\n" + text;
             else
-                __instance.text.Text += spacer + "[FFFFFFFF]- Loaded [FCCE03FF]TheOtherRoles[FFFFFFFF] v1.7\n  by [FCCE03FF]Eisbison";
+                __instance.text.Text += spacer + text;
         }
     }
 
@@ -25,7 +26,7 @@ namespace TheOtherRoles
         static void Postfix(VersionShower __instance)
         {
             __instance.text.Text += "\n[FCCE03FF]TheOtherRoles[FFFFFFFF]";
-            __instance.text.Text += "\nModded by [FCCE03FF]Eisbison";
+            __instance.text.Text += "\nModded by [FCCE03FF]Eisbison[FFFFFFFF]";
         }
     }
 }
index 7f1b7c04dfb69b96e88f502daa5abaa4dc7c2fbe..65f9af82b7cfc07b21e4be7a46d11696501ebbfd 100644 (file)
@@ -149,7 +149,10 @@ public class CustomButton
         }
 
         if (Timer >= 0) {
-            Timer -= Time.deltaTime;
+            if (HasEffect && isEffectActive)
+                Timer -= Time.deltaTime;
+            else if (!PlayerControl.LocalPlayer.inVent)
+                Timer -= Time.deltaTime;
         }
         
         if (Timer <= 0 && HasEffect && isEffectActive) {
index 11184766d9983b5c778526eeab432176a2197537..4e84313790f3ee85a4bb0cfc8942655af9fd7dea 100644 (file)
@@ -12,13 +12,19 @@ using System;
 using System.Text;
 
 namespace TheOtherRoles {
+    enum CustomGameOverReason {
+        LoversWin = 10,
+        TeamJackalWin = 11
+    }
+
     enum WinCondition {
         Default,
         LoversTeamWin,
         LoversSoloWin,
         JesterWin,
         BountyHunterWin,
-        JesterAndBountyHunterWin
+        JesterAndBountyHunterWin,
+        JackalWin
     }
 
     static class AdditionalTempData {
@@ -36,6 +42,12 @@ namespace TheOtherRoles {
 
     [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameEnd))]
     public class OnGameEndPatch {
+        private static GameOverReason gameOverReason;
+        public static void Prefix(AmongUsClient __instance, ref GameOverReason OFLKLGMHBEL, bool JFFPAKGPNJA) {
+            gameOverReason = OFLKLGMHBEL;
+            if ((int)OFLKLGMHBEL >= 10) OFLKLGMHBEL = GameOverReason.ImpostorByKill;
+        }
+
         public static void Postfix(AmongUsClient __instance, GameOverReason OFLKLGMHBEL, bool JFFPAKGPNJA) {
             AdditionalTempData.clear();
 
@@ -55,6 +67,23 @@ namespace TheOtherRoles {
                 
                 if (jesterWinner != null) TempData.winners.Remove(jesterWinner);
             }
+            // Remove Jackal and Sidekick from winners (on Jackal win he will be added again, see below)
+            if (Jackal.jackal != null || Sidekick.sidekick != null) {
+                List<WinningPlayerData> winnersToRemove = new List<WinningPlayerData>();
+                foreach (WinningPlayerData winner in TempData.winners) {
+                    if (winner.Name == Jackal.jackal?.Data?.PlayerName) winnersToRemove.Add(winner);
+                    if (winner.Name == Sidekick.sidekick?.Data?.PlayerName) winnersToRemove.Add(winner);
+                    foreach(var player in Jackal.formerJackals) {
+                        if (winner.Name == player.Data.PlayerName) {
+                            winnersToRemove.Add(winner);
+                        }
+                    }
+                }
+                
+                foreach (var winner in winnersToRemove) {
+                    TempData.winners.Remove(winner);
+                }
+            }
 
             // Jester and Bounty Hunter win condition (should be implemented using a proper GameOverReason in the future)
             bool jesterWin = Jester.jester != null && Jester.jester.Data.IsImpostor;
@@ -79,22 +108,51 @@ namespace TheOtherRoles {
             }
 
             // Lovers win conditions (should be implemented using a proper GameOverReason in the future)
-            else if (Lovers.existingAndAlive()) {
+            else if (Lovers.existingAndAlive() && gameOverReason == (GameOverReason)CustomGameOverReason.LoversWin) {
                 AdditionalTempData.localIsLover = (PlayerControl.LocalPlayer == Lovers.lover1 || PlayerControl.LocalPlayer == Lovers.lover2);
                 // Double win for lovers, crewmates also win
-                if (TempData.DidHumansWin(OFLKLGMHBEL)) {
+                if (!Lovers.existingWithImpLover()) {
                     AdditionalTempData.winCondition = WinCondition.LoversTeamWin;
+                    TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
+                    foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
+                        if (p == null) continue;
+                        if (p == Lovers.lover1 || p == Lovers.lover2)
+                            TempData.winners.Add(new WinningPlayerData(p.Data));
+                        else if (p != Shifter.shifter && p != Jester.jester && p != Jackal.jackal && p != Sidekick.sidekick && !p.Data.IsImpostor)
+                            TempData.winners.Add(new WinningPlayerData(p.Data));
+                    }
                 }
                 // Lovers solo win
-                else if (Lovers.existingWithImpLover()){
+                else {
                     AdditionalTempData.winCondition = WinCondition.LoversSoloWin;
                     TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
                     TempData.winners.Add(new WinningPlayerData(Lovers.lover1.Data));
                     TempData.winners.Add(new WinningPlayerData(Lovers.lover2.Data));
                 }
             }
+            
+            // Jackal win condition (should be implemented using a proper GameOverReason in the future)
+            else if (gameOverReason == (GameOverReason)CustomGameOverReason.TeamJackalWin && (Jackal.jackal != null && !Jackal.jackal.Data.IsDead || Sidekick.sidekick != null && !Sidekick.sidekick.Data.IsDead)) {
+                // Jackal wins if nobody except jackal is alive
+                AdditionalTempData.winCondition = WinCondition.JackalWin;
+                TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
+                WinningPlayerData wpd = new WinningPlayerData(Jackal.jackal.Data);
+                wpd.IsImpostor = false; 
+                TempData.winners.Add(wpd);
+                // If there is a sidekick. The sidekick also wins
+                if (Sidekick.sidekick != null) {
+                    WinningPlayerData wpdSidekick = new WinningPlayerData(Sidekick.sidekick.Data);
+                    wpdSidekick.IsImpostor = false; 
+                    TempData.winners.Add(wpdSidekick);
+                }
+                foreach(var player in Jackal.formerJackals) {
+                    WinningPlayerData wpdFormerJackal = new WinningPlayerData(player.Data);
+                    wpdFormerJackal.IsImpostor = false; 
+                    TempData.winners.Add(wpdFormerJackal);
+                }
+            }
 
-            // Reset Bonus Roles Settings
+            // Reset Role Settings
             clearAndReloadRoles();
             clearGameHistory();
         }
@@ -128,11 +186,16 @@ namespace TheOtherRoles {
                 textRenderer.Text = "Lovers And Crewmates Win";
                 textRenderer.Color = Lovers.color;
                 __instance.BackgroundBar.material.SetColor("_Color", Lovers.color);
-            } else if (AdditionalTempData.winCondition == WinCondition.LoversSoloWin) {
+            } 
+            else if (AdditionalTempData.winCondition == WinCondition.LoversSoloWin) {
                 textRenderer.Text = "Lovers Win";
                 textRenderer.Color = Lovers.color;
                 __instance.BackgroundBar.material.SetColor("_Color", Lovers.color);
             }
+            else if (AdditionalTempData.winCondition == WinCondition.JackalWin) {
+                textRenderer.Text = "Team Jackal Wins";
+                textRenderer.Color = Jackal.color;
+            }
             
             AdditionalTempData.clear();
         }
@@ -140,144 +203,216 @@ namespace TheOtherRoles {
 
     [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.CheckEndCriteria))] 
     class CheckEndCriteriaPatch{
-        private static void ReviveEveryone() {
-            for (int i = 0; i < GameData.Instance.PlayerCount; i++)
-                GameData.Instance.AllPlayers[i].Object.Revive();
-            DeadBody[] array = UnityEngine.Object.FindObjectsOfType<DeadBody>();
-            for (int i = 0; i < array.Length; i++) UnityEngine.Object.Destroy(array[i].gameObject);
-        }
 
-        private static void EndGameForSabotage(ShipStatus __instance)
-        {
-            if (!DestroyableSingleton<TutorialManager>.InstanceExists)
-            {
-                __instance.enabled = false;
-                ShipStatus.RpcEndGame(GameOverReason.ImpostorBySabotage, false);
-                return;
-            }
-            DestroyableSingleton<HudManager>.Instance.ShowPopUp(DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameOverSabotage, new Il2CppReferenceArray<Il2CppSystem.Object>(0)));
+        public static bool Prefix(ShipStatus __instance) {
+            if (!GameData.Instance) return false;
+            var statistics = new PlayerStatistics(__instance);
+            if(CheckAndEndGameForSabotageWin(__instance)) return false;
+            if(CheckAndEndGameForTaskWin(__instance)) return false;
+            if(CheckAndEndGameForLoverWin(__instance, statistics)) return false;
+            if(CheckAndEndGameForJackalWin(__instance, statistics)) return false;
+            if(CheckAndEndGameForImpostorWin(__instance, statistics)) return false;
+            if(CheckAndEndGameForCrewmateWin(__instance, statistics)) return false;
+            return false;
         }
 
-        public static bool Prefix(ShipStatus __instance) {
-            if (!GameData.Instance)
-            {
-                return false;
-            }
+        private static bool CheckAndEndGameForSabotageWin(ShipStatus __instance) {
             ISystemType systemType = __instance.Systems.ContainsKey(SystemTypes.LifeSupp) ? __instance.Systems[SystemTypes.LifeSupp] : null;
-            if (systemType != null)
-            {
+            if (systemType != null) {
                 LifeSuppSystemType lifeSuppSystemType = systemType.TryCast<LifeSuppSystemType>();
-                if (lifeSuppSystemType.Countdown < 0f)
-                {
+                if (lifeSuppSystemType.Countdown < 0f) {
                     EndGameForSabotage(__instance);
                     lifeSuppSystemType.Countdown = 10000f;
+                    return true;
                 }
             }
             ISystemType systemType2 = __instance.Systems.ContainsKey(SystemTypes.Reactor) ? __instance.Systems[SystemTypes.Reactor] : null;
-            if (systemType2 == null) systemType2 = __instance.Systems.ContainsKey(SystemTypes.Laboratory) ? __instance.Systems[SystemTypes.Laboratory] : null;
-            if (systemType2 != null)
-            {
+            if (systemType2 == null) {
+                systemType2 = __instance.Systems.ContainsKey(SystemTypes.Laboratory) ? __instance.Systems[SystemTypes.Laboratory] : null;
+            }
+            if (systemType2 != null) {
                 ReactorSystemType reactorSystemType = systemType2.TryCast<ReactorSystemType>();
-                if (reactorSystemType.Countdown < 0f)
-                {
+                if (reactorSystemType.Countdown < 0f) {
                     EndGameForSabotage(__instance);
                     reactorSystemType.Countdown = 10000f;
+                    return true;
                 }
             }
-            int num = 0;
-            int num2 = 0;
-            int num3 = 0;
-            for (int i = 0; i < GameData.Instance.PlayerCount; i++)
+            return false;
+        }
+
+        private static bool CheckAndEndGameForTaskWin(ShipStatus __instance) {
+            bool localCompletedAllTasks = true;
+            foreach (PlayerTask t in PlayerControl.LocalPlayer.myTasks) {
+                localCompletedAllTasks = localCompletedAllTasks && t.IsComplete;
+            }
+
+            if (!DestroyableSingleton<TutorialManager>.InstanceExists)
             {
-                GameData.PlayerInfo playerInfo = GameData.Instance.AllPlayers[i];
-                if (!playerInfo.Disconnected)
+                if (GameData.Instance.TotalTasks <= GameData.Instance.CompletedTasks)
                 {
-                    if (playerInfo.IsImpostor)
-                    {
-                        num3++;
-                    }
-                    if (!playerInfo.IsDead)
-                    {
-                        if (playerInfo.IsImpostor)
-                        {
-                            num2++;
-                        }
-                        else
-                        {
-                            num++;
-                        }
-                    }
+                    __instance.enabled = false;
+                    ShipStatus.RpcEndGame(GameOverReason.HumansByTask, false);
+                    return true;
                 }
             }
-            if (num2 <= 0 && (!DestroyableSingleton<TutorialManager>.InstanceExists || num3 > 0))
+            else if (localCompletedAllTasks)
             {
+                DestroyableSingleton<HudManager>.Instance.ShowPopUp(DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameOverTaskWin, new Il2CppReferenceArray<Il2CppSystem.Object>(0)));
+                __instance.Begin();
+            }
+            return false;
+        }
+
+        private static bool CheckAndEndGameForLoverWin(ShipStatus __instance, PlayerStatistics statistics) {
+            if (statistics.TeamLoversAlive == 2 && statistics.TotalAlive <= 3) {
                 if (!DestroyableSingleton<TutorialManager>.InstanceExists)
                 {
                     __instance.enabled = false;
-                    ShipStatus.RpcEndGame(GameOverReason.HumansByVote, false);
-                    return false;
+                    ShipStatus.RpcEndGame((GameOverReason)CustomGameOverReason.LoversWin, false); // should be implemented using a proper GameOverReason in the future
+                    return true;
                 }
                 DestroyableSingleton<HudManager>.Instance.ShowPopUp(DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameOverImpostorDead, new Il2CppReferenceArray<Il2CppSystem.Object>(0)));
                 ReviveEveryone();
-                return false;
+                return true;
             }
-            else
-            {
-                if (num > num2)
-                {
-                    bool localCompletedAllTasks = true;
-                    foreach (PlayerTask t in PlayerControl.LocalPlayer.myTasks) {
-                        localCompletedAllTasks = localCompletedAllTasks && t.IsComplete;
-                    }
+            return false;
+        }
 
-                    if (!DestroyableSingleton<TutorialManager>.InstanceExists)
-                    {
-                        if (GameData.Instance.TotalTasks <= GameData.Instance.CompletedTasks)
-                        {
-                            __instance.enabled = false;
-                            ShipStatus.RpcEndGame(GameOverReason.HumansByTask, false);
-                            return false;
-                        }
-                    }
-                    else if (localCompletedAllTasks)
-                    {
-                        DestroyableSingleton<HudManager>.Instance.ShowPopUp(DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameOverTaskWin, new Il2CppReferenceArray<Il2CppSystem.Object>(0)));
-                        __instance.Begin();
-                    }
-                    if (num + num2 == 3 && Lovers.existingAndAlive()) { // 3 players with 2 lovers is always a lover win (either shared with crewmates or solo for lovers, marked as impostor win)
-                        __instance.enabled = false;
-                        ShipStatus.RpcEndGame(Lovers.existingWithImpLover() ? GameOverReason.ImpostorByKill : GameOverReason.HumansByVote, false); // should be implemented using a proper GameOverReason in the future
-                        return false;
-                    }
-                    return false;
-                }
-                if (num == num2 && Lovers.existingAndAlive() && Lovers.existingWithImpLover()) { // 3 vs 3 or 2 vs 2 is not win if both lovers are alive and one is an impostor
-                    return false;
-                }
+        private static bool CheckAndEndGameForJackalWin(ShipStatus __instance, PlayerStatistics statistics) {
+            if (statistics.TeamJackalAlive >= statistics.TotalAlive - statistics.TeamJackalAlive && statistics.TeamImpostorsAlive == 0 && !(statistics.TeamJackalHasAliveLover)) {
                 if (!DestroyableSingleton<TutorialManager>.InstanceExists)
                 {
+                    __instance.enabled = false;
+                    ShipStatus.RpcEndGame((GameOverReason)CustomGameOverReason.TeamJackalWin, false);
+                    return true;
+                }
+                DestroyableSingleton<HudManager>.Instance.ShowPopUp(DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameOverImpostorKills, new Il2CppReferenceArray<Il2CppSystem.Object>(0)));
+                ReviveEveryone();
+                return true;
+            }
+            return false;
+        }
+
+        private static bool CheckAndEndGameForImpostorWin(ShipStatus __instance, PlayerStatistics statistics) {
+            if (statistics.TeamImpostorsAlive >= statistics.TotalAlive - statistics.TeamImpostorsAlive && statistics.TeamJackalAlive == 0 && !(statistics.TeamImpostorHasAliveLover)) {
+                if (!DestroyableSingleton<TutorialManager>.InstanceExists) {
                     __instance.enabled = false;
                     GameOverReason endReason;
-                    switch (TempData.LastDeathReason)
-                    {
-                    case DeathReason.Exile:
-                        endReason = GameOverReason.ImpostorByVote;
-                        break;
-                    case DeathReason.Kill:
-                        endReason = GameOverReason.ImpostorByKill;
-                        break;
-                    default:
-                        endReason = GameOverReason.ImpostorByVote;
-                        break;
+                    switch (TempData.LastDeathReason) {
+                        case DeathReason.Exile:
+                            endReason = GameOverReason.ImpostorByVote;
+                            break;
+                        case DeathReason.Kill:
+                            endReason = GameOverReason.ImpostorByKill;
+                            break;
+                        default:
+                            endReason = GameOverReason.ImpostorByVote;
+                            break;
                     }
                     ShipStatus.RpcEndGame(endReason, false);
-                    return false;
+                    return true;
                 }
                 DestroyableSingleton<HudManager>.Instance.ShowPopUp(DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameOverImpostorKills, new Il2CppReferenceArray<Il2CppSystem.Object>(0)));
-                
                 ReviveEveryone();
-                return false;
+                return true;
+            }
+            return false;
+        }
+
+        private static bool CheckAndEndGameForCrewmateWin(ShipStatus __instance, PlayerStatistics statistics) {
+            if (statistics.TeamImpostorsAlive == 0 && statistics.TeamJackalAlive == 0) {
+                if (!DestroyableSingleton<TutorialManager>.InstanceExists) {
+                    __instance.enabled = false;
+                    ShipStatus.RpcEndGame(GameOverReason.HumansByVote, false);
+                    return true;
+                }
+                DestroyableSingleton<HudManager>.Instance.ShowPopUp(DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameOverImpostorDead, new Il2CppReferenceArray<Il2CppSystem.Object>(0)));
+                ReviveEveryone();
+                return true;
             }
+            return false;
+        }
+
+        private static void ReviveEveryone() {
+            for (int i = 0; i < GameData.Instance.PlayerCount; i++)
+                GameData.Instance.AllPlayers[i].Object.Revive();
+            DeadBody[] array = UnityEngine.Object.FindObjectsOfType<DeadBody>();
+            for (int i = 0; i < array.Length; i++) UnityEngine.Object.Destroy(array[i].gameObject);
+        }
+
+        private static void EndGameForSabotage(ShipStatus __instance)
+        {
+            if (!DestroyableSingleton<TutorialManager>.InstanceExists)
+            {
+                __instance.enabled = false;
+                ShipStatus.RpcEndGame(GameOverReason.ImpostorBySabotage, false);
+                return;
+            }
+            DestroyableSingleton<HudManager>.Instance.ShowPopUp(DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameOverSabotage, new Il2CppReferenceArray<Il2CppSystem.Object>(0)));
+            ReviveEveryone();
+        }
+
+    }
+
+    internal class PlayerStatistics {
+        public int TeamImpostorsAlive {get;set;}
+        public int TeamJackalAlive {get;set;}
+        public int TeamLoversAlive {get;set;}
+        public int TotalAlive {get;set;}
+        public bool TeamImpostorHasAliveLover {get;set;}
+        public bool TeamJackalHasAliveLover {get;set;}
+
+        public PlayerStatistics(ShipStatus __instance) {
+            GetPlayerCounts();
+        }
+
+        private bool isLover(GameData.PlayerInfo p) {
+            return (Lovers.lover1 != null && Lovers.lover1.PlayerId == p.PlayerId) || (Lovers.lover2 != null && Lovers.lover2.PlayerId == p.PlayerId);
+        }
+
+        private void GetPlayerCounts() {
+            int numJackalAlive = 0;
+            int numImpostorsAlive = 0;
+            int numLoversAlive = 0;
+            int numTotalAlive = 0;
+            bool impLover = false;
+            bool jackalLover = false;
+
+            for (int i = 0; i < GameData.Instance.PlayerCount; i++)
+            {
+                GameData.PlayerInfo playerInfo = GameData.Instance.AllPlayers[i];
+                if (!playerInfo.Disconnected)
+                {
+                    if (!playerInfo.IsDead)
+                    {
+                        numTotalAlive++;
+
+                        bool lover = isLover(playerInfo);
+                        if (lover) numLoversAlive++;
+
+                        if (playerInfo.IsImpostor) {
+                            numImpostorsAlive++;
+                            if (lover) impLover = true;
+                        }
+                        if (Jackal.jackal != null && Jackal.jackal.PlayerId == playerInfo.PlayerId) {
+                            numJackalAlive++;
+                            if (lover) jackalLover = true;
+                        }
+                        if (Sidekick.sidekick != null && Sidekick.sidekick.PlayerId == playerInfo.PlayerId) {
+                            numJackalAlive++;
+                            if (lover) jackalLover = true;
+                        }
+                    }
+                }
+            }
+
+            TeamJackalAlive = numJackalAlive;
+            TeamImpostorsAlive = numImpostorsAlive;
+            TeamLoversAlive = numLoversAlive;
+            TotalAlive = numTotalAlive;
+            TeamImpostorHasAliveLover = impLover;
+            TeamJackalHasAliveLover = jackalLover;
         }
     }
 }
\ No newline at end of file
index 046762bdff617c1a5a390f1f3f023c89f5933fd8..1e8832070ffff9e7ca67d0ff71d0ed94dd2a3268 100644 (file)
@@ -50,7 +50,7 @@ namespace TheOtherRoles
             var hudString = stringBuilder.ToString();
 
             int defaultSettingsLines = 19;
-            int roleSettingsLines = 19 + 22;
+            int roleSettingsLines = 19 + 23;
             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 counter = TheOtherRolesPlugin.optionsPage;
@@ -71,7 +71,7 @@ namespace TheOtherRoles
             } else if (counter == 2) {
                 hudString = hudString.Substring(end2 + 1);
             }
-            hudString += "\n Press tab for more...";
+            hudString += "\n Press tab for more...\n\n\n";
             __result = hudString;
         }
     }
index 590473ddfe2ccd094cba755f1904ea46c47d769e..0e9e96d5a2b4ad68058cd924dcf9c7b0c859b2dc 100644 (file)
@@ -128,6 +128,25 @@ namespace TheOtherRoles {
             return true;
         }
 
+        public static void removeTasksFromPlayer(PlayerControl player, bool removeImportantTextTasks = false) {
+            if (player == null) return;
+            var toRemove = new List<PlayerTask>();
+            foreach (PlayerTask task in player.myTasks) {
+                if (!removeImportantTextTasks && task.gameObject.GetComponent<ImportantTextTask>() != null)
+                    continue;
+                if (task.TaskType != TaskTypes.FixComms && 
+                    task.TaskType != TaskTypes.FixLights && 
+                    task.TaskType != TaskTypes.ResetReactor && 
+                    task.TaskType != TaskTypes.ResetSeismic && 
+                    task.TaskType != TaskTypes.RestoreOxy) {
+                    toRemove.Add(task);
+                }
+            }   
+            foreach (PlayerTask task in toRemove) {
+                player.RemoveTask(task);
+            }
+        }
+
         public static IEnumerator Slide2D(Transform target, Vector2 source, Vector2 dest, float duration = 0.75f)
         {
             Vector3 temp = default(Vector3);
@@ -145,145 +164,5 @@ namespace TheOtherRoles {
             target.localPosition = temp;
             yield break;
         }
-
-        public static string colorToHex(Color c) {
-            return string.Format("[{0:X2}{1:X2}{2:X2}{3:X2}]", (int)c.r, (int)c.g, (int)c.b, (int)c.a);
-        }
-    }
-
-    public class SeerInfo {
-        public Color color;
-        public string roleName;
-        public bool isGood;
-
-        public static SeerInfo getSeerInfoForPlayer(PlayerControl p) {
-            string r = "";
-            bool g = true;
-            Color c = Color.white;
-
-            if (Jester.jester != null && p == Jester.jester) {
-                r = "Jester";
-                c = Jester.color;
-                g = false;
-            }
-            else if (Mayor.mayor != null && p == Mayor.mayor) {
-                r = "Mayor";   
-                c = Mayor.color;
-            }
-            else if (Engineer.engineer != null && p == Engineer.engineer) {
-                r = "Engineer";   
-                c = Engineer.color;
-            }
-            else if (Sheriff.sheriff != null && p == Sheriff.sheriff) {
-                r = "Sheriff";   
-                c = Sheriff.color;
-            }
-            else if (Lighter.lighter != null && p == Lighter.lighter) {
-                r = "Lighter";   
-                c = Lighter.color;
-            }
-            else if (Godfather.godfather != null && p == Godfather.godfather) {
-                r = "Godfather";   
-                c = Godfather.color;
-                g = false;
-            }
-            else if (Mafioso.mafioso != null && p == Mafioso.mafioso) {
-                r = "Mafioso";   
-                c = Mafioso.color;
-                g = false;
-            }
-            else if (Janitor.janitor != null && p == Janitor.janitor) {
-                r = "Janitor";
-                c = Janitor.color;
-                g = false;
-            }
-            else if (Morphling.morphling != null && p == Morphling.morphling) {
-                r = "Morphling";
-                c = Morphling.color;
-                g = false;
-            }
-            else if (Camouflager.camouflager != null && p == Camouflager.camouflager) {
-                r = "Camouflager";
-                c = Camouflager.color;
-                g = false;
-            }
-            else if (Vampire.vampire != null && p == Vampire.vampire) {
-                r = "Vampire";
-                c = Vampire.color;
-                g = false;
-            }
-            else if (Detective.detective != null && p == Detective.detective) {
-                r = "Detective";
-                c = Detective.color;
-            }
-            else if (TimeMaster.timeMaster != null && p == TimeMaster.timeMaster) {
-                r = "Time Master";
-                c = TimeMaster.color;
-            }
-            else if (Medic.medic != null && p == Medic.medic) {
-                r = "Medic";
-                c = Medic.color;
-            }
-            else if (Shifter.shifter != null && p == Shifter.shifter) {
-                r = "Shifter";
-                c = Shifter.color;
-                g = false;
-            }
-            else if (Swapper.swapper != null && p == Swapper.swapper) {
-                r = "Swapper";
-                c = Swapper.color;
-            }
-            else if (Lovers.lover1 != null && p == Lovers.lover1) {
-                r = Lovers.lover1.Data.IsImpostor ? "ImpLover" : "Lover";
-                c = Lovers.lover1.Data.IsImpostor ? Palette.ImpostorRed : Lovers.color;
-                g = !Lovers.lover1.Data.IsImpostor;
-            }
-            else if (Lovers.lover2 != null && p == Lovers.lover2) {
-                r = Lovers.lover2.Data.IsImpostor ? "ImpLover" : "Lover";
-                c = Lovers.lover2.Data.IsImpostor ? Palette.ImpostorRed : Lovers.color;
-                g = !Lovers.lover2.Data.IsImpostor;
-            }
-            else if (Seer.seer != null && p == Seer.seer) { 
-                r = "Seer";
-                c = Seer.color;
-            }
-            else if (Spy.spy != null && p == Spy.spy) { 
-                r = "Spy";
-                c = Spy.color;
-            }
-            else if (Child.child != null && p == Child.child) { 
-                r = "Child";
-                c = Child.color;
-            }
-            else if (BountyHunter.bountyHunter != null && p == BountyHunter.bountyHunter) {
-                r = "Bounty Hunter";
-                c = BountyHunter.color;
-                g = false;
-            }
-            else if (Tracker.tracker != null && p == Tracker.tracker) {
-                r = "Tracker";
-                c = Tracker.color;
-            }
-            else if (Snitch.snitch != null && p == Snitch.snitch) {
-                r = "Snitch";
-                c = Snitch.color;
-            }
-            else if (p.Data.IsImpostor) { // Just Impostor
-                r = "Impostor";
-                c = Palette.ImpostorRed;
-                g = false;
-            }
-            else if (!p.Data.IsImpostor) { // Just Crewmate
-                r = "Crewmate";
-                c = Color.white;
-            }
-
-            SeerInfo result = new SeerInfo();
-            result.roleName = r;
-            result.color = c;
-            result.isGood = g;
-
-            return result;
-        }
     }
 }
\ No newline at end of file
index 42495f39205d67a7b71c410d1abcee97a8e33073..0418bebe29816f379a8222b6dcc3a1a8875df99d 100644 (file)
@@ -21,131 +21,19 @@ namespace TheOtherRoles
                 var shifterTeam = new Il2CppSystem.Collections.Generic.List<PlayerControl>();
                 shifterTeam.Add(PlayerControl.LocalPlayer);
                 __instance.yourTeam = shifterTeam;
+            } else if (PlayerControl.LocalPlayer == Jackal.jackal) {
+                var jackalTeam = new Il2CppSystem.Collections.Generic.List<PlayerControl>();
+                jackalTeam.Add(PlayerControl.LocalPlayer);
+                __instance.yourTeam = jackalTeam;
             }
         }
 
         // Intro display role
         static void Postfix(IntroCutscene.CoBegin__d __instance)
         {
-            if (PlayerControl.LocalPlayer == Jester.jester)
-            {
-                __instance.__this.Title.Text = "Jester";
-                __instance.__this.Title.Color = Jester.color;
-                __instance.__this.ImpostorText.Text = "Get voted off of the ship to win";
-                __instance.__this.BackgroundBar.material.color = Jester.color;
-            }
-            else if (PlayerControl.LocalPlayer == Mayor.mayor)
-            {
-                __instance.__this.Title.Text = "Mayor";
-                __instance.__this.Title.Color = Mayor.color;
-                __instance.__this.ImpostorText.Text = "Your vote counts twice";
-                __instance.__this.BackgroundBar.material.color = Mayor.color;   
-            }
-            else if (PlayerControl.LocalPlayer == Engineer.engineer)
-            {
-                __instance.__this.Title.Text = "Engineer";
-                __instance.__this.Title.Color = Engineer.color;
-                __instance.__this.ImpostorText.Text = "Maintain important systems on the ship";
-                __instance.__this.BackgroundBar.material.color = Engineer.color;
-            }
-            else if (PlayerControl.LocalPlayer == Godfather.godfather)
-            {
-                __instance.__this.ImpostorText.gameObject.SetActive(true);
-                __instance.__this.Title.Text = "Godfather";
-                __instance.__this.Title.Color = Godfather.color;
-                __instance.__this.ImpostorText.Text = "Kill all crewmates";
-                __instance.__this.BackgroundBar.material.color = Godfather.color;
-            }
-            else if (PlayerControl.LocalPlayer == Mafioso.mafioso)
-            {
-                __instance.__this.ImpostorText.gameObject.SetActive(true);
-                __instance.__this.Title.Text = "Mafioso";
-                __instance.__this.Title.Color = Mafioso.color;
-                __instance.__this.ImpostorText.Text = "Work with the [FF1919FF]Mafia[FFFFFFFF] to kill the crewmates";
-                __instance.__this.BackgroundBar.material.color = Mafioso.color;
-            }
-            else if (PlayerControl.LocalPlayer == Janitor.janitor)
-            {
-                __instance.__this.ImpostorText.gameObject.SetActive(true);
-                __instance.__this.Title.Text = "Janitor";
-                __instance.__this.Title.Color = Janitor.color;
-                __instance.__this.ImpostorText.Text = "Work with the [FF1919FF]Mafia[FFFFFFFF] by hiding dead bodies";
-                __instance.__this.BackgroundBar.material.color = Janitor.color;
-            }
-            else if (PlayerControl.LocalPlayer == Morphling.morphling)
-            {
-                __instance.__this.ImpostorText.gameObject.SetActive(true);
-                __instance.__this.Title.Text = "Morphling";
-                __instance.__this.Title.Color = Morphling.color;
-                __instance.__this.ImpostorText.Text = "Change your look to not get caught";
-                __instance.__this.BackgroundBar.material.color = Morphling.color;
-            }
-            else if (PlayerControl.LocalPlayer == Camouflager.camouflager)
-            {
-                __instance.__this.ImpostorText.gameObject.SetActive(true);
-                __instance.__this.Title.Text = "Camouflager";
-                __instance.__this.Title.Color = Camouflager.color;
-                __instance.__this.ImpostorText.Text = "Camouflage and kill the crewmates";
-                __instance.__this.BackgroundBar.material.color = Camouflager.color;
-            }
-            else if (PlayerControl.LocalPlayer == Vampire.vampire)
-            {
-                __instance.__this.ImpostorText.gameObject.SetActive(true);
-                __instance.__this.Title.Text = "Vampire";
-                __instance.__this.Title.Color = Vampire.color;
-                __instance.__this.ImpostorText.Text = "Kill the crewmates with your bites";
-                __instance.__this.BackgroundBar.material.color = Vampire.color;
-            }
-            else if (PlayerControl.LocalPlayer == Sheriff.sheriff)
-            {
-                __instance.__this.Title.Text = "Sheriff";
-                __instance.__this.Title.Color = Sheriff.color;
-                __instance.__this.ImpostorText.Text = "Shoot the [FF1919FF]Impostors";
-                __instance.__this.BackgroundBar.material.color = Sheriff.color;
-            }
-            else if (PlayerControl.LocalPlayer == Lighter.lighter)
-            {
-                __instance.__this.Title.Text = "Lighter";
-                __instance.__this.Title.Color = Lighter.color;
-                __instance.__this.ImpostorText.Text = "Your light never goes out";
-                __instance.__this.BackgroundBar.material.color = Lighter.color;
-            }
-            else if (PlayerControl.LocalPlayer == Detective.detective)
-            {
-                __instance.__this.Title.Text = "Detective";
-                __instance.__this.Title.Color = Detective.color;
-                __instance.__this.ImpostorText.Text = "Find the [FF1919FF]Impostors[FFFFFFFF] by examining footprints";
-                __instance.__this.BackgroundBar.material.color = Detective.color;
-            }
-            else if (PlayerControl.LocalPlayer == TimeMaster.timeMaster)
-            {
-                __instance.__this.Title.Text = "Time Master";
-                __instance.__this.Title.Color = TimeMaster.color;
-                __instance.__this.ImpostorText.Text = "Rewind time to find the [FF1919FF]Impostors";
-                __instance.__this.BackgroundBar.material.color = TimeMaster.color;
-            }
-            else if (PlayerControl.LocalPlayer == Medic.medic)
-            {
-                __instance.__this.Title.Text = "Medic";
-                __instance.__this.Title.Color = Medic.color;
-                __instance.__this.ImpostorText.Text = "Create a shield to protect a person";
-                __instance.__this.BackgroundBar.material.color = Medic.color;
-            }
-            else if (PlayerControl.LocalPlayer == Shifter.shifter)
-            {
-                __instance.__this.Title.Text = "Shifter";
-                __instance.__this.Title.Color = Shifter.color;
-                __instance.__this.ImpostorText.Text = "Shift your role before the game ends";
-                __instance.__this.BackgroundBar.material.color = Shifter.color;
-            }
-            else if (PlayerControl.LocalPlayer == Swapper.swapper)
-            {
-                __instance.__this.Title.Text = "Swapper";
-                __instance.__this.Title.Color = Swapper.color;
-                __instance.__this.ImpostorText.Text = "Swap votes to exile the [FF1919FF]Impostors";
-                __instance.__this.BackgroundBar.material.color = Swapper.color;
-            }
-            else if (PlayerControl.LocalPlayer == Lovers.lover1 || PlayerControl.LocalPlayer == Lovers.lover2)
+            RoleInfo roleInfo = RoleInfo.getRoleInfoForPlayer(PlayerControl.LocalPlayer);
+
+            if (PlayerControl.LocalPlayer == Lovers.lover1 || PlayerControl.LocalPlayer == Lovers.lover2)
             {
                 PlayerControl otherLover = PlayerControl.LocalPlayer == Lovers.lover1 ? Lovers.lover2 : Lovers.lover1;
                 __instance.__this.Title.Text = PlayerControl.LocalPlayer.Data.IsImpostor ? "[FF1919FF]Imp[FC03BEFF]Lover" : "Lover";
@@ -154,27 +42,6 @@ namespace TheOtherRoles
                 __instance.__this.ImpostorText.gameObject.SetActive(true);
                 __instance.__this.BackgroundBar.material.color = Lovers.color;
             }
-            else if (PlayerControl.LocalPlayer == Seer.seer)
-            {
-                __instance.__this.Title.Text = "Seer";
-                __instance.__this.Title.Color = Seer.color;
-                __instance.__this.ImpostorText.Text = "Reveal the intentions of everyone on the ship";
-                __instance.__this.BackgroundBar.material.color = Seer.color;
-            }
-            else if (PlayerControl.LocalPlayer == Spy.spy)
-            {
-                __instance.__this.Title.Text = "Spy";
-                __instance.__this.Title.Color = Spy.color;
-                __instance.__this.ImpostorText.Text = "Spy on everyone to find the [FF1919FF]Impostors";
-                __instance.__this.BackgroundBar.material.color = Spy.color;
-            }
-            else if (PlayerControl.LocalPlayer == Child.child)
-            {
-                __instance.__this.Title.Text = "Child";
-                __instance.__this.Title.Color = Child.color;
-                __instance.__this.ImpostorText.Text = "No one will harm you until you grow up";
-                __instance.__this.BackgroundBar.material.color = Child.color;
-            }
             else if (PlayerControl.LocalPlayer == BountyHunter.bountyHunter)
             {
                 __instance.__this.Title.Text = "Bounty Hunter";
@@ -182,19 +49,13 @@ namespace TheOtherRoles
                 __instance.__this.ImpostorText.Text = "Hunt [ED653BFF]" + BountyHunter.target?.Data?.PlayerName + "[FFFFFFFF] down";
                 __instance.__this.BackgroundBar.material.color = BountyHunter.color;
             }
-            else if (PlayerControl.LocalPlayer == Tracker.tracker)
-            {
-                __instance.__this.Title.Text = "Tracker";
-                __instance.__this.Title.Color = Tracker.color;
-                __instance.__this.ImpostorText.Text = "Track the [FF1919FF]Impostors[FFFFFFFF] down";
-                __instance.__this.BackgroundBar.material.color = Tracker.color;
-            }
-            else if (PlayerControl.LocalPlayer == Snitch.snitch)
-            {
-                __instance.__this.Title.Text = "Snitch";
-                __instance.__this.Title.Color = Snitch.color;
-                __instance.__this.ImpostorText.Text = "Finish your tasks to find the [FF1919FF]Impostors[FFFFFFFF]";
-                __instance.__this.BackgroundBar.material.color = Snitch.color;
+            else if (roleInfo.name == "Crewmate" || roleInfo.name == "Impostor") {}
+            else {
+                __instance.__this.Title.Text = roleInfo.name;
+                __instance.__this.Title.Color = roleInfo.color;
+                __instance.__this.ImpostorText.gameObject.SetActive(true);
+                __instance.__this.ImpostorText.Text = roleInfo.introDescription;
+                __instance.__this.BackgroundBar.material.color = roleInfo.color;
             }
         }
     }
index f1b189c3dd489105e72848282be8630e58f654ba..06e5e9cb8afe76a0cd0dcae5967f6e1788c24f56 100644 (file)
@@ -24,13 +24,17 @@ namespace TheOtherRoles
     {
         public const string Id = "me.eisbison.theotherroles";
         public Harmony Harmony { get; } = new Harmony(Id);
+        public static TheOtherRolesPlugin Instance { get { return PluginSingleton<TheOtherRolesPlugin>.Instance; } }
+        internal static BepInEx.Logging.ManualLogSource Logger { get { return Instance.Log; } }
 
         // Roles
         public static int num = 0;
         public static string[] rates = new string[]{"0%", "10%", "20%", "30%", "40%", "50%", "60%", "70%", "80%", "90%", "100%"};
+        public static string[] crewmateRoleCaps = new string[]{"0", "0-1", "1", "1-2", "2", "2-3", "3", "3-4", "4", "4-5", "5", "5-6", "6", "6-7", "7", "7-8", "8", "8-9", "9", "9-10", "10"};
+        public static string[] impostorRoleCaps = new string[]{"0", "0-1", "1", "1-2", "2", "2-3", "3"};
 
-        public static CustomNumberOption crewmateRolesCount = CustomOption.AddNumber(num++.ToString(), "[CCCC00FF]Number Of Crewmate/Neutral Roles", 10, 0, 10, 0.5f);
-        public static CustomNumberOption impostorRolesCount = CustomOption.AddNumber(num++.ToString(), "[CCCC00FF]Number Of Impostor Roles", 3, 0, 3, 0.5f);
+        public static CustomStringOption crewmateRolesCount = CustomOption.AddString(num++.ToString(), "[CCCC00FF]Number Of Crewmate/Neutral Roles", crewmateRoleCaps);
+        public static CustomStringOption impostorRolesCount = CustomOption.AddString(num++.ToString(), "[CCCC00FF]Number Of Impostor Roles", impostorRoleCaps);
 
         public static CustomStringOption mafiaSpawnRate = CustomOption.AddString(num++.ToString(), cs(Janitor.color) + "Mafia", rates);
         public static CustomStringOption morphlingSpawnRate = CustomOption.AddString(num++.ToString(), cs(Morphling.color) + "Morphling", rates);
@@ -55,13 +59,17 @@ namespace TheOtherRoles
         // public static CustomStringOption bountyHunterSpawnRate = CustomOption.AddString(num++.ToString(), cs(BountyHunter.color) + "Bounty Hunter", rates);
         public static CustomStringOption trackerSpawnRate = CustomOption.AddString(num++.ToString(), cs(Tracker.color) + "Tracker", rates);
         public static CustomStringOption snitchSpawnRate = CustomOption.AddString(num++.ToString(), cs(Snitch.color) + "Snitch", rates);
+        public static CustomStringOption jackalSpawnRate = CustomOption.AddString(num++.ToString(), cs(Jackal.color) + "Jackal", rates);
 
         // Role settings
         public static CustomNumberOption janitorCooldown = CustomOption.AddNumber(num++.ToString(), "Janitor Cooldown", 30f, 10f, 60f, 2.5f);
         public static CustomNumberOption morphlingCooldown = CustomOption.AddNumber(num++.ToString(), "Morphling Cooldown", 30f, 10f, 60f, 2.5f);
         public static CustomNumberOption camouflagerCooldown = CustomOption.AddNumber(num++.ToString(), "Camouflager Cooldown", 30f, 10f, 60f, 2.5f);
-        public static CustomNumberOption vampireKillDelay = CustomOption.AddNumber(num++.ToString(), "Vampire Kill Delay", 10f, 2.5f, 30f, 2.5f);
+        public static CustomNumberOption vampireKillDelay = CustomOption.AddNumber(num++.ToString(), "Vampire Kill Delay", 10f, 1f, 20f, 1f);
+        public static CustomNumberOption vampireCooldown = CustomOption.AddNumber(num++.ToString(), "Vampire Cooldown", 30f, 10f, 60f, 2.5f);
+        public static CustomToggleOption vampireCanKillNearGarlics = CustomOption.AddToggle(num++.ToString(), "Vampire Can Kill Near Garlics", true);
 
+        public static CustomNumberOption loversImpLoverRate = CustomOption.AddNumber(num++.ToString(), "Chance That One Lover Is Impostor", 30f, 0f, 100f, 10f);
         public static CustomToggleOption loversBothDie = CustomOption.AddToggle(num++.ToString(), "Both Lovers Die", true);
         public static CustomNumberOption shifterCooldown = CustomOption.AddNumber(num++.ToString(), "Shifter Cooldown", 30f, 10f, 60f, 2.5f);
 
@@ -89,6 +97,17 @@ namespace TheOtherRoles
         public static CustomNumberOption trackerUpdateIntervall = CustomOption.AddNumber(num++.ToString(), "Tracker Update Intervall", 5f, 2.5f, 30f, 2.5f);
         public static CustomNumberOption snitchLeftTasksForImpostors = CustomOption.AddNumber(num++.ToString(), "Task Count Where Impostors See Snitch", 1f, 0f, 5f, 1f);
 
+        public static CustomNumberOption jackalKillCooldown = CustomOption.AddNumber(num++.ToString(), "Jackal/Sidekick Kill Cooldown", 30f, 10f, 60f, 2.5f);
+        public static CustomNumberOption jackalCreateSidekickCooldown = CustomOption.AddNumber(num++.ToString(), "Jackal Create Sidekick Cooldown", 30f, 10f, 60f, 2.5f);
+        public static CustomToggleOption jackalCanUseVents = CustomOption.AddToggle(num++.ToString(), "Jackal can use vents", true);
+        public static CustomToggleOption jackalCanCreateSidekick = CustomOption.AddToggle(num++.ToString(), "Jackal can create a sidekick", false);
+        public static CustomToggleOption sidekickPromotesToJackal = CustomOption.AddToggle(num++.ToString(), "Sidekick gets promoted to Jackal on Jackal death", false);
+        public static CustomToggleOption sidekickCanKill = CustomOption.AddToggle(num++.ToString(), "Sidekick can kill", false);
+        public static CustomToggleOption sidekickCanUseVents = CustomOption.AddToggle(num++.ToString(), "Sidekick can use vents", true);
+        public static CustomToggleOption jackalPromotedFromSidekickCanCreateSidekick = CustomOption.AddToggle(num++.ToString(), "Jackals promoted from Sidekick can create a Sidekick", true);
+        public static CustomToggleOption jackalCanCreateSidekickFromImpostor = CustomOption.AddToggle(num++.ToString(), "Jackals can make an Impostor to his Sidekick", true);
+
+
         public static List<CustomOption> options = new List<CustomOption>();
         public static int optionsPage = 1;
 
@@ -138,12 +157,16 @@ namespace TheOtherRoles
                 // bountyHunterSpawnRate,
                 trackerSpawnRate,
                 snitchSpawnRate,
+                jackalSpawnRate,
 
                 janitorCooldown,
                 morphlingCooldown,
                 camouflagerCooldown,
                 vampireKillDelay,
+                vampireCooldown,
+                vampireCanKillNearGarlics,
 
+                loversImpLoverRate,
                 loversBothDie,
                 shifterCooldown,
 
@@ -169,7 +192,17 @@ namespace TheOtherRoles
                 childGrowingUpDuration,
                 // bountyHunterNotifyBounty,
                 trackerUpdateIntervall,
-                snitchLeftTasksForImpostors
+                snitchLeftTasksForImpostors,
+
+                jackalKillCooldown,
+                jackalCreateSidekickCooldown,
+                jackalCanUseVents,
+                jackalCanCreateSidekick,
+                sidekickPromotesToJackal,
+                sidekickCanKill,
+                sidekickCanUseVents,
+                jackalPromotedFromSidekickCanCreateSidekick,
+                jackalCanCreateSidekickFromImpostor
             };
         }
     }
index 152c01c54c3026a29e683dc267b8d146bd7ce77f..52eb0d0052ef675a0d2e64b0e360515de5dbbbe3 100644 (file)
@@ -158,13 +158,16 @@ namespace TheOtherRoles
                 Swapper.playerId2 = Byte.MaxValue;
 
                 // Lovers save next to be exiled, because RPC of ending game comes before RPC of exiled
-                Lovers.notAckedExiledIsLover = ((Lovers.lover1 != null && Lovers.lover1.PlayerId == IHDMFDEEDEL.PlayerId) || (Lovers.lover2 != null && Lovers.lover2.PlayerId == IHDMFDEEDEL.PlayerId));
+                Lovers.notAckedExiledIsLover = false;
+                if (IHDMFDEEDEL != null)
+                    Lovers.notAckedExiledIsLover = ((Lovers.lover1 != null && Lovers.lover1.PlayerId == IHDMFDEEDEL.PlayerId) || (Lovers.lover2 != null && Lovers.lover2.PlayerId == IHDMFDEEDEL.PlayerId));
             }
         }
 
 
         static void onClick(int i, MeetingHud __instance)
         {
+            if (Swapper.swapper == null || PlayerControl.LocalPlayer != Swapper.swapper || Swapper.swapper.Data.IsDead) return; 
             if (__instance.state == MeetingHud.VoteStates.Results) return;
             if (__instance.playerStates[i].isDead) return;
 
@@ -260,6 +263,7 @@ namespace TheOtherRoles
 
     [HarmonyPatch(typeof(ExileController), "Begin")]
     class ExileBeginPatch {
+
         public static void Prefix(ref GameData.PlayerInfo IHDMFDEEDEL, bool DCHFIBODGIL) {
             // Prevent growing Child exile
             if (Child.child != null && IHDMFDEEDEL != null && IHDMFDEEDEL.PlayerId == Child.child.PlayerId && !Child.isGrownUp()) {
@@ -339,6 +343,10 @@ namespace TheOtherRoles
                         __result = ExileController.Instance.exiled.PlayerName + " was The Tracker.";
                     else if(Snitch.snitch != null && ExileController.Instance.exiled.Object.PlayerId == Snitch.snitch.PlayerId)
                         __result = ExileController.Instance.exiled.PlayerName + " was The Snitch.";
+                    else if(Jackal.jackal != null && ExileController.Instance.exiled.Object.PlayerId == Jackal.jackal.PlayerId)
+                        __result = ExileController.Instance.exiled.PlayerName + " was The Jackal.";
+                    else if(Sidekick.sidekick != null && ExileController.Instance.exiled.Object.PlayerId == Sidekick.sidekick.PlayerId)
+                        __result = ExileController.Instance.exiled.PlayerName + " was The Sidekick.";
                     else
                         __result = ExileController.Instance.exiled.PlayerName + " was not The Impostor.";
                 }
@@ -359,7 +367,7 @@ namespace TheOtherRoles
                     else if(Lovers.lover2 != null && ExileController.Instance.exiled.Object.PlayerId == Lovers.lover2.PlayerId)
                         __result = ExileController.Instance.exiled.PlayerName + " was The ImpLover.";
                     else if(Vampire.vampire != null && ExileController.Instance.exiled.Object.PlayerId == Vampire.vampire.PlayerId)
-                        __result = ExileController.Instance.exiled.PlayerName + " was The Vamipre.";
+                        __result = ExileController.Instance.exiled.PlayerName + " was The Vampire.";
                 }
 
                 // Hide number of remaining impostors on Jester win
index ba587533e26ebd258ee9c2e3de5e63a807870685..4eeae4a55c3bbe4a7960708a2c9fe52b5b513aff 100644 (file)
@@ -44,7 +44,7 @@ namespace TheOtherRoles {
             }
         }
 
-        static PlayerControl setTarget(bool onlyCrewmates = false, bool targetPlayersInVents = false) {
+        static PlayerControl setTarget(bool onlyCrewmates = false, bool targetPlayersInVents = false, List<PlayerControl> untargetablePlayers = null) {
             PlayerControl result = null;
             float num = GameOptionsData.KillDistances[Mathf.Clamp(PlayerControl.GameOptions.KillDistance, 0, 2)];
             if (!ShipStatus.Instance) return result;
@@ -57,6 +57,11 @@ namespace TheOtherRoles {
                 if (!playerInfo.Disconnected && playerInfo.PlayerId != PlayerControl.LocalPlayer.PlayerId && !playerInfo.IsDead && (!onlyCrewmates || !playerInfo.IsImpostor))
                 {
                     PlayerControl @object = playerInfo.Object;
+                    if(untargetablePlayers != null && untargetablePlayers.Any(x => x == @object)) {
+                        // if that player is not targetable: skip check
+                        continue;
+                    }
+
                     if (@object && (!@object.inVent || targetPlayersInVents))
                     {
                         Vector2 vector = @object.GetTruePosition() - truePosition;
@@ -133,6 +138,25 @@ namespace TheOtherRoles {
             Vampire.currentTarget = target;
         }
 
+        static void jackalSetTarget() {
+            if (Jackal.jackal == null || Jackal.jackal != PlayerControl.LocalPlayer) return;
+            var untargetablePlayers = new List<PlayerControl>();
+            if(Jackal.canCreateSidekickFromImpostor) {
+                // Only exclude sidekick from beeing targeted if the jackal can create sidekicks from impostors
+                if(Sidekick.sidekick != null) untargetablePlayers.Add(Sidekick.sidekick);
+            }
+            if(Child.child != null && !Child.isGrownUp()) untargetablePlayers.Add(Child.child); // Exclude Jackal from targeting the Child unless it has grown up
+            Jackal.currentTarget = setTarget(untargetablePlayers : untargetablePlayers);
+        }
+
+        static void sidekickSetTarget() {
+            if (Sidekick.sidekick == null || Sidekick.sidekick != PlayerControl.LocalPlayer) return;
+            var untargetablePlayers = new List<PlayerControl>();
+            if(Jackal.jackal != null) untargetablePlayers.Add(Jackal.jackal);
+            if(Child.child != null && !Child.isGrownUp()) untargetablePlayers.Add(Child.child); // Exclude Sidekick from targeting the Child unless it has grown up
+            Sidekick.currentTarget = setTarget(untargetablePlayers : untargetablePlayers);
+        }
+
         static void engineerUpdate() {
             if (PlayerControl.LocalPlayer.Data.IsImpostor && Engineer.engineer != null) {
                 foreach (Vent vent in ShipStatus.Instance.AllVents) {
@@ -208,6 +232,10 @@ namespace TheOtherRoles {
                 engineerUpdate();
                 // Tracker
                 trackerUpdate();
+                // Jackal
+                jackalSetTarget();
+                // Sidekick
+                sidekickSetTarget();
             } 
         }
     }
@@ -295,12 +323,15 @@ namespace TheOtherRoles {
     public static class MurderPlayerPatch
     {
         public static bool resetToCrewmate = false;
+        public static bool resetToDead = false;
 
         public static void Prefix(PlayerControl __instance, PlayerControl PAIBDFDMIGK)
         {
             // Allow everyone to murder players
             resetToCrewmate = !__instance.Data.IsImpostor;
+            resetToDead = __instance.Data.IsDead;
             __instance.Data.IsImpostor = true;
+            __instance.Data.IsDead = false;
         }
 
         public static void Postfix(PlayerControl __instance, PlayerControl PAIBDFDMIGK)
@@ -311,6 +342,7 @@ namespace TheOtherRoles {
 
             // Reset killer to crewmate if resetToCrewmate
             if (resetToCrewmate) __instance.Data.IsImpostor = false;
+            if (resetToDead) __instance.Data.IsDead = true;
 
             // Lover suicide trigger on murder
             if ((Lovers.lover1 != null && PAIBDFDMIGK == Lovers.lover1) || (Lovers.lover2 != null && PAIBDFDMIGK == Lovers.lover2)) {
@@ -322,10 +354,23 @@ namespace TheOtherRoles {
                     RPCProcedure.loverSuicide(otherLover.PlayerId);
                 }
             }
+            
+            // Sidekick promotion trigger on murder
+            if (Sidekick.promotesToJackal && Sidekick.sidekick != null && !Sidekick.sidekick.Data.IsDead && PAIBDFDMIGK == Jackal.jackal) {
+                MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SidekickPromotes, Hazel.SendOption.None, -1);
+                AmongUsClient.Instance.FinishRpcImmediately(writer);
+                RPCProcedure.sidekickPromotes();
+            }
         }
     }
 
-
+    [HarmonyPatch(typeof(KillAnimation),nameof(KillAnimation.CoPerformKill))]
+    class Test {
+        public static void Prefix(KillAnimation __instance, ref PlayerControl CPKODPCJPOO, ref PlayerControl PAIBDFDMIGK) {
+            if (Vampire.vampire != null && Vampire.vampire == CPKODPCJPOO && Vampire.bitten != null && Vampire.bitten == PAIBDFDMIGK)
+                CPKODPCJPOO = PAIBDFDMIGK;
+        }
+    }
 
     [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Exiled))]
     public static class ExilePlayerPatch
@@ -339,13 +384,49 @@ namespace TheOtherRoles {
             // Lover suicide trigger on exile
             if ((Lovers.lover1 != null && __instance == Lovers.lover1) || (Lovers.lover2 != null && __instance == Lovers.lover2)) {
                 PlayerControl otherLover = __instance == Lovers.lover1 ? Lovers.lover2 : Lovers.lover1;
-                if (PlayerControl.LocalPlayer == __instance && otherLover != null && !otherLover.Data.IsDead && Lovers.bothDie) { // Only the dead lover sends the rpc
-                    MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.LoverSuicide, Hazel.SendOption.None, -1);
-                    writer.Write(otherLover.PlayerId);
-                    AmongUsClient.Instance.FinishRpcImmediately(writer);
-                    RPCProcedure.loverSuicide(otherLover.PlayerId);
+                if (otherLover != null && !otherLover.Data.IsDead && Lovers.bothDie)
+                    otherLover.Exiled();
+            }
+            
+            // Sidekick promotion trigger on exile
+            if (Sidekick.promotesToJackal && Sidekick.sidekick != null && !Sidekick.sidekick.Data.IsDead && __instance == Jackal.jackal) {
+                MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SidekickPromotes, Hazel.SendOption.None, -1);
+                AmongUsClient.Instance.FinishRpcImmediately(writer);
+                RPCProcedure.sidekickPromotes();
+            }
+        }
+    }
+
+    [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetTasks))]
+    public static class Role
+    {
+        public static void Postfix(PlayerControl __instance)
+        {
+            if (PlayerControl.LocalPlayer == null) return;
+
+            // Remove default ImportantTextTasks
+            var toRemove = new List<PlayerTask>();
+            foreach (PlayerTask t in __instance.myTasks) {
+                if (t.gameObject.GetComponent<ImportantTextTask>() != null) {
+                    toRemove.Add(t);
                 }
+            }   
+            foreach (PlayerTask t in toRemove)
+                __instance.RemoveTask(t);
+
+            // Add description
+            RoleInfo roleInfo = RoleInfo.getRoleInfoForPlayer(__instance);        
+            var task = new GameObject("RoleTask").AddComponent<ImportantTextTask>();
+            task.transform.SetParent(__instance.transform, false);
+
+            if (__instance == Jackal.jackal) {
+                var getSidekickText = Jackal.canCreateSidekick ? " and recruit a Sidekick" : "";
+                task.Text = $"{roleInfo.colorHexString()}{roleInfo.name}: Kill everyone{getSidekickText}";  
+            } else {
+                task.Text = $"{roleInfo.colorHexString()}{roleInfo.name}: {roleInfo.shortDescription}";  
             }
+
+            __instance.myTasks.Insert(0, task);
         }
     }
 }
index 49c88988d290bc29e6751f4bc63f3913c79b507a..ea20c462410c1440822d9633cbd9afe328ffd6e4 100644 (file)
@@ -35,7 +35,9 @@ namespace TheOtherRoles
         BountyHunter,
         Tracker,
         Vampire,
-        Snitch
+        Snitch,
+        Jackal,
+        Sidekick
     }
 
     enum CustomRPC
@@ -68,6 +70,10 @@ namespace TheOtherRoles
         VampireBiteNotification = 97,
         VampireTryKill = 98,
         PlaceGarlic = 99,
+        JackalKill = 100,
+        SidekickKill = 101,
+        JackalCreatesSidekick = 102,
+        SidekickPromotes = 103
     }
 
     public static class RPCProcedure {
@@ -87,7 +93,7 @@ namespace TheOtherRoles
                 if (!player.Data.IsImpostor)
                 {
                     player.RemoveInfected();
-                    player.Die(DeathReason.Exile);
+                    player.MurderPlayer(player);
                     player.Data.IsDead = true;
                 }
             }
@@ -169,6 +175,12 @@ namespace TheOtherRoles
                     case RoleId.Snitch:
                         Snitch.snitch = player;
                         break;
+                    case RoleId.Jackal:
+                        Jackal.jackal = player;
+                        break;
+                    case RoleId.Sidekick:
+                        Sidekick.sidekick = player;
+                        break;
                     }
                 }
         }
@@ -312,7 +324,7 @@ namespace TheOtherRoles
                 if (player.PlayerId == targetId && Shifter.shifter != null)
                 {
                     // Suicide when impostor or impostor variants
-                    if (player.Data.IsImpostor) {
+                    if (player.Data.IsImpostor || player == Jackal.jackal || player == Sidekick.sidekick) {
                         Shifter.shifter.MurderPlayer(Shifter.shifter);
                         return;
                     }
@@ -396,7 +408,7 @@ namespace TheOtherRoles
                 Seer.revealedPlayers.Add(target, targetOrMistake);
 
                 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
+                    RoleInfo si = RoleInfo.getRoleInfoForPlayer(target); // Use RoleInfo of 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;
@@ -457,7 +469,7 @@ namespace TheOtherRoles
             if (Vampire.vampire == null || Vampire.bitten == null) return;
 
             if (!Vampire.bitten.Data.IsDead && Helpers.handleMurderAttempt(Vampire.bitten, false)) {
-                Vampire.bitten.MurderPlayer(Vampire.bitten); // Suicide because of the kill animation teleport
+                Vampire.vampire.MurderPlayer(Vampire.bitten);
             }
             Vampire.bitten = null;
         }
@@ -475,6 +487,107 @@ namespace TheOtherRoles
                 if (player.PlayerId == targetId)
                     Tracker.tracked = player;
         }
+
+        public static void jackalKill(byte targetId) {
+            foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+            {
+                if (player.PlayerId == targetId)
+                {
+                    Jackal.jackal.MurderPlayer(player);
+                    return;
+                }
+            }
+        }
+
+        public static void sidekickKill(byte targetId) {
+            foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+            {
+                if (player.PlayerId == targetId)
+                {
+                    Sidekick.sidekick.MurderPlayer(player);
+                    return;
+                }
+            }
+        }
+
+        public static void jackalCreatesSidekick(byte targetId) {
+            foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+            {
+                if (player.PlayerId == targetId)
+                {
+                    if(!Jackal.canCreateSidekickFromImpostor && player.Data.IsImpostor) {
+                        Jackal.fakeSidekick = player;
+                        return;
+                    }
+                    Sidekick.sidekick = player;
+                    player.RemoveInfected();
+
+                    // Crewmate roles
+                    if(player == Jester.jester) Jester.clearAndReload();
+                    if(player == Mayor.mayor) Mayor.clearAndReload();
+                    if(player == Engineer.engineer) Engineer.clearAndReload();
+                    if(player == Sheriff.sheriff) Sheriff.clearAndReload();
+                    if(player == Lighter.lighter) Lighter.clearAndReload();
+                    if(player == Detective.detective) Detective.clearAndReload();
+                    if(player == TimeMaster.timeMaster) TimeMaster.clearAndReload();
+                    if(player == Medic.medic) {
+                        if (Medic.shielded != null) Medic.shielded.myRend.material.SetFloat("_Outline", 0f);
+                        Medic.clearAndReload();
+                    }
+                    if(player == Shifter.shifter) Shifter.clearAndReload();
+                    if(player == Seer.seer) Seer.clearAndReload();
+                    if(player == Spy.spy) Spy.clearAndReload();
+                    if(player == Child.child) Child.clearAndReload();
+                    if(player == Tracker.tracker) Tracker.clearAndReload();
+                    if(player == BountyHunter.bountyHunter) BountyHunter.clearAndReload();
+                    if(player == Snitch.snitch) Snitch.clearAndReload();
+                    if(player == Swapper.swapper) Swapper.clearAndReload();
+
+                    // Impostor roles
+                    if(player == Morphling.morphling) Morphling.clearAndReload();
+                    if(player == Camouflager.camouflager) Camouflager.clearAndReload();
+                    if(player == Godfather.godfather) Godfather.clearAndReload();
+                    if(player == Mafioso.mafioso) Mafioso.clearAndReload();
+                    if(player == Janitor.janitor) Janitor.clearAndReload();
+                    if(player == Vampire.vampire) Vampire.clearAndReload();
+
+                    // The Sidekick stays a part of the lover couple!
+                    
+                    if (PlayerControl.LocalPlayer == null) return;
+                    if (PlayerControl.LocalPlayer == player) {
+                        //Only the Lover keeps his ImportantTextTask
+                        Helpers.removeTasksFromPlayer(player, player != Lovers.lover1 && player != Lovers.lover2);
+                        
+                        var task = new GameObject("RoleTask").AddComponent<ImportantTextTask>();
+                        task.transform.SetParent(player.transform, false);
+                        task.Text = "[00B4EBFF]Sidekick: Help your Jackal to kill everyone";
+                        player.myTasks.Insert(0, task);
+                    }
+
+                    return;
+                }
+            }
+        }
+
+        public static void sidekickPromotes() {
+            var player = Sidekick.sidekick;
+            Jackal.removeCurrentJackal();
+            Jackal.jackal = player;
+            if (Jackal.jackalPromotedFromSidekickCanCreateSidekick == false) {
+                Jackal.canCreateSidekick = false;
+            }
+            Sidekick.clearAndReload();
+            if (PlayerControl.LocalPlayer == null) return;
+            if (PlayerControl.LocalPlayer == player) {
+                Helpers.removeTasksFromPlayer(player, true);
+                var textTask = new GameObject("RoleTask").AddComponent<ImportantTextTask>();
+                textTask.transform.SetParent(player.transform, false);
+                var getSidekickText = Jackal.canCreateSidekick ? " and recruit a Sidekick" : "";
+                textTask.Text = $"[00B4EBFF]Jackal: Kill everyone{getSidekickText}"; 
+                player.myTasks.Insert(0, textTask);
+            }
+            return;
+        }
     }
 
     [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.HandleRpc))]
@@ -567,6 +680,18 @@ namespace TheOtherRoles
                 case (byte)CustomRPC.TrackerUsedTracker:
                     RPCProcedure.trackerUsedTracker(HFPCBBHJIPJ.ReadByte());
                     break;
+                case (byte)CustomRPC.JackalKill:
+                    RPCProcedure.jackalKill(HFPCBBHJIPJ.ReadByte());
+                    break;
+                case (byte)CustomRPC.SidekickKill:
+                    RPCProcedure.sidekickKill(HFPCBBHJIPJ.ReadByte());
+                    break;
+                case (byte)CustomRPC.JackalCreatesSidekick:
+                    RPCProcedure.jackalCreatesSidekick(HFPCBBHJIPJ.ReadByte());
+                    break;
+                case (byte)CustomRPC.SidekickPromotes:
+                    RPCProcedure.sidekickPromotes();
+                    break;
             }
         }
     }
diff --git a/Source Code/Resources/Arrow.png b/Source Code/Resources/Arrow.png
new file mode 100644 (file)
index 0000000..5be952a
Binary files /dev/null and b/Source Code/Resources/Arrow.png differ
diff --git a/Source Code/Resources/Garlic.png b/Source Code/Resources/Garlic.png
new file mode 100644 (file)
index 0000000..87f5c0a
Binary files /dev/null and b/Source Code/Resources/Garlic.png differ
diff --git a/Source Code/Resources/GarlicBackground.png b/Source Code/Resources/GarlicBackground.png
new file mode 100644 (file)
index 0000000..0a3c8d3
Binary files /dev/null and b/Source Code/Resources/GarlicBackground.png differ
diff --git a/Source Code/Resources/GarlicButton.png b/Source Code/Resources/GarlicButton.png
new file mode 100644 (file)
index 0000000..6f11b52
Binary files /dev/null and b/Source Code/Resources/GarlicButton.png differ
diff --git a/Source Code/Resources/SidekickButton.png b/Source Code/Resources/SidekickButton.png
new file mode 100644 (file)
index 0000000..88d0bb7
Binary files /dev/null and b/Source Code/Resources/SidekickButton.png differ
diff --git a/Source Code/Resources/TrackerButton.png b/Source Code/Resources/TrackerButton.png
new file mode 100644 (file)
index 0000000..20bd5ca
Binary files /dev/null and b/Source Code/Resources/TrackerButton.png differ
diff --git a/Source Code/Resources/VampireButton.png b/Source Code/Resources/VampireButton.png
new file mode 100644 (file)
index 0000000..db104d1
Binary files /dev/null and b/Source Code/Resources/VampireButton.png differ
index ab6b3b50785c41c16aa482fe703e08dbbf92f47a..3f11320013f16c30498559b70a05281739eb0329 100644 (file)
@@ -40,8 +40,8 @@ namespace TheOtherRoles
             List<PlayerControl> impostors = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList();
             impostors.RemoveAll(x => !x.Data.IsImpostor);
 
-            float crewCountSettings = TheOtherRolesPlugin.crewmateRolesCount.GetValue();
-            float impCountSettings = TheOtherRolesPlugin.impostorRolesCount.GetValue();
+            float crewCountSettings = (float)TheOtherRolesPlugin.crewmateRolesCount.GetValue() / 2;
+            float impCountSettings = (float)TheOtherRolesPlugin.impostorRolesCount.GetValue() / 2;
 
             if (crewCountSettings % 1 == 0.5f) crewCountSettings += 0.5f * (rnd.Next(2) * 2 - 1);
             if (impCountSettings % 1 == 0.5f) impCountSettings += 0.5f * (rnd.Next(2) * 2 - 1);
@@ -71,6 +71,7 @@ namespace TheOtherRoles
             crewSettings.Add((byte)RoleId.Child, TheOtherRolesPlugin.childSpawnRate.GetValue());
             crewSettings.Add((byte)RoleId.Tracker, TheOtherRolesPlugin.trackerSpawnRate.GetValue());
             crewSettings.Add((byte)RoleId.Snitch, TheOtherRolesPlugin.snitchSpawnRate.GetValue());
+            crewSettings.Add((byte)RoleId.Jackal, TheOtherRolesPlugin.jackalSpawnRate.GetValue());
             // crewSettings.Add((byte)RoleId.BountyHunter, TheOtherRolesPlugin.bountyHunterSpawnRate.GetValue()); BOUNTY HUNTER
 
             // Set multiple player roles
@@ -82,7 +83,7 @@ namespace TheOtherRoles
             }
 
             if (rnd.Next(1, 101) <= TheOtherRolesPlugin.loversSpawnRate.GetValue() * 10) {
-                if (impostors.Count > 0 && crewmates.Count > 0 && maxCrewmateRoles > 0 && maxImpostorRoles > 0 && rnd.Next(1, 101) <= 33) {
+                if (impostors.Count > 0 && crewmates.Count > 0 && maxCrewmateRoles > 0 && maxImpostorRoles > 0 && rnd.Next(1, 101) <= TheOtherRolesPlugin.loversImpLoverRate.GetValue()) {
                     setRoleToRandomPlayer((byte)RoleId.Lover1, impostors); 
                     setRoleToRandomPlayer((byte)RoleId.Lover2, crewmates);
                     maxCrewmateRoles--;
diff --git a/Source Code/RoleInfo.cs b/Source Code/RoleInfo.cs
new file mode 100644 (file)
index 0000000..860dce9
--- /dev/null
@@ -0,0 +1,225 @@
+using HarmonyLib;
+using System;
+using static TheOtherRoles.TheOtherRoles;
+using UnityEngine;
+
+namespace TheOtherRoles
+{
+    public class RoleInfo {
+        public Color color;
+        public string name;
+        public string introDescription;
+        public string shortDescription;
+        public bool isGood;
+
+        RoleInfo(Color color, string name, string introDescription, string shortDescription, bool isGood) {
+            this.color = color;
+            this.name = name;
+            this.introDescription = introDescription;
+            this.shortDescription = shortDescription;
+            this.isGood = isGood;
+        }
+
+        public string colorHexString() {
+            return string.Format("[{0:X2}{1:X2}{2:X2}{3:X2}]", ToByte(color.r), ToByte(color.g), ToByte(color.b), ToByte(color.a));
+        }
+        private static byte ToByte(float f) {
+            f = Mathf.Clamp01(f);
+            return (byte)(f * 255);
+        }
+
+
+        public static RoleInfo getRoleInfoForPlayer(PlayerControl p) {
+            string name = "";
+            string introDescription = "";
+            string shortDescription = "";
+            bool isGood = true;
+            Color color = Color.white;
+
+            if (Jester.jester != null && p == Jester.jester) {
+                name = "Jester";
+                color = Jester.color;
+                introDescription = "Get voted out";
+                shortDescription = introDescription;
+                isGood = false;
+            }
+            else if (Mayor.mayor != null && p == Mayor.mayor) {
+                name = "Mayor";   
+                color = Mayor.color;
+                introDescription = "Your vote counts twice";
+                shortDescription = introDescription;
+            }
+            else if (Engineer.engineer != null && p == Engineer.engineer) {
+                name = "Engineer";   
+                color = Engineer.color;
+                introDescription = "Maintain important systems on the ship";
+                shortDescription = "Repair the ship";
+            }
+            else if (Sheriff.sheriff != null && p == Sheriff.sheriff) {
+                name = "Sheriff";   
+                color = Sheriff.color;
+                introDescription = "Shoot the [FF1919FF]Impostors";
+                shortDescription = "Shoot the Impostors";
+            }
+            else if (Lighter.lighter != null && p == Lighter.lighter) {
+                name = "Lighter";   
+                color = Lighter.color;
+                introDescription = "Your light never goes out";
+                shortDescription = introDescription;
+            }
+            else if (Godfather.godfather != null && p == Godfather.godfather) {
+                name = "Godfather";   
+                color = Godfather.color;
+                introDescription = "Kill all Crewmates";
+                shortDescription = introDescription;
+                isGood = false;
+            }
+            else if (Mafioso.mafioso != null && p == Mafioso.mafioso) {
+                name = "Mafioso";   
+                color = Mafioso.color;
+                introDescription = "Work with the [FF1919FF]Mafia[] to kill the Crewmates";
+                shortDescription = "Kill all Crewmates";
+                isGood = false;
+            }
+            else if (Janitor.janitor != null && p == Janitor.janitor) {
+                name = "Janitor";
+                color = Janitor.color;
+                introDescription = "Work with the [FF1919FF]Mafia[] by hiding dead bodies";
+                shortDescription = "Hide dead bodies";
+                isGood = false;
+            }
+            else if (Morphling.morphling != null && p == Morphling.morphling) {
+                name = "Morphling";
+                color = Morphling.color;
+                introDescription = "Change your look to not get caught";
+                shortDescription = "Change your look";
+                isGood = false;
+            }
+            else if (Camouflager.camouflager != null && p == Camouflager.camouflager) {
+                name = "Camouflager";
+                color = Camouflager.color;
+                introDescription = "Camouflage and kill the Crewmates";
+                shortDescription = "Hide among others";
+                isGood = false;
+            }
+            else if (Vampire.vampire != null && p == Vampire.vampire) {
+                name = "Vampire";
+                color = Vampire.color;
+                introDescription = "Kill the Crewmates with your bites";
+                shortDescription = "Bite your enemies";
+                isGood = false;
+            }
+            else if (Detective.detective != null && p == Detective.detective) {
+                name = "Detective";
+                color = Detective.color;
+                introDescription = "Find the [FF1919FF]Impostors[] by examining footprints";
+                shortDescription = "Examine footprints";
+            }
+            else if (TimeMaster.timeMaster != null && p == TimeMaster.timeMaster) {
+                name = "Time Master";
+                color = TimeMaster.color;
+                introDescription = "Rewind time to find the [FF1919FF]Impostors";
+                shortDescription = "Rewind time";
+            }
+            else if (Medic.medic != null && p == Medic.medic) {
+                name = "Medic";
+                color = Medic.color;
+                introDescription = "Protect someone with your shield";
+                shortDescription = "Protect other players";
+            }
+            else if (Shifter.shifter != null && p == Shifter.shifter) {
+                name = "Shifter";
+                color = Shifter.color;
+                introDescription = "Shift your role before the game ends";
+                shortDescription = "Shift your role";
+                isGood = false;
+            }
+            else if (Swapper.swapper != null && p == Swapper.swapper) {
+                name = "Swapper";
+                color = Swapper.color;
+                introDescription = "Swap votes to exile the [FF1919FF]Impostors";
+                shortDescription = "Swap votes";
+            }
+            else if (Seer.seer != null && p == Seer.seer) { 
+                name = "Seer";
+                color = Seer.color;
+                introDescription = "Reveal the intentions of everyone on the ship";
+                shortDescription = "Reveal all roles";
+            }
+            else if (Spy.spy != null && p == Spy.spy) { 
+                name = "Spy";
+                color = Spy.color;
+                introDescription = "Spy on everyone to find the [FF1919FF]Impostors";
+                shortDescription = "Spy on everyone";
+            }
+            else if (Child.child != null && p == Child.child) { 
+                name = "Child";
+                color = Child.color;
+                introDescription = "No one will harm you until you grow up";
+                shortDescription = "No one will harm you";
+            }
+            else if (BountyHunter.bountyHunter != null && p == BountyHunter.bountyHunter) {
+                name = "Bounty Hunter";
+                color = BountyHunter.color;
+                introDescription = "Hunt your bounty down";
+                shortDescription = "Hunt your bounty";
+                isGood = false;
+            }
+            else if (Tracker.tracker != null && p == Tracker.tracker) {
+                name = "Tracker";
+                color = Tracker.color;
+                introDescription = "Track the [FF1919FF]Impostors[] down";
+                shortDescription = "Track the Impostors down";
+            }
+            else if (Snitch.snitch != null && p == Snitch.snitch) {
+                name = "Snitch";
+                color = Snitch.color;
+                introDescription = "Finish your tasks to find the [FF1919FF]Impostors[]";
+                shortDescription = "Finish your tasks";
+            }
+            else if (Jackal.jackal != null && p == Jackal.jackal) {
+                name = "Jackal";
+                color = Jackal.color;
+                introDescription = "Kill all Crewmates and [FF1919FF]Impostors[FFFFFFFF] to win";
+                shortDescription = "";
+                isGood = false;                
+            }
+            else if (Sidekick.sidekick != null && p == Sidekick.sidekick) {
+                name = "Sidekick";
+                color = Sidekick.color;
+                introDescription = "";
+                shortDescription = "Help your Jackal to kill everyone";
+                isGood = false;
+            }
+            else if ((Lovers.lover1 != null && p == Lovers.lover1) || (Lovers.lover2 != null && p == Lovers.lover2)) {
+                name = p.Data.IsImpostor ? "ImpLover" : "Lover";
+                color = p.Data.IsImpostor ? Palette.ImpostorRed : Lovers.color;
+                introDescription = "You are in [FC03BEFF]Love[]";
+                shortDescription = "You are in love";
+                isGood = !p.Data.IsImpostor;
+            }
+            else if (p.Data.IsImpostor) { // Just Impostor
+                name = "Impostor";
+                color = Palette.ImpostorRed;
+                introDescription = "";
+                shortDescription = "Sabotage and kill everyone";
+                isGood = false;
+            }
+            else { // Just Crewmate
+                name = "Crewmate";
+                color = Color.white;
+                introDescription = "";
+                shortDescription = "Find the Impostors";
+            }
+
+            return new RoleInfo(
+                color,
+                name,
+                introDescription,
+                shortDescription,
+                isGood
+            );
+        }
+    }
+}
\ No newline at end of file
index c6ed7fbb27b1535c82255c8aab1b0e13e42b4878..d5db201cb831a89cc941efe3df0793f75c014e89 100644 (file)
@@ -4,12 +4,23 @@ using static TheOtherRoles.TheOtherRoles;
 
 namespace TheOtherRoles {
 
-    [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.CalculateLightRadius))]
+    [HarmonyPatch(typeof(ShipStatus))]
     public class ShipStatusPatch {
-        public static void Postfix(ref float __result, ShipStatus __instance, [HarmonyArgument(0)] GameData.PlayerInfo PlayerData) {
+
+               [HarmonyPostfix]
+               [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.CalculateLightRadius))]
+        public static void Postfix1(ref float __result, ShipStatus __instance, [HarmonyArgument(0)] GameData.PlayerInfo PlayerData) {
             if (Lighter.lighter != null && Lighter.lighter.PlayerId == PlayerData.PlayerId) {
                 __result = __instance.MaxLightRadius * Lighter.lighterVision;
             }
         }
+
+        [HarmonyPostfix]
+               [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.IsGameOverDueToDeath))]
+               public static void Postfix2(ShipStatus __instance, ref bool __result)
+               {
+                       __result = false;
+               }
     }
+
 }
\ No newline at end of file
index cf70fc25a526c79d708315cef4fb42acdefe5701..4b53622da253f934dc7d45241093a98fc81bcacc 100644 (file)
@@ -45,6 +45,8 @@ namespace TheOtherRoles
             Tracker.clearAndReload();
             Vampire.clearAndReload();
             Snitch.clearAndReload();
+            Jackal.clearAndReload();
+            Sidekick.clearAndReload();
         }
 
         public static class Jester {
@@ -283,11 +285,11 @@ namespace TheOtherRoles
         public static bool notAckedExiledIsLover = false;
 
         public static bool existingAndAlive() {
-            return lover1 != null && lover2 != null && !lover1.Data.IsDead && !lover2.Data.IsDead && !notAckedExiledIsLover; // ADD NOT ACKED IS LOVER
+            return lover1 != null && lover2 != null && !lover1.Data.IsDead && !lover2.Data.IsDead && !lover1.Data.Disconnected && !lover2.Data.Disconnected && !notAckedExiledIsLover; // ADD NOT ACKED IS LOVER
         }
 
         public static bool existingWithImpLover() {
-            return lover1 != null && lover2 != null && (lover1.Data.IsImpostor || lover2.Data.IsImpostor);
+            return lover1 != null && lover2 != null && !lover1.Data.Disconnected && !lover2.Data.Disconnected && (lover1.Data.IsImpostor || lover2.Data.IsImpostor);
         }
 
         public static void clearAndReload() {
@@ -490,6 +492,8 @@ namespace TheOtherRoles
         public static Color color = Palette.ImpostorRed;
 
         public static float delay = 10f;
+        public static float cooldown = float.MaxValue;
+        public static bool canKillNearGarlics = true;
         public static bool localPlacedGarlic = false;
         public static bool garlicsActive = true;
 
@@ -526,6 +530,8 @@ namespace TheOtherRoles
             currentTarget = null;
             garlicsActive = TheOtherRolesPlugin.vampireSpawnRate.GetValue() > 0;
             delay = TheOtherRolesPlugin.vampireKillDelay.GetValue();
+            cooldown = TheOtherRolesPlugin.vampireCooldown.GetValue();
+            canKillNearGarlics = TheOtherRolesPlugin.vampireCanKillNearGarlics.GetValue();
         }
     }
 
@@ -542,4 +548,73 @@ namespace TheOtherRoles
             snitch = null;
         }
     }
+
+    public static class Jackal {
+        public static PlayerControl jackal;
+        public static Color color = new Color(0f / 255f, 180f / 255f, 235f / 255f, 1);
+        public static PlayerControl fakeSidekick;
+
+        public static PlayerControl currentTarget;
+        public static List<PlayerControl> formerJackals = new List<PlayerControl>();
+        
+        public static float cooldown = float.MaxValue;
+        public static float createSidekickCooldown = float.MaxValue;
+        public static bool canUseVents = true;
+        public static bool canCreateSidekick = true;
+        public static Sprite buttonSprite;
+        public static bool jackalPromotedFromSidekickCanCreateSidekick = true;
+        public static bool canCreateSidekickFromImpostor = true;
+
+        public static Sprite getSidekickButtonSprite() {
+            if (buttonSprite) return buttonSprite;
+            buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.SidekickButton.png", 100f);
+            return buttonSprite;
+        }
+
+        public static void removeCurrentJackal() {
+            formerJackals.Add(jackal);
+            jackal = null;
+            currentTarget = null;
+            fakeSidekick = null;
+            cooldown = TheOtherRolesPlugin.jackalKillCooldown.GetValue();
+            createSidekickCooldown = TheOtherRolesPlugin.jackalCreateSidekickCooldown.GetValue();
+        }
+
+        public static void clearAndReload() {
+            jackal = null;
+            currentTarget = null;
+            fakeSidekick = null;
+            cooldown = TheOtherRolesPlugin.jackalKillCooldown.GetValue();
+            createSidekickCooldown = TheOtherRolesPlugin.jackalCreateSidekickCooldown.GetValue();
+            canUseVents = TheOtherRolesPlugin.jackalCanUseVents.GetValue();
+            canCreateSidekick = TheOtherRolesPlugin.jackalCanCreateSidekick.GetValue();
+            jackalPromotedFromSidekickCanCreateSidekick = TheOtherRolesPlugin.jackalPromotedFromSidekickCanCreateSidekick.GetValue();
+            canCreateSidekickFromImpostor = TheOtherRolesPlugin.jackalCanCreateSidekickFromImpostor.GetValue();
+            formerJackals.Clear();
+        }
+        
+    }
+
+    public static class Sidekick {
+        public static PlayerControl sidekick;
+        public static Color color = new Color(0f / 255f, 180f / 255f, 235f / 255f, 1);
+
+        public static PlayerControl currentTarget;
+
+        public static float cooldown = float.MaxValue;
+        public static bool canUseVents = true;
+        public static bool canKill = true;
+        public static bool promotesToJackal = true;
+
+        public static void clearAndReload() {
+            sidekick = null;
+            currentTarget = null;
+            cooldown = TheOtherRolesPlugin.jackalKillCooldown.GetValue();
+            canUseVents = TheOtherRolesPlugin.sidekickCanUseVents.GetValue();
+            canKill = TheOtherRolesPlugin.sidekickCanKill.GetValue();
+            promotesToJackal = TheOtherRolesPlugin.sidekickPromotesToJackal.GetValue();
+        }
+        
+    }
+
 }
\ No newline at end of file
index c0e534e269dd2850d2eacb7e5140b308469812de..efe2bc5afce06f62a5ad9985ba9b3182c0373b83 100644 (file)
         <EmbeddedResource Include="Resources\Footprint.png"/>
         <EmbeddedResource Include="Resources\AdminTableIcon.png"/>
         <EmbeddedResource Include="Resources\SwapperCheck.png"/>
+        <EmbeddedResource Include="Resources\SidekickButton.png"/>
     </ItemGroup>
 
     <Target Name="Copy" AfterTargets="Reobfuscate">
+        <Message Text="Deobfuscated: $(Deobfuscated)" Importance="high"/>
         <Copy SourceFiles="$(OutputPath)reobfuscated/$(AssemblyName)-$(GameVersion).dll" DestinationFolder="$(AmongUs)/BepInEx/plugins/" Condition="'$(Configuration)' == 'Debug'" />
     </Target>
 </Project>
\ No newline at end of file
index 6d20ef667d0feb2ed922494f6ca5fd339b50a4a8..df44591b7c46d409ab4b1696d22bda342d3034af 100644 (file)
@@ -24,7 +24,7 @@ namespace TheOtherRoles
                     player.nameText.Color = Color.white;
                 }
             }
-            if (MeetingHud.Instance != null)
+            if (MeetingHud.Instance != null) {
                 foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates) {
                     PlayerControl playerControl = playersById.ContainsKey((byte)player.TargetPlayerId) ? playersById[(byte)player.TargetPlayerId] : null;
                     if (playerControl != null) {
@@ -36,7 +36,7 @@ namespace TheOtherRoles
                         }
                     }
                 }
-
+            }
             if (PlayerControl.LocalPlayer.Data.IsImpostor) {
                 List<PlayerControl> impostors = PlayerControl.AllPlayerControls.ToArray().ToList();
                 impostors.RemoveAll(x => !x.Data.IsImpostor);
@@ -49,7 +49,7 @@ namespace TheOtherRoles
                             player.NameText.Color =  Palette.ImpostorRed;
                     }
             }
-            
+
         }
 
         static void setPlayerNameColor(PlayerControl p, Color color) {
@@ -97,6 +97,25 @@ namespace TheOtherRoles
                 setPlayerNameColor(Tracker.tracker, Tracker.color);
             else if (Snitch.snitch != null && Snitch.snitch == PlayerControl.LocalPlayer) 
                 setPlayerNameColor(Snitch.snitch, Snitch.color);
+            else if (Jackal.jackal != null && Jackal.jackal == PlayerControl.LocalPlayer) {
+                // Jackal can see his sidekick
+                setPlayerNameColor(Jackal.jackal, Jackal.color);
+                if (Sidekick.sidekick != null) {
+                    setPlayerNameColor(Sidekick.sidekick, Jackal.color);
+                }
+                if (Jackal.fakeSidekick != null) {
+                    setPlayerNameColor(Jackal.fakeSidekick, Jackal.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
+                setPlayerNameColor(Sidekick.sidekick, Sidekick.color);
+                if (Jackal.jackal != null) {
+                    setPlayerNameColor(Jackal.jackal, Jackal.color);
+                }
+            }
 
             // Crewmate roles with no changes: Child
             // Impostor roles with no changes: Morphling, Camouflager, Vampire, Godfather, Janitor and Mafioso
@@ -148,17 +167,6 @@ namespace TheOtherRoles
             }
         }
 
-        static void jesterClearTasks() {
-            if (Jester.jester == null) return;
-
-            var toRemove = new List<PlayerTask>();
-            foreach (PlayerTask task in Jester.jester.myTasks)
-                if (task.TaskType != TaskTypes.FixComms && task.TaskType != TaskTypes.FixLights && task.TaskType != TaskTypes.ResetReactor && task.TaskType != TaskTypes.ResetSeismic && task.TaskType != TaskTypes.RestoreOxy)
-                    toRemove.Add(task);
-            foreach (PlayerTask task in toRemove)
-                Jester.jester.RemoveTask(task);
-        }
-
         static void mafiosoDeactivateKillButtonIfNecessary(HudManager __instance) {
             if (Mafioso.mafioso == null || Mafioso.mafioso != PlayerControl.LocalPlayer) return;
 
@@ -184,17 +192,6 @@ namespace TheOtherRoles
             }
         }
 
-        static void shifterClearTasks() {
-            if (Shifter.shifter == null) return;
-
-            var toRemove = new List<PlayerTask>();
-            foreach (PlayerTask task in Shifter.shifter.myTasks)
-                if (task.TaskType != TaskTypes.FixComms && task.TaskType != TaskTypes.FixLights && task.TaskType != TaskTypes.ResetReactor && task.TaskType != TaskTypes.ResetSeismic && task.TaskType != TaskTypes.RestoreOxy)
-                    toRemove.Add(task);
-            foreach (PlayerTask task in toRemove)
-                Shifter.shifter.RemoveTask(task);
-        }
-
         static void seerUpdate() {
             if (Seer.seer == null || Seer.seer != PlayerControl.LocalPlayer) return;
 
@@ -207,9 +204,9 @@ namespace TheOtherRoles
 
                 // Update color and name regarding settings and given info
                 string result = target.Data.PlayerName;
-                SeerInfo si = SeerInfo.getSeerInfoForPlayer(targetOrMistake);
+                RoleInfo si = RoleInfo.getRoleInfoForPlayer(targetOrMistake);
                 if (Seer.kindOfInfo == 0)
-                    result = target.Data.PlayerName + " (" + si.roleName + ")";
+                    result = target.Data.PlayerName + " (" + si.name + ")";
                 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); 
                 }
@@ -370,10 +367,13 @@ namespace TheOtherRoles
             if (Snitch.snitch.Data.IsDead) return;
 
             int numberOfTasks = 0;
-            foreach (PlayerTask t in Snitch.snitch.myTasks) {
-                if (t.TaskType != TaskTypes.FixComms && t.TaskType != TaskTypes.FixLights && t.TaskType != TaskTypes.ResetReactor && t.TaskType != TaskTypes.ResetSeismic && t.TaskType != TaskTypes.RestoreOxy)
-                    if (!t.IsComplete) numberOfTasks++;
-            }
+            GameData.PlayerInfo playerInfo = Snitch.snitch.Data;
+                       if (!playerInfo.Disconnected && playerInfo.Tasks != null) {
+                               for (int i = 0; i < playerInfo.Tasks.Count; i++) {
+                                       if (!playerInfo.Tasks[i].Complete)
+                                               numberOfTasks++;
+                               }
+                       }
 
             if (PlayerControl.LocalPlayer.Data.IsImpostor && numberOfTasks <= Snitch.taskCountForImpostors) {
                 if (Snitch.localArrows.Count == 0) Snitch.localArrows.Add(new Arrow(Color.blue));
@@ -408,13 +408,13 @@ namespace TheOtherRoles
             // Mafia
             setMafiaNameTags();
             // Jester
-            jesterClearTasks();
+            Helpers.removeTasksFromPlayer(Jester.jester);
             // Mafioso
             mafiosoDeactivateKillButtonIfNecessary(__instance);
             // Janitor
             janitorDeactivateKillButton(__instance);
             // Shifter
-            shifterClearTasks();
+            Helpers.removeTasksFromPlayer(Shifter.shifter);
             // Seer update
             seerUpdate();
             // Spy update();
@@ -429,6 +429,10 @@ namespace TheOtherRoles
             vampireDeactivateKillButton(__instance);
             // Snitch
             snitchUpdate();
+            // Jackal
+            Helpers.removeTasksFromPlayer(Jackal.jackal);
+            // Sidekick
+            Helpers.removeTasksFromPlayer(Sidekick.sidekick);
         }
     }
 }
\ No newline at end of file
index fb0415f4ec0fe815001f3855b5e553517b3cb947..babcb119ad4e6304dadf5348083216315fb846d3 100644 (file)
@@ -24,6 +24,10 @@ namespace TheOtherRoles
             bool roleCouldUse = false;
             if (Engineer.engineer != null && Engineer.engineer == @object)
                 roleCouldUse = true;
+            else if (Jackal.canUseVents && Jackal.jackal != null && Jackal.jackal == @object)
+                roleCouldUse = true;
+            else if (Sidekick.canUseVents && Sidekick.sidekick != null && Sidekick.sidekick == @object)
+                roleCouldUse = true;
             else if (pc.IsImpostor) {
                 if (Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer)
                     roleCouldUse = false;