From: gendelo3 Date: Sun, 23 Feb 2025 14:29:07 +0000 (+0100) Subject: Update to v4.8.0 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=61a70ef5dbf1099c41db8f5bbe37035d68c648b9;p=rhonda%2Ftheotherroles.git Update to v4.8.0 --- diff --git a/README.md b/README.md index 8a4671a..440adc0 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ The [Role Assignment](#role-assignment) section explains how the roles are being # Releases | Among Us - Version| Mod Version | Link | |----------|-------------|-----------------| +| 2024.11.26| v4.8.0| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.8.0/TheOtherRoles.zip) | 2024.11.26| v4.7.0| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.7.0/TheOtherRoles.zip) | 2024.6.18| v4.6.0| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.6.0/TheOtherRoles.zip) @@ -135,6 +136,18 @@ The [Role Assignment](#role-assignment) section explains how the roles are being # Changelog
Click to show the Changelog + +**Version 4.8.0** +- Added an optional Role Draft mode, where players can select their role out of some roles that are shown to them. +- Added a new option to allow the medic to shift the medic shield as well - no more invincible medics. +- Added partial key rebinding - all kill buttons will now use the vanilla kill button shortcut, same for vent and each roles first ability. +- Fixed the way options view panel to match vanilla changes +- Fixed options not showing/hiding sub-options when switching Presets +- Fixed a bug where voting the witch would not save the target +- Fixed a some bugs with the trapper, bomber and portal +- Fixed a bug in PropHunt where you players could not transform into props +- Fixed the summary button for the last game appearing outside the lobby sometimes +- Changed the positioning of the ping tracker in meetings **Version 4.7.0** - Updated to Among Us version 2024.11.26 (Vanilla Updates) @@ -904,6 +917,7 @@ Thanks to miniduikboot & GD for hosting modded servers (and so much more) [TheEpicRoles](https://github.com/LaicosVK/TheEpicRoles) - Idea for the first kill shield (partly) and the tabbed option menu (fully + some code), by **LaicosVK** **DasMonschta** **Nova**\ [Ninja](#ninja), [Thief](#thief), [Lawyer](#lawyer) / [Pursuer](#pursuer), [Deputy](#deputy), [Portalmaker](#portalmaker), [Guesser Modifier](#guesser-modifier) - Idea: [K3ndo](https://github.com/K3ndoo) ; Developed by [Gendelo](https://github.com/gendelo3) & [Mallöris](https://github.com/Mallaris) \ [ugackMiner53](https://github.com/ugackMiner53/PropHunt) - Idea and core code for the Prop Hunt game mode +Role Draft Music: [Unreal Superhero 3 by Kenët & Rez](https://www.youtube.com/watch?v=9STiQ8cCIo0) # Settings The mod adds a few new settings to Among Us (in addition to the role settings): diff --git a/TheOtherRoles/Buttons.cs b/TheOtherRoles/Buttons.cs index 4884187..ff29064 100644 --- a/TheOtherRoles/Buttons.cs +++ b/TheOtherRoles/Buttons.cs @@ -72,6 +72,7 @@ namespace TheOtherRoles private static CustomButton propHuntSpeedboostButton; public static CustomButton propHuntAdminButton; public static CustomButton propHuntFindButton; + public static CustomButton eventKickButton; public static Dictionary> deputyHandcuffedButtons = null; public static PoolablePlayer targetDisplay; @@ -658,7 +659,7 @@ namespace TheOtherRoles Hacker.getAdminSprite(), CustomButton.ButtonPositions.lowerRowRight, __instance, - KeyCode.Q, + KeyCode.G, true, 0f, () => { @@ -719,7 +720,7 @@ namespace TheOtherRoles Hacker.getVitalsSprite(), CustomButton.ButtonPositions.lowerRowCenter, __instance, - KeyCode.Q, + KeyCode.H, true, 0f, () => { @@ -773,7 +774,7 @@ namespace TheOtherRoles Tracker.getTrackCorpsesButtonSprite(), CustomButton.ButtonPositions.lowerRowCenter, __instance, - KeyCode.Q, + KeyCode.G, true, Tracker.corpsesTrackingDuration, () => { @@ -964,7 +965,7 @@ namespace TheOtherRoles Portalmaker.getUsePortalButtonSprite(), new Vector3(0.9f, -0.06f, 0), __instance, - KeyCode.H, + KeyCode.J, mirror: true ); @@ -1004,7 +1005,7 @@ namespace TheOtherRoles Portalmaker.getUsePortalButtonSprite(), new Vector3(0.9f, 1f, 0), __instance, - KeyCode.J, + KeyCode.G, mirror: true ); @@ -1360,7 +1361,7 @@ namespace TheOtherRoles SecurityGuard.getCamSprite(), CustomButton.ButtonPositions.lowerRowRight, __instance, - KeyCode.Q, + KeyCode.G, true, 0f, () => { @@ -2390,6 +2391,26 @@ namespace TheOtherRoles buttonText: "FIND" ); + eventKickButton = new CustomButton( + () => { + EventUtility.kickTarget(); + }, + () => { return EventUtility.isEnabled && Mini.mini != null && !Mini.mini.Data.IsDead && PlayerControl.LocalPlayer != Mini.mini; }, + () => { return EventUtility.currentTarget != null; }, + () => { }, + EventUtility.getKickButtonSprite(), + CustomButton.ButtonPositions.highRowRight, + __instance, + KeyCode.K, + true, + 3f, + () => { + // onEffectEnds + eventKickButton.Timer = 69; + }, + buttonText: "KICK" + ); + // Set the default (or settings from the previous game) timers / durations when spawning the buttons initialized = true; setCustomButtonCooldowns(); diff --git a/TheOtherRoles/CustomGameModes/PropHunt.cs b/TheOtherRoles/CustomGameModes/PropHunt.cs index 346e978..5a54161 100644 --- a/TheOtherRoles/CustomGameModes/PropHunt.cs +++ b/TheOtherRoles/CustomGameModes/PropHunt.cs @@ -13,8 +13,6 @@ using TheOtherRoles.Utilities; using TMPro; using UnityEngine; using UnityEngine.Video; -using static TheOtherRoles.Snitch; -using static UnityEngine.GraphicsBuffer; namespace TheOtherRoles.CustomGameModes { [HarmonyPatch] @@ -78,7 +76,6 @@ namespace TheOtherRoles.CustomGameModes { public static float dangerMeterActive = 0f; private static List duplicatedCollider = new(); - private static GameObject introObject; public static void clearAndReload() { remainingShots.Clear(); @@ -368,7 +365,7 @@ namespace TheOtherRoles.CustomGameModes { } bool whiteListed = false; foreach (var whiteListedWord in whitelistedObjects) { - if (collider.gameObject.name.Contains(whiteListedWord)) whiteListed = true; + if ((bool)(collider.gameObject?.name?.Contains(whiteListedWord))) whiteListed = true; } if (collider.GetComponent() != null || whiteListed) { float dist = Vector2.Distance(origin.transform.position, collider.transform.position); @@ -379,7 +376,9 @@ namespace TheOtherRoles.CustomGameModes { } } return bestCollider.gameObject; - } catch { return null; } + } catch (Exception e) { + TheOtherRolesPlugin.Logger.LogError($"Error in find closest disguise object: {e}"); + return null; } } public static GameObject FindPropByNameAndPos(string propName, float posX) { @@ -450,7 +449,6 @@ namespace TheOtherRoles.CustomGameModes { HudManager.Instance.FullScreen.enabled = false; videoPlayer.Destroy(); assetBundle.Unload(false); - introObject.Destroy(); } else { HudManager.Instance.FullScreen.enabled = true; HudManager.Instance.FullScreen.gameObject.SetActive(true); @@ -523,14 +521,15 @@ namespace TheOtherRoles.CustomGameModes { [HarmonyPostfix] public static void MapSetPostfix() { // Make sure the map in the settings is in sync with the map from li if (TORMapOptions.gameMode != CustomGamemodes.PropHunt && TORMapOptions.gameMode != CustomGamemodes.HideNSeek || AmongUsClient.Instance.IsGameStarted) return; - int map = GameOptionsManager.Instance.currentGameOptions.MapId; + int? map = GameOptionsManager.Instance?.currentGameOptions?.MapId; + if (map == null) return; if (map > 3) map--; if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek) if (CustomOptionHolder.hideNSeekMap.selection != map) - CustomOptionHolder.hideNSeekMap.updateSelection(map); + CustomOptionHolder.hideNSeekMap.updateSelection((int)map); if (TORMapOptions.gameMode == CustomGamemodes.PropHunt) if (CustomOptionHolder.propHuntMap.selection != map) - CustomOptionHolder.propHuntMap.updateSelection(map); + CustomOptionHolder.propHuntMap.updateSelection((int)map); } diff --git a/TheOtherRoles/CustomOptionHolder.cs b/TheOtherRoles/CustomOptionHolder.cs index 2b13899..66c3436 100644 --- a/TheOtherRoles/CustomOptionHolder.cs +++ b/TheOtherRoles/CustomOptionHolder.cs @@ -21,8 +21,18 @@ namespace TheOtherRoles { public static CustomOption modifiersCountMin; public static CustomOption modifiersCountMax; + public static CustomOption isDraftMode; + public static CustomOption draftModeAmountOfChoices; + public static CustomOption draftModeTimeToChoose; + public static CustomOption draftModeShowRoles; + public static CustomOption draftModeHideImpRoles; + public static CustomOption draftModeHideNeutralRoles; + public static CustomOption anyPlayerCanStopStart; public static CustomOption enableEventMode; + public static CustomOption eventReallyNoMini; + public static CustomOption eventKicksPerRound; + public static CustomOption eventHeavyAge; public static CustomOption deadImpsBlockSabotage; public static CustomOption mafiaSpawnRate; @@ -315,6 +325,7 @@ namespace TheOtherRoles { public static CustomOption modifierArmored; public static CustomOption modifierShifter; + public static CustomOption modifierShifterShiftsMedicShield; public static CustomOption maxNumberOfMeetings; public static CustomOption blockSkippingInEmergencyMeetings; @@ -433,6 +444,13 @@ namespace TheOtherRoles { if (Utilities.EventUtility.canBeEnabled) enableEventMode = CustomOption.Create(10423, Types.General, cs(Color.green, "Enable Special Mode"), true, null, true); + isDraftMode = CustomOption.Create(600, Types.General, cs(Color.yellow, "Enable Role Draft"), false, null, true, null, "Role Draft"); + draftModeAmountOfChoices = CustomOption.Create(601, Types.General, cs(Color.yellow, "Max Amount Of Roles\nTo Choose From"), 5f, 2f, 15f, 1f, isDraftMode, false); + draftModeTimeToChoose = CustomOption.Create(602, Types.General, cs(Color.yellow, "Time For Selection"), 5f, 3f, 20f, 1f, isDraftMode, false); + draftModeShowRoles = CustomOption.Create(603, Types.General, cs(Color.yellow, "Show Picked Roles"), false, isDraftMode, false); + draftModeHideImpRoles = CustomOption.Create(604, Types.General, cs(Color.yellow, "Hide Impostor Roles"), false, draftModeShowRoles, false); + draftModeHideNeutralRoles = CustomOption.Create(605, Types.General, cs(Color.yellow, "Hide Neutral Roles"), false, draftModeShowRoles, false); + // Using new id's for the options to not break compatibilty with older versions crewmateRolesCountMin = CustomOption.Create(300, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Minimum Crewmate Roles"), 15f, 0f, 15f, 1f, null, true, heading: "Min/Max Roles"); crewmateRolesCountMax = CustomOption.Create(301, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Crewmate Roles"), 15f, 0f, 15f, 1f); @@ -718,6 +736,11 @@ namespace TheOtherRoles { modifierMini = CustomOption.Create(1061, Types.Modifier, cs(Color.yellow, "Mini"), rates, null, true); modifierMiniGrowingUpDuration = CustomOption.Create(1062, Types.Modifier, "Mini Growing Up Duration", 400f, 100f, 1500f, 100f, modifierMini); modifierMiniGrowingUpInMeeting = CustomOption.Create(1063, Types.Modifier, "Mini Grows Up In Meeting", true, modifierMini); + if (Utilities.EventUtility.canBeEnabled || Utilities.EventUtility.isEnabled) { + eventKicksPerRound = CustomOption.Create(10424, Types.Modifier, cs(Color.green, "Maximum Kicks Mini Suffers"), 4f, 0f, 14f, 1f, modifierMini); + eventHeavyAge = CustomOption.Create(10425, Types.Modifier, cs(Color.green, "Age At Which Mini Is Heavy"), 12f, 6f, 18f, 0.5f, modifierMini); + eventReallyNoMini = CustomOption.Create(10426, Types.Modifier, cs(Color.green, "Really No Mini :("), false, modifierMini, invertedParent: true); + } modifierVip = CustomOption.Create(1070, Types.Modifier, cs(Color.yellow, "VIP"), rates, null, true); modifierVipQuantity = CustomOption.Create(1071, Types.Modifier, cs(Color.yellow, "VIP Quantity"), ratesModifier, modifierVip); @@ -736,6 +759,7 @@ namespace TheOtherRoles { modifierArmored = CustomOption.Create(1101, Types.Modifier, cs(Color.yellow, "Armored"), rates, null, true); modifierShifter = CustomOption.Create(1100, Types.Modifier, cs(Color.yellow, "Shifter"), rates, null, true); + modifierShifterShiftsMedicShield = CustomOption.Create(1102, Types.Modifier, "Can Shift Medic Shield", false, modifierShifter); // Guesser Gamemode (2000 - 2999) guesserGamemodeCrewNumber = CustomOption.Create(2001, Types.Guesser, cs(Guesser.color, "Number of Crew Guessers"), 15f, 0f, 15f, 1f, null, true, heading: "Amount of Guessers"); diff --git a/TheOtherRoles/Helpers.cs b/TheOtherRoles/Helpers.cs index 2e51993..202a48f 100644 --- a/TheOtherRoles/Helpers.cs +++ b/TheOtherRoles/Helpers.cs @@ -33,7 +33,7 @@ namespace TheOtherRoles { } public static class Helpers { - + public static string previousEndGameSummary = ""; public static Dictionary CachedSprites = new(); public static Sprite loadSpriteFromResources(string path, float pixelsPerUnit, bool cache=true) { diff --git a/TheOtherRoles/Main.cs b/TheOtherRoles/Main.cs index a3a42d4..1d38bfe 100644 --- a/TheOtherRoles/Main.cs +++ b/TheOtherRoles/Main.cs @@ -32,7 +32,7 @@ namespace TheOtherRoles public class TheOtherRolesPlugin : BasePlugin { public const string Id = "me.eisbison.theotherroles"; - public const string VersionString = "4.7.0"; + public const string VersionString = "4.8.0"; public static uint betaDays = 0; // amount of days for the build to be usable (0 for infinite!) public static Version Version = Version.Parse(VersionString); diff --git a/TheOtherRoles/Modules/CustomColors.cs b/TheOtherRoles/Modules/CustomColors.cs index 9800553..50fc75d 100644 --- a/TheOtherRoles/Modules/CustomColors.cs +++ b/TheOtherRoles/Modules/CustomColors.cs @@ -204,7 +204,6 @@ namespace TheOtherRoles.Modules { str = ColorUtility.ToHtmlStringRGB(c); color = c.r + c.g + c.b > 180 ? Palette.Black : Palette.White; - TheOtherRolesPlugin.Logger.LogMessage($"{c.r}, {c.g}, {c.b}"); } __instance.playerColorText.text = __instance.player.ColorBlindName; __instance.playerNameText.text = "" + (string.IsNullOrEmpty(sender.Data.PlayerName) ? "..." : sender.Data.PlayerName); diff --git a/TheOtherRoles/Modules/CustomOptions.cs b/TheOtherRoles/Modules/CustomOptions.cs index cdbf309..f1af1fd 100644 --- a/TheOtherRoles/Modules/CustomOptions.cs +++ b/TheOtherRoles/Modules/CustomOptions.cs @@ -52,10 +52,11 @@ namespace TheOtherRoles { public CustomOptionType type; public Action onChange = null; public string heading = ""; + public bool invertedParent; // Option creation - public CustomOption(int id, CustomOptionType type, string name, System.Object[] selections, System.Object defaultValue, CustomOption parent, bool isHeader, Action onChange = null, string heading = "") { + public CustomOption(int id, CustomOptionType type, string name, System.Object[] selections, System.Object defaultValue, CustomOption parent, bool isHeader, Action onChange = null, string heading = "", bool invertedParent = false) { this.id = id; this.name = parent == null ? name : "- " + name; this.selections = selections; @@ -66,6 +67,7 @@ namespace TheOtherRoles { this.type = type; this.onChange = onChange; this.heading = heading; + this.invertedParent = invertedParent; selection = 0; if (id != 0) { entry = TheOtherRolesPlugin.Instance.Config.Bind($"Preset{preset}", id.ToString(), defaultSelection); @@ -74,19 +76,19 @@ namespace TheOtherRoles { options.Add(this); } - public static CustomOption Create(int id, CustomOptionType type, string name, string[] selections, CustomOption parent = null, bool isHeader = false, Action onChange = null, string heading = "") { - return new CustomOption(id, type, name, selections, "", parent, isHeader, onChange, heading); + public static CustomOption Create(int id, CustomOptionType type, string name, string[] selections, CustomOption parent = null, bool isHeader = false, Action onChange = null, string heading = "", bool invertedParent = false) { + return new CustomOption(id, type, name, selections, "", parent, isHeader, onChange, heading, invertedParent); } - public static CustomOption Create(int id, CustomOptionType type, string name, float defaultValue, float min, float max, float step, CustomOption parent = null, bool isHeader = false, Action onChange = null, string heading = "") { + public static CustomOption Create(int id, CustomOptionType type, string name, float defaultValue, float min, float max, float step, CustomOption parent = null, bool isHeader = false, Action onChange = null, string heading = "", bool invertedParent = false) { List selections = new(); for (float s = min; s <= max; s += step) selections.Add(s); - return new CustomOption(id, type, name, selections.ToArray(), defaultValue, parent, isHeader, onChange, heading); + return new CustomOption(id, type, name, selections.ToArray(), defaultValue, parent, isHeader, onChange, heading, invertedParent); } - public static CustomOption Create(int id, CustomOptionType type, string name, bool defaultValue, CustomOption parent = null, bool isHeader = false, Action onChange = null, string heading = "") { - return new CustomOption(id, type, name, new string[]{"Off", "On"}, defaultValue ? "On" : "Off", parent, isHeader, onChange, heading); + public static CustomOption Create(int id, CustomOptionType type, string name, bool defaultValue, CustomOption parent = null, bool isHeader = false, Action onChange = null, string heading = "", bool invertedParent = false) { + return new CustomOption(id, type, name, new string[]{"Off", "On"}, defaultValue ? "On" : "Off", parent, isHeader, onChange, heading, invertedParent); } // Static behaviour @@ -106,6 +108,17 @@ namespace TheOtherRoles { stringOption.ValueText.text = option.selections[option.selection].ToString(); } } + + // make sure to reload all tabs, even the ones in the background, because they might have changed when the preset was switched! + if (AmongUsClient.Instance?.AmHost == true) { + foreach (var entry in GameOptionsMenuStartPatch.currentGOMs) { + CustomOptionType optionType = (CustomOptionType)entry.Key; + GameOptionsMenu gom = entry.Value; + if (gom != null) { + GameOptionsMenuStartPatch.updateGameOptionsMenu(optionType, gom); + } + } + } } public static void saveVanillaOptions() { @@ -190,13 +203,7 @@ namespace TheOtherRoles { try { if (onChange != null) onChange(); } catch { } - if (AmongUsClient.Instance?.AmHost == true) { - var currentTab = GameOptionsMenuStartPatch.currentTabs.FirstOrDefault(x => x.active).GetComponent(); - if (currentTab != null) { - var optionType = options.First(x => x.optionBehaviour == currentTab.Children[0]).type; - GameOptionsMenuStartPatch.updateGameOptionsMenu(optionType, currentTab); - } - } + if (optionBehaviour != null && optionBehaviour is StringOption stringOption) { stringOption.oldValue = stringOption.Value = selection; @@ -215,6 +222,15 @@ namespace TheOtherRoles { ShareOptionSelections();// Share all selections } + if (AmongUsClient.Instance?.AmHost == true) { + var currentTab = GameOptionsMenuStartPatch.currentTabs.FirstOrDefault(x => x.active).GetComponent(); + if (currentTab != null) { + var optionType = options.First(x => x.optionBehaviour == currentTab.Children[0]).type; + GameOptionsMenuStartPatch.updateGameOptionsMenu(optionType, currentTab); + } + + } + } public static byte[] serializeOptions() { @@ -455,15 +471,19 @@ namespace TheOtherRoles { float num = 1.44f; int i = 0; - int singles = 0; + int singles = 1; int headers = 0; int lines = 0; var curType = CustomOptionType.Modifier; + int numBonus = 0; foreach (var option in relevantOptions) { if (option.isHeader && (int)optionType != 99 || (int)optionType == 99 && curType != option.type) { curType = option.type; - if (i != 0) num -= 0.59f; + if (i != 0) { + num -= 0.85f; + numBonus++; + } if (i % 2 != 0) singles++; headers++; // for header CategoryHeaderMasked categoryHeaderMasked = UnityEngine.Object.Instantiate(__instance.categoryHeaderOrigin); @@ -478,7 +498,7 @@ namespace TheOtherRoles { categoryHeaderMasked.transform.localScale = Vector3.one; categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, num, -2f); __instance.settingsInfo.Add(categoryHeaderMasked.gameObject); - num -= 0.85f; + num -= 1.05f; i = 0; } else if (option.parent != null && (option.parent.selection == 0 || option.parent.parent != null && option.parent.parent.selection == 0)) continue; // Hides options, for which the parent is disabled! if (option == CustomOptionHolder.crewmateRolesCountMax || option == CustomOptionHolder.neutralRolesCountMax || option == CustomOptionHolder.impostorRolesCountMax || option == CustomOptionHolder.modifiersCountMax || option == CustomOptionHolder.crewmateRolesFill) @@ -492,7 +512,7 @@ namespace TheOtherRoles { lines++; num2 = -8.95f; if (i > 0) { - num -= 0.59f; + num -= 0.85f; } } else { num2 = -3f; @@ -515,8 +535,8 @@ namespace TheOtherRoles { i++; } - float actual_spacing = (headers * 0.85f + lines * 0.59f) / (headers + lines); - __instance.scrollBar.CalculateAndSetYBounds((float)(__instance.settingsInfo.Count + singles * 2 + headers), 2f, 6f, actual_spacing); + float actual_spacing = (headers * 1.05f + lines * 0.85f) / (headers + lines) * 1.01f; + __instance.scrollBar.CalculateAndSetYBounds((float)(__instance.settingsInfo.Count + singles * 2 + headers), 2f, 5f, actual_spacing); } @@ -620,12 +640,13 @@ namespace TheOtherRoles { class GameOptionsMenuStartPatch { public static List currentTabs = new(); public static List currentButtons = new(); - + public static Dictionary currentGOMs = new(); public static void Postfix(GameSettingMenu __instance) { currentTabs.ForEach(x => x?.Destroy()); currentButtons.ForEach(x => x?.Destroy()); currentTabs = new(); currentButtons = new(); + currentGOMs.Clear(); if (GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek) return; @@ -701,7 +722,8 @@ namespace TheOtherRoles { categoryHeaderMasked.transform.localScale = Vector3.one * 0.63f; categoryHeaderMasked.transform.localPosition = new Vector3(-0.903f, num, -2f); num -= 0.63f; - } else if (option.parent != null && (option.parent.selection == 0 || option.parent.parent != null && option.parent.parent.selection == 0)) continue; // Hides options, for which the parent is disabled! + } else if (option.parent != null && (option.parent.selection == 0 && !option.invertedParent || option.parent.parent != null && option.parent.parent.selection == 0 && !option.parent.invertedParent)) continue; // Hides options, for which the parent is disabled! + else if (option.parent != null && option.parent.selection != 0 && option.invertedParent) continue; OptionBehaviour optionBehaviour = UnityEngine.Object.Instantiate(menu.stringOptionOrigin, Vector3.zero, Quaternion.identity, menu.settingsContainer); optionBehaviour.transform.localPosition = new Vector3(0.952f, num, -2f); optionBehaviour.SetClickMask(menu.ButtonClickMask); @@ -788,6 +810,7 @@ namespace TheOtherRoles { currentTabs.Add(torSettingsTab); torSettingsTab.SetActive(false); + currentGOMs.Add((byte)optionType, torSettingsGOM); } public static void updateGameOptionsMenu(CustomOptionType optionType, GameOptionsMenu torSettingsGOM) { @@ -976,7 +999,7 @@ namespace TheOtherRoles { if (type == CustomOption.CustomOptionType.Modifier) line += buildModifierExtras(option); sb.AppendLine(line); } - else if (option.parent.getSelection() > 0) { + else if (option.parent.getSelection() > 0 || option.invertedParent && option.parent.getSelection() == 0) { if (option.id == 103) //Deputy sb.AppendLine($"- {Helpers.cs(Deputy.color, "Deputy")}: {option.selections[option.selection].ToString()}"); else if (option.id == 224) //Sidekick @@ -992,7 +1015,7 @@ namespace TheOtherRoles { if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek && option.type != CustomOptionType.HideNSeekMain && option.type != CustomOptionType.HideNSeekRoles) continue; if (TORMapOptions.gameMode == CustomGamemodes.PropHunt && option.type != CustomOptionType.PropHunt) continue; if (option.parent != null) { - bool isIrrelevant = option.parent.getSelection() == 0 || (option.parent.parent != null && option.parent.parent.getSelection() == 0); + bool isIrrelevant = (option.parent.getSelection() == 0 && !option.invertedParent) || (option.parent.parent != null && option.parent.parent.getSelection() == 0 && !option.parent.invertedParent); Color c = isIrrelevant ? Color.grey : Color.white; // No use for now if (isIrrelevant) continue; @@ -1244,6 +1267,8 @@ namespace TheOtherRoles { } if (Input.GetKeyDown(KeyCode.F1)) HudManagerUpdate.ToggleSettings(HudManager.Instance); + if (Input.GetKeyDown(KeyCode.F2) && LobbyBehaviour.Instance) + HudManagerUpdate.ToggleSummary(HudManager.Instance); if (TheOtherRolesPlugin.optionsPage >= GameOptionsDataPatch.maxPage) TheOtherRolesPlugin.optionsPage = 0; } } @@ -1363,6 +1388,9 @@ namespace TheOtherRoles { private static GameObject settingsBackground; public static void OpenSettings(HudManager __instance) { if (__instance.FullScreen == null || MapBehaviour.Instance && MapBehaviour.Instance.IsOpen) return; + if (summaryTMP) { + CloseSummary(); + } settingsBackground = GameObject.Instantiate(__instance.FullScreen.gameObject, __instance.transform); settingsBackground.SetActive(true); var renderer = settingsBackground.GetComponent(); @@ -1390,9 +1418,54 @@ namespace TheOtherRoles { else OpenSettings(__instance); } + [HarmonyPrefix] + public static void Prefix3(HudManager __instance) { + if (!summaryTMP) return; + summaryTMP.text = Helpers.previousEndGameSummary; + + summaryTMP.transform.localPosition = new Vector3(- 3 * 1.2f, 2.2f, -500f); + + } + + private static TMPro.TextMeshPro summaryTMP = null; + private static GameObject summaryBackground; + public static void OpenSummary(HudManager __instance) { + if (__instance.FullScreen == null || MapBehaviour.Instance && MapBehaviour.Instance.IsOpen || Helpers.previousEndGameSummary.IsNullOrWhiteSpace()) return; + if (settingsTMPs[0]) { + CloseSettings(); + } + summaryBackground = GameObject.Instantiate(__instance.FullScreen.gameObject, __instance.transform); + summaryBackground.SetActive(true); + var renderer = summaryBackground.GetComponent(); + renderer.color = new Color(0.2f, 0.2f, 0.2f, 0.9f); + renderer.enabled = true; + + + summaryTMP = GameObject.Instantiate(__instance.KillButton.cooldownTimerText, __instance.transform); + summaryTMP.alignment = TMPro.TextAlignmentOptions.TopLeft; + summaryTMP.enableWordWrapping = false; + summaryTMP.transform.localScale = Vector3.one * 0.3f; + summaryTMP.gameObject.SetActive(true); + + } + + public static void CloseSummary() { + summaryTMP?.gameObject.Destroy(); + summaryTMP = null; + if (summaryBackground) summaryBackground.Destroy(); + } + + public static void ToggleSummary(HudManager __instance) { + if (summaryTMP) CloseSummary(); + else OpenSummary(__instance); + } + static PassiveButton toggleSettingsButton; static GameObject toggleSettingsButtonObject; + static PassiveButton toggleSummaryButton; + static GameObject toggleSummaryButtonObject; + static GameObject toggleZoomButtonObject; static PassiveButton toggleZoomButton; @@ -1416,7 +1489,6 @@ namespace TheOtherRoles { toggleSettingsButtonObject.SetActive(__instance.MapButton.gameObject.active && !(MapBehaviour.Instance && MapBehaviour.Instance.IsOpen) && GameOptionsManager.Instance.currentGameOptions.GameMode != GameModes.HideNSeek); toggleSettingsButtonObject.transform.localPosition = __instance.MapButton.transform.localPosition + new Vector3(0, -0.8f, -500f); - if (!toggleZoomButton || !toggleZoomButtonObject) { // add a special button for settings viewing: toggleZoomButtonObject = GameObject.Instantiate(__instance.MapButton.gameObject, __instance.MapButton.transform.parent); @@ -1439,5 +1511,34 @@ namespace TheOtherRoles { var posOffset = Helpers.zoomOutStatus ? new Vector3(-1.27f, -7.92f, -52f) : new Vector3(0, -1.6f, -52f); toggleZoomButtonObject.transform.localPosition = HudManager.Instance.MapButton.transform.localPosition + posOffset; } + + [HarmonyPostfix] + public static void Postfix2(HudManager __instance) { + if (AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Started) { + if (toggleSummaryButtonObject != null) { + toggleSummaryButtonObject.SetActive(false); + toggleSummaryButtonObject.Destroy(); + toggleSummaryButton.Destroy(); + } + return; + } + if (!toggleSummaryButton || !toggleSummaryButtonObject) { + // add a special button for settings viewing: + toggleSummaryButtonObject = GameObject.Instantiate(__instance.MapButton.gameObject, __instance.MapButton.transform.parent); + toggleSummaryButtonObject.transform.localPosition = __instance.MapButton.transform.localPosition + new Vector3(0, -1.25f, -500f); + toggleSummaryButtonObject.name = "TOGGLESUMMARYSBUTTON"; + SpriteRenderer renderer = toggleSummaryButtonObject.transform.Find("Inactive").GetComponent(); + SpriteRenderer rendererActive = toggleSummaryButtonObject.transform.Find("Active").GetComponent(); + toggleSummaryButtonObject.transform.Find("Background").localPosition = Vector3.zero; + renderer.sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Endscreen.png", 100f); + rendererActive.sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.EndscreenActive.png", 100f); + toggleSummaryButton = toggleSummaryButtonObject.GetComponent(); + toggleSummaryButton.OnClick.RemoveAllListeners(); + toggleSummaryButton.OnClick.AddListener((Action)(() => ToggleSummary(__instance))); + } + toggleSummaryButtonObject.SetActive(__instance.SettingsButton.gameObject.active && LobbyBehaviour.Instance && !Helpers.previousEndGameSummary.IsNullOrWhiteSpace() && GameOptionsManager.Instance.currentGameOptions.GameMode != GameModes.HideNSeek + && AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started); + toggleSummaryButtonObject.transform.localPosition = __instance.SettingsButton.transform.localPosition + new Vector3(-1.45f, 0.03f, -500f); + } } } diff --git a/TheOtherRoles/Modules/RoleDraft.cs b/TheOtherRoles/Modules/RoleDraft.cs new file mode 100644 index 0000000..97bd2fa --- /dev/null +++ b/TheOtherRoles/Modules/RoleDraft.cs @@ -0,0 +1,434 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using HarmonyLib; +using Hazel; +using BepInEx.Unity.IL2CPP.Utils.Collections; +using System.Collections; +using TheOtherRoles.Patches; +using static TheOtherRoles.TheOtherRoles; +using UnityEngine.UI; +using Reactor.Utilities.Extensions; + +namespace TheOtherRoles.Modules +{ + [HarmonyPatch] + class RoleDraft + { + public static bool isEnabled => CustomOptionHolder.isDraftMode.getBool() && (TORMapOptions.gameMode == CustomGamemodes.Classic || TORMapOptions.gameMode == CustomGamemodes.Guesser); + public static bool isRunning = false; + + public static List pickOrder = new(); + private static bool picked = false; + private static float timer = 0f; + private static List buttons = new List(); + private static TMPro.TextMeshPro feedText; + public static List alreadyPicked = new(); + public static IEnumerator CoSelectRoles(IntroCutscene __instance) + { + isRunning = true; + SoundEffectsManager.play("draft", volume: 1f, true, true); + alreadyPicked.Clear(); + bool playedAlert = false; + feedText = UnityEngine.Object.Instantiate(__instance.TeamTitle, __instance.transform); + var aspectPosition = feedText.gameObject.AddComponent(); + aspectPosition.Alignment = AspectPosition.EdgeAlignments.LeftTop; + aspectPosition.DistanceFromEdge = new Vector2(1.62f, 1.2f); + aspectPosition.AdjustPosition(); + feedText.transform.localScale = new Vector3(0.6f, 0.6f, 1); + feedText.text = "Player's Picks:\n\n"; + feedText.alignment = TMPro.TextAlignmentOptions.TopLeft; + feedText.autoSizeTextContainer = true; + feedText.fontSize = 3f; + feedText.enableAutoSizing = false; + __instance.TeamTitle.transform.localPosition = __instance.TeamTitle.transform.localPosition + new Vector3(1f, 0f); + __instance.TeamTitle.text = "Currently Picking:"; + __instance.BackgroundBar.enabled = false; + __instance.TeamTitle.transform.localScale = new Vector3(0.25f, 0.25f, 1f); + __instance.TeamTitle.autoSizeTextContainer = true; + __instance.TeamTitle.enableAutoSizing = false; + __instance.TeamTitle.fontSize = 5; + __instance.TeamTitle.alignment = TMPro.TextAlignmentOptions.Top; + __instance.ImpostorText.gameObject.SetActive(false); + GameObject.Find("BackgroundLayer")?.SetActive(false); + foreach (var player in UnityEngine.Object.FindObjectsOfType()) + { + if (player.name.Contains("Dummy")) + { + player.gameObject.SetActive(false); + } + } + __instance.FrontMost.gameObject.SetActive(false); + + if (AmongUsClient.Instance.AmHost) + { + sendPickOrder(); + } + + while (pickOrder.Count == 0) + { + yield return null; + } + + while (pickOrder.Count > 0) { + picked = false; + timer = 0; + float maxTimer = CustomOptionHolder.draftModeTimeToChoose.getFloat(); + string playerText = ""; + while (timer < maxTimer || !picked) { + if (pickOrder.Count == 0) + break; + // wait for pick + timer += Time.deltaTime; + if (PlayerControl.LocalPlayer.PlayerId == pickOrder[0]) { + if (!playedAlert) { + playedAlert = true; + SoundManager.Instance.PlaySound(ShipStatus.Instance.SabotageSound, false, 1f, null); + } + // Animate beginning of choice, by changing background color + float min = 50 / 255f; + Color backGroundColor = new Color(min, min, min, 1); + if (timer < 1) { + float max = 230 / 255f; + if (timer < 0.5f) { // White flash + float p = timer / 0.5f; + float value = (float)Math.Pow(p, 2f) * max; + backGroundColor = new Color(value, value, value, 1); + } else { + float p = (1 - timer) / 0.5f; + float value = (float)Math.Pow(p, 2f) * max + (1 - (float)Math.Pow(p, 2f)) * min; + backGroundColor = new Color(value, value, value, 1); + } + + } + HudManager.Instance.FullScreen.color = backGroundColor; + GameObject.Find("BackgroundLayer")?.SetActive(false); + + // enable pick, wait for pick + Color youColor = timer - (int)timer > 0.5 ? Color.red : Color.yellow; + playerText = Helpers.cs(youColor, "You!"); + // Available Roles: + List availableRoles = new(); + foreach (RoleInfo roleInfo in RoleInfo.allRoleInfos) { + int impostorCount = PlayerControl.AllPlayerControls.ToArray().ToList().Where(x => x.Data.Role.IsImpostor).Count(); + if (roleInfo.isModifier) continue; + // Remove Impostor Roles + if (PlayerControl.LocalPlayer.Data.Role.IsImpostor && !roleInfo.isImpostor) continue; + if (!PlayerControl.LocalPlayer.Data.Role.IsImpostor && roleInfo.isImpostor) continue; + + RoleManagerSelectRolesPatch.RoleAssignmentData roleData = RoleManagerSelectRolesPatch.getRoleAssignmentData(); + roleData.crewSettings.Add((byte)RoleId.Sheriff, CustomOptionHolder.sheriffSpawnRate.getSelection()); + if (CustomOptionHolder.sheriffSpawnRate.getSelection() > 0) + roleData.crewSettings.Add((byte)RoleId.Deputy, CustomOptionHolder.deputySpawnRate.getSelection()); + if (roleData.neutralSettings.ContainsKey((byte)roleInfo.roleId) && roleData.neutralSettings[(byte)roleInfo.roleId] == 0) continue; + else if (roleData.impSettings.ContainsKey((byte)roleInfo.roleId) && roleData.impSettings[(byte)roleInfo.roleId] == 0) continue; + else if (roleData.crewSettings.ContainsKey((byte)roleInfo.roleId) && roleData.crewSettings[(byte)roleInfo.roleId] == 0) continue; + else if (new List() { RoleId.Janitor, RoleId.Godfather, RoleId.Mafioso }.Contains(roleInfo.roleId) && (CustomOptionHolder.mafiaSpawnRate.getSelection() == 0 || GameOptionsManager.Instance.currentGameOptions.NumImpostors < 3)) continue; + else if (roleInfo.roleId == RoleId.Sidekick) continue; + if (roleInfo.roleId == RoleId.Deputy && Sheriff.sheriff == null) continue; + if (roleInfo.roleId == RoleId.Pursuer) continue; + if (roleInfo.roleId == RoleId.Spy && impostorCount < 2) continue; + if (roleInfo.roleId == RoleId.Prosecutor && (CustomOptionHolder.lawyerIsProsecutorChance.getSelection() == 0 || CustomOptionHolder.lawyerSpawnRate.getSelection() == 0)) continue; + if (roleInfo.roleId == RoleId.Lawyer && (CustomOptionHolder.lawyerIsProsecutorChance.getSelection() == 10 || CustomOptionHolder.lawyerSpawnRate.getSelection() == 0)) continue; + if (TORMapOptions.gameMode == CustomGamemodes.Guesser && (roleInfo.roleId == RoleId.EvilGuesser || roleInfo.roleId == RoleId.NiceGuesser)) continue; + if (alreadyPicked.Contains((byte)roleInfo.roleId) && roleInfo.roleId != RoleId.Crewmate) continue; + if (CustomOptionHolder.crewmateRolesFill.getBool() && roleInfo.roleId == RoleId.Crewmate) continue; + + int impsPicked = alreadyPicked.Where(x => RoleInfo.roleInfoById[(RoleId)x].isImpostor).Count(); + + // Hanlde forcing of 100% roles for impostors + if (PlayerControl.LocalPlayer.Data.Role.IsImpostor) { + int impsMax = CustomOptionHolder.impostorRolesCountMax.getSelection(); + int impsMin = CustomOptionHolder.impostorRolesCountMin.getSelection(); + if (impsMin > impsMax) impsMin = impsMax; + int impsLeft = pickOrder.Where(x => Helpers.playerById(x).Data.Role.IsImpostor).Count(); + int imps100 = roleData.impSettings.Where(x => x.Value == 10).Count(); + if (imps100 > impsMax) imps100 = impsMax; + int imps100Picked = alreadyPicked.Where(x => roleData.impSettings.GetValueSafe(x) == 10).Count(); + if (imps100 - imps100Picked >= impsLeft && !(roleData.impSettings.Where(x => x.Value == 10 && x.Key == (byte)roleInfo.roleId).Count() > 0)) continue; + if (impsMin - impsPicked >= impsLeft && roleInfo.roleId == RoleId.Impostor) continue; + if (impsPicked >= impsMax && roleInfo.roleId != RoleId.Impostor) continue; + } + + // Player is no impostor! Handle forcing of 100% roles for crew and neutral + else { + // No more neutrals possible! + int neutralsPicked = alreadyPicked.Where(x => RoleInfo.roleInfoById[(RoleId)x].isNeutral).Count(); + int crewPicked = alreadyPicked.Count - impsPicked - neutralsPicked; + int neutralsMax = CustomOptionHolder.neutralRolesCountMax.getSelection(); + int neutralsMin = CustomOptionHolder.neutralRolesCountMin.getSelection(); + int neutrals100 = roleData.neutralSettings.Where(x => x.Value == 10).Count(); + if (neutrals100 > neutralsMin) neutralsMin = neutrals100; + if (neutralsMin > neutralsMax) neutralsMin = neutralsMax; + + // If crewmate fill disabled and crew picked the amount of allowed crewmates alreay: no more crewmate except vanilla crewmate allowed! + int crewLimit = PlayerControl.AllPlayerControls.Count - impostorCount - (neutralsMin > neutrals100 ? neutralsMin : neutrals100 > neutralsMax ? neutralsMax : neutrals100); + int maxCrew = CustomOptionHolder.crewmateRolesFill.getBool() ? CustomOptionHolder.crewmateRolesCountMax.getSelection() : crewLimit; + if (maxCrew > crewLimit) + maxCrew = crewLimit; + if (crewPicked >= crewLimit && !roleInfo.isNeutral && roleInfo.roleId != RoleId.Crewmate) continue; + // Fill roles means no crewmates allowed! + if (CustomOptionHolder.crewmateRolesFill.getBool() && roleInfo.roleId == RoleId.Crewmate) continue; + + bool allowAnyNeutral = false; + if (neutralsPicked >= neutralsMax && roleInfo.isNeutral) continue; + // More neutrals needed? Then no more crewmates! This takes precedence over crew roles set to 100%! + var crewmatesLeft = pickOrder.Count - pickOrder.Where(x => Helpers.playerById(x).Data.Role.IsImpostor).Count(); + + if (crewmatesLeft <= neutralsMin - neutralsPicked && !roleInfo.isNeutral) { + continue; + } else if (neutralsMin - neutrals100 > neutralsPicked) + allowAnyNeutral = true; + // Handle 100% Roles PER Faction. + + int neutrals100Picked = alreadyPicked.Where(x => roleData.neutralSettings.GetValueSafe(x) == 10).Count(); + if (neutrals100 > neutralsMax) neutrals100 = neutralsMax; + + int crew100 = roleData.crewSettings.Where(x => x.Value == 10).Count(); + int crew100Picked = alreadyPicked.Where(x => roleData.crewSettings.GetValueSafe(x) == 10).Count(); + if (neutrals100 > neutralsMax) neutrals100 = neutralsMax; + + if (crew100 > maxCrew) crew100 = maxCrew; + if ((neutrals100 - neutrals100Picked >= crewmatesLeft || roleInfo.isNeutral && neutrals100 - neutrals100Picked >= neutralsMax - neutralsPicked) && !(neutrals100Picked >= neutralsMax) && !(roleData.neutralSettings.Where(x => x.Value == 10 && x.Key == (byte)roleInfo.roleId).Count() > 0)) continue; + if (!(allowAnyNeutral && roleInfo.isNeutral) && crew100 - crew100Picked >= crewmatesLeft && !(roleData.crewSettings.Where(x => x.Value == 10 && x.Key == (byte)roleInfo.roleId).Count() > 0)) continue; + + if (!(allowAnyNeutral && roleInfo.isNeutral) && neutrals100 + crew100 - neutrals100Picked - crew100Picked >= crewmatesLeft && !(roleData.crewSettings.Where(x => x.Value == 10 && x.Key == (byte)roleInfo.roleId).Count() > 0 || roleData.neutralSettings.Where(x => x.Value == 10 && x.Key == (byte)roleInfo.roleId).Count() > 0)) continue; + + } + // Handle role pairings that are blocked, e.g. Vampire Warlock, Cleaner Vulture etc. + bool blocked = false; + foreach (var blockedRoleId in CustomOptionHolder.blockedRolePairings) { + if (alreadyPicked.Contains(blockedRoleId.Key) && blockedRoleId.Value.ToList().Contains((byte)roleInfo.roleId)) { + blocked = true; + break; + } + } + if (blocked) continue; + + + availableRoles.Add(roleInfo); + } + + // Fallback for if all roles are somehow removed. (This is only the case if there is a bug, hence print a warning + if (availableRoles.Count == 0) { + if (PlayerControl.LocalPlayer.Data.Role.IsImpostor) + availableRoles.Add(RoleInfo.impostor); + else + availableRoles.Add(RoleInfo.crewmate); + TheOtherRolesPlugin.Logger.LogWarning("Draft Mode: Fallback triggered, because no roles were left. Forced addition of basegame Imp/Crewmate"); + } + + List originalAvailable = new(availableRoles); + + // remove some roles, so that you can't always get the same roles: + if (availableRoles.Count > CustomOptionHolder.draftModeAmountOfChoices.getFloat()) { + int countToRemove = availableRoles.Count - (int)CustomOptionHolder.draftModeAmountOfChoices.getFloat(); + while (countToRemove-- > 0) { + var toRemove = availableRoles.OrderBy(_ => Guid.NewGuid()).First(); + availableRoles.Remove(toRemove); + } + } + + if (timer >= maxTimer) { + sendPick((byte)originalAvailable.OrderBy(_ => Guid.NewGuid()).First().roleId); + } + + + if (GameObject.Find("RoleButton") == null) { + SoundEffectsManager.play("timemasterShield"); + int i = 0; + int buttonsPerRow = 4; + int lastRow = availableRoles.Count / buttonsPerRow; + int buttonsInLastRow = availableRoles.Count % buttonsPerRow; + + foreach (RoleInfo roleInfo in availableRoles) { + float row = i / buttonsPerRow; + float col = i % buttonsPerRow; + if (buttonsInLastRow != 0 && row == lastRow) { + col += (buttonsPerRow - buttonsInLastRow) / 2f; + } + // planned rows: maximum of 4, hence the following calculation for rows as well: + row += (4 - lastRow - 1) / 2f; + + ActionButton actionButton = UnityEngine.Object.Instantiate(HudManager.Instance.KillButton, __instance.TeamTitle.transform); + actionButton.gameObject.SetActive(true); + actionButton.gameObject.name = "RoleButton"; + actionButton.transform.localPosition = new Vector3(-8.4f + col * 5.5f, -10 - row * 3f); + actionButton.transform.localScale = new Vector3(2f, 2f); + actionButton.SetCoolDown(0, 0); + GameObject textHolder = new GameObject("textHolder"); + var text = textHolder.AddComponent(); + text.text = roleInfo.name.Replace(" ", "\n"); + text.horizontalAlignment = TMPro.HorizontalAlignmentOptions.Center; + text.fontSize = 5; + textHolder.layer = actionButton.gameObject.layer; + text.outlineWidth = 0.1f; + text.outlineColor = Color.white; + text.color = roleInfo.color; + textHolder.transform.SetParent(actionButton.transform, false); + textHolder.transform.localPosition = new Vector3(0, text.text.Contains("\n") ? -1.975f : -2.2f, -1); + GameObject actionButtonGameObject = actionButton.gameObject; + SpriteRenderer actionButtonRenderer = actionButton.graphic; + Material actionButtonMat = actionButtonRenderer.material; + + PassiveButton button = actionButton.GetComponent(); + button.OnClick = new Button.ButtonClickedEvent(); + button.OnClick.AddListener((Action)(() => { + sendPick((byte)roleInfo.roleId); + })); + HudManager.Instance.StartCoroutine(Effects.Lerp(0.5f, new Action((p) => { + actionButton.OverrideText(""); + }))); + buttons.Add(actionButton); + i++; + } + } + + } else { + int currentPick = PlayerControl.AllPlayerControls.Count - pickOrder.Count + 1; + playerText = $"Anonymous Player {currentPick}"; + HudManager.Instance.FullScreen.color = Color.black; + } + __instance.TeamTitle.text = $"{Helpers.cs(Color.white, "Welcome to the Role Draft!")}\n\n\n Currently Picking:\n\n\n{playerText}"; + int waitMore = pickOrder.IndexOf(PlayerControl.LocalPlayer.PlayerId); + string waitMoreText = ""; + if (waitMore > 0) { + waitMoreText = $" ({waitMore} rounds until your turn)"; + } + __instance.TeamTitle.text += $"\n\n{waitMoreText}\nRandom Selection In... {(int)(maxTimer + 1 - timer)}\n {(SoundManager.MusicVolume > -80 ? "♫ Music: Ultimate Superhero 3 - Kenët & Rez ♫" : "")}"; + yield return null; + } + } + HudManager.Instance.FullScreen.color = Color.black; + __instance.FrontMost.gameObject.SetActive(true); + GameObject.Find("BackgroundLayer")?.SetActive(true); + if (AmongUsClient.Instance.AmHost) + { + RoleManagerSelectRolesPatch.assignRoleTargets(null); // Assign targets for Lawyer & Prosecutor + if (RoleManagerSelectRolesPatch.isGuesserGamemode) RoleManagerSelectRolesPatch.assignGuesserGamemode(); + RoleManagerSelectRolesPatch.assignModifiers(); // Assign modifier + } + + float myTimer = 0f; + while (myTimer < 3f) + { + myTimer += Time.deltaTime; + Color c = new Color(0, 0, 0, myTimer / 3.0f); + __instance.FrontMost.color = c; + yield return null; + } + + SoundEffectsManager.stop("draft"); + isRunning = false; + yield break; + } + + public static void receivePick(byte playerId, byte roleId) + { + if (!isEnabled) return; + RPCProcedure.setRole(roleId, playerId); + alreadyPicked.Add(roleId); + try + { + pickOrder.Remove(playerId); + timer = 0; + picked = true; + RoleInfo roleInfo = RoleInfo.allRoleInfos.First(x => (byte)x.roleId == roleId); + string roleString = Helpers.cs(roleInfo.color, roleInfo.name); + int roleLength = roleInfo.name.Length; // Not used for now, but stores the amount of charactes of the roleString. + if (!CustomOptionHolder.draftModeShowRoles.getBool() && !(playerId == PlayerControl.LocalPlayer.PlayerId)) { + roleString = "Unknown Role"; + roleLength = roleString.Length; + } + else if (CustomOptionHolder.draftModeHideImpRoles.getBool() && roleInfo.isImpostor && !(playerId == PlayerControl.LocalPlayer.PlayerId)) { + roleString = Helpers.cs(Palette.ImpostorRed, "Impostor Role"); + roleLength = "Impostor Role".Length; + } + else if (CustomOptionHolder.draftModeHideNeutralRoles.getBool() && roleInfo.isNeutral && !(playerId == PlayerControl.LocalPlayer.PlayerId)) { + roleString = Helpers.cs(Palette.Blue, "Neutral Role"); + roleLength = "Neutral Role".Length; + } + string line = $"{(playerId == PlayerControl.LocalPlayer.PlayerId ? "You" : alreadyPicked.Count)}:"; + line = line + string.Concat(Enumerable.Repeat(" ", 6 - line.Length)) + roleString; + feedText.text += line + "\n"; + SoundEffectsManager.play("select"); + } + catch (Exception e) { TheOtherRolesPlugin.Logger.LogError(e); } + } + + public static void sendPick(byte RoleId) + { + SoundEffectsManager.stop("timeMasterShield"); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.DraftModePick, SendOption.Reliable, -1); + writer.Write(PlayerControl.LocalPlayer.PlayerId); + writer.Write(RoleId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + receivePick(PlayerControl.LocalPlayer.PlayerId, RoleId); + + // destroy all the buttons: + foreach (var button in buttons) + { + button?.gameObject?.Destroy(); + } + buttons.Clear(); + } + + + public static void sendPickOrder() + { + pickOrder = PlayerControl.AllPlayerControls.ToArray().Select(x => x.PlayerId).OrderBy(_ => Guid.NewGuid()).ToList().ToList(); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.DraftModePickOrder, SendOption.Reliable, -1); + writer.Write((byte)pickOrder.Count); + foreach (var item in pickOrder) + { + writer.Write(item); + } + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + + public static void receivePickOrder(int amount, MessageReader reader) + { + pickOrder.Clear(); + for (int i = 0; i < amount; i++) + { + pickOrder.Add(reader.ReadByte()); + } + } + + class PatchedEnumerator() : IEnumerable + { + public IEnumerator enumerator; + public IEnumerator Postfix; + public IEnumerator GetEnumerator() + { + while (enumerator.MoveNext()) + { + yield return enumerator.Current; + } + while (Postfix.MoveNext()) + yield return Postfix.Current; + } + } + + + [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.ShowTeam))] + + class ShowRolePatch + { + [HarmonyPostfix] + public static void Postfix(IntroCutscene __instance, ref Il2CppSystem.Collections.IEnumerator __result) + { + if (!isEnabled) return; + var newEnumerator = new PatchedEnumerator() + { + enumerator = __result.WrapToManaged(), + Postfix = CoSelectRoles(__instance) + }; + __result = newEnumerator.GetEnumerator().WrapToIl2Cpp(); + } + + } + } +} diff --git a/TheOtherRoles/Objects/Bomb.cs b/TheOtherRoles/Objects/Bomb.cs index f881162..51cc0a0 100644 --- a/TheOtherRoles/Objects/Bomb.cs +++ b/TheOtherRoles/Objects/Bomb.cs @@ -94,7 +94,11 @@ namespace TheOtherRoles.Objects { AmongUsClient.Instance.FinishRpcImmediately(writer); GameHistory.overrideDeathReasonAndKiller(PlayerControl.LocalPlayer, DeadPlayer.CustomDeathReason.Bomb, killer: Bomber.bomber); } - SoundEffectsManager.playAtPosition("bombExplosion", position, range: Bomber.hearRange) ; + try { + SoundEffectsManager.playAtPosition("bombExplosion", position, maxDuration: 1.6f, range: Bomber.hearRange); + } catch (Exception e) { + TheOtherRolesPlugin.Logger.LogWarning($"Exception in Sound Effect for Bomb explosion: {e}"); + } } Bomber.clearBomb(); canDefuse = false; diff --git a/TheOtherRoles/Objects/CustomButton.cs b/TheOtherRoles/Objects/CustomButton.cs index ce4d85a..967a0a9 100644 --- a/TheOtherRoles/Objects/CustomButton.cs +++ b/TheOtherRoles/Objects/CustomButton.cs @@ -1,6 +1,8 @@ using Il2CppSystem.Runtime.ExceptionServices; +using Rewired; using System; using System.Collections.Generic; +using TheOtherRoles.Modules; using TMPro; using UnityEngine; using UnityEngine.UI; @@ -9,6 +11,8 @@ using static TheOtherRoles.TheOtherRoles; namespace TheOtherRoles.Objects { public class CustomButton { public static List buttons = new List(); + public static KeyCode Action2Keycode = KeyCode.G; //TheOtherRolesPlugin.Instance.Config.Bind("Buttons", "Action2Keycode", KeyCode.G, "Second Ability Button Key").Value; + public static KeyCode Action3Keycode = KeyCode.H; // TheOtherRolesPlugin.Instance.Config.Bind("Buttons", "Action3Keycode", KeyCode.H, "Third Ability Button Key").Value; public ActionButton actionButton; public GameObject actionButtonGameObject; public SpriteRenderer actionButtonRenderer; @@ -32,6 +36,7 @@ namespace TheOtherRoles.Objects { public HudManager hudManager; public bool mirror; public KeyCode? hotkey; + public KeyCode? originalHotkey; public string buttonText; public bool isHandcuffed = false; private static readonly int Desat = Shader.PropertyToID("_Desat"); @@ -44,6 +49,7 @@ namespace TheOtherRoles.Objects { public static readonly Vector3 upperRowCenter = new Vector3(-1f, 1f, 0f); // Not usable for imps beacuse of new button positions! public static readonly Vector3 upperRowLeft = new Vector3(-2f, 1f, 0f); public static readonly Vector3 upperRowFarLeft = new Vector3(-3f, 1f, 0f); + public static readonly Vector3 highRowRight = new Vector3(0f, 2.06f, 0f); } public CustomButton(Action OnClick, Func HasButton, Func CouldUse, Action OnMeetingEnds, Sprite Sprite, Vector3 PositionOffset, HudManager hudManager, KeyCode? hotkey, bool HasEffect, float EffectDuration, Action OnEffectEnds, bool mirror = false, string buttonText = "") @@ -62,6 +68,7 @@ namespace TheOtherRoles.Objects { this.mirror = mirror; this.hotkey = hotkey; this.buttonText = buttonText; + originalHotkey = hotkey; Timer = 16.2f; buttons.Add(this); actionButton = UnityEngine.Object.Instantiate(hudManager.KillButton, hudManager.KillButton.transform.parent); @@ -73,7 +80,6 @@ namespace TheOtherRoles.Objects { this.showButtonText = (actionButtonRenderer.sprite == Sprite || buttonText != ""); button.OnClick = new Button.ButtonClickedEvent(); button.OnClick.AddListener((UnityEngine.Events.UnityAction)onClickEvent); - setActive(false); } @@ -148,6 +154,33 @@ namespace TheOtherRoles.Objects { } } + + // Reload the rebound hotkeys from the among us settings. + public static void ReloadHotkeys() { + foreach (var button in buttons) { + // Q button is used only for killing! This rebinds every button that would use Q to use the currently set killing button in among us. + if (button.originalHotkey == KeyCode.Q) { + Player player = Rewired.ReInput.players.GetPlayer(0); + string keycode = player.controllers.maps.GetFirstButtonMapWithAction(8, true).elementIdentifierName; + button.hotkey = (KeyCode)Enum.Parse(typeof(KeyCode), keycode); + } + // F is the default ability button. All buttons that would use F now use the ability button. + if (button.originalHotkey == KeyCode.F) { + Player player = Rewired.ReInput.players.GetPlayer(0); + string keycode = player.controllers.maps.GetFirstButtonMapWithAction(49, true).elementIdentifierName; + button.hotkey = (KeyCode)Enum.Parse(typeof(KeyCode), keycode); + } + + if (button.originalHotkey == KeyCode.G) { + button.hotkey = Action2Keycode; + } + if (button.originalHotkey == KeyCode.H) { + button.hotkey = Action3Keycode; + } + } + + } + public void setActive(bool isActive) { if (isActive) { actionButtonGameObject.SetActive(true); @@ -210,7 +243,7 @@ namespace TheOtherRoles.Objects { actionButtonMat.SetFloat(Desat, 1f); } - if (Timer >= 0) { + if (Timer >= 0 && !RoleDraft.isRunning) { // Make sure role draft has finished or isnt running if (HasEffect && isEffectActive) Timer -= Time.deltaTime; else if (!localPlayer.inVent && moveable) diff --git a/TheOtherRoles/Objects/Portal.cs b/TheOtherRoles/Objects/Portal.cs index 11fa4b4..a6e4af3 100644 --- a/TheOtherRoles/Objects/Portal.cs +++ b/TheOtherRoles/Objects/Portal.cs @@ -108,7 +108,7 @@ namespace TheOtherRoles.Objects { else if (secondPortal == null) { secondPortal = this; } - var lastRoom = FastDestroyableSingleton.Instance?.roomTracker.LastRoom.RoomId; + var lastRoom = FastDestroyableSingleton.Instance?.roomTracker?.LastRoom?.RoomId; this.room = lastRoom != null ? DestroyableSingleton.Instance.GetString((SystemTypes)lastRoom) : "Open Field"; } diff --git a/TheOtherRoles/Objects/Trap.cs b/TheOtherRoles/Objects/Trap.cs index d613388..3232ae1 100644 --- a/TheOtherRoles/Objects/Trap.cs +++ b/TheOtherRoles/Objects/Trap.cs @@ -17,7 +17,7 @@ namespace TheOtherRoles.Objects { public bool triggerable = false; private int usedCount = 0; private int neededCount = Trapper.trapCountToReveal; - public List trappedPlayer = new List(); + public List trappedPlayer = new List(); private Arrow arrow = new Arrow(Color.blue); private static Sprite trapSprite; @@ -84,13 +84,13 @@ namespace TheOtherRoles.Objects { } player.moveable = false; player.NetTransform.Halt(); - Trapper.playersOnMap.Add(player); + Trapper.playersOnMap.Add(player.PlayerId); if (localIsTrapper) t.arrow.arrow.SetActive(true); FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(Trapper.trapDuration, new Action((p) => { if (p == 1f) { player.moveable = true; - Trapper.playersOnMap.RemoveAll(x => x == player); + Trapper.playersOnMap.RemoveAll(x => x == player.PlayerId); if (trapPlayerIdMap.ContainsKey(playerId)) trapPlayerIdMap.Remove(playerId); t.arrow.arrow.SetActive(false); } @@ -100,9 +100,8 @@ namespace TheOtherRoles.Objects { t.revealed = true; } - t.trappedPlayer.Add(player); + t.trappedPlayer.Add(player.PlayerId); t.triggerable = true; - } public static void Update() { @@ -116,7 +115,7 @@ namespace TheOtherRoles.Objects { Trap target = null; foreach (Trap trap in traps) { if (trap.arrow.arrow.active) trap.arrow.Update(); - if (trap.revealed || !trap.triggerable || trap.trappedPlayer.Contains(player)) continue; + if (trap.revealed || !trap.triggerable || trap.trappedPlayer.Contains(player.PlayerId)) continue; if (player.inVent || !player.CanMove) continue; float distance = Vector2.Distance(trap.trap.transform.position, player.GetTruePosition()); if (distance <= ud && distance < closestDistance) { diff --git a/TheOtherRoles/Patches/ClientOptionsPatch.cs b/TheOtherRoles/Patches/ClientOptionsPatch.cs index 36b2b61..e88dd83 100644 --- a/TheOtherRoles/Patches/ClientOptionsPatch.cs +++ b/TheOtherRoles/Patches/ClientOptionsPatch.cs @@ -20,7 +20,11 @@ namespace TheOtherRoles.Patches new("Ghosts Can Additionally See Modifier", () => TORMapOptions.ghostsSeeModifier = TheOtherRolesPlugin.GhostsSeeModifier.Value = !TheOtherRolesPlugin.GhostsSeeModifier.Value, TheOtherRolesPlugin.GhostsSeeModifier.Value), new("Show Role Summary", () => TORMapOptions.showRoleSummary = TheOtherRolesPlugin.ShowRoleSummary.Value = !TheOtherRolesPlugin.ShowRoleSummary.Value, TheOtherRolesPlugin.ShowRoleSummary.Value), new("Show Lighter / Darker", () => TORMapOptions.showLighterDarker = TheOtherRolesPlugin.ShowLighterDarker.Value = !TheOtherRolesPlugin.ShowLighterDarker.Value, TheOtherRolesPlugin.ShowLighterDarker.Value), - new("Enable Sound Effects", () => TORMapOptions.enableSoundEffects = TheOtherRolesPlugin.EnableSoundEffects.Value = !TheOtherRolesPlugin.EnableSoundEffects.Value, TheOtherRolesPlugin.EnableSoundEffects.Value), + new("Enable Sound Effects", () => { + TORMapOptions.enableSoundEffects = TheOtherRolesPlugin.EnableSoundEffects.Value = !TheOtherRolesPlugin.EnableSoundEffects.Value; + if (!TORMapOptions.enableSoundEffects) SoundEffectsManager.stopAll(); + return TORMapOptions.enableSoundEffects; + }, TheOtherRolesPlugin.EnableSoundEffects.Value), new("Show Vents On Map", () => TORMapOptions.ShowVentsOnMap = TheOtherRolesPlugin.ShowVentsOnMap.Value = !TheOtherRolesPlugin.ShowVentsOnMap.Value, TheOtherRolesPlugin.ShowVentsOnMap.Value), new("Show Chat Notifications", () => TORMapOptions.ShowChatNotifications = TheOtherRolesPlugin.ShowChatNotifications.Value = !TheOtherRolesPlugin.ShowChatNotifications.Value, TheOtherRolesPlugin.ShowChatNotifications.Value), }; @@ -189,7 +193,7 @@ namespace TheOtherRoles.Patches button.Background.color = button.onState ? Color.green : Palette.ImpostorRed; })); - passiveButton.OnMouseOver.AddListener((Action) (() => button.Background.color = new Color32(34 ,139, 34, byte.MaxValue))); + passiveButton.OnMouseOver.AddListener((Action) (() => button.Background.color = button.onState ? new Color32(34 ,139, 34, byte.MaxValue): new Color32(139, 34, 34, byte.MaxValue))); passiveButton.OnMouseOut.AddListener((Action) (() => button.Background.color = button.onState ? Color.green : Palette.ImpostorRed)); foreach (var spr in button.gameObject.GetComponentsInChildren()) diff --git a/TheOtherRoles/Patches/CredentialsPatch.cs b/TheOtherRoles/Patches/CredentialsPatch.cs index 778e86a..82aa761 100644 --- a/TheOtherRoles/Patches/CredentialsPatch.cs +++ b/TheOtherRoles/Patches/CredentialsPatch.cs @@ -40,9 +40,9 @@ $@" Special thanks to Smeggy"; if (HideNSeek.isHideNSeekGM) gameModeText = $"Hide 'N Seek"; else if (HandleGuesser.isGuesserGm) gameModeText = $"Guesser"; else if (PropHunt.isPropHuntGM) gameModeText = "Prop Hunt"; - if (gameModeText != "") gameModeText = Helpers.cs(Color.yellow, gameModeText) + "\n"; + if (gameModeText != "") gameModeText = Helpers.cs(Color.yellow, gameModeText) + (MeetingHud.Instance ? " " : "\n"); __instance.text.text = $"TheOtherRoles v{TheOtherRolesPlugin.Version.ToString() + (TheOtherRolesPlugin.betaDays > 0 ? "-BETA" : "")}\n{gameModeText}" + __instance.text.text; - position.DistanceFromEdge = new Vector3(2.25f, 0.11f, 0); + position.DistanceFromEdge = MeetingHud.Instance ? new Vector3(1.25f, 0.15f, 0) : new Vector3(1.55f, 0.15f, 0); } else { string gameModeText = $""; if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek) gameModeText = $"Hide 'N Seek"; diff --git a/TheOtherRoles/Patches/EndGamePatch.cs b/TheOtherRoles/Patches/EndGamePatch.cs index ac3c131..0e75acb 100644 --- a/TheOtherRoles/Patches/EndGamePatch.cs +++ b/TheOtherRoles/Patches/EndGamePatch.cs @@ -388,6 +388,7 @@ namespace TheOtherRoles.Patches { var roleSummaryTextMeshRectTransform = roleSummaryTextMesh.GetComponent(); roleSummaryTextMeshRectTransform.anchoredPosition = new Vector2(position.x + 3.5f, position.y - 0.1f); roleSummaryTextMesh.text = roleSummaryText.ToString(); + Helpers.previousEndGameSummary = $"{roleSummaryText.ToString()}"; } AdditionalTempData.clear(); } diff --git a/TheOtherRoles/Patches/ExileControllerPatch.cs b/TheOtherRoles/Patches/ExileControllerPatch.cs index 5ff9fb3..adb4008 100644 --- a/TheOtherRoles/Patches/ExileControllerPatch.cs +++ b/TheOtherRoles/Patches/ExileControllerPatch.cs @@ -10,7 +10,7 @@ using TheOtherRoles.Utilities; using UnityEngine; namespace TheOtherRoles.Patches { - [HarmonyPatch(typeof(ExileController), nameof(ExileController.Begin))] + [HarmonyPatch(typeof(ExileController), nameof(ExileController.BeginForGameplay))] [HarmonyPriority(Priority.First)] class ExileControllerBeginPatch { public static void Prefix(ExileController __instance, [HarmonyArgument(0)]ref NetworkedPlayerInfo exiled) { @@ -57,7 +57,7 @@ namespace TheOtherRoles.Patches { if (Witch.witch != null && Witch.futureSpelled != null && AmongUsClient.Instance.AmHost) { bool exiledIsWitch = exiled != null && exiled.PlayerId == Witch.witch.PlayerId; bool witchDiesWithExiledLover = exiled != null && Lovers.existing() && Lovers.bothDie && (Lovers.lover1.PlayerId == Witch.witch.PlayerId || Lovers.lover2.PlayerId == Witch.witch.PlayerId) && (exiled.PlayerId == Lovers.lover1.PlayerId || exiled.PlayerId == Lovers.lover2.PlayerId); - + if ((witchDiesWithExiledLover || exiledIsWitch) && Witch.witchVoteSavesTargets) Witch.futureSpelled = new List(); foreach (PlayerControl target in Witch.futureSpelled) { if (target != null && !target.Data.IsDead && Helpers.checkMuderAttempt(Witch.witch, target, true) == MurderAttemptResult.PerformKill){ diff --git a/TheOtherRoles/Patches/GameStartManagerPatch.cs b/TheOtherRoles/Patches/GameStartManagerPatch.cs index 1e0b3c6..205aed8 100644 --- a/TheOtherRoles/Patches/GameStartManagerPatch.cs +++ b/TheOtherRoles/Patches/GameStartManagerPatch.cs @@ -89,6 +89,8 @@ namespace TheOtherRoles.Patches { } } } + + // Display message to the host if (AmongUsClient.Instance.AmHost) { if (versionMismatch) { @@ -124,8 +126,12 @@ namespace TheOtherRoles.Patches { PassiveButton startButtonPassiveButton = copiedStartButton.GetComponent(); void StopStartFunc() { __instance.ResetStartState(); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.StopStart, Hazel.SendOption.Reliable, -1); + writer.Write(PlayerControl.LocalPlayer.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); copiedStartButton.Destroy(); startingTimer = 0; + SoundManager.Instance.StopSound(GameStartManager.Instance.gameStartSound); } startButtonPassiveButton.OnClick.AddListener((Action)(() => StopStartFunc())); __instance.StartCoroutine(Effects.Lerp(.1f, new System.Action((p) => { @@ -180,12 +186,13 @@ namespace TheOtherRoles.Patches { PassiveButton startButtonPassiveButton = copiedStartButton.GetComponent(); void StopStartFunc() { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.StopStart, Hazel.SendOption.Reliable, AmongUsClient.Instance.HostId); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.StopStart, Hazel.SendOption.Reliable, -1); writer.Write(PlayerControl.LocalPlayer.PlayerId); AmongUsClient.Instance.FinishRpcImmediately(writer); copiedStartButton.Destroy(); __instance.GameStartText.text = String.Empty; startingTimer = 0; + SoundManager.Instance.StopSound(GameStartManager.Instance.gameStartSound); } startButtonPassiveButton.OnClick.AddListener((Action)(() => StopStartFunc())); __instance.StartCoroutine(Effects.Lerp(.1f, new System.Action((p) => { diff --git a/TheOtherRoles/Patches/IntroPatch.cs b/TheOtherRoles/Patches/IntroPatch.cs index cbf12f4..ceda0a8 100644 --- a/TheOtherRoles/Patches/IntroPatch.cs +++ b/TheOtherRoles/Patches/IntroPatch.cs @@ -8,6 +8,7 @@ using Hazel; using TheOtherRoles.Utilities; using TheOtherRoles.CustomGameModes; +using TheOtherRoles.Modules; namespace TheOtherRoles.Patches { [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.OnDestroy))] @@ -82,10 +83,7 @@ namespace TheOtherRoles.Patches { BountyHunter.cooldownText.transform.localScale = Vector3.one * 0.4f; BountyHunter.cooldownText.gameObject.SetActive(true); } - } - - // Force Reload of SoundEffectHolder - SoundEffectsManager.Load(); + } // First kill if (AmongUsClient.Instance.AmHost && TORMapOptions.shieldFirstKill && TORMapOptions.firstKillName != "" && !HideNSeek.isHideNSeekGM && !PropHunt.isPropHuntGM) { @@ -169,14 +167,26 @@ namespace TheOtherRoles.Patches { } yourTeam = fakeImpostorTeam; } + + // Role draft: If spy is enabled, don't show the team + if (CustomOptionHolder.spySpawnRate.getSelection() > 0 && PlayerControl.AllPlayerControls.ToArray().ToList().Where(x => x.Data.Role.IsImpostor).Count() > 1) { + var fakeImpostorTeam = new Il2CppSystem.Collections.Generic.List(); // The local player always has to be the first one in the list (to be displayed in the center) + fakeImpostorTeam.Add(PlayerControl.LocalPlayer); + yourTeam = fakeImpostorTeam; + } } public static void setupIntroTeam(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List yourTeam) { List infos = RoleInfo.getRoleInfoForPlayer(PlayerControl.LocalPlayer); RoleInfo roleInfo = infos.Where(info => !info.isModifier).FirstOrDefault(); - if (roleInfo == null) return; - if (roleInfo.isNeutral) { - var neutralColor = new Color32(76, 84, 78, 255); + var neutralColor = new Color32(76, 84, 78, 255); + if (roleInfo == null || roleInfo == RoleInfo.crewmate) { + if (RoleDraft.isEnabled && CustomOptionHolder.neutralRolesCountMax.getSelection() > 0) { + __instance.TeamTitle.text = "Crewmate" + Helpers.cs(Color.white, " / ") + Helpers.cs(neutralColor, "Neutral") + ""; + } + return; + } + if (roleInfo.isNeutral) { __instance.BackgroundBar.material.color = neutralColor; __instance.TeamTitle.text = "Neutral"; __instance.TeamTitle.color = neutralColor; diff --git a/TheOtherRoles/Patches/MainMenuPatch.cs b/TheOtherRoles/Patches/MainMenuPatch.cs index 8dbdc85..6dfd161 100644 --- a/TheOtherRoles/Patches/MainMenuPatch.cs +++ b/TheOtherRoles/Patches/MainMenuPatch.cs @@ -21,6 +21,10 @@ namespace TheOtherRoles.Modules { private static AnnouncementPopUp popUp; private static void Prefix(MainMenuManager __instance) { + + // Force Reload of SoundEffectHolder + SoundEffectsManager.Load(); + var template = GameObject.Find("ExitGameButton"); var template2 = GameObject.Find("CreditsButton"); if (template == null || template2 == null) return; @@ -115,8 +119,12 @@ TownOfUs - Idea for the Swapper, Shifter, Arsonist and a similar Mayor role came Ottomated - Idea for the Morphling, Snitch and Camouflager role came from Ottomated Crowded-Mod - Our implementation for 10+ player lobbies was inspired by the one from the Crowded Mod Team Goose-Goose-Duck - Idea for the Vulture role came from Slushiegoose -TheEpicRoles - Idea for the first kill shield (partly) and the tabbed option menu (fully + some code), by LaicosVK DasMonschta Nova -ugackMiner53 - Idea and core code for the Prop Hunt game mode"; +TheEpicRoles - Idea for the first kill shield (partly) and the (old) tabbed option menu (fully + some code), by LaicosVK DasMonschta Nova +ugackMiner53 - Idea and core code for the Prop Hunt game mode +Role Draft Music: [https://www.youtube.com/watch?v=9STiQ8cCIo0]Unreal Superhero 3 by Kenët & Rez[] + +License: TheOtherRoles is licensed under the [https://github.com/TheOtherRolesAU/TheOtherRoles?tab=GPL-3.0-1-ov-file#readme]GPLv3[] +"; creditsString += ""; Assets.InnerNet.Announcement creditsAnnouncement = new() { diff --git a/TheOtherRoles/Patches/MapBehaviourPatch.cs b/TheOtherRoles/Patches/MapBehaviourPatch.cs index 2830179..872a817 100644 --- a/TheOtherRoles/Patches/MapBehaviourPatch.cs +++ b/TheOtherRoles/Patches/MapBehaviourPatch.cs @@ -40,23 +40,25 @@ namespace TheOtherRoles.Patches { __instance.HerePoint.transform.SetLocalZ(-2.1f); if (Trapper.trapper != null && PlayerControl.LocalPlayer.PlayerId == Trapper.trapper.PlayerId) { - foreach (PlayerControl player in Trapper.playersOnMap) { - if (herePoints.ContainsKey(player.PlayerId)) continue; - Vector3 v = Trap.trapPlayerIdMap[player.PlayerId].trap.transform.position; + foreach (byte playerId in Trapper.playersOnMap) { + if (herePoints.ContainsKey(playerId)) continue; + Vector3 v = Trap.trapPlayerIdMap[playerId].trap.transform.position; v /= MapUtilities.CachedShipStatus.MapScale; v.x *= Mathf.Sign(MapUtilities.CachedShipStatus.transform.localScale.x); v.z = -2.1f; var herePoint = UnityEngine.Object.Instantiate(__instance.HerePoint, __instance.HerePoint.transform.parent, true); herePoint.transform.localPosition = v; herePoint.enabled = true; - int colorId = player.CurrentOutfit.ColorId; + PlayerControl player = Helpers.playerById(playerId); + if (player == null) continue; + int colorId = player.CurrentOutfit.ColorId; if (Trapper.anonymousMap) player.CurrentOutfit.ColorId = 6; player.SetPlayerMaterialColors(herePoint); player.CurrentOutfit.ColorId = colorId; - herePoints.Add(player.PlayerId, herePoint); + herePoints.Add(playerId, herePoint); } - foreach (var s in herePoints.Where(x => !Trapper.playersOnMap.Contains(Helpers.playerById(x.Key))).ToList()) { - UnityEngine.Object.Destroy(s.Value); + foreach (var s in herePoints.Where(x => !Trapper.playersOnMap.Contains(x.Key)).ToList()) { + UnityEngine.Object.Destroy(s.Value.gameObject); herePoints.Remove(s.Key); } } else if (Snitch.snitch != null && PlayerControl.LocalPlayer.PlayerId == Snitch.snitch.PlayerId && !Snitch.snitch.Data.IsDead && Snitch.mode != Snitch.Mode.Chat) { @@ -88,7 +90,7 @@ namespace TheOtherRoles.Patches { } } else { foreach (var s in herePoints) { - UnityEngine.Object.Destroy(s.Value); + UnityEngine.Object.Destroy(s.Value.gameObject); herePoints.Remove(s.Key); } } diff --git a/TheOtherRoles/Patches/MeetingPatch.cs b/TheOtherRoles/Patches/MeetingPatch.cs index da0f91b..f022048 100644 --- a/TheOtherRoles/Patches/MeetingPatch.cs +++ b/TheOtherRoles/Patches/MeetingPatch.cs @@ -713,7 +713,8 @@ namespace TheOtherRoles.Patches { if (!trap.revealed) continue; string message = $"Trap {trap.instanceId}: \n"; trap.trappedPlayer = trap.trappedPlayer.OrderBy(x => rnd.Next()).ToList(); - foreach (PlayerControl p in trap.trappedPlayer) { + foreach (byte playerId in trap.trappedPlayer) { + PlayerControl p = Helpers.playerById(playerId); if (Trapper.infoType == 0) message += RoleInfo.GetRolesString(p, false, false, true) + "\n"; else if (Trapper.infoType == 1) { if (Helpers.isNeutral(p) || p.Data.Role.IsImpostor) message += "Evil Role \n"; @@ -755,7 +756,7 @@ namespace TheOtherRoles.Patches { if (PlayerControl.LocalPlayer.Data.IsDead && output != "") FastDestroyableSingleton.Instance.Chat.AddChat(PlayerControl.LocalPlayer, $"{output}"); - Trapper.playersOnMap = new List(); + Trapper.playersOnMap = new (); Snitch.playerRoomMap = new Dictionary(); // Remove revealed traps diff --git a/TheOtherRoles/Patches/PlayerControlPatch.cs b/TheOtherRoles/Patches/PlayerControlPatch.cs index 790f27a..65989d4 100644 --- a/TheOtherRoles/Patches/PlayerControlPatch.cs +++ b/TheOtherRoles/Patches/PlayerControlPatch.cs @@ -21,7 +21,7 @@ namespace TheOtherRoles.Patches { public static class PlayerControlFixedUpdatePatch { // Helpers - static PlayerControl setTarget(bool onlyCrewmates = false, bool targetPlayersInVents = false, List untargetablePlayers = null, PlayerControl targetingPlayer = null) { + public static PlayerControl setTarget(bool onlyCrewmates = false, bool targetPlayersInVents = false, List untargetablePlayers = null, PlayerControl targetingPlayer = null) { PlayerControl result = null; float num = AmongUs.GameOptions.GameOptionsData.KillDistances[Mathf.Clamp(GameOptionsManager.Instance.currentNormalGameOptions.KillDistance, 0, 2)]; if (!MapUtilities.CachedShipStatus) return result; @@ -51,7 +51,7 @@ namespace TheOtherRoles.Patches { return result; } - static void setPlayerOutline(PlayerControl target, Color color) { + public static void setPlayerOutline(PlayerControl target, Color color) { if (target == null || target.cosmetics?.currentBodySprite?.BodySprite == null) return; color = color.SetAlpha(Chameleon.visibility(target.PlayerId)); diff --git a/TheOtherRoles/Patches/RoleAssignmentPatch.cs b/TheOtherRoles/Patches/RoleAssignmentPatch.cs index 012903b..ba60885 100644 --- a/TheOtherRoles/Patches/RoleAssignmentPatch.cs +++ b/TheOtherRoles/Patches/RoleAssignmentPatch.cs @@ -9,8 +9,10 @@ using AmongUs.GameOptions; using TheOtherRoles.Utilities; using static TheOtherRoles.TheOtherRoles; using TheOtherRoles.CustomGameModes; +using TheOtherRoles.Modules; -namespace TheOtherRoles.Patches { +namespace TheOtherRoles.Patches +{ [HarmonyPatch(typeof(RoleOptionsCollectionV08), nameof(RoleOptionsCollectionV08.GetNumPerGame))] class RoleOptionsDataGetNumPerGamePatch{ public static void Postfix(ref int __result) { @@ -51,7 +53,8 @@ namespace TheOtherRoles.Patches { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ResetVaribles, Hazel.SendOption.Reliable, -1); AmongUsClient.Instance.FinishRpcImmediately(writer); RPCProcedure.resetVariables(); - if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek || TORMapOptions.gameMode == CustomGamemodes.PropHunt || GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek) return; // Don't assign Roles in Hide N Seek + if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek || TORMapOptions.gameMode == CustomGamemodes.PropHunt || GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek + || RoleDraft.isEnabled) return; // Don't assign Roles in Hide N Seek assignRoles(); } @@ -383,7 +386,7 @@ namespace TheOtherRoles.Patches { } } - private static void assignRoleTargets(RoleAssignmentData data) { + public static void assignRoleTargets(RoleAssignmentData data) { // Set Lawyer or Prosecutor Target if (Lawyer.lawyer != null) { var possibleTargets = new List(); @@ -413,7 +416,7 @@ namespace TheOtherRoles.Patches { } } - private static void assignModifiers() { + public static void assignModifiers() { var modifierMin = CustomOptionHolder.modifiersCountMin.getSelection(); var modifierMax = CustomOptionHolder.modifiersCountMax.getSelection(); if (modifierMin > modifierMax) modifierMin = modifierMax; @@ -485,7 +488,7 @@ namespace TheOtherRoles.Patches { assignModifiersToPlayers(chanceModifierToAssign, players, modifierCount); // Assign chance modifier } - private static void assignGuesserGamemode() { + public static void assignGuesserGamemode() { List impPlayer = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList(); List neutralPlayer = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList(); List crewPlayer = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList(); @@ -596,7 +599,13 @@ namespace TheOtherRoles.Patches { case RoleId.Tiebreaker: selection = CustomOptionHolder.modifierTieBreaker.getSelection(); break; case RoleId.Mini: - selection = CustomOptionHolder.modifierMini.getSelection(); break; + selection = CustomOptionHolder.modifierMini.getSelection(); + if (EventUtility.isEnabled) { + selection = 10; + if (CustomOptionHolder.modifierMini.getSelection() == 0 && CustomOptionHolder.eventReallyNoMini.getBool()) + selection = 0; + } + break; case RoleId.Bait: selection = CustomOptionHolder.modifierBait.getSelection(); if (multiplyQuantity) selection *= CustomOptionHolder.modifierBaitQuantity.getQuantity(); diff --git a/TheOtherRoles/Patches/UpdatePatch.cs b/TheOtherRoles/Patches/UpdatePatch.cs index 9079436..0c1d717 100644 --- a/TheOtherRoles/Patches/UpdatePatch.cs +++ b/TheOtherRoles/Patches/UpdatePatch.cs @@ -314,7 +314,13 @@ namespace TheOtherRoles.Patches { { if (GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek) return; if (Deputy.handcuffedKnows.ContainsKey(PlayerControl.LocalPlayer.PlayerId) && Deputy.handcuffedKnows[PlayerControl.LocalPlayer.PlayerId] > 0 || MeetingHud.Instance) __instance.ImpostorVentButton.Hide(); - else if (PlayerControl.LocalPlayer.roleCanUseVents() && !__instance.ImpostorVentButton.isActiveAndEnabled) __instance.ImpostorVentButton.Show(); + else if (PlayerControl.LocalPlayer.roleCanUseVents() && !__instance.ImpostorVentButton.isActiveAndEnabled) { + __instance.ImpostorVentButton.Show(); + + } + if (Rewired.ReInput.players.GetPlayer(0).GetButtonDown(RewiredConsts.Action.UseVent) && !PlayerControl.LocalPlayer.Data.Role.IsImpostor && PlayerControl.LocalPlayer.roleCanUseVents()) { + __instance.ImpostorVentButton.DoClick(); + } } diff --git a/TheOtherRoles/Patches/UsablesPatch.cs b/TheOtherRoles/Patches/UsablesPatch.cs index 1d05421..19cc39e 100644 --- a/TheOtherRoles/Patches/UsablesPatch.cs +++ b/TheOtherRoles/Patches/UsablesPatch.cs @@ -104,12 +104,12 @@ namespace TheOtherRoles.Patches { Deputy.setHandcuffedKnows(); return false; } - if (Trapper.playersOnMap.Contains(PlayerControl.LocalPlayer)) return false; + if (Trapper.playersOnMap.Contains(PlayerControl.LocalPlayer.PlayerId)) return false; bool canUse; bool couldUse; __instance.CanUse(PlayerControl.LocalPlayer.Data, out canUse, out couldUse); - bool canMoveInVents = PlayerControl.LocalPlayer != Spy.spy && !Trapper.playersOnMap.Contains(PlayerControl.LocalPlayer); + bool canMoveInVents = PlayerControl.LocalPlayer != Spy.spy && !Trapper.playersOnMap.Contains(PlayerControl.LocalPlayer.PlayerId); if (!canUse) return false; // No need to execute the native method as using is disallowed anyways bool isEnter = !PlayerControl.LocalPlayer.inVent; @@ -139,7 +139,7 @@ namespace TheOtherRoles.Patches { [HarmonyPatch(typeof(Vent), nameof(Vent.TryMoveToVent))] public static class MoveToVentPatch { public static bool Prefix(Vent otherVent) { - return !Trapper.playersOnMap.Contains(PlayerControl.LocalPlayer); + return !Trapper.playersOnMap.Contains(PlayerControl.LocalPlayer.PlayerId); } } diff --git a/TheOtherRoles/RPC.cs b/TheOtherRoles/RPC.cs index cecd478..7b5c294 100644 --- a/TheOtherRoles/RPC.cs +++ b/TheOtherRoles/RPC.cs @@ -17,6 +17,7 @@ using AmongUs.Data; using AmongUs.GameOptions; using Assets.CoreScripts; using Reactor.Utilities.Extensions; +using TheOtherRoles.Modules; namespace TheOtherRoles { public enum RoleId { @@ -164,10 +165,13 @@ namespace TheOtherRoles PropHuntStartTimer, PropHuntSetInvis, PropHuntSetSpeedboost, + DraftModePickOrder, + DraftModePick, // Other functionality ShareTimer, ShareGhostInfo, + EventKick, } public static class RPCProcedure { @@ -186,12 +190,14 @@ namespace TheOtherRoles clearAndReloadRoles(); clearGameHistory(); setCustomButtonCooldowns(); + CustomButton.ReloadHotkeys(); reloadPluginOptions(); Helpers.toggleZoom(reset : true); GameStartManagerPatch.GameStartManagerUpdatePatch.startingTimer = 0; SurveillanceMinigamePatch.nightVisionOverlays = null; EventUtility.clearAndReload(); MapBehaviourPatch.clearAndReload(); + HudManagerUpdate.CloseSummary(); } public static void HandleShareOptions(byte numberOfOptions, MessageReader reader) { @@ -231,7 +237,10 @@ namespace TheOtherRoles } public static void stopStart(byte playerId) { - if (AmongUsClient.Instance.AmHost && CustomOptionHolder.anyPlayerCanStopStart.getBool()) { + if (!CustomOptionHolder.anyPlayerCanStopStart.getBool()) + return; + SoundManager.Instance.StopSound(GameStartManager.Instance.gameStartSound); + if (AmongUsClient.Instance.AmHost) { GameStartManager.Instance.ResetStartState(); PlayerControl.LocalPlayer.RpcSendChat($"{Helpers.playerById(playerId).Data.PlayerName} stopped the game start!"); } @@ -1649,6 +1658,12 @@ namespace TheOtherRoles case (byte)CustomRPC.PropHuntSetSpeedboost: RPCProcedure.propHuntSetSpeedboost(reader.ReadByte()); break; + case (byte)CustomRPC.DraftModePickOrder: + RoleDraft.receivePickOrder(reader.ReadByte(), reader); + break; + case (byte)CustomRPC.DraftModePick: + RoleDraft.receivePick(reader.ReadByte(), reader.ReadByte()); + break; case (byte)CustomRPC.ShareGhostInfo: RPCProcedure.receiveGhostInfo(reader.ReadByte(), reader); break; @@ -1659,6 +1674,11 @@ namespace TheOtherRoles byte roomId = reader.ReadByte(); RPCProcedure.shareRoom(roomPlayer, roomId); break; + case (byte)CustomRPC.EventKick: + byte kickSource = reader.ReadByte(); + byte kickTarget = reader.ReadByte(); + EventUtility.handleKick(Helpers.playerById(kickSource), Helpers.playerById(kickTarget), reader.ReadSingle()); + break; } } } diff --git a/TheOtherRoles/Resources/Endscreen.png b/TheOtherRoles/Resources/Endscreen.png new file mode 100644 index 0000000..32a1e5a Binary files /dev/null and b/TheOtherRoles/Resources/Endscreen.png differ diff --git a/TheOtherRoles/Resources/EndscreenActive.png b/TheOtherRoles/Resources/EndscreenActive.png new file mode 100644 index 0000000..1fda220 Binary files /dev/null and b/TheOtherRoles/Resources/EndscreenActive.png differ diff --git a/TheOtherRoles/Resources/EventKickButton.png b/TheOtherRoles/Resources/EventKickButton.png new file mode 100644 index 0000000..e8d8efa Binary files /dev/null and b/TheOtherRoles/Resources/EventKickButton.png differ diff --git a/TheOtherRoles/Resources/SoundEffects/SoundEffectSourcesAndLicenses.md b/TheOtherRoles/Resources/SoundEffects/SoundEffectSourcesAndLicenses.md index 33f11e8..3e2e26d 100644 --- a/TheOtherRoles/Resources/SoundEffects/SoundEffectSourcesAndLicenses.md +++ b/TheOtherRoles/Resources/SoundEffects/SoundEffectSourcesAndLicenses.md @@ -40,3 +40,5 @@ |portalUse | | [Static electronic noise - Xbox 360](https://freesound.org/people/scenes/sounds/431654/ ) + [Teleport Slurp](https://freesound.org/people/GameAudio/sounds/220163/ ) | CC0 1.0 both | |fail | | [twitch: SmeggyTV: Mein Auge](https://www.twitch.tv/smeggytv/clip/KindQuaintEndiveDancingBanana-Zoc-bXUnqNCoqQD5 ) | License granted for use in TOR | |trapperTrap | | [Karabiner_Click_04.wav](https://freesound.org/people/Rudmer_Rotteveel/sounds/457454/ ) & [Steel Spring Bear Trap](https://freesound.org/people/fractionalist/sounds/644245/ )| CC0 1.0 both | +|select | | [Game Menu Select Sound 2](https://freesound.org/people/digimistic/sounds/705174/) | CC0 1.0 | +|draft| [Unreal Superhero 3 by Kenët & Rez](https://www.youtube.com/watch?v=9STiQ8cCIo0) | License granted for use in TOR | diff --git a/TheOtherRoles/Resources/SoundEffects/toraudio b/TheOtherRoles/Resources/SoundEffects/toraudio index b3fb5d0..b9b4e5d 100644 Binary files a/TheOtherRoles/Resources/SoundEffects/toraudio and b/TheOtherRoles/Resources/SoundEffects/toraudio differ diff --git a/TheOtherRoles/RoleInfo.cs b/TheOtherRoles/RoleInfo.cs index 9ed2a45..1efad09 100644 --- a/TheOtherRoles/RoleInfo.cs +++ b/TheOtherRoles/RoleInfo.cs @@ -19,6 +19,8 @@ namespace TheOtherRoles public RoleId roleId; public bool isNeutral; public bool isModifier; + public bool isImpostor => color == Palette.ImpostorRed && !(roleId == RoleId.Spy); + public static Dictionary roleInfoById = new(); public RoleInfo(string name, Color color, string introDescription, string shortDescription, RoleId roleId, bool isNeutral = false, bool isModifier = false) { this.color = color; @@ -28,6 +30,7 @@ namespace TheOtherRoles this.roleId = roleId; this.isNeutral = isNeutral; this.isModifier = isModifier; + roleInfoById.TryAdd(roleId, this); } public static RoleInfo jester = new RoleInfo("Jester", Jester.color, "Get voted out", "Get voted out", RoleId.Jester, true); diff --git a/TheOtherRoles/SoundEffectsManager.cs b/TheOtherRoles/SoundEffectsManager.cs index 8061cca..7c62aaf 100644 --- a/TheOtherRoles/SoundEffectsManager.cs +++ b/TheOtherRoles/SoundEffectsManager.cs @@ -15,6 +15,7 @@ namespace TheOtherRoles { private static Dictionary soundEffects = new(); + //private static List currentSources = new(); public static void Load() { @@ -44,32 +45,50 @@ namespace TheOtherRoles { // Convenience: As as SoundEffects are stored in the same folder, allow using just the name as well //if (!path.Contains(".")) path = "TheOtherRoles.Resources.SoundEffects." + path + ".raw"; - path = "assets/audio/" + path.ToLower() + ".ogg"; + if (!path.Contains("assets")) path = "assets/audio/" + path.ToLower() + ".ogg"; AudioClip returnValue; return soundEffects.TryGetValue(path, out returnValue) ? returnValue : null; } - public static void play(string path, float volume=0.8f, bool loop = false) + public static AudioSource play(string path, float volume=0.8f, bool loop = false, bool musicChannel=false) { - if (!TORMapOptions.enableSoundEffects) return; + if (!TORMapOptions.enableSoundEffects) return null; AudioClip clipToPlay = get(path); stop(path); if (Constants.ShouldPlaySfx() && clipToPlay != null) { - AudioSource source = SoundManager.Instance.PlaySound(clipToPlay, false, volume); + AudioSource source = SoundManager.Instance.PlaySound(clipToPlay, false, volume, audioMixer: musicChannel ? SoundManager.Instance.MusicChannel : null); + //currentSources.Add(source); source.loop = loop; + return source; } + return null; } public static void playAtPosition(string path, Vector2 position, float maxDuration = 15f, float range = 5f, bool loop = false) { if (!TORMapOptions.enableSoundEffects || !Constants.ShouldPlaySfx()) return; AudioClip clipToPlay = get(path); + TheOtherRolesPlugin.Logger.LogMessage("play at position"); + if (clipToPlay == null) { + TheOtherRolesPlugin.Logger.LogMessage("clip is null"); + return; + } AudioSource source = SoundManager.Instance.PlaySound(clipToPlay, false, 1f); + if (source == null) { + TheOtherRolesPlugin.Logger.LogMessage("source is null"); + return; + } + //currentSources.Add(source); source.loop = loop; HudManager.Instance.StartCoroutine(Effects.Lerp(maxDuration, new Action((p) => { if (source != null) { - if (p == 1) { + if (p == 1 && source.isPlaying) { source.Stop(); + try { + //currentSources.Remove(source); + source.Destroy(); + } + catch { } } float distance, volume; distance = Vector2.Distance(position, PlayerControl.LocalPlayer.GetTruePosition()); @@ -80,17 +99,35 @@ namespace TheOtherRoles source.volume = volume; } }))); + TheOtherRolesPlugin.Logger.LogMessage("end play at position"); } public static void stop(string path) { var soundToStop = get(path); - if (soundToStop != null) - if (Constants.ShouldPlaySfx()) SoundManager.Instance.StopSound(soundToStop); + if (soundToStop != null) { + try { + SoundManager.Instance?.StopSound(soundToStop); + } + catch (Exception e) { TheOtherRolesPlugin.Logger.LogWarning($"Exception in stop sound: {e}"); } + } } public static void stopAll() { if (soundEffects == null) return; - foreach (var path in soundEffects.Keys) stop(path); + try { + foreach (var path in soundEffects.Keys) { + stop(path); + } + } + catch { } + + /*try { + foreach (var source in currentSources) { + source?.Stop(); + } + currentSources.Clear(); + } + catch { }*/ } } } diff --git a/TheOtherRoles/TheOtherRoles.cs b/TheOtherRoles/TheOtherRoles.cs index ee52ab3..befce56 100644 --- a/TheOtherRoles/TheOtherRoles.cs +++ b/TheOtherRoles/TheOtherRoles.cs @@ -1560,7 +1560,7 @@ namespace TheOtherRoles vision = CustomOptionHolder.lawyerVision.getFloat(); lawyerKnowsRole = CustomOptionHolder.lawyerKnowsRole.getBool(); targetCanBeJester = CustomOptionHolder.lawyerTargetCanBeJester.getBool(); - canCallEmergency = CustomOptionHolder.jesterCanCallEmergency.getBool(); + canCallEmergency = CustomOptionHolder.lawyerCanCallEmergency.getBool(); } } @@ -1723,7 +1723,7 @@ namespace TheOtherRoles public static int rechargedTasks = 3; public static int charges = 1; public static int trapCountToReveal = 2; - public static List playersOnMap = new List(); + public static List playersOnMap = new List(); public static bool anonymousMap = false; public static int infoType = 0; // 0 = Role, 1 = Good/Evil, 2 = Name public static float trapDuration = 5f; @@ -1744,7 +1744,7 @@ namespace TheOtherRoles rechargedTasks = Mathf.RoundToInt(CustomOptionHolder.trapperRechargeTasksNumber.getFloat()); charges = Mathf.RoundToInt(CustomOptionHolder.trapperMaxCharges.getFloat()) / 2; trapCountToReveal = Mathf.RoundToInt(CustomOptionHolder.trapperTrapNeededTriggerToReveal.getFloat()); - playersOnMap = new List(); + playersOnMap = new (); anonymousMap = CustomOptionHolder.trapperAnonymousMap.getBool(); infoType = CustomOptionHolder.trapperInfoType.getSelection(); trapDuration = CustomOptionHolder.trapperTrapDuration.getFloat(); @@ -1774,6 +1774,7 @@ namespace TheOtherRoles } public static void clearBomb(bool flag = true) { + TheOtherRolesPlugin.Logger.LogDebug("Clearing Bomb!"); if (bomb != null) { UnityEngine.Object.Destroy(bomb.bomb); UnityEngine.Object.Destroy(bomb.background); @@ -2052,6 +2053,8 @@ namespace TheOtherRoles public static PlayerControl futureShift; public static PlayerControl currentTarget; + public static bool shiftsMedicShield = false; + private static Sprite buttonSprite; public static Sprite getButtonSprite() { if (buttonSprite) return buttonSprite; @@ -2088,6 +2091,8 @@ namespace TheOtherRoles } else if (Medic.medic != null && Medic.medic == player2) { if (repeat) shiftRole(player2, player1, false); Medic.medic = player1; + if (Medic.shielded != null && Medic.shielded == player1 && shiftsMedicShield) + Medic.shielded = player2; } else if (Swapper.swapper != null && Swapper.swapper == player2) { if (repeat) shiftRole(player2, player1, false); Swapper.swapper = player1; @@ -2128,6 +2133,7 @@ namespace TheOtherRoles shifter = null; currentTarget = null; futureShift = null; + shiftsMedicShield = CustomOptionHolder.modifierShifterShiftsMedicShield.getBool(); } } } diff --git a/TheOtherRoles/TheOtherRoles.csproj b/TheOtherRoles/TheOtherRoles.csproj index 4d0f4ab..526508d 100644 --- a/TheOtherRoles/TheOtherRoles.csproj +++ b/TheOtherRoles/TheOtherRoles.csproj @@ -1,7 +1,7 @@  net6.0 - 4.7.0 + 4.8.0 TheOtherRoles Eisbison latest diff --git a/TheOtherRoles/Utilities/EventUtility.cs b/TheOtherRoles/Utilities/EventUtility.cs index ea9a9ee..4270f54 100644 --- a/TheOtherRoles/Utilities/EventUtility.cs +++ b/TheOtherRoles/Utilities/EventUtility.cs @@ -5,29 +5,48 @@ using UnityEngine; using TheOtherRoles; using TheOtherRoles.Patches; -using static TheOtherRoles.TheOtherRoles; using System.Linq; using InnerNet; using TheOtherRoles.Modules; using HarmonyLib; +using Hazel; namespace TheOtherRoles.Utilities; [HarmonyPatch] public static class EventUtility { + private static Sprite kickButtonSprite; + + public static Sprite getKickButtonSprite() { + + if (kickButtonSprite) return kickButtonSprite; + kickButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.EventKickButton.png", 115f); + return kickButtonSprite; + } + public static void Load() { if (!isEnabled) return; } public static void clearAndReload() { + kickCounter = 0; } public static void Update() { - if (!isEnabled || AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started || TheOtherRoles.rnd == null || IntroCutscene.Instance) return; + //if (!isEnabled || AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started || TheOtherRoles.rnd == null || IntroCutscene.Instance) return; + + // set Target + var untargetablePlayers = new List(); + foreach (var player in PlayerControl.AllPlayerControls) { + if (Mini.mini != player) + untargetablePlayers.Add(player); + } + currentTarget = PlayerControlFixedUpdatePatch.setTarget(untargetablePlayers: untargetablePlayers); + PlayerControlFixedUpdatePatch.setPlayerOutline(currentTarget, Color.yellow); } - public static DateTime enabled = DateTime.FromBinary(638475264000000000); + public static DateTime enabled = new DateTime(DateTime.Today.Year, 4, 1); public static bool isEventDate => DateTime.Today.Date == enabled; public static bool canBeEnabled => DateTime.Today.Date >= enabled && DateTime.Today.Date <= enabled.AddDays(7); // One Week after the EVENT @@ -35,8 +54,6 @@ public static class EventUtility { public static void meetingEndsUpdate() { if (!isEnabled) return; - // TODO - Implement Horse hats - // PlayerControl.LocalPlayer.RpcSetHat(CustomHatLoader.horseHatProductIds[rnd.Next(CustomHatLoader.horseHatProductIds.Count)]); } @@ -49,7 +66,80 @@ public static class EventUtility { } public static void gameEndsUpdate() { - if (!isEnabled) return; + + } + + + public static PlayerControl currentTarget; + private static bool currentlyKicking; + private static int kickCounter = 0; + + public static void kickTarget() { + // send rpc to kick target + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.EventKick, Hazel.SendOption.Reliable, -1); + writer.Write(PlayerControl.LocalPlayer.PlayerId); + writer.Write(currentTarget.PlayerId); + System.Random rnd = new System.Random(); + float kickDistance = 1 + (float)rnd.NextDouble() * 1.5f; // 1- 2.5 + writer.Write(kickDistance); + AmongUsClient.Instance.FinishRpcImmediately(writer); + handleKick(PlayerControl.LocalPlayer, currentTarget, kickDistance); + } + + public static void handleKick(PlayerControl source, PlayerControl target, float kickDistance) { + if (currentlyKicking || !isEnabled) return; + + kickCounter++; + + // actual movement, canceled if meeting started + if (Mini.growingProgress() * 18 >= CustomOptionHolder.eventHeavyAge.getFloat() || kickCounter > CustomOptionHolder.eventKicksPerRound.getFloat()) { // boing flip + target = source; + source = Mini.mini; + } + + SoundEffectsManager.playAtPosition("fail" , target.GetTruePosition(), 3, 3); + + if (target == PlayerControl.LocalPlayer) { + PlayerControl.LocalPlayer.moveable = false; + PlayerControl.LocalPlayer.NetTransform.Halt(); + } + Vector2 direction = Vector3.Normalize(target.transform.position - source.transform.position); + + + float kickDuration = 3f; + float speed = kickDistance / kickDuration; + Vector2 targetPosition = (Vector2)target.transform.position + direction * kickDistance; + Vector2 startPosition = target.transform.position; + + HudManager.Instance.StartCoroutine(Effects.Lerp(kickDuration, new Action((p) => { + float rotAngle = 360 * 4 * (1 - Mathf.Pow(1 - p, 4)) * (direction.x > 0 ? 1 : -1); + currentlyKicking = true; + + if (MeetingHud.Instance) { + currentlyKicking = false; + rotAngle = 0f; + } else { + if (p == 1) { + if (target == PlayerControl.LocalPlayer) { + PlayerControl.LocalPlayer.moveable = true; + PlayerControl.LocalPlayer.NetTransform.RpcSnapTo(PlayerControl.LocalPlayer.transform.position); + } + rotAngle = 0f; + currentlyKicking = false; + target.NetTransform.Halt(); + } + target.transform.SetLocalEulerAngles(new Vector3(0f, 0f, rotAngle), RotationOrder.OrderXYZ); + + // move the player: + Vector3 targetStep = startPosition + (1 - Mathf.Pow(1 - p, 4)) * direction * kickDistance; + + if (!PhysicsHelpers.AnythingBetween(target.GetTruePosition(), target.GetTruePosition() + direction * 1f, Constants.ShipAndObjectsMask, false)) { + target.transform.position = targetStep; + } + } + }))); + + }