From 40b9dfdb1db70f12fb28562b3b44486aa7a83a77 Mon Sep 17 00:00:00 2001 From: Eisbison Date: Wed, 17 Nov 2021 11:52:36 +0100 Subject: [PATCH] v3.1.0 --- README.md | 35 +- TheOtherRoles/Buttons.cs | 77 +-- ...CustomOptions.cs => CustomOptionHolder.cs} | 437 +----------------- TheOtherRoles/Helpers.cs | 102 ++-- TheOtherRoles/Main.cs | 2 +- TheOtherRoles/MapOptions.cs | 2 + TheOtherRoles/Modules/CustomColors.cs | 8 - TheOtherRoles/Modules/CustomOptions.cs | 433 +++++++++++++++++ TheOtherRoles/Patches/CredentialsPatch.cs | 13 +- TheOtherRoles/Patches/ExileControllerPatch.cs | 3 + .../Patches/GameStartManagerPatch.cs | 67 ++- TheOtherRoles/Patches/MeetingPatch.cs | 16 +- TheOtherRoles/Patches/PlayerControlPatch.cs | 91 ++-- TheOtherRoles/Patches/UpdatePatch.cs | 11 + TheOtherRoles/Patches/UsablesPatch.cs | 33 +- TheOtherRoles/RPC.cs | 79 +--- TheOtherRoles/Resources/PathfindButton.png | Bin 0 -> 11237 bytes TheOtherRoles/TheOtherRoles.cs | 30 ++ TheOtherRoles/TheOtherRoles.csproj | 7 +- 19 files changed, 794 insertions(+), 652 deletions(-) rename TheOtherRoles/{CustomOptions.cs => CustomOptionHolder.cs} (55%) create mode 100644 TheOtherRoles/Modules/CustomOptions.cs create mode 100644 TheOtherRoles/Resources/PathfindButton.png diff --git a/README.md b/README.md index e1876e1..c8e9135 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein # Releases | Among Us - Version| Mod Version | Link | |----------|-------------|-----------------| +| 2021.11.9.5s| v3.1.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v3.1.0/TheOtherRoles.zip) | 2021.11.9.5s| v3.0.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v3.0.0/TheOtherRoles.zip) | 2021.6.30s| v2.9.2| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.9.2/TheOtherRoles.zip) | 2021.6.30s| v2.9.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.9.1/TheOtherRoles.zip) @@ -80,8 +81,21 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein
Click to show the Changelog +**Version 3.1.0** +- Hopefully temporary fixing the issue of being kicked by Innersloth servers for regular kills, until Innersloth fixes it on their side. +- **NOTE:** Do not combine modded and unmodded versions of the game (even if you don't activate anything). Because of the kicking fix, your kills won't be performed for players that do not share the exact same modded version. Due to this you now can't start a game as the host, if not everyone in the lobby has the same version of the mod. Additionally you'll be kicked out of a lobby after 10 seconds, if the host doesn't have the mod installed (or the same mod version). +- **Tracker:** The Tracker has been reworked by [Alex2911](https://github.com/Alex2911). The Tracker now has an additional optional ability that tracks all corpses on the map for a few seconds. +- Add new option: Allow Parallel MedBay Scans +- Add new [Guesser](#guesser) option: "Guesses Visible In Ghost Chat" +- Add new [Guesser](#guesser) option: "Guesses Ignore The Medic Shield". If this option is set to false, no matter what the Guesser guessed, no one will die and the shielded player/Medic might be notified +- Add new [Medic](#medic) option: "Medic Sees Murder Attempt On Shielded Player". This includes attempts from any kind of killer (Sheriff, Jackal, Guesser if the shield is not being ignored, ...) +- During meetings the [Detective](#detective), [Hacker](#hacker) and [Medium](#medium) now display, whether a player wears a darker or lighter color +- Bug fix: Bounty Hunter, Mini and Engineer in vent kills do not result in players being kicked anymore +- Bug fix: The Trickster vent button now doesn't show the text "vent" twice anymore +- Bug fix: Fixed the visual bug where both Lovers always showed dead during the meeting after a correct guess of one of them even if the option "Both Lovers Die" was disabled + **Version 3.0.0** -- Update to Among Us version v2021.11.9.5s +- Updated to Among Us version v2021.11.9.5s - **Note:** We wanted to update as fast as possible, that's why you can't use both the Innersloth and mod roles at the same time. We'll make that possible in the future, but there are various things that need to be modified (e.g. Shifter, Guesser, ...) to make that work, so that'll take a little longer. Also, be aware that this version might contain more bugs than usual because Innersloth changed a lot of things and we might have missed some of them. - Ability buttons are now bind to the Q key (if it's a killing ability) or to the F key (otherwise). We'll make the binds adaptable in the future. - For now we removed the option "Jester Can Sabotage" @@ -450,6 +464,7 @@ The mod adds a few new settings to Among Us (in addition to the role settings): - **Maximum Number Of Meetings:** You can set the maximum number of meetings that can be called in total (Every player still has personal maximum of buttons, but if the maximum number of meetings is reached you can't use your meetings even if you have some left. Impostor and Jackal meetings also count) - **Allow Skips On Emergency Meetings:** If set to false, there will not be a skip button in emergency meetings. If a player does not vote, he'll vote himself. - **Hide Player Names:** Hides the names of all players that have role which is unknown to you. Team Lovers/Impostors/Jackal still see the names of their teammates. Impostors can also see the name of the Spy and everyone can still see the age of the mini. +- **Allow Parallel MedBay Scans:** Allows players to perform their MedBay scans at the same time - **Ghosts Can See Roles** - **Ghosts Can See Votes** - **Ghosts Can See The Number Of Remaining Tasks** @@ -718,6 +733,7 @@ The Guesser can shoot players during the meeting, by guessing its role. If the g You can select how many players can be shot per game and if multiple players can be shot during a single meeting.\ The guesses Impostor and Crewmate are only right, if the player is part of the corresponding team and has no special role.\ You can only shoot during the voting time.\ +Depending on the options, the Guesser can't guess the shielded player and depending on the Medic options the Medic/shielded player might be notified (no one will die, independently of what the Guesser guessed).\ \ **NOTE:** - The vote of a shot player, won't be counted @@ -731,8 +747,9 @@ You can only shoot during the voting time.\ | Guesser Spawn Chance | - | Chance That The Guesser Is An Impostor | - | Guesser Number Of Shots Per Game| - -| Guesser Number Of Shots Per Meeting| - | Guesser Can Shoot Multiple Times Per Meeting| - +| Guesses Visible In Ghost Chat| - +| Guesses Ignore The Medic Shield| - ----------------------- @@ -849,6 +866,7 @@ The Detective's other feature shows when they report a corpse: they receive clue - When people change their colors (because of a morph or camouflage), all the footprints also change their colors (also the ones that were already on the ground). If the effects are over, all footprints switch back to the original color. - The Detective does not see footprints of players that sit in vents - More information about the [colors](#colors) +- During the meetings you can see, whether a player wears a darker or a lighter color, represented by (D) or (L) in the names. ### Game Options | Name | Description | @@ -903,9 +921,10 @@ The Mini cannot be killed until it turns 18 years old, however it can be voted o ### **Team: Crewmates** The Medic can shield (highlighted by an outline around the player) one player per game, which makes the player unkillable.\ The shielded player can still be voted out and might also be an Impostor.\ -If set in the options, the shielded player will get a red flash on their screen if someone (Impostor, Sheriff, ...) tried to murder them. +If set in the options, the shielded player and/or the Medic will get a red flash on their screen if someone (Impostor, Sheriff, ...) tried to murder them. If the Medic dies, the shield disappears with them.\ The Sheriff will not die if they try to kill a shielded Crewmate and won't perform a kill if they try to kill a shielded Impostor.\ +Depending on the options, guesses from the Guesser will be blocked by the shield and the shielded player/medic might be notified.\ The Medic's other feature shows when they report a corpse: they will see how long ago the player died. \ **NOTE:** @@ -921,6 +940,7 @@ The Medic's other feature shows when they report a corpse: they will see how lon | Show Shielded Player | Sets who sees if a player has a shield | "Everyone", "Shielded + Medic", "Medic" | Shielded Player Sees Murder Attempt| Whether a shielded player sees if someone tries to kill him | True/false | | Shield Will Be Set After Next Meeting | - | True/false +| Medic Sees Murder Attempt On Shielded Player | - | If anyone tries to harm the shielded player (Impostor, Sheriff, Guesser, ...), the Medic will see a red flash ----------------------- ## Mayor @@ -944,6 +964,7 @@ Otherwise he sees the same information as everyone else. **NOTE:** - If the Morphling morphs or the Camouflager camouflages, the colors on the admin table change accordingly - More information about the [colors](#colors) +- During the meetings you can see, whether a player wears a darker or a lighter color, represented by (D) or (L) in the names. ### Game Options | Name | Description | @@ -1020,10 +1041,10 @@ and can't fix lights and comms. ## Tracker ### **Team: Crewmates** -The Tracker can select one player to track. -Depending on the options the Tracker can track a different person after each meeting or the Tracker tracks the same person for the whole game. +The Tracker can select one player to track. Depending on the options the Tracker can track a different person after each meeting or the Tracker tracks the same person for the whole game. An arrow points to the last tracked position of the player. The arrow updates its position every few seconds (configurable). +Depending on the options, the Tracker has another ability: He can track all corpses on the map for a set amount of time. He will keep tracking corpses, even if they were cleaned or eaten by the Vulture. ### Game Options | Name | Description @@ -1031,6 +1052,9 @@ The arrow updates its position every few seconds (configurable). | Tracker Spawn Chance | - | Tracker Update Interval | Sets how often the position is being updated | Tracker Reset Target After Meeting | - +| Tracker Can Track Corpses | - +| Corpses Tracking Cooldown | - +| Corpses Tracking Duration | - ----------------------- ## Snitch @@ -1177,6 +1201,7 @@ an outline or all vents do). Created by [Mallöris](https://github.com/Mallaris)\ \ The medium is a crewmate who can ask the souls of dead players for information. Like the Seer, it sees the places where the players have died (after the next meeting) and can question them. It then gets random information about the soul or the killer in the chat. The souls only stay for one round, i.e. until the next meeting. Depending on the options, the souls can only be questioned once and then disappear. +During the meetings you can see, whether a player wears a darker or a lighter color, represented by (D) or (L) in the names. Questions: What is your Role? diff --git a/TheOtherRoles/Buttons.cs b/TheOtherRoles/Buttons.cs index a3e5f91..f471fe7 100644 --- a/TheOtherRoles/Buttons.cs +++ b/TheOtherRoles/Buttons.cs @@ -19,7 +19,8 @@ namespace TheOtherRoles private static CustomButton morphlingButton; private static CustomButton camouflagerButton; private static CustomButton hackerButton; - private static CustomButton trackerButton; + private static CustomButton trackerTrackPlayerButton; + private static CustomButton trackerTrackCorpsesButton; private static CustomButton vampireKillButton; private static CustomButton garlicButton; private static CustomButton jackalKillButton; @@ -48,7 +49,7 @@ namespace TheOtherRoles camouflagerButton.MaxTimer = Camouflager.cooldown; hackerButton.MaxTimer = Hacker.cooldown; vampireKillButton.MaxTimer = Vampire.cooldown; - trackerButton.MaxTimer = 0f; + trackerTrackPlayerButton.MaxTimer = 0f; garlicButton.MaxTimer = 0f; jackalKillButton.MaxTimer = Jackal.cooldown; sidekickKillButton.MaxTimer = Sidekick.cooldown; @@ -63,6 +64,7 @@ namespace TheOtherRoles arsonistButton.MaxTimer = Arsonist.cooldown; vultureEatButton.MaxTimer = Vulture.cooldown; mediumButton.MaxTimer = Medium.cooldown; + trackerTrackCorpsesButton.MaxTimer = Tracker.corpsesTrackingCooldown; timeMasterShieldButton.EffectDuration = TimeMaster.shieldDuration; hackerButton.EffectDuration = Hacker.duration; @@ -73,7 +75,7 @@ namespace TheOtherRoles lightsOutButton.EffectDuration = Trickster.lightsOutDuration; arsonistButton.EffectDuration = Arsonist.duration; mediumButton.EffectDuration = Medium.duration; - + trackerTrackCorpsesButton.EffectDuration = Tracker.corpsesTrackingDuration; // Already set the timer to the max, as the button is enabled during the game and not available at the start lightsOutButton.Timer = lightsOutButton.MaxTimer; } @@ -170,6 +172,7 @@ namespace TheOtherRoles // Sheriff Kill sheriffKillButton = new CustomButton( () => { + // Not using Helpers.checkMuderAttemptAndKill here, since the Sheriff can kill himself eventhough he has a shield, ... if (Medic.shielded != null && Medic.shielded == Sheriff.currentTarget) { MessageWriter attemptWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShieldedMurderAttempt, Hazel.SendOption.Reliable, -1); AmongUsClient.Instance.FinishRpcImmediately(attemptWriter); @@ -187,10 +190,12 @@ namespace TheOtherRoles else { targetId = PlayerControl.LocalPlayer.PlayerId; } - MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SheriffKill, Hazel.SendOption.Reliable, -1); + + MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1); + killWriter.Write(Sheriff.sheriff.Data.PlayerId); killWriter.Write(targetId); AmongUsClient.Instance.FinishRpcImmediately(killWriter); - RPCProcedure.sheriffKill(targetId); + RPCProcedure.uncheckedMurderPlayer(Sheriff.sheriff.Data.PlayerId, targetId); sheriffKillButton.Timer = sheriffKillButton.MaxTimer; Sheriff.currentTarget = null; @@ -353,7 +358,7 @@ namespace TheOtherRoles ); // Tracker button - trackerButton = new CustomButton( + trackerTrackPlayerButton = new CustomButton( () => { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.TrackerUsedTracker, Hazel.SendOption.Reliable, -1); writer.Write(Tracker.currentTarget.PlayerId); @@ -369,9 +374,29 @@ namespace TheOtherRoles KeyCode.F ); + trackerTrackCorpsesButton = new CustomButton( + () => { Tracker.corpsesTrackingTimer = Tracker.corpsesTrackingDuration; }, + () => { return Tracker.tracker != null && Tracker.tracker == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead && Tracker.canTrackCorpses; }, + () => { return PlayerControl.LocalPlayer.CanMove; }, + () => { + trackerTrackCorpsesButton.Timer = trackerTrackCorpsesButton.MaxTimer; + trackerTrackCorpsesButton.isEffectActive = false; + trackerTrackCorpsesButton.actionButton.cooldownTimerText.color = Palette.EnabledColor; + }, + Tracker.getTrackCorpsesButtonSprite(), + new Vector3(-2.7f, -0.06f, 0), + __instance, + KeyCode.Q, + true, + Tracker.corpsesTrackingDuration, + () => { + trackerTrackCorpsesButton.Timer = trackerTrackCorpsesButton.MaxTimer; + } + ); + vampireKillButton = new CustomButton( () => { - if (Helpers.handleMurderAttempt(Vampire.vampire, Vampire.currentTarget)) { + if (Helpers.checkMuderAttempt(Vampire.vampire, Vampire.currentTarget)) { if (Vampire.targetNearGarlic) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1); writer.Write(Vampire.vampire.PlayerId); @@ -392,19 +417,13 @@ namespace TheOtherRoles HudManager.Instance.StartCoroutine(Effects.Lerp(Vampire.delay, new Action((p) => { // Delayed action if (p == 1f) { - if (Vampire.bitten != null && !Vampire.bitten.Data.IsDead && Helpers.handleMurderAttempt(Vampire.vampire, Vampire.bitten)) { - // Perform kill - MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VampireTryKill, Hazel.SendOption.Reliable, -1); - AmongUsClient.Instance.FinishRpcImmediately(killWriter); - RPCProcedure.vampireTryKill(); - } else { - // Notify players about clearing bitten - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VampireSetBitten, Hazel.SendOption.Reliable, -1); - writer.Write(byte.MaxValue); - writer.Write(byte.MaxValue); - AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.vampireSetBitten(byte.MaxValue, byte.MaxValue); - } + // Perform kill if possible and reset bitten (regardless whether the kill was successful or not) + Helpers.checkMuderAttemptAndKill(Vampire.vampire, Vampire.bitten); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VampireSetBitten, Hazel.SendOption.Reliable, -1); + writer.Write(byte.MaxValue); + writer.Write(byte.MaxValue); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.vampireSetBitten(byte.MaxValue, byte.MaxValue); } }))); @@ -486,12 +505,8 @@ namespace TheOtherRoles // Jackal Kill jackalKillButton = new CustomButton( () => { - if (!Helpers.handleMurderAttempt(Jackal.jackal, Jackal.currentTarget)) return; - byte targetId = Jackal.currentTarget.PlayerId; - MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.JackalKill, Hazel.SendOption.Reliable, -1); - killWriter.Write(targetId); - AmongUsClient.Instance.FinishRpcImmediately(killWriter); - RPCProcedure.jackalKill(targetId); + if (!Helpers.checkMuderAttemptAndKill(Jackal.jackal, Jackal.currentTarget)) return; + jackalKillButton.Timer = jackalKillButton.MaxTimer; Jackal.currentTarget = null; }, @@ -507,12 +522,7 @@ namespace TheOtherRoles // Sidekick Kill sidekickKillButton = new CustomButton( () => { - if (!Helpers.handleMurderAttempt(Sidekick.sidekick, Sidekick.currentTarget)) return; - byte targetId = Sidekick.currentTarget.PlayerId; - MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SidekickKill, Hazel.SendOption.Reliable, -1); - killWriter.Write(targetId); - AmongUsClient.Instance.FinishRpcImmediately(killWriter); - RPCProcedure.sidekickKill(targetId); + if (!Helpers.checkMuderAttemptAndKill(Sidekick.sidekick, Sidekick.currentTarget)) return; sidekickKillButton.Timer = sidekickKillButton.MaxTimer; Sidekick.currentTarget = null; @@ -656,8 +666,9 @@ namespace TheOtherRoles Warlock.curseVictim = Warlock.currentTarget; warlockCurseButton.Sprite = Warlock.getCurseKillButtonSprite(); warlockCurseButton.Timer = 1f; - } else if (Warlock.curseVictim != null && Warlock.curseVictimTarget != null && Helpers.handleMurderAttempt(Warlock.warlock, Warlock.curseVictimTarget)) { + } else if (Warlock.curseVictim != null && Warlock.curseVictimTarget != null && Helpers.checkMuderAttempt(Warlock.warlock, Warlock.curseVictimTarget)) { // Curse Kill + // Not using Helpers.checkMuderAttemptAndKill here directly, since we need to share the Warlock.curseKillTarget Warlock.curseKillTarget = Warlock.curseVictimTarget; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.WarlockCurseKill, Hazel.SendOption.Reliable, -1); diff --git a/TheOtherRoles/CustomOptions.cs b/TheOtherRoles/CustomOptionHolder.cs similarity index 55% rename from TheOtherRoles/CustomOptions.cs rename to TheOtherRoles/CustomOptionHolder.cs index 30d9fee..0e08a33 100644 --- a/TheOtherRoles/CustomOptions.cs +++ b/TheOtherRoles/CustomOptionHolder.cs @@ -55,6 +55,8 @@ namespace TheOtherRoles { public static CustomOption guesserIsImpGuesserRate; public static CustomOption guesserNumberOfShots; public static CustomOption guesserHasMultipleShotsPerMeeting; + public static CustomOption guesserShowInfoInGhostChat; + public static CustomOption guesserKillsThroughShield; public static CustomOption jesterSpawnRate; public static CustomOption jesterCanCallEmergency; @@ -118,6 +120,7 @@ namespace TheOtherRoles { public static CustomOption medicShowShielded; public static CustomOption medicShowAttemptToShielded; public static CustomOption medicSetShieldAfterMeeting; + public static CustomOption medicShowAttemptToMedic; public static CustomOption swapperSpawnRate; public static CustomOption swapperCanCallEmergency; @@ -136,6 +139,9 @@ namespace TheOtherRoles { public static CustomOption trackerSpawnRate; public static CustomOption trackerUpdateIntervall; public static CustomOption trackerResetTargetAfterMeeting; + public static CustomOption trackerCanTrackCorpses; + public static CustomOption trackerCorpsesTrackingCooldown; + public static CustomOption trackerCorpsesTrackingDuration; public static CustomOption snitchSpawnRate; public static CustomOption snitchLeftTasksForReveal; @@ -185,6 +191,8 @@ namespace TheOtherRoles { public static CustomOption blockSkippingInEmergencyMeetings; public static CustomOption noVoteIsSelfVote; public static CustomOption hidePlayerNames; + public static CustomOption allowParallelMedBayScans; + internal static Dictionary blockedRolePairings = new Dictionary(); @@ -263,6 +271,8 @@ namespace TheOtherRoles { guesserIsImpGuesserRate = CustomOption.Create(311, "Chance That The Guesser Is An Impostor", rates, guesserSpawnRate); guesserNumberOfShots = CustomOption.Create(312, "Guesser Number Of Shots", 2f, 1f, 15f, 1f, guesserSpawnRate); guesserHasMultipleShotsPerMeeting = CustomOption.Create(313, "Guesser Can Shoot Multiple Times Per Meeting", false, guesserSpawnRate); + guesserShowInfoInGhostChat = CustomOption.Create(314, "Guesses Visible In Ghost Chat", true, guesserSpawnRate); + guesserKillsThroughShield = CustomOption.Create(315, "Guesses Ignore The Medic Shield", true, guesserSpawnRate); jesterSpawnRate = CustomOption.Create(60, cs(Jester.color, "Jester"), rates, null, true); jesterCanCallEmergency = CustomOption.Create(61, "Jester can call emergency meeting", true, jesterSpawnRate); @@ -325,6 +335,7 @@ namespace TheOtherRoles { medicShowShielded = CustomOption.Create(143, "Show Shielded Player", new string[] {"Everyone", "Shielded + Medic", "Medic"}, medicSpawnRate); medicShowAttemptToShielded = CustomOption.Create(144, "Shielded Player Sees Murder Attempt", false, medicSpawnRate); medicSetShieldAfterMeeting = CustomOption.Create(145, "Shield Will Be Set After The Next Meeting", false, medicSpawnRate); + medicShowAttemptToMedic = CustomOption.Create(146, "Medic Sees Murder Attempt On Shielded Player", false, medicSpawnRate); swapperSpawnRate = CustomOption.Create(150, cs(Swapper.color, "Swapper"), rates, null, true); swapperCanCallEmergency = CustomOption.Create(151, "Swapper can call emergency meeting", false, swapperSpawnRate); @@ -343,6 +354,10 @@ namespace TheOtherRoles { trackerSpawnRate = CustomOption.Create(200, cs(Tracker.color, "Tracker"), rates, null, true); trackerUpdateIntervall = CustomOption.Create(201, "Tracker Update Intervall", 5f, 2.5f, 30f, 2.5f, trackerSpawnRate); trackerResetTargetAfterMeeting = CustomOption.Create(202, "Tracker Reset Target After Meeting", false, trackerSpawnRate); + trackerCanTrackCorpses = CustomOption.Create(203, "Tracker Can Track Corpses", true, trackerSpawnRate); + trackerCorpsesTrackingCooldown = CustomOption.Create(204, "Corpses Tracking Cooldown", 30f, 0f, 120f, 5f, trackerCanTrackCorpses); + trackerCorpsesTrackingDuration = CustomOption.Create(205, "Corpses Tracking Duration", 5f, 2.5f, 30f, 2.5f, trackerCanTrackCorpses); + snitchSpawnRate = CustomOption.Create(210, cs(Snitch.color, "Snitch"), rates, null, true); snitchLeftTasksForReveal = CustomOption.Create(211, "Task Count Where The Snitch Will Be Revealed", 1f, 0f, 5f, 1f, snitchSpawnRate); snitchIncludeTeamJackal = CustomOption.Create(212, "Include Team Jackal", false, snitchSpawnRate); @@ -374,6 +389,7 @@ namespace TheOtherRoles { blockSkippingInEmergencyMeetings = CustomOption.Create(4, "Block Skipping In Emergency Meetings", false); noVoteIsSelfVote = CustomOption.Create(5, "No Vote Is Self Vote", false, blockSkippingInEmergencyMeetings); hidePlayerNames = CustomOption.Create(6, "Hide Player Names", false); + allowParallelMedBayScans = CustomOption.Create(7, "Allow Parallel MedBay Scans", false); blockedRolePairings.Add((byte)RoleId.Vampire, new [] { (byte)RoleId.Warlock}); blockedRolePairings.Add((byte)RoleId.Warlock, new [] { (byte)RoleId.Vampire}); @@ -382,425 +398,4 @@ namespace TheOtherRoles { } } - - public class CustomOption { - public static List options = new List(); - public static int preset = 0; - - public int id; - public string name; - public System.Object[] selections; - - public int defaultSelection; - public ConfigEntry entry; - public int selection; - public OptionBehaviour optionBehaviour; - public CustomOption parent; - public bool isHeader; - - // Option creation - - public CustomOption(int id, string name, System.Object[] selections, System.Object defaultValue, CustomOption parent, bool isHeader) { - this.id = id; - this.name = parent == null ? name : "- " + name; - this.selections = selections; - int index = Array.IndexOf(selections, defaultValue); - this.defaultSelection = index >= 0 ? index : 0; - this.parent = parent; - this.isHeader = isHeader; - selection = 0; - if (id != 0) { - entry = TheOtherRolesPlugin.Instance.Config.Bind($"Preset{preset}", id.ToString(), defaultSelection); - selection = Mathf.Clamp(entry.Value, 0, selections.Length - 1); - } - options.Add(this); - } - - public static CustomOption Create(int id, string name, string[] selections, CustomOption parent = null, bool isHeader = false) { - return new CustomOption(id, name, selections, "", parent, isHeader); - } - - public static CustomOption Create(int id, string name, float defaultValue, float min, float max, float step, CustomOption parent = null, bool isHeader = false) { - List selections = new List(); - for (float s = min; s <= max; s += step) - selections.Add(s); - return new CustomOption(id, name, selections.Cast().ToArray(), defaultValue, parent, isHeader); - } - - public static CustomOption Create(int id, string name, bool defaultValue, CustomOption parent = null, bool isHeader = false) { - return new CustomOption(id, name, new string[]{"Off", "On"}, defaultValue ? "On" : "Off", parent, isHeader); - } - - // Static behaviour - - public static void switchPreset(int newPreset) { - CustomOption.preset = newPreset; - foreach (CustomOption option in CustomOption.options) { - if (option.id == 0) continue; - - option.entry = TheOtherRolesPlugin.Instance.Config.Bind($"Preset{preset}", option.id.ToString(), option.defaultSelection); - option.selection = Mathf.Clamp(option.entry.Value, 0, option.selections.Length - 1); - if (option.optionBehaviour != null && option.optionBehaviour is StringOption stringOption) { - stringOption.oldValue = stringOption.Value = option.selection; - stringOption.ValueText.text = option.selections[option.selection].ToString(); - } - } - } - - public static void ShareOptionSelections() { - if (PlayerControl.AllPlayerControls.Count <= 1 || AmongUsClient.Instance?.AmHost == false && PlayerControl.LocalPlayer == null) return; - foreach (CustomOption option in CustomOption.options) { - MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShareOptionSelection, Hazel.SendOption.Reliable); - messageWriter.WritePacked((uint)option.id); - messageWriter.WritePacked((uint)Convert.ToUInt32(option.selection)); - messageWriter.EndMessage(); - } - } - - // Getter - - public int getSelection() { - return selection; - } - - public bool getBool() { - return selection > 0; - } - - public float getFloat() { - return (float)selections[selection]; - } - - // Option changes - - public void updateSelection(int newSelection) { - selection = Mathf.Clamp((newSelection + selections.Length) % selections.Length, 0, selections.Length - 1); - if (optionBehaviour != null && optionBehaviour is StringOption stringOption) { - stringOption.oldValue = stringOption.Value = selection; - stringOption.ValueText.text = selections[selection].ToString(); - - if (AmongUsClient.Instance?.AmHost == true && PlayerControl.LocalPlayer) { - if (id == 0) switchPreset(selection); // Switch presets - else if (entry != null) entry.Value = selection; // Save selection to config - - ShareOptionSelections();// Share all selections - } - } - } - } - - [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Start))] - class GameOptionsMenuStartPatch { - public static void Postfix(GameOptionsMenu __instance) { - if (GameObject.Find("TORSettings") != null) { // Settings setup has already been performed, fixing the title of the tab and returning - GameObject.Find("TORSettings").transform.FindChild("GameGroup").FindChild("Text").GetComponent().SetText("The Other Roles Settings"); - return; - } - - // Setup TOR tab - var template = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(); - if (template == null) return; - var gameSettings = GameObject.Find("Game Settings"); - var gameSettingMenu = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(); - var torSettings = UnityEngine.Object.Instantiate(gameSettings, gameSettings.transform.parent); - var torMenu = torSettings.transform.FindChild("GameGroup").FindChild("SliderInner").GetComponent(); - torSettings.name = "TORSettings"; - - var roleTab = GameObject.Find("RoleTab"); - var gameTab = GameObject.Find("GameTab"); - - var torTab = UnityEngine.Object.Instantiate(roleTab, roleTab.transform.parent); - var torTabHighlight = torTab.transform.FindChild("Hat Button").FindChild("Tab Background").GetComponent(); - torTab.transform.FindChild("Hat Button").FindChild("Icon").GetComponent().sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TabIcon.png", 100f); - - gameTab.transform.position += Vector3.left * 0.5f; - torTab.transform.position += Vector3.right * 0.5f; - roleTab.transform.position += Vector3.left * 0.5f; - - var tabs = new GameObject[]{gameTab, roleTab, torTab}; - for (int i = 0; i < tabs.Length; i++) { - var button = tabs[i].GetComponentInChildren(); - if (button == null) continue; - int copiedIndex = i; - button.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent(); - button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { - gameSettingMenu.RegularGameSettings.SetActive(false); - gameSettingMenu.RolesSettings.gameObject.SetActive(false); - torSettings.gameObject.SetActive(false); - gameSettingMenu.GameSettingsHightlight.enabled = false; - gameSettingMenu.RolesSettingsHightlight.enabled = false; - torTabHighlight.enabled = false; - if (copiedIndex == 0) { - gameSettingMenu.RegularGameSettings.SetActive(true); - gameSettingMenu.GameSettingsHightlight.enabled = true; - } else if (copiedIndex == 1) { - gameSettingMenu.RolesSettings.gameObject.SetActive(true); - gameSettingMenu.RolesSettingsHightlight.enabled = true; - } else if (copiedIndex == 2) { - torSettings.gameObject.SetActive(true); - torTabHighlight.enabled = true; - } - })); - } - - foreach (OptionBehaviour option in torMenu.GetComponentsInChildren()) - UnityEngine.Object.Destroy(option.gameObject); - List torOptions = new List(); - - for (int i = 0; i < CustomOption.options.Count; i++) { - CustomOption option = CustomOption.options[i]; - if (option.optionBehaviour == null) { - StringOption stringOption = UnityEngine.Object.Instantiate(template, torMenu.transform); - torOptions.Add(stringOption); - stringOption.OnValueChanged = new Action((o) => {}); - stringOption.TitleText.text = option.name; - stringOption.Value = stringOption.oldValue = option.selection; - stringOption.ValueText.text = option.selections[option.selection].ToString(); - - option.optionBehaviour = stringOption; - } - option.optionBehaviour.gameObject.SetActive(true); - } - - torMenu.Children = torOptions.ToArray(); - torSettings.gameObject.SetActive(false); - - // Adapt task count for main options - - var commonTasksOption = __instance.Children.FirstOrDefault(x => x.name == "NumCommonTasks").TryCast(); - if(commonTasksOption != null) commonTasksOption.ValidRange = new FloatRange(0f, 4f); - - var shortTasksOption = __instance.Children.FirstOrDefault(x => x.name == "NumShortTasks").TryCast(); - if(shortTasksOption != null) shortTasksOption.ValidRange = new FloatRange(0f, 23f); - - var longTasksOption = __instance.Children.FirstOrDefault(x => x.name == "NumLongTasks").TryCast(); - if(longTasksOption != null) longTasksOption.ValidRange = new FloatRange(0f, 15f); - } - } - - [HarmonyPatch(typeof(StringOption), nameof(StringOption.OnEnable))] - public class StringOptionEnablePatch { - public static bool Prefix(StringOption __instance) { - CustomOption option = CustomOption.options.FirstOrDefault(option => option.optionBehaviour == __instance); - if (option == null) return true; - - __instance.OnValueChanged = new Action((o) => {}); - __instance.TitleText.text = option.name; - __instance.Value = __instance.oldValue = option.selection; - __instance.ValueText.text = option.selections[option.selection].ToString(); - - return false; - } - } - - [HarmonyPatch(typeof(StringOption), nameof(StringOption.Increase))] - public class StringOptionIncreasePatch - { - public static bool Prefix(StringOption __instance) - { - CustomOption option = CustomOption.options.FirstOrDefault(option => option.optionBehaviour == __instance); - if (option == null) return true; - option.updateSelection(option.selection + 1); - return false; - } - } - - [HarmonyPatch(typeof(StringOption), nameof(StringOption.Decrease))] - public class StringOptionDecreasePatch - { - public static bool Prefix(StringOption __instance) - { - CustomOption option = CustomOption.options.FirstOrDefault(option => option.optionBehaviour == __instance); - if (option == null) return true; - option.updateSelection(option.selection - 1); - return false; - } - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSyncSettings))] - public class RpcSyncSettingsPatch - { - public static void Postfix() - { - CustomOption.ShareOptionSelections(); - } - } - - - [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Update))] - class GameOptionsMenuUpdatePatch - { - private static float timer = 1f; - public static void Postfix(GameOptionsMenu __instance) { - if (__instance.Children.Length < 100) return; // TODO: Introduce a cleaner way to seperate the TOR settings from the game settings - - __instance.GetComponentInParent().YBounds.max = -0.5F + __instance.Children.Length * 0.55F; - timer += Time.deltaTime; - if (timer < 0.1f) return; - timer = 0f; - - float offset = 2.75f; - foreach (CustomOption option in CustomOption.options) { - if (option?.optionBehaviour != null && option.optionBehaviour.gameObject != null) { - bool enabled = true; - var parent = option.parent; - while (parent != null && enabled) { - enabled = parent.selection != 0; - parent = parent.parent; - } - option.optionBehaviour.gameObject.SetActive(enabled); - if (enabled) { - offset -= option.isHeader ? 0.75f : 0.5f; - option.optionBehaviour.transform.localPosition = new Vector3(option.optionBehaviour.transform.localPosition.x, offset, option.optionBehaviour.transform.localPosition.z); - } - } - } - } - } - - [HarmonyPatch(typeof(GameSettingMenu), nameof(GameSettingMenu.Start))] - class GameSettingMenuStartPatch { - public static void Prefix(GameSettingMenu __instance) { - __instance.HideForOnline = new Transform[]{}; - } - - public static void Postfix(GameSettingMenu __instance) { - // Setup mapNameTransform - var mapNameTransform = __instance.AllItems.FirstOrDefault(x => x.gameObject.activeSelf && x.name.Equals("MapName", StringComparison.OrdinalIgnoreCase)); - if (mapNameTransform == null) return; - - var options = new Il2CppSystem.Collections.Generic.List>(); - for (int i = 0; i < Constants.MapNames.Length; i++) { - var kvp = new Il2CppSystem.Collections.Generic.KeyValuePair(); - kvp.key = Constants.MapNames[i]; - kvp.value = i; - options.Add(kvp); - } - mapNameTransform.GetComponent().Values = options; - } - } - - [HarmonyPatch(typeof(Constants), nameof(Constants.ShouldFlipSkeld))] - class ConstantsShouldFlipSkeldPatch { - public static bool Prefix(ref bool __result) { - if (PlayerControl.GameOptions == null) return true; - __result = PlayerControl.GameOptions.MapId == 3; - return false; - } - } - - [HarmonyPatch] - class GameOptionsDataPatch - { - private static IEnumerable TargetMethods() { - return typeof(GameOptionsData).GetMethods().Where(x => x.ReturnType == typeof(string) && x.GetParameters().Length == 1 && x.GetParameters()[0].ParameterType == typeof(int)); - } - - private static void Postfix(ref string __result) - { - StringBuilder sb = new StringBuilder(__result); - foreach (CustomOption option in CustomOption.options) { - if (option.parent == null) { - if (option == CustomOptionHolder.crewmateRolesCountMin) { - var optionName = CustomOptionHolder.cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Crewmate Roles"); - var min = CustomOptionHolder.crewmateRolesCountMin.getSelection(); - var max = CustomOptionHolder.crewmateRolesCountMax.getSelection(); - if (min > max) min = max; - var optionValue = (min == max) ? $"{max}" : $"{min} - {max}"; - sb.AppendLine($"{optionName}: {optionValue}"); - } else if (option == CustomOptionHolder.neutralRolesCountMin) { - var optionName = CustomOptionHolder.cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Neutral Roles"); - var min = CustomOptionHolder.neutralRolesCountMin.getSelection(); - var max = CustomOptionHolder.neutralRolesCountMax.getSelection(); - if (min > max) min = max; - var optionValue = (min == max) ? $"{max}" : $"{min} - {max}"; - sb.AppendLine($"{optionName}: {optionValue}"); - } else if (option == CustomOptionHolder.impostorRolesCountMin) { - 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 (min > max) min = max; - var optionValue = (min == max) ? $"{max}" : $"{min} - {max}"; - sb.AppendLine($"{optionName}: {optionValue}"); - } else if ((option == CustomOptionHolder.crewmateRolesCountMax) || (option == CustomOptionHolder.neutralRolesCountMax) || (option == CustomOptionHolder.impostorRolesCountMax)) { - continue; - } else { - sb.AppendLine($"{option.name}: {option.selections[option.selection].ToString()}"); - } - - } - } - CustomOption parent = null; - foreach (CustomOption option in CustomOption.options) - if (option.parent != null) { - if (option.parent != parent) { - sb.AppendLine(); - parent = option.parent; - } - sb.AppendLine($"{option.name}: {option.selections[option.selection].ToString()}"); - } - - var hudString = sb.ToString(); - - int defaultSettingsLines = 23; - int roleSettingsLines = defaultSettingsLines + 38; - int detailedSettingsP1 = roleSettingsLines + 40; - int detailedSettingsP2 = detailedSettingsP1 + 42; - int detailedSettingsP3 = detailedSettingsP2 + 42; - int end1 = hudString.TakeWhile(c => (defaultSettingsLines -= (c == '\n' ? 1 : 0)) > 0).Count(); - int end2 = hudString.TakeWhile(c => (roleSettingsLines -= (c == '\n' ? 1 : 0)) > 0).Count(); - int end3 = hudString.TakeWhile(c => (detailedSettingsP1 -= (c == '\n' ? 1 : 0)) > 0).Count(); - int end4 = hudString.TakeWhile(c => (detailedSettingsP2 -= (c == '\n' ? 1 : 0)) > 0).Count(); - int end5 = hudString.TakeWhile(c => (detailedSettingsP3 -= (c == '\n' ? 1 : 0)) > 0).Count(); - int counter = TheOtherRolesPlugin.optionsPage; - if (counter == 0) { - hudString = hudString.Substring(0, end1) + "\n"; - } else if (counter == 1) { - hudString = hudString.Substring(end1 + 1, end2 - end1); - // Temporary fix, should add a new CustomOption for spaces - int gap = 2; - int index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count(); - hudString = hudString.Insert(index, "\n"); - gap = 6; - index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count(); - hudString = hudString.Insert(index, "\n"); - gap = 19; - index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count(); - hudString = hudString.Insert(index + 1, "\n"); - gap = 24; - index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count(); - hudString = hudString.Insert(index + 1, "\n"); - } else if (counter == 2) { - hudString = hudString.Substring(end2 + 1, end3 - end2); - } else if (counter == 3) { - hudString = hudString.Substring(end3 + 1, end4 - end3); - } else if (counter == 4) { - hudString = hudString.Substring(end4 + 1, end5 - end4); - } else if (counter == 5) { - hudString = hudString.Substring(end5 + 1); - } - - hudString += $"\n Press tab for more... ({counter+1}/6)"; - __result = hudString; - } - } - - [HarmonyPatch(typeof(KeyboardJoystick), nameof(KeyboardJoystick.Update))] - public static class GameOptionsNextPagePatch - { - public static void Postfix(KeyboardJoystick __instance) - { - if(Input.GetKeyDown(KeyCode.Tab)) { - TheOtherRolesPlugin.optionsPage = (TheOtherRolesPlugin.optionsPage + 1) % 6; - } - } - } - - - [HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))] - public class GameSettingsScalePatch { - public static void Prefix(HudManager __instance) { - if (__instance.GameSettings != null) __instance.GameSettings.fontSize = 1.2f; - } - } } \ No newline at end of file diff --git a/TheOtherRoles/Helpers.cs b/TheOtherRoles/Helpers.cs index c1ef9f0..c557d34 100644 --- a/TheOtherRoles/Helpers.cs +++ b/TheOtherRoles/Helpers.cs @@ -78,44 +78,14 @@ namespace TheOtherRoles { return res; } - public static bool handleMurderAttempt(PlayerControl killer, PlayerControl target, bool isMeetingStart = false) { - // Block impostor shielded kill - if (Medic.shielded != null && Medic.shielded == target) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(killer.NetId, (byte)CustomRPC.ShieldedMurderAttempt, Hazel.SendOption.Reliable, -1); - AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.shieldedMurderAttempt(); - - return false; - } - // Block impostor not fully grown mini kill - else if (Mini.mini != null && target == Mini.mini && !Mini.isGrownUp()) { - return false; - } - // Block Time Master with time shield kill - else if (TimeMaster.shieldActive && TimeMaster.timeMaster != null && TimeMaster.timeMaster == target) { - if (!isMeetingStart) { // Only rewind the attempt was not called because a meeting startet - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(killer.NetId, (byte)CustomRPC.TimeMasterRewindTime, Hazel.SendOption.Reliable, -1); - AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.timeMasterRewindTime(); - } - return false; - } - return true; - } - public static void handleVampireBiteOnBodyReport() { - // Murder the bitten player before the meeting starts or reset the bitten player - if (Vampire.bitten != null && !Vampire.bitten.Data.IsDead && Helpers.handleMurderAttempt(Vampire.vampire, Vampire.bitten, true)) { - MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VampireTryKill, Hazel.SendOption.Reliable, -1); - AmongUsClient.Instance.FinishRpcImmediately(killWriter); - RPCProcedure.vampireTryKill(); - } else { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VampireSetBitten, Hazel.SendOption.Reliable, -1); - writer.Write(byte.MaxValue); - writer.Write(byte.MaxValue); - AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.vampireSetBitten(byte.MaxValue, byte.MaxValue); - } + // Murder the bitten player and reset bitten (regardless whether the kill was successful or not) + Helpers.checkMuderAttemptAndKill(Vampire.vampire, Vampire.bitten, true); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VampireSetBitten, Hazel.SendOption.Reliable, -1); + writer.Write(byte.MaxValue); + writer.Write(byte.MaxValue); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.vampireSetBitten(byte.MaxValue, byte.MaxValue); } public static void refreshRoleDescription(PlayerControl player) { @@ -294,5 +264,63 @@ namespace TheOtherRoles { } return roleCouldUse; } + + public static bool checkMuderAttempt(PlayerControl killer, PlayerControl target, bool isMeetingStart = false) { + // Modified vanilla checks + if (AmongUsClient.Instance.IsGameOver) return false; + if (killer == null || killer.Data == null || killer.Data.IsDead || killer.Data.Disconnected) return false; // Allow non Impostor kills compared to vanilla code + if (target == null || target.Data == null || target.Data.IsDead || target.Data.Disconnected) return false; // Allow killing players in vents compared to vanilla code + + // Block impostor shielded kill + if (Medic.shielded != null && Medic.shielded == target) { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(killer.NetId, (byte)CustomRPC.ShieldedMurderAttempt, Hazel.SendOption.Reliable, -1); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.shieldedMurderAttempt(); + return false; + } + + // Block impostor not fully grown mini kill + else if (Mini.mini != null && target == Mini.mini && !Mini.isGrownUp()) { + return false; + } + + // Block Time Master with time shield kill + else if (TimeMaster.shieldActive && TimeMaster.timeMaster != null && TimeMaster.timeMaster == target) { + if (!isMeetingStart) { // Only rewind the attempt was not called because a meeting startet + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(killer.NetId, (byte)CustomRPC.TimeMasterRewindTime, Hazel.SendOption.Reliable, -1); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.timeMasterRewindTime(); + } + return false; + } + return true; + } + + public static bool checkMuderAttemptAndKill(PlayerControl killer, PlayerControl target, bool isMeetingStart = false) { + // The local player checks for the validity of the kill and performs it afterwards (different to vanilla, where the host performs all the checks) + // The kill attempt will be shared using a custom RPC, hence combining modded and unmodded versions is impossible + + bool attemptIsSuccessful = checkMuderAttempt(killer, target, isMeetingStart); + if (attemptIsSuccessful) { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1); + writer.Write(killer.PlayerId); + writer.Write(target.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.uncheckedMurderPlayer(killer.PlayerId, target.PlayerId); + } + return attemptIsSuccessful; + } + + public static void shareGameVersion() { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VersionHandshake, Hazel.SendOption.Reliable, -1); + writer.Write((byte)TheOtherRolesPlugin.Version.Major); + writer.Write((byte)TheOtherRolesPlugin.Version.Minor); + writer.Write((byte)TheOtherRolesPlugin.Version.Build); + writer.WritePacked(AmongUsClient.Instance.ClientId); + writer.Write((byte)(TheOtherRolesPlugin.Version.Revision < 0 ? 0xFF : TheOtherRolesPlugin.Version.Revision)); + writer.Write(Assembly.GetExecutingAssembly().ManifestModule.ModuleVersionId.ToByteArray()); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.versionHandshake(TheOtherRolesPlugin.Version.Major, TheOtherRolesPlugin.Version.Minor, TheOtherRolesPlugin.Version.Build, TheOtherRolesPlugin.Version.Revision, Assembly.GetExecutingAssembly().ManifestModule.ModuleVersionId, AmongUsClient.Instance.ClientId); + } } } diff --git a/TheOtherRoles/Main.cs b/TheOtherRoles/Main.cs index 81e84f9..a54986a 100644 --- a/TheOtherRoles/Main.cs +++ b/TheOtherRoles/Main.cs @@ -21,7 +21,7 @@ namespace TheOtherRoles public class TheOtherRolesPlugin : BasePlugin { public const string Id = "me.eisbison.theotherroles"; - public const string VersionString = "3.0.0"; + public const string VersionString = "3.1.0"; public static System.Version Version = System.Version.Parse(VersionString); internal static BepInEx.Logging.ManualLogSource Logger; diff --git a/TheOtherRoles/MapOptions.cs b/TheOtherRoles/MapOptions.cs index 488f9cd..94531fe 100644 --- a/TheOtherRoles/MapOptions.cs +++ b/TheOtherRoles/MapOptions.cs @@ -15,6 +15,7 @@ namespace TheOtherRoles{ public static bool ghostsSeeTasks = true; public static bool ghostsSeeVotes = true; public static bool showRoleSummary = true; + public static bool allowParallelMedBayScans = false; // Updating values public static int meetingsCount = 0; @@ -32,6 +33,7 @@ public static void clearAndReloadMapOptions() { blockSkippingInEmergencyMeetings = CustomOptionHolder.blockSkippingInEmergencyMeetings.getBool(); noVoteIsSelfVote = CustomOptionHolder.noVoteIsSelfVote.getBool(); hidePlayerNames = CustomOptionHolder.hidePlayerNames.getBool(); + allowParallelMedBayScans = CustomOptionHolder.allowParallelMedBayScans.getBool(); ghostsSeeRoles = TheOtherRolesPlugin.GhostsSeeRoles.Value; ghostsSeeTasks = TheOtherRolesPlugin.GhostsSeeTasks.Value; ghostsSeeVotes = TheOtherRolesPlugin.GhostsSeeVotes.Value; diff --git a/TheOtherRoles/Modules/CustomColors.cs b/TheOtherRoles/Modules/CustomColors.cs index 87b96cb..d0c9265 100644 --- a/TheOtherRoles/Modules/CustomColors.cs +++ b/TheOtherRoles/Modules/CustomColors.cs @@ -14,14 +14,6 @@ namespace TheOtherRoles.Modules { public static List lighterColors = new List(){ 3, 4, 5, 7, 10, 11, 13, 14, 17 }; public static uint pickableColors = (uint)Palette.ColorNames.Length; - /* version 1 - private static readonly List ORDER = new List() { 7, 17, 5, 33, 4, - 30, 0, 19, 27, 3, - 13, 25, 18, 15, 23, - 8, 32, 1, 21, 31, - 10, 34, 12, 14, 28, - 22, 29, 11, 26, 2, - 20, 24, 9, 16, 6 }; */ private static readonly List ORDER = new List() { 7, 14, 5, 33, 4, 30, 0, 19, 27, 3, 17, 25, 18, 13, 23, diff --git a/TheOtherRoles/Modules/CustomOptions.cs b/TheOtherRoles/Modules/CustomOptions.cs new file mode 100644 index 0000000..4629a38 --- /dev/null +++ b/TheOtherRoles/Modules/CustomOptions.cs @@ -0,0 +1,433 @@ +using System.Collections.Generic; +using UnityEngine; +using BepInEx.Configuration; +using System; +using System.Linq; +using HarmonyLib; +using Hazel; +using System.Reflection; +using System.Text; +using static TheOtherRoles.TheOtherRoles; + +namespace TheOtherRoles { + public class CustomOption { + public static List options = new List(); + public static int preset = 0; + + public int id; + public string name; + public System.Object[] selections; + + public int defaultSelection; + public ConfigEntry entry; + public int selection; + public OptionBehaviour optionBehaviour; + public CustomOption parent; + public bool isHeader; + + // Option creation + + public CustomOption(int id, string name, System.Object[] selections, System.Object defaultValue, CustomOption parent, bool isHeader) { + this.id = id; + this.name = parent == null ? name : "- " + name; + this.selections = selections; + int index = Array.IndexOf(selections, defaultValue); + this.defaultSelection = index >= 0 ? index : 0; + this.parent = parent; + this.isHeader = isHeader; + selection = 0; + if (id != 0) { + entry = TheOtherRolesPlugin.Instance.Config.Bind($"Preset{preset}", id.ToString(), defaultSelection); + selection = Mathf.Clamp(entry.Value, 0, selections.Length - 1); + } + options.Add(this); + } + + public static CustomOption Create(int id, string name, string[] selections, CustomOption parent = null, bool isHeader = false) { + return new CustomOption(id, name, selections, "", parent, isHeader); + } + + public static CustomOption Create(int id, string name, float defaultValue, float min, float max, float step, CustomOption parent = null, bool isHeader = false) { + List selections = new List(); + for (float s = min; s <= max; s += step) + selections.Add(s); + return new CustomOption(id, name, selections.Cast().ToArray(), defaultValue, parent, isHeader); + } + + public static CustomOption Create(int id, string name, bool defaultValue, CustomOption parent = null, bool isHeader = false) { + return new CustomOption(id, name, new string[]{"Off", "On"}, defaultValue ? "On" : "Off", parent, isHeader); + } + + // Static behaviour + + public static void switchPreset(int newPreset) { + CustomOption.preset = newPreset; + foreach (CustomOption option in CustomOption.options) { + if (option.id == 0) continue; + + option.entry = TheOtherRolesPlugin.Instance.Config.Bind($"Preset{preset}", option.id.ToString(), option.defaultSelection); + option.selection = Mathf.Clamp(option.entry.Value, 0, option.selections.Length - 1); + if (option.optionBehaviour != null && option.optionBehaviour is StringOption stringOption) { + stringOption.oldValue = stringOption.Value = option.selection; + stringOption.ValueText.text = option.selections[option.selection].ToString(); + } + } + } + + public static void ShareOptionSelections() { + if (PlayerControl.AllPlayerControls.Count <= 1 || AmongUsClient.Instance?.AmHost == false && PlayerControl.LocalPlayer == null) return; + foreach (CustomOption option in CustomOption.options) { + MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShareOptionSelection, Hazel.SendOption.Reliable); + messageWriter.WritePacked((uint)option.id); + messageWriter.WritePacked((uint)Convert.ToUInt32(option.selection)); + messageWriter.EndMessage(); + } + } + + // Getter + + public int getSelection() { + return selection; + } + + public bool getBool() { + return selection > 0; + } + + public float getFloat() { + return (float)selections[selection]; + } + + // Option changes + + public void updateSelection(int newSelection) { + selection = Mathf.Clamp((newSelection + selections.Length) % selections.Length, 0, selections.Length - 1); + if (optionBehaviour != null && optionBehaviour is StringOption stringOption) { + stringOption.oldValue = stringOption.Value = selection; + stringOption.ValueText.text = selections[selection].ToString(); + + if (AmongUsClient.Instance?.AmHost == true && PlayerControl.LocalPlayer) { + if (id == 0) switchPreset(selection); // Switch presets + else if (entry != null) entry.Value = selection; // Save selection to config + + ShareOptionSelections();// Share all selections + } + } + } + } + + [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Start))] + class GameOptionsMenuStartPatch { + public static void Postfix(GameOptionsMenu __instance) { + if (GameObject.Find("TORSettings") != null) { // Settings setup has already been performed, fixing the title of the tab and returning + GameObject.Find("TORSettings").transform.FindChild("GameGroup").FindChild("Text").GetComponent().SetText("The Other Roles Settings"); + return; + } + + // Setup TOR tab + var template = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(); + if (template == null) return; + var gameSettings = GameObject.Find("Game Settings"); + var gameSettingMenu = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(); + var torSettings = UnityEngine.Object.Instantiate(gameSettings, gameSettings.transform.parent); + var torMenu = torSettings.transform.FindChild("GameGroup").FindChild("SliderInner").GetComponent(); + torSettings.name = "TORSettings"; + + var roleTab = GameObject.Find("RoleTab"); + var gameTab = GameObject.Find("GameTab"); + + var torTab = UnityEngine.Object.Instantiate(roleTab, roleTab.transform.parent); + var torTabHighlight = torTab.transform.FindChild("Hat Button").FindChild("Tab Background").GetComponent(); + torTab.transform.FindChild("Hat Button").FindChild("Icon").GetComponent().sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TabIcon.png", 100f); + + gameTab.transform.position += Vector3.left * 0.5f; + torTab.transform.position += Vector3.right * 0.5f; + roleTab.transform.position += Vector3.left * 0.5f; + + var tabs = new GameObject[]{gameTab, roleTab, torTab}; + for (int i = 0; i < tabs.Length; i++) { + var button = tabs[i].GetComponentInChildren(); + if (button == null) continue; + int copiedIndex = i; + button.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent(); + button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { + gameSettingMenu.RegularGameSettings.SetActive(false); + gameSettingMenu.RolesSettings.gameObject.SetActive(false); + torSettings.gameObject.SetActive(false); + gameSettingMenu.GameSettingsHightlight.enabled = false; + gameSettingMenu.RolesSettingsHightlight.enabled = false; + torTabHighlight.enabled = false; + if (copiedIndex == 0) { + gameSettingMenu.RegularGameSettings.SetActive(true); + gameSettingMenu.GameSettingsHightlight.enabled = true; + } else if (copiedIndex == 1) { + gameSettingMenu.RolesSettings.gameObject.SetActive(true); + gameSettingMenu.RolesSettingsHightlight.enabled = true; + } else if (copiedIndex == 2) { + torSettings.gameObject.SetActive(true); + torTabHighlight.enabled = true; + } + })); + } + + foreach (OptionBehaviour option in torMenu.GetComponentsInChildren()) + UnityEngine.Object.Destroy(option.gameObject); + List torOptions = new List(); + + for (int i = 0; i < CustomOption.options.Count; i++) { + CustomOption option = CustomOption.options[i]; + if (option.optionBehaviour == null) { + StringOption stringOption = UnityEngine.Object.Instantiate(template, torMenu.transform); + torOptions.Add(stringOption); + stringOption.OnValueChanged = new Action((o) => {}); + stringOption.TitleText.text = option.name; + stringOption.Value = stringOption.oldValue = option.selection; + stringOption.ValueText.text = option.selections[option.selection].ToString(); + + option.optionBehaviour = stringOption; + } + option.optionBehaviour.gameObject.SetActive(true); + } + + torMenu.Children = torOptions.ToArray(); + torSettings.gameObject.SetActive(false); + + // Adapt task count for main options + + var commonTasksOption = __instance.Children.FirstOrDefault(x => x.name == "NumCommonTasks").TryCast(); + if(commonTasksOption != null) commonTasksOption.ValidRange = new FloatRange(0f, 4f); + + var shortTasksOption = __instance.Children.FirstOrDefault(x => x.name == "NumShortTasks").TryCast(); + if(shortTasksOption != null) shortTasksOption.ValidRange = new FloatRange(0f, 23f); + + var longTasksOption = __instance.Children.FirstOrDefault(x => x.name == "NumLongTasks").TryCast(); + if(longTasksOption != null) longTasksOption.ValidRange = new FloatRange(0f, 15f); + } + } + + [HarmonyPatch(typeof(StringOption), nameof(StringOption.OnEnable))] + public class StringOptionEnablePatch { + public static bool Prefix(StringOption __instance) { + CustomOption option = CustomOption.options.FirstOrDefault(option => option.optionBehaviour == __instance); + if (option == null) return true; + + __instance.OnValueChanged = new Action((o) => {}); + __instance.TitleText.text = option.name; + __instance.Value = __instance.oldValue = option.selection; + __instance.ValueText.text = option.selections[option.selection].ToString(); + + return false; + } + } + + [HarmonyPatch(typeof(StringOption), nameof(StringOption.Increase))] + public class StringOptionIncreasePatch + { + public static bool Prefix(StringOption __instance) + { + CustomOption option = CustomOption.options.FirstOrDefault(option => option.optionBehaviour == __instance); + if (option == null) return true; + option.updateSelection(option.selection + 1); + return false; + } + } + + [HarmonyPatch(typeof(StringOption), nameof(StringOption.Decrease))] + public class StringOptionDecreasePatch + { + public static bool Prefix(StringOption __instance) + { + CustomOption option = CustomOption.options.FirstOrDefault(option => option.optionBehaviour == __instance); + if (option == null) return true; + option.updateSelection(option.selection - 1); + return false; + } + } + + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSyncSettings))] + public class RpcSyncSettingsPatch + { + public static void Postfix() + { + CustomOption.ShareOptionSelections(); + } + } + + + [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Update))] + class GameOptionsMenuUpdatePatch + { + private static float timer = 1f; + public static void Postfix(GameOptionsMenu __instance) { + if (__instance.Children.Length < 100) return; // TODO: Introduce a cleaner way to seperate the TOR settings from the game settings + + __instance.GetComponentInParent().YBounds.max = -0.5F + __instance.Children.Length * 0.55F; + timer += Time.deltaTime; + if (timer < 0.1f) return; + timer = 0f; + + float offset = 2.75f; + foreach (CustomOption option in CustomOption.options) { + if (option?.optionBehaviour != null && option.optionBehaviour.gameObject != null) { + bool enabled = true; + var parent = option.parent; + while (parent != null && enabled) { + enabled = parent.selection != 0; + parent = parent.parent; + } + option.optionBehaviour.gameObject.SetActive(enabled); + if (enabled) { + offset -= option.isHeader ? 0.75f : 0.5f; + option.optionBehaviour.transform.localPosition = new Vector3(option.optionBehaviour.transform.localPosition.x, offset, option.optionBehaviour.transform.localPosition.z); + } + } + } + } + } + + [HarmonyPatch(typeof(GameSettingMenu), nameof(GameSettingMenu.Start))] + class GameSettingMenuStartPatch { + public static void Prefix(GameSettingMenu __instance) { + __instance.HideForOnline = new Transform[]{}; + } + + public static void Postfix(GameSettingMenu __instance) { + // Setup mapNameTransform + var mapNameTransform = __instance.AllItems.FirstOrDefault(x => x.gameObject.activeSelf && x.name.Equals("MapName", StringComparison.OrdinalIgnoreCase)); + if (mapNameTransform == null) return; + + var options = new Il2CppSystem.Collections.Generic.List>(); + for (int i = 0; i < Constants.MapNames.Length; i++) { + var kvp = new Il2CppSystem.Collections.Generic.KeyValuePair(); + kvp.key = Constants.MapNames[i]; + kvp.value = i; + options.Add(kvp); + } + mapNameTransform.GetComponent().Values = options; + } + } + + [HarmonyPatch(typeof(Constants), nameof(Constants.ShouldFlipSkeld))] + class ConstantsShouldFlipSkeldPatch { + public static bool Prefix(ref bool __result) { + if (PlayerControl.GameOptions == null) return true; + __result = PlayerControl.GameOptions.MapId == 3; + return false; + } + } + + [HarmonyPatch] + class GameOptionsDataPatch + { + private static IEnumerable TargetMethods() { + return typeof(GameOptionsData).GetMethods().Where(x => x.ReturnType == typeof(string) && x.GetParameters().Length == 1 && x.GetParameters()[0].ParameterType == typeof(int)); + } + + private static void Postfix(ref string __result) + { + StringBuilder sb = new StringBuilder(__result); + foreach (CustomOption option in CustomOption.options) { + if (option.parent == null) { + if (option == CustomOptionHolder.crewmateRolesCountMin) { + var optionName = CustomOptionHolder.cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Crewmate Roles"); + var min = CustomOptionHolder.crewmateRolesCountMin.getSelection(); + var max = CustomOptionHolder.crewmateRolesCountMax.getSelection(); + if (min > max) min = max; + var optionValue = (min == max) ? $"{max}" : $"{min} - {max}"; + sb.AppendLine($"{optionName}: {optionValue}"); + } else if (option == CustomOptionHolder.neutralRolesCountMin) { + var optionName = CustomOptionHolder.cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Neutral Roles"); + var min = CustomOptionHolder.neutralRolesCountMin.getSelection(); + var max = CustomOptionHolder.neutralRolesCountMax.getSelection(); + if (min > max) min = max; + var optionValue = (min == max) ? $"{max}" : $"{min} - {max}"; + sb.AppendLine($"{optionName}: {optionValue}"); + } else if (option == CustomOptionHolder.impostorRolesCountMin) { + 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 (min > max) min = max; + var optionValue = (min == max) ? $"{max}" : $"{min} - {max}"; + sb.AppendLine($"{optionName}: {optionValue}"); + } else if ((option == CustomOptionHolder.crewmateRolesCountMax) || (option == CustomOptionHolder.neutralRolesCountMax) || (option == CustomOptionHolder.impostorRolesCountMax)) { + continue; + } else { + sb.AppendLine($"{option.name}: {option.selections[option.selection].ToString()}"); + } + + } + } + CustomOption parent = null; + foreach (CustomOption option in CustomOption.options) + if (option.parent != null) { + if (option.parent != parent) { + sb.AppendLine(); + parent = option.parent; + } + sb.AppendLine($"{option.name}: {option.selections[option.selection].ToString()}"); + } + + var hudString = sb.ToString(); + + int defaultSettingsLines = 23; + int roleSettingsLines = defaultSettingsLines + 38; + int detailedSettingsP1 = roleSettingsLines + 40; + int detailedSettingsP2 = detailedSettingsP1 + 42; + int detailedSettingsP3 = detailedSettingsP2 + 42; + int end1 = hudString.TakeWhile(c => (defaultSettingsLines -= (c == '\n' ? 1 : 0)) > 0).Count(); + int end2 = hudString.TakeWhile(c => (roleSettingsLines -= (c == '\n' ? 1 : 0)) > 0).Count(); + int end3 = hudString.TakeWhile(c => (detailedSettingsP1 -= (c == '\n' ? 1 : 0)) > 0).Count(); + int end4 = hudString.TakeWhile(c => (detailedSettingsP2 -= (c == '\n' ? 1 : 0)) > 0).Count(); + int end5 = hudString.TakeWhile(c => (detailedSettingsP3 -= (c == '\n' ? 1 : 0)) > 0).Count(); + int counter = TheOtherRolesPlugin.optionsPage; + if (counter == 0) { + hudString = hudString.Substring(0, end1) + "\n"; + } else if (counter == 1) { + hudString = hudString.Substring(end1 + 1, end2 - end1); + // Temporary fix, should add a new CustomOption for spaces + int gap = 2; + int index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count(); + hudString = hudString.Insert(index, "\n"); + gap = 6; + index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count(); + hudString = hudString.Insert(index, "\n"); + gap = 19; + index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count(); + hudString = hudString.Insert(index + 1, "\n"); + gap = 24; + index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count(); + hudString = hudString.Insert(index + 1, "\n"); + } else if (counter == 2) { + hudString = hudString.Substring(end2 + 1, end3 - end2); + } else if (counter == 3) { + hudString = hudString.Substring(end3 + 1, end4 - end3); + } else if (counter == 4) { + hudString = hudString.Substring(end4 + 1, end5 - end4); + } else if (counter == 5) { + hudString = hudString.Substring(end5 + 1); + } + + hudString += $"\n Press tab for more... ({counter+1}/6)"; + __result = hudString; + } + } + + [HarmonyPatch(typeof(KeyboardJoystick), nameof(KeyboardJoystick.Update))] + public static class GameOptionsNextPagePatch + { + public static void Postfix(KeyboardJoystick __instance) + { + if(Input.GetKeyDown(KeyCode.Tab)) { + TheOtherRolesPlugin.optionsPage = (TheOtherRolesPlugin.optionsPage + 1) % 6; + } + } + } + + + [HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))] + public class GameSettingsScalePatch { + public static void Prefix(HudManager __instance) { + if (__instance.GameSettings != null) __instance.GameSettings.fontSize = 1.2f; + } + } +} \ No newline at end of file diff --git a/TheOtherRoles/Patches/CredentialsPatch.cs b/TheOtherRoles/Patches/CredentialsPatch.cs index bf04743..d241b39 100644 --- a/TheOtherRoles/Patches/CredentialsPatch.cs +++ b/TheOtherRoles/Patches/CredentialsPatch.cs @@ -11,7 +11,7 @@ namespace TheOtherRoles.Patches { public static class CredentialsPatch { public static string fullCredentials = $@"TheOtherRoles v{TheOtherRolesPlugin.Version.ToString()} -Modded by Eisbison, EndOfFile +Modded by Eisbison, EndOfFile Thunderstorm584 & Mallöris Button design by Bavari"; @@ -19,6 +19,8 @@ Button design by Bavari"; $@"Modded by Eisbison, Thunderstorm584, EndOfFile & Mallöris Design by Bavari"; + public static string contributorsCredentials = "GitHub Contributors: Alex2911, gendelo3"; + [HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))] private static class VersionShowerPatch { @@ -27,17 +29,12 @@ Design by Bavari"; if (amongUsLogo == null) return; var credentials = UnityEngine.Object.Instantiate(__instance.text); - credentials.transform.position = new Vector3(0, 0.1f, 0); - credentials.SetText(mainMenuCredentials); + credentials.transform.position = new Vector3(0, 0, 0); + credentials.SetText($"v{TheOtherRolesPlugin.Version.ToString()}\n\n{mainMenuCredentials}\n\n{contributorsCredentials}"); credentials.alignment = TMPro.TextAlignmentOptions.Center; credentials.fontSize *= 0.75f; - var version = UnityEngine.Object.Instantiate(credentials); - version.transform.position = new Vector3(0, -0.25f, 0); - version.SetText($"v{TheOtherRolesPlugin.Version.ToString()}"); - credentials.transform.SetParent(amongUsLogo.transform); - version.transform.SetParent(amongUsLogo.transform); } } diff --git a/TheOtherRoles/Patches/ExileControllerPatch.cs b/TheOtherRoles/Patches/ExileControllerPatch.cs index 12fc88f..a0648a0 100644 --- a/TheOtherRoles/Patches/ExileControllerPatch.cs +++ b/TheOtherRoles/Patches/ExileControllerPatch.cs @@ -132,6 +132,9 @@ namespace TheOtherRoles.Patches { Seer.deadBodyPositions = new List(); } + // Tracker reset deadBodyPositions + Tracker.deadBodyPositions = new List(); + // Arsonist deactivate dead poolable players if (Arsonist.arsonist != null && Arsonist.arsonist == PlayerControl.LocalPlayer) { int visibleCounter = 0; diff --git a/TheOtherRoles/Patches/GameStartManagerPatch.cs b/TheOtherRoles/Patches/GameStartManagerPatch.cs index 1a52349..21ed43b 100644 --- a/TheOtherRoles/Patches/GameStartManagerPatch.cs +++ b/TheOtherRoles/Patches/GameStartManagerPatch.cs @@ -11,6 +11,7 @@ namespace TheOtherRoles.Patches { public class GameStartManagerPatch { public static Dictionary playerVersions = new Dictionary(); private static float timer = 600f; + private static float kickingTimer = 0f; private static bool versionSent = false; private static string lobbyCodeText = ""; @@ -21,6 +22,8 @@ namespace TheOtherRoles.Patches { versionSent = false; // Reset lobby countdown timer timer = 600f; + // Reset kicking timer + kickingTimer = 0f; // Copy lobby code string code = InnerNet.GameCode.IntToGameName(AmongUsClient.Instance.GameId); GUIUtility.systemCopyBuffer = code; @@ -42,15 +45,7 @@ namespace TheOtherRoles.Patches { // Send version as soon as PlayerControl.LocalPlayer exists if (PlayerControl.LocalPlayer != null && !versionSent) { versionSent = true; - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VersionHandshake, Hazel.SendOption.Reliable, -1); - writer.Write((byte)TheOtherRolesPlugin.Version.Major); - writer.Write((byte)TheOtherRolesPlugin.Version.Minor); - writer.Write((byte)TheOtherRolesPlugin.Version.Build); - writer.WritePacked(AmongUsClient.Instance.ClientId); - writer.Write((byte)(TheOtherRolesPlugin.Version.Revision < 0 ? 0xFF : TheOtherRolesPlugin.Version.Revision)); - writer.Write(Assembly.GetExecutingAssembly().ManifestModule.ModuleVersionId.ToByteArray()); - AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.versionHandshake(TheOtherRolesPlugin.Version.Major, TheOtherRolesPlugin.Version.Minor, TheOtherRolesPlugin.Version.Build, TheOtherRolesPlugin.Version.Revision, Assembly.GetExecutingAssembly().ManifestModule.ModuleVersionId, AmongUsClient.Instance.ClientId); + Helpers.shareGameVersion(); } // Host update with version handshake infos @@ -81,15 +76,35 @@ namespace TheOtherRoles.Patches { } } if (blockStart) { - // __instance.StartButton.color = Palette.DisabledClear; // Allow the start for this version to test the feature, blocking it with the next version + __instance.StartButton.color = __instance.startLabelText.color = Palette.DisabledClear; __instance.GameStartText.text = message; __instance.GameStartText.transform.localPosition = __instance.StartButton.transform.localPosition + Vector3.up * 2; } else { - // __instance.StartButton.color = ((__instance.LastPlayerCount >= __instance.MinPlayers) ? Palette.EnabledColor : Palette.DisabledClear); // Allow the start for this version to test the feature, blocking it with the next version + __instance.StartButton.color = __instance.startLabelText.color = ((__instance.LastPlayerCount >= __instance.MinPlayers) ? Palette.EnabledColor : Palette.DisabledClear); __instance.GameStartText.transform.localPosition = __instance.StartButton.transform.localPosition; } } + // Client update with handshake infos + if (!AmongUsClient.Instance.AmHost) { + if (!playerVersions.ContainsKey(AmongUsClient.Instance.HostId) || TheOtherRolesPlugin.Version.CompareTo(playerVersions[AmongUsClient.Instance.HostId].version) != 0) { + kickingTimer += Time.deltaTime; + if (kickingTimer > 10) { + kickingTimer = 0; + AmongUsClient.Instance.ExitGame(DisconnectReasons.ExitGame); + SceneChanger.ChangeScene("MainMenu"); + } + + __instance.GameStartText.text = $"The host has no or a different version of The Other Roles\nYou will be kicked in {Math.Round(10 - kickingTimer)}s"; + __instance.GameStartText.transform.localPosition = __instance.StartButton.transform.localPosition + Vector3.up * 2; + } else { + __instance.GameStartText.transform.localPosition = __instance.StartButton.transform.localPosition; + if (__instance.startState != GameStartManager.StartingStates.Countdown) { + __instance.GameStartText.text = String.Empty; + } + } + } + // Lobby code replacement __instance.GameRoomName.text = TheOtherRolesPlugin.StreamerMode.Value ? $"{TheOtherRolesPlugin.StreamerModeReplacementText.Value}" : lobbyCodeText; @@ -115,16 +130,26 @@ namespace TheOtherRoles.Patches { // Block game start if not everyone has the same mod version bool continueStart = true; - // Allow the start for this version to test the feature, blocking it with the next version - // if (AmongUsClient.Instance.AmHost) { - // foreach (InnerNet.ClientData client in AmongUsClient.Instance.allClients) { - // if (client.Character == null) continue; - // var dummyComponent = client.Character.GetComponent(); - // if (dummyComponent != null && dummyComponent.enabled) continue; - // if (!playerVersions.ContainsKey(client.Id) || (playerVersions[client.Id].Item1 != TheOtherRolesPlugin.Major || playerVersions[client.Id].Item2 != TheOtherRolesPlugin.Minor || playerVersions[client.Id].Item3 != TheOtherRolesPlugin.Patch)) - // continueStart = false; - // } - // } + if (AmongUsClient.Instance.AmHost) { + foreach (InnerNet.ClientData client in AmongUsClient.Instance.allClients) { + if (client.Character == null) continue; + var dummyComponent = client.Character.GetComponent(); + if (dummyComponent != null && dummyComponent.enabled) + continue; + + if (!playerVersions.ContainsKey(client.Id)) { + continueStart = false; + break; + } + + PlayerVersion PV = playerVersions[client.Id]; + int diff = TheOtherRolesPlugin.Version.CompareTo(PV.version); + if (diff != 0 || !PV.GuidMatches()) { + continueStart = false; + break; + } + } + } return continueStart; } } diff --git a/TheOtherRoles/Patches/MeetingPatch.cs b/TheOtherRoles/Patches/MeetingPatch.cs index 5b58012..1d725d6 100644 --- a/TheOtherRoles/Patches/MeetingPatch.cs +++ b/TheOtherRoles/Patches/MeetingPatch.cs @@ -278,6 +278,16 @@ namespace TheOtherRoles.Patches { PlayerControl target = Helpers.playerById((byte)__instance.playerStates[buttonTarget].TargetPlayerId); if (!(__instance.state == MeetingHud.VoteStates.Voted || __instance.state == MeetingHud.VoteStates.NotVoted) || target == null || Guesser.remainingShots <= 0 ) return; + if (!Guesser.killsThroughShield && target == Medic.shielded) { // Depending on the options, shooting the shielded player will not allow the guess, notifiy everyone about the kill attempt and close the window + __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true)); + UnityEngine.Object.Destroy(container.gameObject); + + MessageWriter murderAttemptWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShieldedMurderAttempt, Hazel.SendOption.Reliable, -1); + AmongUsClient.Instance.FinishRpcImmediately(murderAttemptWriter); + RPCProcedure.shieldedMurderAttempt(); + return; + } + var mainRoleInfo = RoleInfo.getRoleInfoForPlayer(target).FirstOrDefault(); if (mainRoleInfo == null) return; @@ -291,11 +301,15 @@ namespace TheOtherRoles.Patches { else __instance.playerStates.ToList().ForEach(x => { if (x.transform.FindChild("ShootButton") != null) UnityEngine.Object.Destroy(x.transform.FindChild("ShootButton").gameObject); }); - // Shoot player + // Shoot player and send chat info if activated MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.GuesserShoot, Hazel.SendOption.Reliable, -1); writer.Write(target.PlayerId); AmongUsClient.Instance.FinishRpcImmediately(writer); RPCProcedure.guesserShoot(target.PlayerId); + if (Guesser.showInfoInGhostChat) { + string msg = $"Guesser guessed the role {roleInfo.name} for {target.name}!"; + target.RpcSendChat(msg); // As the target is dead at this point, only ghosts will see the message. + } } })); diff --git a/TheOtherRoles/Patches/PlayerControlPatch.cs b/TheOtherRoles/Patches/PlayerControlPatch.cs index 055fa6d..4a73702 100644 --- a/TheOtherRoles/Patches/PlayerControlPatch.cs +++ b/TheOtherRoles/Patches/PlayerControlPatch.cs @@ -286,33 +286,56 @@ namespace TheOtherRoles.Patches { } static void trackerUpdate() { - if (Tracker.arrow?.arrow == null) return; + // Handle player tracking + if (Tracker.arrow?.arrow != null) { + if (Tracker.tracker == null || PlayerControl.LocalPlayer != Tracker.tracker) { + Tracker.arrow.arrow.SetActive(false); + return; + } + + if (Tracker.tracker != null && Tracker.tracked != null && PlayerControl.LocalPlayer == Tracker.tracker && !Tracker.tracker.Data.IsDead) { + Tracker.timeUntilUpdate -= Time.fixedDeltaTime; + + if (Tracker.timeUntilUpdate <= 0f) { + bool trackedOnMap = !Tracker.tracked.Data.IsDead; + Vector3 position = Tracker.tracked.transform.position; + if (!trackedOnMap) { // Check for dead body + DeadBody body = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(b => b.ParentId == Tracker.tracked.PlayerId); + if (body != null) { + trackedOnMap = true; + position = body.transform.position; + } + } - if (Tracker.tracker == null || PlayerControl.LocalPlayer != Tracker.tracker) { - Tracker.arrow.arrow.SetActive(false); - return; + Tracker.arrow.Update(position); + Tracker.arrow.arrow.SetActive(trackedOnMap); + Tracker.timeUntilUpdate = Tracker.updateIntervall; + } else { + Tracker.arrow.Update(); + } + } } - if (Tracker.tracker != null && Tracker.tracked != null && PlayerControl.LocalPlayer == Tracker.tracker && !Tracker.tracker.Data.IsDead) { - Tracker.timeUntilUpdate -= Time.fixedDeltaTime; + // Handle corpses tracking + if (Tracker.tracker != null && Tracker.tracker == PlayerControl.LocalPlayer && Tracker.corpsesTrackingTimer >= 0f && !Tracker.tracker.Data.IsDead) { + bool arrowsCountChanged = Tracker.localArrows.Count != Tracker.deadBodyPositions.Count(); + int index = 0; - if (Tracker.timeUntilUpdate <= 0f) { - bool trackedOnMap = !Tracker.tracked.Data.IsDead; - Vector3 position = Tracker.tracked.transform.position; - if (!trackedOnMap) { // Check for dead body - DeadBody body = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(b => b.ParentId == Tracker.tracked.PlayerId); - if (body != null) { - trackedOnMap = true; - position = body.transform.position; - } + if (arrowsCountChanged) { + foreach (Arrow arrow in Tracker.localArrows) UnityEngine.Object.Destroy(arrow.arrow); + Tracker.localArrows = new List(); + } + foreach (Vector3 position in Tracker.deadBodyPositions) { + if (arrowsCountChanged) { + Tracker.localArrows.Add(new Arrow(Tracker.color)); + Tracker.localArrows[index].arrow.SetActive(true); } - - Tracker.arrow.Update(position); - Tracker.arrow.arrow.SetActive(trackedOnMap); - Tracker.timeUntilUpdate = Tracker.updateIntervall; - } else { - Tracker.arrow.Update(); + if (Tracker.localArrows[index] != null) Tracker.localArrows[index].Update(position); + index++; } + } else if (Tracker.localArrows.Count > 0) { + foreach (Arrow arrow in Tracker.localArrows) UnityEngine.Object.Destroy(arrow.arrow); + Tracker.localArrows = new List(); } } @@ -562,6 +585,7 @@ namespace TheOtherRoles.Patches { } } } + static void vultureUpdate() { if (Vulture.vulture == null || PlayerControl.LocalPlayer != Vulture.vulture || Vulture.localArrows == null || !Vulture.showArrows) return; if (Vulture.vulture.Data.IsDead) { @@ -714,28 +738,6 @@ namespace TheOtherRoles.Patches { Helpers.handleVampireBiteOnBodyReport(); } } - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckMurder))] - class RpcMurderPlayer { - public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)]PlayerControl target) { - PlayerControl killer = __instance; - if (AmongUsClient.Instance.IsGameOver || !AmongUsClient.Instance.AmHost) return false; - if (!target || killer.Data.IsDead || killer.Data.Disconnected) return false; // Allow non Impostor kills compared to vanilla code - if (target.Data == null || target.Data.IsDead) return false; // Allow killing players in vents compared to vanilla code - - if (Helpers.handleMurderAttempt(killer, target)) { // Custom checks - if (Mini.mini != null && killer == Mini.mini || BountyHunter.bountyHunter != null && killer == BountyHunter.bountyHunter) { // Not checked by official servers - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(killer.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1); - writer.Write(killer.PlayerId); - writer.Write(target.PlayerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.uncheckedMurderPlayer(killer.PlayerId, target.PlayerId); - } else { // Checked by official servers - killer.RpcMurderPlayer(target); - } - } - return false; - } - } [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.LocalPlayer.CmdReportDeadBody))] class BodyReportPatch @@ -854,6 +856,9 @@ namespace TheOtherRoles.Patches { } if (Seer.deadBodyPositions != null) Seer.deadBodyPositions.Add(target.transform.position); + // Tracker store body positions + if (Tracker.deadBodyPositions != null) Tracker.deadBodyPositions.Add(target.transform.position); + // Medium add body if (Medium.deadBodies != null) { Medium.featureDeadBodies.Add(new Tuple(deadPlayer, target.transform.position)); diff --git a/TheOtherRoles/Patches/UpdatePatch.cs b/TheOtherRoles/Patches/UpdatePatch.cs index 62d5085..dc96ff7 100644 --- a/TheOtherRoles/Patches/UpdatePatch.cs +++ b/TheOtherRoles/Patches/UpdatePatch.cs @@ -166,6 +166,16 @@ namespace TheOtherRoles.Patches { if (Lovers.lover1.PlayerId == player.TargetPlayerId || Lovers.lover2.PlayerId == player.TargetPlayerId) player.NameText.text += suffix; } + + // Hacker and Detective + if (PlayerControl.LocalPlayer != null && !PlayerControl.LocalPlayer.Data.IsDead && (PlayerControl.LocalPlayer == Hacker.hacker || PlayerControl.LocalPlayer == Detective.detective || PlayerControl.LocalPlayer == Medium.medium)) { + if (MeetingHud.Instance != null) { + foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates) { + var target = Helpers.playerById(player.TargetPlayerId); + if (target != null) player.NameText.text += $" ({(Helpers.isLighterColor(target.Data.DefaultOutfit.ColorId) ? "L" : "D")})"; + } + } + } } static void updateShielded() { @@ -180,6 +190,7 @@ namespace TheOtherRoles.Patches { Hacker.hackerTimer -= Time.deltaTime; Lighter.lighterTimer -= Time.deltaTime; Trickster.lightsOutTimer -= Time.deltaTime; + Tracker.corpsesTrackingTimer -= Time.deltaTime; } public static void miniUpdate() { diff --git a/TheOtherRoles/Patches/UsablesPatch.cs b/TheOtherRoles/Patches/UsablesPatch.cs index f269301..05007e4 100644 --- a/TheOtherRoles/Patches/UsablesPatch.cs +++ b/TheOtherRoles/Patches/UsablesPatch.cs @@ -99,17 +99,31 @@ namespace TheOtherRoles.Patches { [HarmonyPatch(typeof(VentButton), nameof(VentButton.SetTarget))] class VentButtonSetTargetPatch { + static Sprite defaultVentSprite = null; static void Postfix(VentButton __instance) { // Trickster render special vent button if (Trickster.trickster != null && Trickster.trickster == PlayerControl.LocalPlayer) { - if (__instance.currentTarget != null && __instance.currentTarget.gameObject != null && __instance.currentTarget.gameObject.name.StartsWith("JackInTheBoxVent_")) - __instance.graphic.sprite = Trickster.getTricksterVentButtonSprite(); - else - __instance.graphic.sprite = DestroyableSingleton.Instance.GetImage(ImageNames.VentButton); + if (defaultVentSprite == null) defaultVentSprite = __instance.graphic.sprite; + bool isSpecialVent = __instance.currentTarget != null && __instance.currentTarget.gameObject != null && __instance.currentTarget.gameObject.name.StartsWith("JackInTheBoxVent_"); + __instance.graphic.sprite = isSpecialVent ? Trickster.getTricksterVentButtonSprite() : defaultVentSprite; + __instance.buttonLabelText.enabled = !isSpecialVent; } } } + [HarmonyPatch(typeof(KillButton), nameof(KillButton.DoClick))] + class KillButtonDoClickPatch { + public static bool Prefix(KillButton __instance) { + if (__instance.isActiveAndEnabled && __instance.currentTarget && !__instance.isCoolingDown && !PlayerControl.LocalPlayer.Data.IsDead && PlayerControl.LocalPlayer.CanMove) { + // Use an unchecked kill command, to allow shorter kill cooldowns etc. without getting kicked + Helpers.checkMuderAttemptAndKill(PlayerControl.LocalPlayer, __instance.currentTarget); + __instance.SetTarget(null); + } + return false; + } + } + + [HarmonyPatch(typeof(SabotageButton), nameof(SabotageButton.Refresh))] class SabotageButtonRefreshPatch { static void Postfix() { @@ -447,4 +461,15 @@ namespace TheOtherRoles.Patches { } } } + + [HarmonyPatch(typeof(MedScanMinigame), nameof(MedScanMinigame.FixedUpdate))] + class MedScanMinigameFixedUpdatePatch { + static void Prefix(MedScanMinigame __instance) { + if (MapOptions.allowParallelMedBayScans) { + __instance.medscan.CurrentUser = PlayerControl.LocalPlayer.PlayerId; + __instance.medscan.UsersList.Clear(); + } + } + } + } \ No newline at end of file diff --git a/TheOtherRoles/RPC.cs b/TheOtherRoles/RPC.cs index 602a823..df4edfa 100644 --- a/TheOtherRoles/RPC.cs +++ b/TheOtherRoles/RPC.cs @@ -58,7 +58,7 @@ namespace TheOtherRoles { // Main Controls - ResetVaribles = 50, + ResetVaribles = 60, ShareOptionSelection, ForceEnd, SetRole, @@ -69,10 +69,9 @@ namespace TheOtherRoles // Role functionality - EngineerFixLights = 81, + EngineerFixLights = 91, EngineerUsedRepair, CleanBody, - SheriffKill, MedicSetShielded, ShieldedMurderAttempt, TimeMasterShield, @@ -83,10 +82,7 @@ namespace TheOtherRoles CamouflagerCamouflage, TrackerUsedTracker, VampireSetBitten, - VampireTryKill, PlaceGarlic, - JackalKill, - SidekickKill, JackalCreatesSidekick, SidekickPromotes, ErasePlayerRoles, @@ -256,8 +252,10 @@ namespace TheOtherRoles ver = new System.Version(major, minor, build); else ver = new System.Version(major, minor, build, revision); - GameStartManagerPatch.playerVersions[clientId] = new GameStartManagerPatch.PlayerVersion(ver, guid); + + if (AmongUsClient.Instance.AmHost && clientId != AmongUsClient.Instance.ClientId) // Host returns version whenever another player sends one + Helpers.shareGameVersion(); } public static void useUncheckedVent(int ventId, byte playerId, byte isEnter) { @@ -308,17 +306,6 @@ namespace TheOtherRoles } } - public static void sheriffKill(byte targetId) { - foreach (PlayerControl player in PlayerControl.AllPlayerControls) - { - if (player.PlayerId == targetId) - { - Sheriff.sheriff.MurderPlayer(player); - return; - } - } - } - public static void timeMasterRewindTime() { TimeMaster.shieldActive = false; // Shield is no longer active when rewinding if(TimeMaster.timeMaster != null && TimeMaster.timeMaster == PlayerControl.LocalPlayer) { @@ -355,7 +342,12 @@ namespace TheOtherRoles } public static void shieldedMurderAttempt() { - if (Medic.shielded != null && Medic.shielded == PlayerControl.LocalPlayer && Medic.showAttemptToShielded && HudManager.Instance?.FullScreen != null) { + if (Medic.shielded == null || Medic.medic == null) return; + + bool isShieldedAndShow = Medic.shielded == PlayerControl.LocalPlayer && Medic.showAttemptToShielded; + bool isMedicAndShow = Medic.medic == PlayerControl.LocalPlayer && Medic.showAttemptToMedic; + + if ((isShieldedAndShow || isMedicAndShow) && HudManager.Instance?.FullScreen != null) { HudManager.Instance.FullScreen.enabled = true; HudManager.Instance.StartCoroutine(Effects.Lerp(0.5f, new Action((p) => { var renderer = HudManager.Instance.FullScreen; @@ -486,13 +478,6 @@ namespace TheOtherRoles } } - public static void vampireTryKill() { - if (Vampire.bitten != null && !Vampire.bitten.Data.IsDead) { - Vampire.vampire.MurderPlayer(Vampire.bitten); - } - Vampire.bitten = null; - } - public static void placeGarlic(byte[] buff) { Vector3 position = Vector3.zero; position.x = BitConverter.ToSingle(buff, 0*sizeof(float)); @@ -507,28 +492,6 @@ namespace TheOtherRoles 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) { @@ -708,8 +671,8 @@ namespace TheOtherRoles PlayerControl target = Helpers.playerById(playerId); if (target == null) return; target.Exiled(); - PlayerControl partner = target.getPartner(); // Lover check - byte partnerId = partner != null ? partner.PlayerId : playerId; + PlayerControl dyingLoverPartner = Lovers.bothDie ? target.getPartner() : null; // Lover check + byte partnerId = dyingLoverPartner != null ? dyingLoverPartner.PlayerId : playerId; Guesser.remainingShots = Mathf.Max(0, Guesser.remainingShots - 1); if (Constants.ShouldPlaySfx()) SoundManager.Instance.PlaySound(target.KillSfx, false, 0.8f); if (MeetingHud.Instance) { @@ -725,8 +688,8 @@ namespace TheOtherRoles if (HudManager.Instance != null && Guesser.guesser != null) if (PlayerControl.LocalPlayer == target) HudManager.Instance.KillOverlay.ShowKillAnimation(Guesser.guesser.Data, target.Data); - else if (partner != null && PlayerControl.LocalPlayer == partner) - HudManager.Instance.KillOverlay.ShowKillAnimation(partner.Data, partner.Data); + else if (dyingLoverPartner != null && PlayerControl.LocalPlayer == dyingLoverPartner) + HudManager.Instance.KillOverlay.ShowKillAnimation(dyingLoverPartner.Data, dyingLoverPartner.Data); } } @@ -802,9 +765,6 @@ namespace TheOtherRoles case (byte)CustomRPC.CleanBody: RPCProcedure.cleanBody(reader.ReadByte()); break; - case (byte)CustomRPC.SheriffKill: - RPCProcedure.sheriffKill(reader.ReadByte()); - break; case (byte)CustomRPC.TimeMasterRewindTime: RPCProcedure.timeMasterRewindTime(); break; @@ -836,21 +796,12 @@ namespace TheOtherRoles byte reset = reader.ReadByte(); RPCProcedure.vampireSetBitten(bittenId, reset); break; - case (byte)CustomRPC.VampireTryKill: - RPCProcedure.vampireTryKill(); - break; case (byte)CustomRPC.PlaceGarlic: RPCProcedure.placeGarlic(reader.ReadBytesAndSize()); break; case (byte)CustomRPC.TrackerUsedTracker: RPCProcedure.trackerUsedTracker(reader.ReadByte()); break; - case (byte)CustomRPC.JackalKill: - RPCProcedure.jackalKill(reader.ReadByte()); - break; - case (byte)CustomRPC.SidekickKill: - RPCProcedure.sidekickKill(reader.ReadByte()); - break; case (byte)CustomRPC.JackalCreatesSidekick: RPCProcedure.jackalCreatesSidekick(reader.ReadByte()); break; diff --git a/TheOtherRoles/Resources/PathfindButton.png b/TheOtherRoles/Resources/PathfindButton.png new file mode 100644 index 0000000000000000000000000000000000000000..32be1b1df348e4a7feff8540a8a2b4b6d7a98840 GIT binary patch literal 11237 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 P00000NkvXXu0mjfXrF*` literal 0 HcmV?d00001 diff --git a/TheOtherRoles/TheOtherRoles.cs b/TheOtherRoles/TheOtherRoles.cs index 1fdbc26..b173d4e 100644 --- a/TheOtherRoles/TheOtherRoles.cs +++ b/TheOtherRoles/TheOtherRoles.cs @@ -252,6 +252,7 @@ namespace TheOtherRoles public static int showShielded = 0; public static bool showAttemptToShielded = false; + public static bool showAttemptToMedic = false; public static bool setShieldAfterMeeting = false; public static Color shieldedColor = new Color32(0, 221, 255, byte.MaxValue); @@ -272,6 +273,7 @@ namespace TheOtherRoles usedShield = false; showShielded = CustomOptionHolder.medicShowShielded.getSelection(); showAttemptToShielded = CustomOptionHolder.medicShowAttemptToShielded.getBool(); + showAttemptToMedic = CustomOptionHolder.medicShowAttemptToMedic.getBool(); setShieldAfterMeeting = CustomOptionHolder.medicSetShieldAfterMeeting.getBool(); } } @@ -528,9 +530,15 @@ namespace TheOtherRoles public static class Tracker { public static PlayerControl tracker; public static Color color = new Color32(100, 58, 220, byte.MaxValue); + public static List localArrows = new List(); public static float updateIntervall = 5f; public static bool resetTargetAfterMeeting = false; + public static bool canTrackCorpses = false; + public static float corpsesTrackingCooldown = 30f; + public static float corpsesTrackingDuration = 5f; + public static float corpsesTrackingTimer = 0f; + public static List deadBodyPositions = new List(); public static PlayerControl currentTarget; public static PlayerControl tracked; @@ -538,6 +546,14 @@ namespace TheOtherRoles public static float timeUntilUpdate = 0f; public static Arrow arrow = new Arrow(Color.blue); + private static Sprite trackCorpsesButtonSprite; + public static Sprite getTrackCorpsesButtonSprite() + { + if (trackCorpsesButtonSprite) return trackCorpsesButtonSprite; + trackCorpsesButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.PathfindButton.png", 115f); + return trackCorpsesButtonSprite; + } + private static Sprite buttonSprite; public static Sprite getButtonSprite() { if (buttonSprite) return buttonSprite; @@ -559,6 +575,16 @@ namespace TheOtherRoles timeUntilUpdate = 0f; updateIntervall = CustomOptionHolder.trackerUpdateIntervall.getFloat(); resetTargetAfterMeeting = CustomOptionHolder.trackerResetTargetAfterMeeting.getBool(); + if (localArrows != null) { + foreach (Arrow arrow in localArrows) + if (arrow?.arrow != null) + UnityEngine.Object.Destroy(arrow.arrow); + } + deadBodyPositions = new List(); + corpsesTrackingTimer = 0f; + corpsesTrackingCooldown = CustomOptionHolder.trackerCorpsesTrackingCooldown.getFloat(); + corpsesTrackingDuration = CustomOptionHolder.trackerCorpsesTrackingDuration.getFloat(); + canTrackCorpses = CustomOptionHolder.trackerCanTrackCorpses.getBool(); } } @@ -949,6 +975,8 @@ namespace TheOtherRoles public static int remainingShots = 2; public static bool hasMultipleShotsPerMeeting = false; + public static bool showInfoInGhostChat = true; + public static bool killsThroughShield = true; public static Sprite getTargetSprite() { if (targetSprite) return targetSprite; @@ -961,6 +989,8 @@ namespace TheOtherRoles remainingShots = Mathf.RoundToInt(CustomOptionHolder.guesserNumberOfShots.getFloat()); hasMultipleShotsPerMeeting = CustomOptionHolder.guesserHasMultipleShotsPerMeeting.getBool(); + showInfoInGhostChat = CustomOptionHolder.guesserShowInfoInGhostChat.getBool(); + killsThroughShield = CustomOptionHolder.guesserKillsThroughShield.getBool(); } } diff --git a/TheOtherRoles/TheOtherRoles.csproj b/TheOtherRoles/TheOtherRoles.csproj index 49971c9..8bceeed 100644 --- a/TheOtherRoles/TheOtherRoles.csproj +++ b/TheOtherRoles/TheOtherRoles.csproj @@ -1,7 +1,7 @@ netstandard2.1 - 3.0.0 + 3.1.0 TheOtherRoles Eisbison @@ -22,11 +22,6 @@ - - - - - -- 2.39.5