From: Eisbison Date: Sat, 27 Mar 2021 14:57:38 +0000 (+0100) Subject: Update source code X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=835df7791246b873314fbdcb469b1e932a990066;p=rhonda%2Ftheotherroles.git Update source code --- diff --git a/Source Code/Buttons.cs b/Source Code/Buttons.cs index fc16fec..3f59c9b 100644 --- a/Source Code/Buttons.cs +++ b/Source Code/Buttons.cs @@ -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 diff --git a/Source Code/CredentialsPatch.cs b/Source Code/CredentialsPatch.cs index 9b4394f..28edb41 100644 --- a/Source Code/CredentialsPatch.cs +++ b/Source Code/CredentialsPatch.cs @@ -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]"; } } } diff --git a/Source Code/CustomButton.cs b/Source Code/CustomButton.cs index 7f1b7c0..65f9af8 100644 --- a/Source Code/CustomButton.cs +++ b/Source Code/CustomButton.cs @@ -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) { diff --git a/Source Code/EndGamePatch.cs b/Source Code/EndGamePatch.cs index 1118476..4e84313 100644 --- a/Source Code/EndGamePatch.cs +++ b/Source Code/EndGamePatch.cs @@ -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 winnersToRemove = new List(); + 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(); + 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(); 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 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(); - for (int i = 0; i < array.Length; i++) UnityEngine.Object.Destroy(array[i].gameObject); - } - private static void EndGameForSabotage(ShipStatus __instance) - { - if (!DestroyableSingleton.InstanceExists) - { - __instance.enabled = false; - ShipStatus.RpcEndGame(GameOverReason.ImpostorBySabotage, false); - return; - } - DestroyableSingleton.Instance.ShowPopUp(DestroyableSingleton.Instance.GetString(StringNames.GameOverSabotage, new Il2CppReferenceArray(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(); - 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(); - 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.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.InstanceExists || num3 > 0)) + else if (localCompletedAllTasks) { + DestroyableSingleton.Instance.ShowPopUp(DestroyableSingleton.Instance.GetString(StringNames.GameOverTaskWin, new Il2CppReferenceArray(0))); + __instance.Begin(); + } + return false; + } + + private static bool CheckAndEndGameForLoverWin(ShipStatus __instance, PlayerStatistics statistics) { + if (statistics.TeamLoversAlive == 2 && statistics.TotalAlive <= 3) { if (!DestroyableSingleton.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.Instance.ShowPopUp(DestroyableSingleton.Instance.GetString(StringNames.GameOverImpostorDead, new Il2CppReferenceArray(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.InstanceExists) - { - if (GameData.Instance.TotalTasks <= GameData.Instance.CompletedTasks) - { - __instance.enabled = false; - ShipStatus.RpcEndGame(GameOverReason.HumansByTask, false); - return false; - } - } - else if (localCompletedAllTasks) - { - DestroyableSingleton.Instance.ShowPopUp(DestroyableSingleton.Instance.GetString(StringNames.GameOverTaskWin, new Il2CppReferenceArray(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.InstanceExists) { + __instance.enabled = false; + ShipStatus.RpcEndGame((GameOverReason)CustomGameOverReason.TeamJackalWin, false); + return true; + } + DestroyableSingleton.Instance.ShowPopUp(DestroyableSingleton.Instance.GetString(StringNames.GameOverImpostorKills, new Il2CppReferenceArray(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.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.Instance.ShowPopUp(DestroyableSingleton.Instance.GetString(StringNames.GameOverImpostorKills, new Il2CppReferenceArray(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.InstanceExists) { + __instance.enabled = false; + ShipStatus.RpcEndGame(GameOverReason.HumansByVote, false); + return true; + } + DestroyableSingleton.Instance.ShowPopUp(DestroyableSingleton.Instance.GetString(StringNames.GameOverImpostorDead, new Il2CppReferenceArray(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(); + for (int i = 0; i < array.Length; i++) UnityEngine.Object.Destroy(array[i].gameObject); + } + + private static void EndGameForSabotage(ShipStatus __instance) + { + if (!DestroyableSingleton.InstanceExists) + { + __instance.enabled = false; + ShipStatus.RpcEndGame(GameOverReason.ImpostorBySabotage, false); + return; + } + DestroyableSingleton.Instance.ShowPopUp(DestroyableSingleton.Instance.GetString(StringNames.GameOverSabotage, new Il2CppReferenceArray(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 diff --git a/Source Code/GameOptionsPatch.cs b/Source Code/GameOptionsPatch.cs index 046762b..1e88320 100644 --- a/Source Code/GameOptionsPatch.cs +++ b/Source Code/GameOptionsPatch.cs @@ -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; } } diff --git a/Source Code/Helpers.cs b/Source Code/Helpers.cs index 590473d..0e9e96d 100644 --- a/Source Code/Helpers.cs +++ b/Source Code/Helpers.cs @@ -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(); + foreach (PlayerTask task in player.myTasks) { + if (!removeImportantTextTasks && task.gameObject.GetComponent() != 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 diff --git a/Source Code/IntroPatch.cs b/Source Code/IntroPatch.cs index 42495f3..0418beb 100644 --- a/Source Code/IntroPatch.cs +++ b/Source Code/IntroPatch.cs @@ -21,131 +21,19 @@ namespace TheOtherRoles var shifterTeam = new Il2CppSystem.Collections.Generic.List(); shifterTeam.Add(PlayerControl.LocalPlayer); __instance.yourTeam = shifterTeam; + } else if (PlayerControl.LocalPlayer == Jackal.jackal) { + var jackalTeam = new Il2CppSystem.Collections.Generic.List(); + 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; } } } diff --git a/Source Code/Main.cs b/Source Code/Main.cs index f1b189c..06e5e9c 100644 --- a/Source Code/Main.cs +++ b/Source Code/Main.cs @@ -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.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 options = new List(); 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 }; } } diff --git a/Source Code/MeetingPatch.cs b/Source Code/MeetingPatch.cs index 152c01c..52eb0d0 100644 --- a/Source Code/MeetingPatch.cs +++ b/Source Code/MeetingPatch.cs @@ -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 diff --git a/Source Code/PlayerControlPatch.cs b/Source Code/PlayerControlPatch.cs index ba58753..4eeae4a 100644 --- a/Source Code/PlayerControlPatch.cs +++ b/Source Code/PlayerControlPatch.cs @@ -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 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(); + 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(); + 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(); + foreach (PlayerTask t in __instance.myTasks) { + if (t.gameObject.GetComponent() != 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(); + 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); } } } diff --git a/Source Code/RPC.cs b/Source Code/RPC.cs index 49c8898..ea20c46 100644 --- a/Source Code/RPC.cs +++ b/Source Code/RPC.cs @@ -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(); + 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(); + 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 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 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 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 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 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 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 index 0000000..db104d1 Binary files /dev/null and b/Source Code/Resources/VampireButton.png differ diff --git a/Source Code/RoleAssignmentPatch.cs b/Source Code/RoleAssignmentPatch.cs index ab6b3b5..3f11320 100644 --- a/Source Code/RoleAssignmentPatch.cs +++ b/Source Code/RoleAssignmentPatch.cs @@ -40,8 +40,8 @@ namespace TheOtherRoles List 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 index 0000000..860dce9 --- /dev/null +++ b/Source Code/RoleInfo.cs @@ -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 diff --git a/Source Code/ShipStatusPatch.cs b/Source Code/ShipStatusPatch.cs index c6ed7fb..d5db201 100644 --- a/Source Code/ShipStatusPatch.cs +++ b/Source Code/ShipStatusPatch.cs @@ -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 diff --git a/Source Code/TheOtherRoles.cs b/Source Code/TheOtherRoles.cs index cf70fc2..4b53622 100644 --- a/Source Code/TheOtherRoles.cs +++ b/Source Code/TheOtherRoles.cs @@ -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 formerJackals = new List(); + + 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 diff --git a/Source Code/TheOtherRoles.csproj b/Source Code/TheOtherRoles.csproj index c0e534e..efe2bc5 100644 --- a/Source Code/TheOtherRoles.csproj +++ b/Source Code/TheOtherRoles.csproj @@ -50,9 +50,11 @@ + + \ No newline at end of file diff --git a/Source Code/UpdatePatch.cs b/Source Code/UpdatePatch.cs index 6d20ef6..df44591 100644 --- a/Source Code/UpdatePatch.cs +++ b/Source Code/UpdatePatch.cs @@ -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 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(); - 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(); - 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 diff --git a/Source Code/UsablesPatch.cs b/Source Code/UsablesPatch.cs index fb0415f..babcb11 100644 --- a/Source Code/UsablesPatch.cs +++ b/Source Code/UsablesPatch.cs @@ -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;