From 7f76a001d568372a1ce5c7cd0756f6771c187edb Mon Sep 17 00:00:00 2001 From: gendelo3 Date: Sat, 20 May 2023 18:06:04 +0200 Subject: [PATCH] Update v4.3.2 --- README.md | 28 ++++- TheOtherRoles/Buttons.cs | 8 +- TheOtherRoles/CustomOptionHolder.cs | 4 + TheOtherRoles/GameHistory.cs | 31 ++++- TheOtherRoles/Helpers.cs | 7 +- TheOtherRoles/Main.cs | 2 +- TheOtherRoles/Modules/CustomOptions.cs | 116 +++++++++++++++++- TheOtherRoles/Objects/Bomb.cs | 15 ++- TheOtherRoles/Patches/EndGamePatch.cs | 12 +- TheOtherRoles/Patches/ExileControllerPatch.cs | 52 ++++---- .../Patches/HauntMenuMinigamePatch.cs | 2 +- TheOtherRoles/Patches/MapBehaviourPatch.cs | 4 +- TheOtherRoles/Patches/MeetingPatch.cs | 42 +++++++ TheOtherRoles/Patches/PlayerControlPatch.cs | 38 ++++-- TheOtherRoles/Patches/RoleAssignmentPatch.cs | 21 ++-- TheOtherRoles/RPC.cs | 43 +++++-- TheOtherRoles/Resources/CopyButton.png | Bin 0 -> 18976 bytes TheOtherRoles/Resources/PasteButton.png | Bin 0 -> 18187 bytes TheOtherRoles/RoleInfo.cs | 50 +++++++- TheOtherRoles/TheOtherRoles.cs | 14 ++- TheOtherRoles/TheOtherRoles.csproj | 6 +- 21 files changed, 418 insertions(+), 77 deletions(-) create mode 100644 TheOtherRoles/Resources/CopyButton.png create mode 100644 TheOtherRoles/Resources/PasteButton.png diff --git a/README.md b/README.md index 5b3507b..f358cd9 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein # Releases | Among Us - Version| Mod Version | Link | |----------|-------------|-----------------| +| 2023.03.28s| v4.3.2| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.3.2/TheOtherRoles.zip) | 2023.03.28s| v4.3.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.3.1/TheOtherRoles.zip) | 2023.02.28s| v4.3.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.3.0/TheOtherRoles.zip) | 2022.12.14s| v4.2.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.2.1/TheOtherRoles.zip) @@ -121,9 +122,21 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein # Changelog
Click to show the Changelog + +**Version 4.3.2** +- Added new option to Thief: Can Guess To Steal Role +- Added new option to Guesser Mode: Force Thief Guesser +- Added the death reason to the additional infos for ghosts. +- Added the possibility to copy and paste the settings for the current preset to / from the clipboard (buttons) +- Added a feature to the swapper: The swap resets if one of the target players is guessed or disconnects +- Fixed the Security Guard Mobile Cams and the Hacker Vitals Panel when playing on LevelImposter maps (don't rename the panels for now, please) +- Fixed several bugs in Vanilla Hide'n'Seek +- Fixed an issue where Crewmate "Fill" did not work correcty if min neutrals was set bigger than max neutrals +- Fixed a bug where the colorblind-text of the ninja remained visible when using the ninja-ability +- Fixed a bug where the bomb could explode in a meeting due to desync and bad timing +- Fixed a bug where the host Eraser could guess erased players **Version 4.3.1** -- Update to Among Us 2023.3.28 - Added new options (Information mode - Chat/Map/Both, Roles - Evil Killing / Evil) to the snitch and fixed bugs affecting the snitch - Changed the maximum amount per modifier to 15 (where applicable) - Changed the colorblind text and player name to be behind certain objects again (rock on Polus etc.) @@ -1340,6 +1353,11 @@ If the Thief tries to kill any non-killing role (Crewmate or Neutral), they die - If the option "Thief Can Kill Sheriff" is On, the Thief has tasks which will ONLY begin to count, if they kill the Sheriff. While the Thief hasn't fired, their tasks do not count towards the taskwin. - If the option "Thief Can Kill Sheriff" is Off, the Thief will not have tasks. +<<<<<<< HEAD +======= +- If the Thief kills the witch, already witched players stay witched (except for the Thief). +- If the Thief can guess to steal the role, guessing the witch will either save all targets or none of the targets (depending on the setting for Witch: Voting The Witch Saves All The Targets) +>>>>>>> dev/main ### Game Options | Name | Description | @@ -1349,6 +1367,10 @@ the Sheriff. While the Thief hasn't fired, their tasks do not count towards the | Thief Can Kill Sheriff | - | Thief Has Impostor Vision | - | Thief Can Use Vents | - +<<<<<<< HEAD +======= +| Thief Can Guess To Steal Role | - +>>>>>>> dev/main ----------------------- ## Mayor @@ -1992,6 +2014,10 @@ Players can additionally have a modifier, if enabled (e.g. Medic Guesser Mini). | Number of Neutral Guessers | - | Number of Impostor Guessers | - | Force Jackal Guesser | If set to "On", the first neutral role who will be Guesser is the Jackal. +<<<<<<< HEAD +======= +| Force Thief Guesser | If set to "On", the first (or second if Force Jackal Guesser) neutral role who will be Guesser is the Thief. +>>>>>>> dev/main | Guessers Can Have A Modifier | - | Guesser Number Of Shots | - | Guesser Can Shoot Multiple Times Per Meeting | - diff --git a/TheOtherRoles/Buttons.cs b/TheOtherRoles/Buttons.cs index 72cc7a8..f47b087 100644 --- a/TheOtherRoles/Buttons.cs +++ b/TheOtherRoles/Buttons.cs @@ -463,7 +463,7 @@ namespace TheOtherRoles medicShieldButton = new CustomButton( () => { medicShieldButton.Timer = 0f; - + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, Medic.setShieldAfterMeeting ? (byte)CustomRPC.SetFutureShielded : (byte)CustomRPC.MedicSetShielded, Hazel.SendOption.Reliable, -1); writer.Write(Medic.currentTarget.PlayerId); AmongUsClient.Instance.FinishRpcImmediately(writer); @@ -474,7 +474,7 @@ namespace TheOtherRoles Medic.meetingAfterShielding = false; SoundEffectsManager.play("medicShield"); - }, + }, () => { return Medic.medic != null && Medic.medic == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead; }, () => { return !Medic.usedShield && Medic.currentTarget && CachedPlayer.LocalPlayer.PlayerControl.CanMove; }, () => {}, @@ -650,7 +650,7 @@ namespace TheOtherRoles () => { if (GameOptionsManager.Instance.currentNormalGameOptions.MapId != 1) { if (Hacker.vitals == null) { - var e = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(x => x.gameObject.name.Contains("panel_vitals")); + var e = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(x => x.gameObject.name.Contains("panel_vitals") || x.gameObject.name.Contains("Vitals")); if (e == null || Camera.main == null) return; Hacker.vitals = UnityEngine.Object.Instantiate(e.MinigamePrefab, Camera.main.transform, false); } @@ -1262,7 +1262,7 @@ namespace TheOtherRoles if (GameOptionsManager.Instance.currentNormalGameOptions.MapId != 1) { if (SecurityGuard.minigame == null) { byte mapId = GameOptionsManager.Instance.currentNormalGameOptions.MapId; - var e = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(x => x.gameObject.name.Contains("Surv_Panel")); + var e = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(x => x.gameObject.name.Contains("Surv_Panel") || x.name.Contains("Cam")); if (mapId == 0 || mapId == 3) e = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(x => x.gameObject.name.Contains("SurvConsole")); else if (mapId == 4) e = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(x => x.gameObject.name.Contains("task_cams")); if (e == null || Camera.main == null) return; diff --git a/TheOtherRoles/CustomOptionHolder.cs b/TheOtherRoles/CustomOptionHolder.cs index 0752809..d932b15 100644 --- a/TheOtherRoles/CustomOptionHolder.cs +++ b/TheOtherRoles/CustomOptionHolder.cs @@ -234,6 +234,7 @@ namespace TheOtherRoles { public static CustomOption thiefHasImpVision; public static CustomOption thiefCanUseVents; public static CustomOption thiefCanKillSheriff; + public static CustomOption thiefCanStealWithGuess; public static CustomOption trapperSpawnRate; @@ -322,6 +323,7 @@ namespace TheOtherRoles { public static CustomOption guesserGamemodeNeutralNumber; public static CustomOption guesserGamemodeImpNumber; public static CustomOption guesserForceJackalGuesser; + public static CustomOption guesserForceThiefGuesser; public static CustomOption guesserGamemodeHaveModifier; public static CustomOption guesserGamemodeNumberOfShots; public static CustomOption guesserGamemodeHasMultipleShotsPerMeeting; @@ -613,6 +615,7 @@ namespace TheOtherRoles { thiefCanKillSheriff = CustomOption.Create(402, Types.Neutral, "Thief Can Kill Sheriff", true, thiefSpawnRate); thiefHasImpVision = CustomOption.Create(403, Types.Neutral, "Thief Has Impostor Vision", true, thiefSpawnRate); thiefCanUseVents = CustomOption.Create(404, Types.Neutral, "Thief Can Use Vents", true, thiefSpawnRate); + thiefCanStealWithGuess = CustomOption.Create(405, Types.Neutral, "Thief Can Guess To Steal A Role (If Guesser)", false, thiefSpawnRate); trapperSpawnRate = CustomOption.Create(410, Types.Crewmate, cs(Trapper.color, "Trapper"), rates, null, true); trapperCooldown = CustomOption.Create(420, Types.Crewmate, "Trapper Cooldown", 30f, 5f, 120f, 5f, trapperSpawnRate); @@ -675,6 +678,7 @@ namespace TheOtherRoles { guesserGamemodeNeutralNumber = CustomOption.Create(2002, Types.Guesser, cs(Guesser.color, "Number of Neutral Guessers"), 15f, 1f, 15f, 1f, null, true); guesserGamemodeImpNumber = CustomOption.Create(2003, Types.Guesser, cs(Guesser.color, "Number of Impostor Guessers"), 15f, 1f, 15f, 1f, null, true); guesserForceJackalGuesser = CustomOption.Create(2007, Types.Guesser, "Force Jackal Guesser", false, null, true); + guesserForceThiefGuesser = CustomOption.Create(2011, Types.Guesser, "Force Thief Guesser", false, null, true); guesserGamemodeHaveModifier = CustomOption.Create(2004, Types.Guesser, "Guessers Can Have A Modifier", true, null); guesserGamemodeNumberOfShots = CustomOption.Create(2005, Types.Guesser, "Guesser Number Of Shots", 3f, 1f, 15f, 1f, null); guesserGamemodeHasMultipleShotsPerMeeting = CustomOption.Create(2006, Types.Guesser, "Guesser Can Shoot Multiple Times Per Meeting", false, null); diff --git a/TheOtherRoles/GameHistory.cs b/TheOtherRoles/GameHistory.cs index 2d9e75e..c61ea5f 100644 --- a/TheOtherRoles/GameHistory.cs +++ b/TheOtherRoles/GameHistory.cs @@ -1,23 +1,37 @@ using System.Collections.Generic; using System; using UnityEngine; +using System.Linq; namespace TheOtherRoles { public class DeadPlayer { + public enum CustomDeathReason { + Exile, + Kill, + Disconnect, + Guess, + Shift, + LawyerSuicide, + LoverSuicide, // not necessary + WitchExile, + Bomb + }; + public PlayerControl player; public DateTime timeOfDeath; - public DeathReason deathReason; + public CustomDeathReason deathReason; public PlayerControl killerIfExisting; public bool wasCleaned; - public DeadPlayer(PlayerControl player, DateTime timeOfDeath, DeathReason deathReason, PlayerControl killerIfExisting) { + public DeadPlayer(PlayerControl player, DateTime timeOfDeath, CustomDeathReason deathReason, PlayerControl killerIfExisting) { this.player = player; this.timeOfDeath = timeOfDeath; this.deathReason = deathReason; this.killerIfExisting = killerIfExisting; this.wasCleaned = false; } + } static class GameHistory { @@ -28,5 +42,18 @@ namespace TheOtherRoles { localPlayerPositions = new List>(); deadPlayers = new List(); } + + public static void overrideDeathReasonAndKiller(PlayerControl player, DeadPlayer.CustomDeathReason deathReason, PlayerControl killer = null) { + var target = deadPlayers.FirstOrDefault(x => x.player.PlayerId == player.PlayerId); + if (target != null) { + target.deathReason = deathReason; + if (killer != null) { + target.killerIfExisting = killer; + } + } else if (player != null) { // Create dead player if needed: + var dp = new DeadPlayer(player, DateTime.UtcNow, deathReason, killer); + deadPlayers.Add(dp); + } + } } } \ No newline at end of file diff --git a/TheOtherRoles/Helpers.cs b/TheOtherRoles/Helpers.cs index c35bdb2..7b78aef 100644 --- a/TheOtherRoles/Helpers.cs +++ b/TheOtherRoles/Helpers.cs @@ -14,6 +14,7 @@ using System.Threading.Tasks; using System.Net; using TheOtherRoles.CustomGameModes; using Reactor.Utilities.Extensions; +using AmongUs.GameOptions; namespace TheOtherRoles { @@ -205,7 +206,7 @@ namespace TheOtherRoles { } public static bool shouldShowGhostInfo() { - return CachedPlayer.LocalPlayer.PlayerControl != null && CachedPlayer.LocalPlayer.PlayerControl.Data.IsDead && TORMapOptions.ghostsSeeInformation; + return CachedPlayer.LocalPlayer.PlayerControl != null && CachedPlayer.LocalPlayer.PlayerControl.Data.IsDead && TORMapOptions.ghostsSeeInformation || AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Ended; } public static void clearAllTasks(this PlayerControl player) { @@ -378,6 +379,8 @@ namespace TheOtherRoles { if (killer == null || killer.Data == null || (killer.Data.IsDead && !ignoreIfKillerIsDead) || killer.Data.Disconnected) return MurderAttemptResult.SuppressKill; // Allow non Impostor kills compared to vanilla code if (target == null || target.Data == null || target.Data.IsDead || target.Data.Disconnected) return MurderAttemptResult.SuppressKill; // Allow killing players in vents compared to vanilla code + if (GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek) return MurderAttemptResult.PerformKill; + // Handle first kill attempt if (TORMapOptions.shieldFirstKill && TORMapOptions.firstKillPlayer == target) return MurderAttemptResult.SuppressKill; @@ -417,7 +420,7 @@ namespace TheOtherRoles { } // Thief if hit crew only kill if setting says so, but also kill the thief. - else if (killer == Thief.thief && !target.Data.Role.IsImpostor && !new List {RoleInfo.jackal, Thief.canKillSheriff ? RoleInfo.sheriff : null, RoleInfo.sidekick }.Contains(targetRole)) { + else if (Thief.isFailedThiefKill(target, killer, targetRole)) { Thief.suicideFlag = true; return MurderAttemptResult.SuppressKill; } diff --git a/TheOtherRoles/Main.cs b/TheOtherRoles/Main.cs index 25d9afe..88d7472 100644 --- a/TheOtherRoles/Main.cs +++ b/TheOtherRoles/Main.cs @@ -31,7 +31,7 @@ namespace TheOtherRoles public class TheOtherRolesPlugin : BasePlugin { public const string Id = "me.eisbison.theotherroles"; - public const string VersionString = "4.3.1"; + public const string VersionString = "4.3.2"; public static uint betaDays = 0; // amount of days for the build to be usable (0 for infinite!) public static Version Version = Version.Parse(VersionString); diff --git a/TheOtherRoles/Modules/CustomOptions.cs b/TheOtherRoles/Modules/CustomOptions.cs index ac0d83b..8692cee 100644 --- a/TheOtherRoles/Modules/CustomOptions.cs +++ b/TheOtherRoles/Modules/CustomOptions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using UnityEngine; using BepInEx.Configuration; using System; +using System.IO; using System.Linq; using HarmonyLib; using Hazel; @@ -178,6 +179,74 @@ namespace TheOtherRoles { ShareOptionSelections();// Share all selections } } + + public static byte[] serializeOptions() { + using (MemoryStream memoryStream = new MemoryStream()) { + using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream)) { + int lastId = -1; + foreach (var option in CustomOption.options.OrderBy(x => x.id)) { + if (option.id == 0) continue; + bool consecutive = lastId + 1 == option.id; + lastId = option.id; + + binaryWriter.Write((byte)(option.selection + (consecutive ? 128 : 0))); + if (!consecutive) binaryWriter.Write((ushort)option.id); + } + binaryWriter.Flush(); + memoryStream.Position = 0L; + return memoryStream.ToArray(); + } + } + } + + public static void deserializeOptions(byte[] inputValues) { + BinaryReader reader = new BinaryReader(new MemoryStream(inputValues)); + int lastId = -1; + while (reader.BaseStream.Position < inputValues.Length) { + try { + int selection = reader.ReadByte(); + int id = -1; + bool consecutive = selection >= 128; + if (consecutive) { + selection -= 128; + id = lastId + 1; + } else { + id = reader.ReadUInt16(); + } + if (id == 0) continue; + lastId = id; + CustomOption option = CustomOption.options.First(option => option.id == id); + option.updateSelection(selection); + } catch (Exception e) { + TheOtherRolesPlugin.Logger.LogWarning($"{e}: while deserializing - tried to paste invalid settings!"); + } + } + } + + // Copy to or paste from clipboard (as string) + public static void copyToClipboard() { + GUIUtility.systemCopyBuffer = $"{TheOtherRolesPlugin.VersionString}!{Convert.ToBase64String(serializeOptions())}!{vanillaSettings.Value}"; + } + + public static bool pasteFromClipboard() { + string allSettings = GUIUtility.systemCopyBuffer; + try { + var settingsSplit = allSettings.Split("!"); + string versionInfo = settingsSplit[0]; + string torSettings = settingsSplit[1]; + string vanillaSettingsSub = settingsSplit[2]; + deserializeOptions(Convert.FromBase64String(torSettings)); + + vanillaSettings.Value = vanillaSettingsSub; + loadVanillaOptions(); + return true; + } catch (Exception e) { + TheOtherRolesPlugin.Logger.LogWarning($"{e}: tried to paste invalid settings!"); + SoundEffectsManager.Load(); + SoundEffectsManager.play("fail"); + return false; + } + } } [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Start))] @@ -194,7 +263,42 @@ namespace TheOtherRoles { case CustomGamemodes.HideNSeek: createHideNSeekTabs(__instance); break; - } + } + + // create copy to clipboard and paste from clipboard buttons. + var template = GameObject.Find("CloseButton"); + var copyButton = GameObject.Instantiate(template, template.transform.parent); + copyButton.transform.localPosition += Vector3.down * 0.8f; + var copyButtonPassive = copyButton.GetComponent(); + var copyButtonRenderer = copyButton.GetComponent(); + copyButtonRenderer.sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.CopyButton.png", 175f); + copyButtonPassive.OnClick.RemoveAllListeners(); + copyButtonPassive.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent(); + copyButtonPassive.OnClick.AddListener((System.Action)(() => { + copyToClipboard(); + copyButtonRenderer.color = Color.green; + __instance.StartCoroutine(Effects.Lerp(1f, new System.Action((p) => { + if (p > 0.95) + copyButtonRenderer.color = Color.white; + }))); + })); + var pasteButton = GameObject.Instantiate(template, template.transform.parent); + pasteButton.transform.localPosition += Vector3.down * 1.6f; + var pasteButtonPassive = pasteButton.GetComponent(); + var pasteButtonRenderer = pasteButton.GetComponent(); + pasteButtonRenderer.sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.PasteButton.png", 175f); + pasteButtonPassive.OnClick.RemoveAllListeners(); + pasteButtonPassive.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent(); + pasteButtonPassive.OnClick.AddListener((System.Action)(() => { + pasteButtonRenderer.color = Color.yellow; + bool success = pasteFromClipboard(); + pasteButtonRenderer.color = success ? Color.green : Color.red; + __instance.StartCoroutine(Effects.Lerp(1f, new System.Action((p) => { + if (p > 0.95) + pasteButtonRenderer.color = Color.white; + }))); + })); + } private static void createClassicTabs(GameOptionsMenu __instance) { @@ -794,8 +898,11 @@ namespace TheOtherRoles { string optionValue = ""; if (CustomOptionHolder.crewmateRolesFill.getBool()) { var crewCount = PlayerControl.AllPlayerControls.Count - GameOptionsManager.Instance.currentGameOptions.NumImpostors; - min = crewCount - CustomOptionHolder.neutralRolesCountMax.getSelection(); - max = crewCount - CustomOptionHolder.neutralRolesCountMin.getSelection(); + int minNeutral = CustomOptionHolder.neutralRolesCountMin.getSelection(); + int maxNeutral = CustomOptionHolder.neutralRolesCountMax.getSelection(); + if (minNeutral > maxNeutral) minNeutral = maxNeutral; + min = crewCount - maxNeutral; + max = crewCount - minNeutral; if (min < 0) min = 0; if (max < 0) max = 0; optionValue = "Fill: "; @@ -814,6 +921,7 @@ namespace TheOtherRoles { var optionName = CustomOptionHolder.cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Impostor Roles"); var min = CustomOptionHolder.impostorRolesCountMin.getSelection(); var max = CustomOptionHolder.impostorRolesCountMax.getSelection(); + if (max > GameOptionsManager.Instance.currentGameOptions.NumImpostors) max = GameOptionsManager.Instance.currentGameOptions.NumImpostors; if (min > max) min = max; var optionValue = (min == max) ? $"{max}" : $"{min} - {max}"; sb.AppendLine($"{optionName}: {optionValue}"); @@ -1091,4 +1199,4 @@ namespace TheOtherRoles { toggleSettingsButtonObject.transform.localPosition = __instance.MapButton.transform.localPosition + new Vector3(0, -0.66f, -500f); } } -} +} \ No newline at end of file diff --git a/TheOtherRoles/Objects/Bomb.cs b/TheOtherRoles/Objects/Bomb.cs index b5b37d0..07d8c71 100644 --- a/TheOtherRoles/Objects/Bomb.cs +++ b/TheOtherRoles/Objects/Bomb.cs @@ -85,6 +85,15 @@ namespace TheOtherRoles.Objects { var distance = Vector2.Distance(position, CachedPlayer.LocalPlayer.transform.position); // every player only checks that for their own client (desynct with positions sucks) if (distance < Bomber.destructionRange && !CachedPlayer.LocalPlayer.Data.IsDead) { Helpers.checkMurderAttemptAndKill(Bomber.bomber, CachedPlayer.LocalPlayer.PlayerControl, false, false, true, true); + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareGhostInfo, Hazel.SendOption.Reliable, -1); + writer.Write(CachedPlayer.LocalPlayer.PlayerId); + writer.Write((byte)RPCProcedure.GhostInfoTypes.DeathReasonAndKiller); + writer.Write(CachedPlayer.LocalPlayer.PlayerId); + writer.Write((byte)DeadPlayer.CustomDeathReason.Bomb); + writer.Write(Bomber.bomber.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + GameHistory.overrideDeathReasonAndKiller(CachedPlayer.LocalPlayer, DeadPlayer.CustomDeathReason.Bomb, killer: Bomber.bomber); } SoundEffectsManager.playAtPosition("bombExplosion", position, range: Bomber.hearRange) ; } @@ -100,6 +109,10 @@ namespace TheOtherRoles.Objects { } Bomber.bomb.background.transform.Rotate(Vector3.forward * 50 * Time.fixedDeltaTime); + if (MeetingHud.Instance && Bomber.bomb != null) { + Bomber.clearBomb(); + } + if (Vector2.Distance(CachedPlayer.LocalPlayer.PlayerControl.GetTruePosition(), Bomber.bomb.bomb.transform.position) > 1f) canDefuse = false; else canDefuse = true; } @@ -108,4 +121,4 @@ namespace TheOtherRoles.Objects { backgroundSprite = null; } } -} \ No newline at end of file +} diff --git a/TheOtherRoles/Patches/EndGamePatch.cs b/TheOtherRoles/Patches/EndGamePatch.cs index e95583d..304fdb9 100644 --- a/TheOtherRoles/Patches/EndGamePatch.cs +++ b/TheOtherRoles/Patches/EndGamePatch.cs @@ -52,10 +52,12 @@ namespace TheOtherRoles.Patches { internal class PlayerRoleInfo { public string PlayerName { get; set; } public List Roles {get;set;} + public string RoleNames { get; set; } public int TasksCompleted {get;set;} public int TasksTotal {get;set;} public bool IsGuesser {get; set;} public int? Kills {get; set;} + public bool IsAlive { get; set; } } } @@ -82,7 +84,8 @@ namespace TheOtherRoles.Patches { if (killCount == 0 && !(new List() { RoleInfo.sheriff, RoleInfo.jackal, RoleInfo.sidekick, RoleInfo.thief }.Contains(RoleInfo.getRoleInfoForPlayer(playerControl, false).FirstOrDefault()) || playerControl.Data.Role.IsImpostor)) { killCount = null; } - AdditionalTempData.playerRoles.Add(new AdditionalTempData.PlayerRoleInfo() { PlayerName = playerControl.Data.PlayerName, Roles = roles, TasksTotal = tasksTotal, TasksCompleted = tasksCompleted, IsGuesser = isGuesser, Kills = killCount }); + string roleString = RoleInfo.GetRolesString(playerControl, true, true, false); + AdditionalTempData.playerRoles.Add(new AdditionalTempData.PlayerRoleInfo() { PlayerName = playerControl.Data.PlayerName, Roles = roles, RoleNames = roleString, TasksTotal = tasksTotal, TasksCompleted = tasksCompleted, IsGuesser = isGuesser, Kills = killCount, IsAlive = !playerControl.Data.IsDead }); } // Remove Jester, Arsonist, Vulture, Jackal, former Jackals and Sidekick from winners (if they win, they'll be readded) @@ -343,11 +346,12 @@ namespace TheOtherRoles.Patches { } roleSummaryText.AppendLine("Players and roles at the end of the game:"); foreach(var data in AdditionalTempData.playerRoles) { - var roles = string.Join(" ", data.Roles.Select(x => Helpers.cs(x.color, x.name))); - if (data.IsGuesser) roles += " (Guesser)"; + //var roles = string.Join(" ", data.Roles.Select(x => Helpers.cs(x.color, x.name))); + string roles = data.RoleNames; + //if (data.IsGuesser) roles += " (Guesser)"; var taskInfo = data.TasksTotal > 0 ? $" - ({data.TasksCompleted}/{data.TasksTotal})" : ""; if (data.Kills != null) taskInfo += $" - (Kills: {data.Kills})"; - roleSummaryText.AppendLine($"{data.PlayerName} - {roles}{taskInfo}"); + roleSummaryText.AppendLine($"{Helpers.cs(data.IsAlive ? Color.white : new Color(.7f,.7f,.7f), data.PlayerName)} - {roles}{taskInfo}"); } TMPro.TMP_Text roleSummaryTextMesh = roleSummary.GetComponent(); roleSummaryTextMesh.alignment = TMPro.TextAlignmentOptions.TopLeft; diff --git a/TheOtherRoles/Patches/ExileControllerPatch.cs b/TheOtherRoles/Patches/ExileControllerPatch.cs index 6620fe2..b294f0b 100644 --- a/TheOtherRoles/Patches/ExileControllerPatch.cs +++ b/TheOtherRoles/Patches/ExileControllerPatch.cs @@ -14,7 +14,7 @@ namespace TheOtherRoles.Patches { [HarmonyPriority(Priority.First)] class ExileControllerBeginPatch { public static GameData.PlayerInfo lastExiled; - public static void Prefix(ExileController __instance, [HarmonyArgument(0)]ref GameData.PlayerInfo exiled, [HarmonyArgument(1)]bool tie) { + public static void Prefix(ExileController __instance, [HarmonyArgument(0)] ref GameData.PlayerInfo exiled, [HarmonyArgument(1)] bool tie) { lastExiled = exiled; // Medic shield @@ -43,6 +43,7 @@ namespace TheOtherRoles.Patches { writer.Write(target.PlayerId); AmongUsClient.Instance.FinishRpcImmediately(writer); RPCProcedure.erasePlayerRoles(target.PlayerId); + Eraser.alreadyErased.Add(target.PlayerId); } } } @@ -63,19 +64,28 @@ namespace TheOtherRoles.Patches { if ((witchDiesWithExiledLover || exiledIsWitch) && Witch.witchVoteSavesTargets) Witch.futureSpelled = new List(); foreach (PlayerControl target in Witch.futureSpelled) { - if (target != null && !target.Data.IsDead && Helpers.checkMuderAttempt(Witch.witch, target, true) == MurderAttemptResult.PerformKill) - { + if (target != null && !target.Data.IsDead && Helpers.checkMuderAttempt(Witch.witch, target, true) == MurderAttemptResult.PerformKill) { if (exiled != null && Lawyer.lawyer != null && (target == Lawyer.lawyer || target == Lovers.otherLover(Lawyer.lawyer)) && Lawyer.target != null && Lawyer.isProsecutor && Lawyer.target.PlayerId == exiled.PlayerId) continue; - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.UncheckedExilePlayer, Hazel.SendOption.Reliable, -1); - writer.Write(target.PlayerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.uncheckedExilePlayer(target.PlayerId); if (target == Lawyer.target && Lawyer.lawyer != null) { MessageWriter writer2 = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.LawyerPromotesToPursuer, Hazel.SendOption.Reliable, -1); AmongUsClient.Instance.FinishRpcImmediately(writer2); RPCProcedure.lawyerPromotesToPursuer(); } + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.UncheckedExilePlayer, Hazel.SendOption.Reliable, -1); + writer.Write(target.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.uncheckedExilePlayer(target.PlayerId); + + MessageWriter writer3 = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareGhostInfo, Hazel.SendOption.Reliable, -1); + writer3.Write(CachedPlayer.LocalPlayer.PlayerId); + writer3.Write((byte)RPCProcedure.GhostInfoTypes.DeathReasonAndKiller); + writer3.Write(target.PlayerId); + writer3.Write((byte)DeadPlayer.CustomDeathReason.WitchExile); + writer3.Write(Witch.witch.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer3); + + GameHistory.overrideDeathReasonAndKiller(target, DeadPlayer.CustomDeathReason.WitchExile, killer: Witch.witch); } } } @@ -92,7 +102,7 @@ namespace TheOtherRoles.Patches { TORMapOptions.camerasToAdd = new List(); foreach (Vent vent in TORMapOptions.ventsToSeal) { - PowerTools.SpriteAnim animator = vent.GetComponent(); + PowerTools.SpriteAnim animator = vent.GetComponent(); animator?.Stop(); vent.EnterVentAnim = vent.ExitVentAnim = null; vent.myRend.sprite = animator == null ? SecurityGuard.getStaticVentSealedSprite() : SecurityGuard.getAnimatedVentSealedSprite(); @@ -149,9 +159,6 @@ namespace TheOtherRoles.Patches { else if (exiled != null && Jester.jester != null && Jester.jester.PlayerId == exiled.PlayerId) { Jester.triggerJesterWin = true; } - // Prosecutor win condition - else if (exiled != null && Lawyer.lawyer != null && Lawyer.target != null && Lawyer.isProsecutor && Lawyer.target.PlayerId == exiled.PlayerId && !Lawyer.lawyer.Data.IsDead) - Lawyer.triggerProsecutorWin = true; // Reset custom button timers where necessary @@ -173,14 +180,14 @@ namespace TheOtherRoles.Patches { var rend = soul.AddComponent(); soul.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover); rend.sprite = Seer.getSoulSprite(); - - if(Seer.limitSoulDuration) { + + if (Seer.limitSoulDuration) { FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(Seer.soulDuration, new Action((p) => { if (rend != null) { var tmp = rend.color; tmp.a = Mathf.Clamp01(1 - p); rend.color = tmp; - } + } if (p == 1f && rend != null && rend.gameObject != null) UnityEngine.Object.Destroy(rend.gameObject); }))); } @@ -208,8 +215,7 @@ namespace TheOtherRoles.Patches { } // Deputy check Promotion, see if the sheriff still exists. The promotion will be after the meeting. - if (Deputy.deputy != null) - { + if (Deputy.deputy != null) { PlayerControlFixedUpdatePatch.deputyCheckPromotion(isMeeting: true); } @@ -224,8 +230,8 @@ namespace TheOtherRoles.Patches { Medium.souls = new List(); } - if (Medium.featureDeadBodies != null) { - foreach ((DeadPlayer db, Vector3 ps) in Medium.featureDeadBodies) { + if (Medium.futureDeadBodies != null) { + foreach ((DeadPlayer db, Vector3 ps) in Medium.futureDeadBodies) { GameObject s = new GameObject(); //s.transform.position = ps; s.transform.position = new Vector3(ps.x, ps.y, ps.y / 1000 - 1f); @@ -235,8 +241,8 @@ namespace TheOtherRoles.Patches { rend.sprite = Medium.getSoulSprite(); Medium.souls.Add(rend); } - Medium.deadBodies = Medium.featureDeadBodies; - Medium.featureDeadBodies = new List>(); + Medium.deadBodies = Medium.futureDeadBodies; + Medium.futureDeadBodies = new List>(); } } @@ -250,7 +256,7 @@ namespace TheOtherRoles.Patches { foreach (Trap trap in Trap.traps) trap.triggerable = false; FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(GameOptionsManager.Instance.currentNormalGameOptions.KillCooldown / 2 + 2, new Action((p) => { - if (p == 1f) foreach (Trap trap in Trap.traps) trap.triggerable = true; + if (p == 1f) foreach (Trap trap in Trap.traps) trap.triggerable = true; }))); } } @@ -265,7 +271,7 @@ namespace TheOtherRoles.Patches { [HarmonyPatch(typeof(TranslationController), nameof(TranslationController.GetString), new Type[] { typeof(StringNames), typeof(Il2CppReferenceArray) })] class ExileControllerMessagePatch { - static void Postfix(ref string __result, [HarmonyArgument(0)]StringNames id) { + static void Postfix(ref string __result, [HarmonyArgument(0)] StringNames id) { try { if (ExileController.Instance != null && ExileController.Instance.exiled != null) { PlayerControl player = Helpers.playerById(ExileController.Instance.exiled.Object.PlayerId); @@ -286,4 +292,4 @@ namespace TheOtherRoles.Patches { } } } -} +} \ No newline at end of file diff --git a/TheOtherRoles/Patches/HauntMenuMinigamePatch.cs b/TheOtherRoles/Patches/HauntMenuMinigamePatch.cs index ac44ba1..b378f32 100644 --- a/TheOtherRoles/Patches/HauntMenuMinigamePatch.cs +++ b/TheOtherRoles/Patches/HauntMenuMinigamePatch.cs @@ -71,7 +71,7 @@ namespace TheOtherRoles.Patches { [HarmonyPostfix] [HarmonyPatch(typeof(AbilityButton), nameof(AbilityButton.Update))] public static void showOrHideAbilityButtonPostfix(AbilityButton __instance) { - if (CachedPlayer.LocalPlayer.Data.IsDead && CustomOptionHolder.finishTasksBeforeHauntingOrZoomingOut.getBool()) { + if (CachedPlayer.LocalPlayer.Data.IsDead && (CustomOptionHolder.finishTasksBeforeHauntingOrZoomingOut.getBool() || GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek)) { // player has haunt button. var (playerCompleted, playerTotal) = TasksHandler.taskInfo(CachedPlayer.LocalPlayer.Data); int numberOfLeftTasks = playerTotal - playerCompleted; diff --git a/TheOtherRoles/Patches/MapBehaviourPatch.cs b/TheOtherRoles/Patches/MapBehaviourPatch.cs index 8463591..4825fd4 100644 --- a/TheOtherRoles/Patches/MapBehaviourPatch.cs +++ b/TheOtherRoles/Patches/MapBehaviourPatch.cs @@ -72,7 +72,7 @@ namespace TheOtherRoles.Patches { } } } - HudManagerUpdate.CloseSettings(); - } + HudManagerUpdate.CloseSettings(); + } } } diff --git a/TheOtherRoles/Patches/MeetingPatch.cs b/TheOtherRoles/Patches/MeetingPatch.cs index 02c540a..ecefe9f 100644 --- a/TheOtherRoles/Patches/MeetingPatch.cs +++ b/TheOtherRoles/Patches/MeetingPatch.cs @@ -296,6 +296,46 @@ namespace TheOtherRoles.Patches { } } + public static void swapperCheckAndReturnSwap(MeetingHud __instance, byte dyingPlayerId) { + // someone was guessed or dced in the meeting, check if this affects the swapper. + if (Swapper.swapper == null || __instance.state == MeetingHud.VoteStates.Results) return; + + // reset swap. + bool reset = false; + if (dyingPlayerId == Swapper.playerId1 || dyingPlayerId == Swapper.playerId2) { + reset = true; + Swapper.playerId1 = Swapper.playerId2 = byte.MaxValue; + } + + + // Only for the swapper: Reset all the buttons and charges value to their original state. + if (CachedPlayer.LocalPlayer.PlayerControl != Swapper.swapper) return; + + + // check if dying player was a selected player (but not confirmed yet) + for (int i = 0; i < __instance.playerStates.Count; i++) { + reset = reset || selections[i] && __instance.playerStates[i].TargetPlayerId == dyingPlayerId; + if (reset) break; + } + + if (!reset) return; + + + for (int i = 0; i < selections.Length; i++) { + selections[i] = false; + PlayerVoteArea playerVoteArea = __instance.playerStates[i]; + if (playerVoteArea.AmDead || (playerVoteArea.TargetPlayerId == Swapper.swapper.PlayerId && Swapper.canOnlySwapOthers)) continue; + renderers[i].color = Color.red; + Swapper.charges++; + int copyI = i; + swapperButtonList[i].OnClick.RemoveAllListeners(); + swapperButtonList[i].OnClick.AddListener((System.Action)(() => swapperOnClick(copyI, __instance))); + } + meetingExtraButtonText.text = $"Swaps: {Swapper.charges}"; + meetingExtraButtonLabel.text = Helpers.cs(Color.red, "Confirm Swap"); + + } + static void mayorToggleVoteTwice(MeetingHud __instance) { __instance.playerStates[0].Cancel(); // This will stop the underlying buttons of the template from showing up if (__instance.state == MeetingHud.VoteStates.Results || Mayor.mayor.Data.IsDead) return; @@ -667,6 +707,7 @@ namespace TheOtherRoles.Patches { else message += p.Data.PlayerName + "\n"; } FastDestroyableSingleton.Instance.Chat.AddChat(Trapper.trapper, $"{message}"); + } } @@ -698,6 +739,7 @@ namespace TheOtherRoles.Patches { } } + if (CachedPlayer.LocalPlayer.Data.IsDead && output != "") FastDestroyableSingleton.Instance.Chat.AddChat(CachedPlayer.LocalPlayer, $"{output}"); Trapper.playersOnMap = new List(); diff --git a/TheOtherRoles/Patches/PlayerControlPatch.cs b/TheOtherRoles/Patches/PlayerControlPatch.cs index 6669eb1..1da097d 100644 --- a/TheOtherRoles/Patches/PlayerControlPatch.cs +++ b/TheOtherRoles/Patches/PlayerControlPatch.cs @@ -886,6 +886,8 @@ namespace TheOtherRoles.Patches { } } + + public static void trapperUpdate() { if (Trapper.trapper == null || CachedPlayer.LocalPlayer.PlayerControl != Trapper.trapper || Trapper.trapper.Data.IsDead) return; var (playerCompleted, _) = TasksHandler.taskInfo(Trapper.trapper.Data); @@ -956,7 +958,6 @@ namespace TheOtherRoles.Patches { } } } - public static void Postfix(PlayerControl __instance) { if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started || GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek) return; @@ -1161,7 +1162,7 @@ namespace TheOtherRoles.Patches { public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)]PlayerControl target) { // Collect dead player info - DeadPlayer deadPlayer = new DeadPlayer(target, DateTime.UtcNow, DeathReason.Kill, __instance); + DeadPlayer deadPlayer = new DeadPlayer(target, DateTime.UtcNow, DeadPlayer.CustomDeathReason.Kill, __instance); GameHistory.deadPlayers.Add(deadPlayer); // Reset killer to crewmate if resetToCrewmate @@ -1180,6 +1181,7 @@ namespace TheOtherRoles.Patches { PlayerControl otherLover = target == Lovers.lover1 ? Lovers.lover2 : Lovers.lover1; if (otherLover != null && !otherLover.Data.IsDead && Lovers.bothDie) { otherLover.MurderPlayer(otherLover); + GameHistory.overrideDeathReasonAndKiller(otherLover, DeadPlayer.CustomDeathReason.LoverSuicide); } } @@ -1208,7 +1210,7 @@ namespace TheOtherRoles.Patches { // Medium add body if (Medium.deadBodies != null) { - Medium.featureDeadBodies.Add(new Tuple(deadPlayer, target.transform.position)); + Medium.futureDeadBodies.Add(new Tuple(deadPlayer, target.transform.position)); } // Set bountyHunter cooldown @@ -1349,9 +1351,10 @@ namespace TheOtherRoles.Patches { public static void Postfix(PlayerControl __instance) { // Collect dead player info - DeadPlayer deadPlayer = new DeadPlayer(__instance, DateTime.UtcNow, DeathReason.Exile, null); + DeadPlayer deadPlayer = new DeadPlayer(__instance, DateTime.UtcNow, DeadPlayer.CustomDeathReason.Exile, null); GameHistory.deadPlayers.Add(deadPlayer); + // Remove fake tasks when player dies if (__instance.hasFakeTasks() || __instance == Lawyer.lawyer || __instance == Pursuer.pursuer || __instance == Thief.thief) __instance.clearAllTasks(); @@ -1359,10 +1362,12 @@ namespace TheOtherRoles.Patches { // 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 (otherLover != null && !otherLover.Data.IsDead && Lovers.bothDie) + if (otherLover != null && !otherLover.Data.IsDead && Lovers.bothDie) { otherLover.Exiled(); - } - + GameHistory.overrideDeathReasonAndKiller(otherLover, DeadPlayer.CustomDeathReason.LoverSuicide); + } + + } // Sidekick promotion trigger on exile if (Sidekick.promotesToJackal && Sidekick.sidekick != null && !Sidekick.sidekick.Data.IsDead && __instance == Jackal.jackal && Jackal.jackal == CachedPlayer.LocalPlayer.PlayerControl) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.SidekickPromotes, Hazel.SendOption.Reliable, -1); @@ -1372,6 +1377,7 @@ namespace TheOtherRoles.Patches { // Pursuer promotion trigger on exile & suicide (the host sends the call such that everyone recieves the update before a possible game End) if (Lawyer.lawyer != null && __instance == Lawyer.target) { + PlayerControl lawyer = Lawyer.lawyer; if (AmongUsClient.Instance.AmHost && ((Lawyer.target != Jester.jester && !Lawyer.isProsecutor) || Lawyer.targetWasGuessed)) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.LawyerPromotesToPursuer, Hazel.SendOption.Reliable, -1); AmongUsClient.Instance.FinishRpcImmediately(writer); @@ -1381,6 +1387,15 @@ namespace TheOtherRoles.Patches { if (!Lawyer.targetWasGuessed && !Lawyer.isProsecutor) { if (Lawyer.lawyer != null) Lawyer.lawyer.Exiled(); if (Pursuer.pursuer != null) Pursuer.pursuer.Exiled(); + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareGhostInfo, Hazel.SendOption.Reliable, -1); + writer.Write(CachedPlayer.LocalPlayer.PlayerId); + writer.Write((byte)RPCProcedure.GhostInfoTypes.DeathReasonAndKiller); + writer.Write(lawyer.PlayerId); + writer.Write((byte)DeadPlayer.CustomDeathReason.LawyerSuicide); + writer.Write(lawyer.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + GameHistory.overrideDeathReasonAndKiller(lawyer, DeadPlayer.CustomDeathReason.LawyerSuicide, lawyer); // TODO: only executed on host?! } } } @@ -1428,4 +1443,13 @@ namespace TheOtherRoles.Patches { return false; } } + + [HarmonyPatch(typeof(GameData), nameof(GameData.HandleDisconnect), new[] {typeof(PlayerControl), typeof(DisconnectReasons) })] + public static class GameDataHandleDisconnectPatch { + public static void Prefix(GameData __instance, PlayerControl player, DisconnectReasons reason) { + if (MeetingHud.Instance) { + MeetingHudPatch.swapperCheckAndReturnSwap(MeetingHud.Instance, player.PlayerId); + } + } + } } diff --git a/TheOtherRoles/Patches/RoleAssignmentPatch.cs b/TheOtherRoles/Patches/RoleAssignmentPatch.cs index 0ccd570..a403f0e 100644 --- a/TheOtherRoles/Patches/RoleAssignmentPatch.cs +++ b/TheOtherRoles/Patches/RoleAssignmentPatch.cs @@ -81,17 +81,17 @@ namespace TheOtherRoles.Patches { var impostorMin = CustomOptionHolder.impostorRolesCountMin.getSelection(); var impostorMax = CustomOptionHolder.impostorRolesCountMax.getSelection(); - // Automatically force everyone to get a role by setting crew Min / Max according to Neutral Settings - if (CustomOptionHolder.crewmateRolesFill.getBool()) { - crewmateMax = crewmates.Count - neutralMin; - crewmateMin = crewmates.Count - neutralMax; - } - // Make sure min is less or equal to max if (crewmateMin > crewmateMax) crewmateMin = crewmateMax; if (neutralMin > neutralMax) neutralMin = neutralMax; if (impostorMin > impostorMax) impostorMin = impostorMax; + // Automatically force everyone to get a role by setting crew Min / Max according to Neutral Settings + if (CustomOptionHolder.crewmateRolesFill.getBool()) { + crewmateMax = crewmates.Count - neutralMin; + crewmateMin = crewmates.Count - neutralMax; + } + // Get the maximum allowed count of each role type based on the minimum and maximum option int crewCountSettings = rnd.Next(crewmateMin, crewmateMax + 1); int neutralCountSettings = rnd.Next(neutralMin, neutralMax + 1); @@ -487,13 +487,18 @@ namespace TheOtherRoles.Patches { neutralPlayer.RemoveAll(x => !Helpers.isNeutral(x)); crewPlayer.RemoveAll(x => x.Data.Role.IsImpostor || Helpers.isNeutral(x)); assignGuesserGamemodeToPlayers(crewPlayer, Mathf.RoundToInt(CustomOptionHolder.guesserGamemodeCrewNumber.getFloat())); - assignGuesserGamemodeToPlayers(neutralPlayer, Mathf.RoundToInt(CustomOptionHolder.guesserGamemodeNeutralNumber.getFloat()), CustomOptionHolder.guesserForceJackalGuesser.getBool()); + assignGuesserGamemodeToPlayers(neutralPlayer, Mathf.RoundToInt(CustomOptionHolder.guesserGamemodeNeutralNumber.getFloat()), CustomOptionHolder.guesserForceJackalGuesser.getBool(), CustomOptionHolder.guesserForceThiefGuesser.getBool()); assignGuesserGamemodeToPlayers(impPlayer, Mathf.RoundToInt(CustomOptionHolder.guesserGamemodeImpNumber.getFloat())); } - private static void assignGuesserGamemodeToPlayers(List playerList, int count, bool forceJackal = false) { + private static void assignGuesserGamemodeToPlayers(List playerList, int count, bool forceJackal = false, bool forceThief = false) { for (int i = 0; i < count && playerList.Count > 0; i++) { var index = rnd.Next(0, playerList.Count); + if (forceThief && !forceJackal) { + if (Thief.thief != null) + index = playerList.FindIndex(x => x == Thief.thief); + forceThief = false; + } if (forceJackal) { if (Jackal.jackal != null) index = playerList.FindIndex(x => x == Jackal.jackal); diff --git a/TheOtherRoles/RPC.cs b/TheOtherRoles/RPC.cs index 9405f5f..b149343 100644 --- a/TheOtherRoles/RPC.cs +++ b/TheOtherRoles/RPC.cs @@ -15,10 +15,11 @@ using TheOtherRoles.Utilities; using TheOtherRoles.CustomGameModes; using AmongUs.Data; using AmongUs.GameOptions; +using UnityEngine.UIElements; namespace TheOtherRoles { - enum RoleId { + public enum RoleId { Jester, Mayor, Portalmaker, @@ -481,8 +482,8 @@ namespace TheOtherRoles } public static void cleanBody(byte playerId, byte cleaningPlayerId) { - if (Medium.featureDeadBodies != null) { - var deadBody = Medium.featureDeadBodies.Find(x => x.Item1.player.PlayerId == playerId).Item1; + if (Medium.futureDeadBodies != null) { + var deadBody = Medium.futureDeadBodies.Find(x => x.Item1.player.PlayerId == playerId).Item1; if (deadBody != null) deadBody.wasCleaned = true; } @@ -558,6 +559,7 @@ namespace TheOtherRoles // Suicide (exile) when impostor or impostor variants if (player.Data.Role.IsImpostor || Helpers.isNeutral(player)) { oldShifter.Exiled(); + GameHistory.overrideDeathReasonAndKiller(oldShifter, DeadPlayer.CustomDeathReason.Shift, player); if (oldShifter == Lawyer.target && AmongUsClient.Instance.AmHost && Lawyer.lawyer != null) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.LawyerPromotesToPursuer, Hazel.SendOption.Reliable, -1); AmongUsClient.Instance.FinishRpcImmediately(writer); @@ -799,16 +801,20 @@ namespace TheOtherRoles { target.cosmetics.currentBodySprite.BodySprite.color = Color.white; target.cosmetics.colorBlindText.gameObject.SetActive(DataManager.Settings.Accessibility.ColorBlindMode); + target.cosmetics.colorBlindText.color = target.cosmetics.colorBlindText.color.SetAlpha(1f); + if (Camouflager.camouflageTimer <= 0) target.setDefaultLook(); Ninja.isInvisble = false; return; } target.setLook("", 6, "", "", "", ""); - Color color = Color.clear; - if (CachedPlayer.LocalPlayer.Data.Role.IsImpostor || CachedPlayer.LocalPlayer.Data.IsDead) color.a = 0.1f; + Color color = Color.clear; + bool canSee = CachedPlayer.LocalPlayer.Data.Role.IsImpostor || CachedPlayer.LocalPlayer.Data.IsDead; + if (canSee) color.a = 0.1f; target.cosmetics.currentBodySprite.BodySprite.color = color; target.cosmetics.colorBlindText.gameObject.SetActive(false); + target.cosmetics.colorBlindText.color = target.cosmetics.colorBlindText.color.SetAlpha(canSee ? 0.1f : 0f); Ninja.invisibleTimer = Ninja.invisibleDuration; Ninja.isInvisble = true; } @@ -925,12 +931,23 @@ namespace TheOtherRoles if (Lawyer.target != null && dyingTarget == Lawyer.target) Lawyer.targetWasGuessed = true; // Lawyer shouldn't be exiled with the client for guesses PlayerControl dyingLoverPartner = Lovers.bothDie ? dyingTarget.getPartner() : null; // Lover check if (Lawyer.target != null && dyingLoverPartner == Lawyer.target) Lawyer.targetWasGuessed = true; // Lawyer shouldn't be exiled with the client for guesses + + PlayerControl guesser = Helpers.playerById(killerId); + if (Thief.thief != null && Thief.thief.PlayerId == killerId && Thief.canStealWithGuess) { + RoleInfo roleInfo = RoleInfo.allRoleInfos.FirstOrDefault(x => (byte)x.roleId == guessedRoleId); + if (!Thief.thief.Data.IsDead && !Thief.isFailedThiefKill(dyingTarget, guesser, roleInfo)) { + RPCProcedure.thiefStealsRole(dyingTarget.PlayerId); + } + } + dyingTarget.Exiled(); + GameHistory.overrideDeathReasonAndKiller(dyingTarget, DeadPlayer.CustomDeathReason.Guess, guesser); byte partnerId = dyingLoverPartner != null ? dyingLoverPartner.PlayerId : dyingTargetId; HandleGuesser.remainingShots(killerId, true); if (Constants.ShouldPlaySfx()) SoundManager.Instance.PlaySound(dyingTarget.KillSfx, false, 0.8f); if (MeetingHud.Instance) { + MeetingHudPatch.swapperCheckAndReturnSwap(MeetingHud.Instance, dyingTargetId); foreach (PlayerVoteArea pva in MeetingHud.Instance.playerStates) { if (pva.TargetPlayerId == dyingTargetId || pva.TargetPlayerId == partnerId) { pva.SetDead(pva.DidReport, true); @@ -948,7 +965,6 @@ namespace TheOtherRoles if (AmongUsClient.Instance.AmHost) MeetingHud.Instance.CheckForEndVoting(); } - PlayerControl guesser = Helpers.playerById(killerId); if (FastDestroyableSingleton.Instance != null && guesser != null) if (CachedPlayer.LocalPlayer.PlayerControl == dyingTarget) { FastDestroyableSingleton.Instance.KillOverlay.ShowKillAnimation(guesser.Data, dyingTarget.Data); @@ -972,7 +988,7 @@ namespace TheOtherRoles } } - + PlayerControl guessedTarget = Helpers.playerById(guessedTargetId); if (CachedPlayer.LocalPlayer.Data.IsDead && guessedTarget != null && guesser != null) { RoleInfo roleInfo = RoleInfo.allRoleInfos.FirstOrDefault(x => (byte)x.roleId == guessedRoleId); @@ -1032,7 +1048,14 @@ namespace TheOtherRoles if (target == Cleaner.cleaner) Cleaner.cleaner = thief; if (target == Warlock.warlock) Warlock.warlock = thief; if (target == BountyHunter.bountyHunter) BountyHunter.bountyHunter = thief; - if (target == Witch.witch) Witch.witch = thief; + if (target == Witch.witch) { + Witch.witch = thief; + if (MeetingHud.Instance) + if (Witch.witchVoteSavesTargets) // In a meeting, if the thief guesses the witch, all targets are saved or no target is saved. + Witch.futureSpelled = new(); + else // If thief kills witch during the round, remove the thief from the list of spelled people, keep the rest + Witch.futureSpelled.RemoveAll(x => x.PlayerId == thief.PlayerId); + } if (target == Ninja.ninja) Ninja.ninja = thief; if (target == Bomber.bomber) Bomber.bomber = thief; if (target.Data.Role.IsImpostor) { @@ -1111,6 +1134,7 @@ namespace TheOtherRoles BlankUsed, DetectiveOrMedicInfo, VampireTimer, + DeathReasonAndKiller, } public static void receiveGhostInfo (byte senderId, MessageReader reader) { @@ -1152,6 +1176,9 @@ namespace TheOtherRoles case GhostInfoTypes.VampireTimer: HudManagerStartPatch.vampireKillButton.Timer = (float)reader.ReadByte(); break; + case GhostInfoTypes.DeathReasonAndKiller: + GameHistory.overrideDeathReasonAndKiller(Helpers.playerById(reader.ReadByte()), (DeadPlayer.CustomDeathReason)reader.ReadByte(), Helpers.playerById(reader.ReadByte())); + break; } } diff --git a/TheOtherRoles/Resources/CopyButton.png b/TheOtherRoles/Resources/CopyButton.png new file mode 100644 index 0000000000000000000000000000000000000000..4d769648a2072f8842dbdbe8c799c285ea04e419 GIT binary patch literal 18976 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 { return x.PlayerControl != Arsonist.arsonist && !x.Data.IsDead && !x.Data.Disconnected && !Arsonist.dousedPlayers.Any(y => y.PlayerId == x.PlayerId); })} left)"); if (p == Jackal.fakeSidekick) roleName = Helpers.cs(Sidekick.color, $" (fake SK)") + roleName; + // Death Reason on Ghosts + if (p.Data.IsDead) { + string deathReasonString = ""; + var deadPlayer = GameHistory.deadPlayers.FirstOrDefault(x => x.player.PlayerId == p.PlayerId); + + Color killerColor = new(); + if (deadPlayer != null && deadPlayer.killerIfExisting != null) { + killerColor = RoleInfo.getRoleInfoForPlayer(deadPlayer.killerIfExisting, false).FirstOrDefault().color; + } + + if (deadPlayer != null) { + switch (deadPlayer.deathReason) { + case DeadPlayer.CustomDeathReason.Disconnect: + deathReasonString = " - disconnected"; + break; + case DeadPlayer.CustomDeathReason.Exile: + deathReasonString = " - voted out"; + break; + case DeadPlayer.CustomDeathReason.Kill: + deathReasonString = $" - killed by {Helpers.cs(killerColor, deadPlayer.killerIfExisting.Data.PlayerName)}"; + break; + case DeadPlayer.CustomDeathReason.Guess: + if (deadPlayer.killerIfExisting.Data.PlayerName == p.Data.PlayerName) + deathReasonString = $" - failed guess"; + else + deathReasonString = $" - guessed by {Helpers.cs(killerColor, deadPlayer.killerIfExisting.Data.PlayerName)}"; + break; + case DeadPlayer.CustomDeathReason.Shift: + deathReasonString = $" - {Helpers.cs(Color.yellow, "shifted")} {Helpers.cs(killerColor, deadPlayer.killerIfExisting.Data.PlayerName)}"; + break; + case DeadPlayer.CustomDeathReason.WitchExile: + deathReasonString = $" - {Helpers.cs(Witch.color, "witched")} by {Helpers.cs(killerColor, deadPlayer.killerIfExisting.Data.PlayerName)}"; + break; + case DeadPlayer.CustomDeathReason.LoverSuicide: + deathReasonString = $" - {Helpers.cs(Lovers.color, "lover died")}"; + break; + case DeadPlayer.CustomDeathReason.LawyerSuicide: + deathReasonString = $" - {Helpers.cs(Lawyer.color, "bad Lawyer")}"; + break; + case DeadPlayer.CustomDeathReason.Bomb: + deathReasonString = $" - bombed by {Helpers.cs(killerColor, deadPlayer.killerIfExisting.Data.PlayerName)}"; + break; + } + roleName = roleName + deathReasonString; + } + } } } return roleName; diff --git a/TheOtherRoles/TheOtherRoles.cs b/TheOtherRoles/TheOtherRoles.cs index fbd9a3d..5891768 100644 --- a/TheOtherRoles/TheOtherRoles.cs +++ b/TheOtherRoles/TheOtherRoles.cs @@ -639,7 +639,7 @@ namespace TheOtherRoles if (p == Ninja.ninja && Ninja.isInvisble) continue; p.setDefaultLook(); - } + } } public static void clearAndReload() { @@ -1346,7 +1346,7 @@ namespace TheOtherRoles public static DeadPlayer soulTarget; public static Color color = new Color32(98, 120, 115, byte.MaxValue); public static List> deadBodies = new List>(); - public static List> featureDeadBodies = new List>(); + public static List> futureDeadBodies = new List>(); public static List souls = new List(); public static DateTime meetingStartTime = DateTime.UtcNow; @@ -1388,7 +1388,7 @@ namespace TheOtherRoles target = null; soulTarget = null; deadBodies = new List>(); - featureDeadBodies = new List>(); + futureDeadBodies = new List>(); souls = new List(); meetingStartTime = DateTime.UtcNow; cooldown = CustomOptionHolder.mediumCooldown.getFloat(); @@ -1651,6 +1651,7 @@ namespace TheOtherRoles public static bool hasImpostorVision; public static bool canUseVents; public static bool canKillSheriff; + public static bool canStealWithGuess; public static void clearAndReload() { thief = null; @@ -1661,10 +1662,15 @@ namespace TheOtherRoles cooldown = CustomOptionHolder.thiefCooldown.getFloat(); canUseVents = CustomOptionHolder.thiefCanUseVents.getBool(); canKillSheriff = CustomOptionHolder.thiefCanKillSheriff.getBool(); + canStealWithGuess = CustomOptionHolder.thiefCanStealWithGuess.getBool(); + } + + public static bool isFailedThiefKill(PlayerControl target, PlayerControl killer, RoleInfo targetRole) { + return killer == Thief.thief && !target.Data.Role.IsImpostor && !new List { RoleInfo.jackal, canKillSheriff ? RoleInfo.sheriff : null, RoleInfo.sidekick }.Contains(targetRole); } } - public static class Trapper { + public static class Trapper { public static PlayerControl trapper; public static Color color = new Color32(110, 57, 105, byte.MaxValue); diff --git a/TheOtherRoles/TheOtherRoles.csproj b/TheOtherRoles/TheOtherRoles.csproj index cda0696..74a639b 100644 --- a/TheOtherRoles/TheOtherRoles.csproj +++ b/TheOtherRoles/TheOtherRoles.csproj @@ -1,7 +1,7 @@  net6.0 - 4.3.1 + 4.3.2 TheOtherRoles Eisbison latest @@ -13,8 +13,8 @@ - - + + -- 2.39.5