diff --git a/Strings.xlsx b/Strings.xlsx index 6439e17..ce0954e 100644 Binary files a/Strings.xlsx and b/Strings.xlsx differ diff --git a/TheOtherRoles/Buttons/Buttons.cs b/TheOtherRoles/Buttons/Buttons.cs index 17c08b3..be2121f 100644 --- a/TheOtherRoles/Buttons/Buttons.cs +++ b/TheOtherRoles/Buttons/Buttons.cs @@ -2150,7 +2150,7 @@ public static void createButtonsPostfix(HudManager __instance) ButtonPositions.upperRowCenter, __instance, modKillInput.keyCode, - buttonText: GetString("killButtonText") + buttonText: GetString("killButtonText") ); PavlovsdogKillSelfText = Object.Instantiate(pavlovsdogsKillButton.actionButton.cooldownTimerText, pavlovsdogsKillButton.actionButton.cooldownTimerText.transform.parent); @@ -3629,7 +3629,7 @@ public static void createButtonsPostfix(HudManager __instance) }, () => { - return Pursuer.Player != null && Pursuer.Player.Contains(PlayerControl.LocalPlayer) && + return Pursuer.Player != null && Pursuer.Player.MContains(PlayerControl.LocalPlayer) && PlayerControl.LocalPlayer.IsAlive()/* && Pursuer.blanks < Pursuer.blanksNumber*/; }, () => @@ -3672,7 +3672,7 @@ public static void createButtonsPostfix(HudManager __instance) }, () => { - return Survivor.Player != null && Survivor.Player.Contains(PlayerControl.LocalPlayer) && + return Survivor.Player != null && Survivor.Player.MContains(PlayerControl.LocalPlayer) && PlayerControl.LocalPlayer.IsAlive() && Survivor.vestEnable/* && Survivor.remainingVests > 0*/; }, () => @@ -3727,7 +3727,7 @@ public static void createButtonsPostfix(HudManager __instance) }, () => { - return Survivor.Player != null && Survivor.Player.Contains(PlayerControl.LocalPlayer) && + return Survivor.Player != null && Survivor.Player.MContains(PlayerControl.LocalPlayer) && PlayerControl.LocalPlayer.IsAlive() && Survivor.blanksEnable/* && Survivor.remainingBlanks > 0*/; }, () => diff --git a/TheOtherRoles/Buttons/CustomButton.cs b/TheOtherRoles/Buttons/CustomButton.cs index ae72e6b..7d4b053 100644 --- a/TheOtherRoles/Buttons/CustomButton.cs +++ b/TheOtherRoles/Buttons/CustomButton.cs @@ -28,6 +28,9 @@ public class CustomButton public Func HasButton; public bool HasEffect; public KeyCode? hotkey; + //public KeyCode? originalHotkey; + //public static KeyCode Action2Keycode = KeyCode.G; + //public static KeyCode Action3Keycode = KeyCode.H; public HudManager hudManager; public bool isEffectActive; public bool isHandcuffed; @@ -68,6 +71,7 @@ public CustomButton(Action OnClick, Func HasButton, Func CouldUse, A showButtonText = actionButtonRenderer.sprite == Sprite || buttonText != ""; button.OnClick = new Button.ButtonClickedEvent(); button.OnClick.AddListener((UnityAction)onClickEvent); + //originalHotkey = hotkey; Timer = 10.5f; SetHotKeyGuide(); @@ -148,7 +152,7 @@ public static void ResetAllCooldowns(float Time = -1) Error($"NullReferenceException from ResetAllCooldowns(), if theres only one warning its fine\n{e}", "CustomButton"); } } - PlayerControl.LocalPlayer.killTimer = time; + PlayerControl.LocalPlayer.SetKillTimer(time); } public static void resetKillButton(PlayerControl p, float time = -1) @@ -157,7 +161,7 @@ public static void resetKillButton(PlayerControl p, float time = -1) if (p.Data.Role.IsImpostor) { if (time == -1) time = ModOption.KillCooddown; - p.killTimer = time; + p.SetKillTimer(time); } pelicanKillButton.Timer = time == -1 ? pelicanKillButton.MaxTimer : time; @@ -281,6 +285,37 @@ public void Update() OnClick = InitialOnClick; } + // 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 = 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 = 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 static GameObject SetKeyGuide(GameObject button, KeyCode key, Vector2 pos) { Sprite numSprite = null; @@ -364,5 +399,6 @@ public static class ButtonPositions public static readonly Vector3 upperRowCenter = new(-1f, 1f, 0f); // Not usable for imps beacuse of new button positions! public static readonly Vector3 upperRowLeft = new(-2f, 1f, 0f); public static readonly Vector3 upperRowFarLeft = new(-3f, 1f, 0f); + public static readonly Vector3 highRowRight = new(0f, 2.06f, 0f); } } \ No newline at end of file diff --git a/TheOtherRoles/Helper/Helpers.cs b/TheOtherRoles/Helper/Helpers.cs index 45d8a06..171af05 100644 --- a/TheOtherRoles/Helper/Helpers.cs +++ b/TheOtherRoles/Helper/Helpers.cs @@ -60,6 +60,7 @@ public static class Helpers public static bool isAirship => GameOptionsManager.Instance.CurrentGameOptions.MapId == 4; public static bool isFungle => GameOptionsManager.Instance.CurrentGameOptions.MapId == 5; + public static string previousEndGameSummary = ""; public static System.Random rnd => new(Guid.NewGuid().GetHashCode()); public static bool isUsingTransportation(this PlayerControl pc) => pc.inMovingPlat || pc.onLadder; @@ -263,7 +264,7 @@ public static bool isEvilNeutral(PlayerControl player) public static bool isKiller(this PlayerControl player) { - return player != null && (player.isImpostor() || isKillerNeutral(player)); + return player != null && (player.IsImpostor() || isKillerNeutral(player)); } public static bool isCrew(this PlayerControl player) @@ -271,7 +272,7 @@ public static bool isCrew(this PlayerControl player) return player != null && !player.Data.Role.IsImpostor && !isNeutral(player); } - public static bool isImpostor(this PlayerControl player, bool Spy = false) + public static bool IsImpostor(this PlayerControl player, bool Spy = false) { if (Spy && Roles.Crewmate.Spy.spy != null && Roles.Crewmate.Spy.spy == player) return true; return player != null && player.Data.Role.IsImpostor; @@ -281,7 +282,7 @@ public static string teamString(PlayerControl player) { var killerTeam = ""; if (isNeutral(player)) killerTeam = "NeutralRolesText".Translate(); - else if (player.isImpostor()) killerTeam = "ImpostorRolesText".Translate(); + else if (player.IsImpostor()) killerTeam = "ImpostorRolesText".Translate(); else if (player.isCrew()) killerTeam = "CrewmateRolesText".Translate(); return killerTeam; } @@ -599,10 +600,19 @@ public static int Count(this Il2CppSystem.Collections.Generic.List list, F { int count = 0; foreach (T obj in list) - if (func == null || func(obj)) - count++; + if (func == null || func(obj)) count++; return count; } + + public static bool MContains(this IEnumerable source, T item) where T : class + { + if (source == null) + return false; + foreach (var i in source) + if (i == item) return true; + return false; + } + public static Color HexToColor(string hex) { _ = ColorUtility.TryParseHtmlString("#" + hex, out var color); diff --git a/TheOtherRoles/Helper/LogHelper.cs b/TheOtherRoles/Helper/LogHelper.cs index 2350be6..4beca89 100644 --- a/TheOtherRoles/Helper/LogHelper.cs +++ b/TheOtherRoles/Helper/LogHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Text; using BepInEx; using BepInEx.Logging; @@ -24,9 +25,11 @@ internal static void SetLogSource(ManualLogSource Source) public static void SendLog(string text, string tag = "", LogLevel logLevel = LogLevel.Info) { + StackFrame stack = new(2); string time = DateTime.Now.ToString("HH:mm:ss"); - if (!string.IsNullOrWhiteSpace(tag)) text = $"[{time}] [{tag}] {text}"; - else text = $"[{time}] {text}"; + string className = stack.GetMethod().ReflectedType.Name; + if (!string.IsNullOrWhiteSpace(tag)) text = $"[{time}] [{className}] [{tag}] {text}"; + else text = $"[{time}] [{className}] {text}"; switch (logLevel) { diff --git a/TheOtherRoles/Main.cs b/TheOtherRoles/Main.cs index 33f6427..88c1319 100644 --- a/TheOtherRoles/Main.cs +++ b/TheOtherRoles/Main.cs @@ -18,7 +18,7 @@ namespace TheOtherRoles; [ReactorModFlags(ModFlags.RequireOnAllClients)] public class TheOtherRolesPlugin : BasePlugin { - public const string Id = "TheOtherUs.Options.v2"; // Config files name + public const string Id = "TheOtherUs.Options.v3"; // Config files name public const string ModName = MyPluginInfo.PLUGIN_NAME; public const string VersionString = MyPluginInfo.PLUGIN_VERSION; diff --git a/TheOtherRoles/Modules/ChatCommands.cs b/TheOtherRoles/Modules/ChatCommands.cs index 2d447d8..7c9e93c 100644 --- a/TheOtherRoles/Modules/ChatCommands.cs +++ b/TheOtherRoles/Modules/ChatCommands.cs @@ -261,7 +261,7 @@ public static void Postfix(ChatBubble __instance, [HarmonyArgument(0)] string pl .FirstOrDefault(x => x.Data != null && x.Data.PlayerName.Equals(playerName, StringComparison.Ordinal)); if (PlayerControl.LocalPlayer != null && PlayerControl.LocalPlayer.Data.Role.IsImpostor && __instance != null - && (Spy.spy != null && sourcePlayer.PlayerId == Spy.spy.PlayerId)) + && Spy.spy != null && sourcePlayer.PlayerId == Spy.spy.PlayerId) { __instance.NameText.color = Palette.ImpostorRed; } diff --git a/TheOtherRoles/Modules/KeyboardHandler.cs b/TheOtherRoles/Modules/KeyboardHandler.cs index 112b725..e20e679 100644 --- a/TheOtherRoles/Modules/KeyboardHandler.cs +++ b/TheOtherRoles/Modules/KeyboardHandler.cs @@ -55,13 +55,6 @@ private static void Postfix(KeyboardJoystick __instance) GameStartManager.Instance.countDownTimer = 0; } } - if (PlayerControl.LocalPlayer.IsAlive() && !PlayerControl.LocalPlayer.isImpostor()) - { - if (KeyboardJoystick.player.GetButtonDown(50)) - { - DestroyableSingleton.Instance.ImpostorVentButton.DoClick(); - } - } } public static string RandomString(int length) { diff --git a/TheOtherRoles/Modules/PlayerData.cs b/TheOtherRoles/Modules/PlayerData.cs index 204503c..659129a 100644 --- a/TheOtherRoles/Modules/PlayerData.cs +++ b/TheOtherRoles/Modules/PlayerData.cs @@ -8,8 +8,8 @@ public class PlayerData { private Dictionary _data; private Dictionary _playerdata; - private T defaultvalue = default; - private bool nonsetinit = false; + private T defaultvalue; + private bool nonsetinit; public T Local { get => this[PlayerControl.LocalPlayer.PlayerId]; diff --git a/TheOtherRoles/Modules/RoleDraft.cs b/TheOtherRoles/Modules/RoleDraft.cs new file mode 100644 index 0000000..4a2b54c --- /dev/null +++ b/TheOtherRoles/Modules/RoleDraft.cs @@ -0,0 +1,466 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using BepInEx.Unity.IL2CPP.Utils.Collections; +using Hazel; +using Reactor.Utilities.Extensions; +using UnityEngine; +using UnityEngine.UI; +using static TheOtherRoles.Patches.RoleManagerSelectRolesPatch; + +namespace TheOtherRoles.Modules; + +[HarmonyPatch] +internal class RoleDraft +{ + public static bool isEnabled => CustomOptionHolder.isDraftMode.GetBool() && (ModOption.gameMode is CustomGamemodes.Classic or CustomGamemodes.Guesser); + public static bool isRunning; + + public static List pickOrder = new(); + private static bool picked; + private static float timer; + private static List buttons = new(); + 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 = GetString("RoleDraft.FeedText"); + 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; + } + + var roleData = getRoleAssignmentData(); + roleData.crewSettings.Add((byte)RoleId.Sheriff, CustomOptionHolder.sheriffSpawnRate.GetSelection()); + if (CustomOptionHolder.sheriffSpawnRate.GetSelection() > 0) + roleData.crewSettings.Add((byte)RoleId.Deputy, CustomOptionHolder.deputySpawnRate.GetSelection()); + + while (pickOrder.Count > 0) + { + picked = false; + timer = 0; + float maxTimer = CustomOptionHolder.draftModeTimeToChoose.GetFloat(); + string playerText = ""; + while (timer < maxTimer || !picked) + { + if (pickOrder.Count == 0) + break; + + if (AmongUsClient.Instance.AmHost) + { + byte targetId = pickOrder[0]; + var target = playerById(targetId); + if (target.Data.Disconnected) + { + var writer = StartRPC(PlayerControl.LocalPlayer, CustomRPC.DraftModePick); + writer.Write(targetId); + writer.Write((byte)RoleId.Crewmate); + writer.EndRPC(); + receivePick(targetId, (byte)RoleId.Crewmate); + + picked = true; + 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 = cs(youColor, "RoleDraft.You".Translate()); + // Available Roles: + List availableRoles = new(); + foreach (RoleInfo roleInfo in RoleInfo.allRoleInfos) + { + if (roleInfo.roleType is RoleType.Modifier or RoleType.Ghost or RoleType.Special) continue; + int impostorCount = PlayerControl.AllPlayerControls.ToList().Count(x => x.Data.Role.IsImpostor); + // Remove Impostor Roles + if (PlayerControl.LocalPlayer.Data.Role.IsImpostor && !roleInfo.IsImpostor) continue; + if (!PlayerControl.LocalPlayer.Data.Role.IsImpostor && roleInfo.IsImpostor) continue; + + // 跳过概率为0的职业 + if (roleData.neutralSettings.ContainsKey((byte)roleInfo.roleId) && roleData.neutralSettings[(byte)roleInfo.roleId] == 0) continue; + else if (roleData.killerNeutralSettings.ContainsKey((byte)roleInfo.roleId) && roleData.killerNeutralSettings[(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; + // 排除不应该直接分配的职业 + if (roleInfo.roleId is RoleId.Sidekick or RoleId.Pavlovsdogs or RoleId.Pursuer) continue; + if (roleInfo.roleId == RoleId.Deputy && Sheriff.Player == null) continue; + if (roleInfo.roleId == RoleId.Spy && impostorCount < 2) continue; + if (ModOption.gameMode == CustomGamemodes.Guesser && (roleInfo.roleId == RoleId.Vigilante || roleInfo.roleId == RoleId.Assassin)) continue; + if (alreadyPicked.Contains((byte)roleInfo.roleId) && roleInfo.roleId != RoleId.Crewmate) continue; + + int impsPicked = alreadyPicked.Count(x => RoleInfo.RoleInfoById[(RoleId)x].IsImpostor); + + // Handle forcing of 100% roles for impostors + if (PlayerControl.LocalPlayer.Data.Role.IsImpostor) + { + int impsMax = roleData.maxImpostorRoles; + int impsLeft = pickOrder.Count(x => playerById(x).Data.Role.IsImpostor); + int imps100 = roleData.impSettings.Count(x => x.Value == 10); + if (imps100 > impsMax) imps100 = impsMax; + int imps100Picked = alreadyPicked.Count(x => roleData.impSettings.GetValueSafe(x) == 10); + if (imps100 - imps100Picked >= impsLeft && !roleData.impSettings.Any(x => x.Value == 10 && x.Key == (byte)roleInfo.roleId)) continue; + if (impsMax - 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.Count(roleData.neutralSettings.ContainsKey); + int killerNeutralsPicked = alreadyPicked.Count(roleData.killerNeutralSettings.ContainsKey); + int crewPicked = alreadyPicked.Count - impsPicked - neutralsPicked - killerNeutralsPicked; + int neutralsMax = roleData.maxNeutralRoles; + int killerNeutralsMax = roleData.maxKillerNeutralRoles; + + int neutrals100 = roleData.neutralSettings.Count(x => x.Value == 10); + int killerNeutrals100 = roleData.killerNeutralSettings.Count(x => x.Value == 10); + + if (neutrals100 > neutralsMax) neutrals100 = neutralsMax; + if (killerNeutrals100 > killerNeutralsMax) neutrals100 = killerNeutralsMax; + + bool isNeutral = roleData.neutralSettings.ContainsKey((byte)roleInfo.roleId); + bool isKillerNeutral = roleData.killerNeutralSettings.ContainsKey((byte)roleInfo.roleId); + bool isCrewmate = roleData.crewSettings.ContainsKey((byte)roleInfo.roleId); + bool isImpostor = roleData.impSettings.ContainsKey((byte)roleInfo.roleId); + + // 防止分配多余的中立 + if (neutralsPicked >= neutralsMax && isNeutral) continue; + if (killerNeutralsPicked >= killerNeutralsMax && isKillerNeutral) continue; + + int neutrals100Picked = alreadyPicked.Count(x => roleData.neutralSettings.GetValueSafe(x) == 10); + if (neutrals100 - neutrals100Picked >= (pickOrder.Count - impsPicked) && isNeutral && !roleData.neutralSettings.Any(x => x.Value == 10 && x.Key == (byte)roleInfo.roleId)) + continue; + + int killerNeutrals100Picked = alreadyPicked.Count(x => roleData.killerNeutralSettings.GetValueSafe(x) == 10); + if (killerNeutrals100 - killerNeutrals100Picked >= (pickOrder.Count - impsPicked) && isKillerNeutral && !roleData.killerNeutralSettings.Any(x => x.Value == 10 && x.Key == (byte)roleInfo.roleId)) + continue; + + int crew100 = roleData.crewSettings.Count(x => x.Value == 10); + int crew100Picked = alreadyPicked.Count(x => roleData.crewSettings.GetValueSafe(x) == 10); + if (crew100 - crew100Picked >= (pickOrder.Count - impsPicked - neutralsPicked - killerNeutralsPicked) && !isNeutral && !isKillerNeutral && !roleData.crewSettings.Any(x => x.Value == 10 && x.Key == (byte)roleInfo.roleId)) + continue; + } + + // Handle role pairings that are blocked, e.g. Vampire Warlock, Cleaner Vulture etc. + bool blocked = false; + foreach (var blockedRoleId in 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); + Warn("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 = string.Format(GetString("RoleDraft.PlayerText"), currentPick); + HudManager.Instance.FullScreen.color = Color.black; + } + + __instance.TeamTitle.text = string.Format(GetString("RoleDraft.TeamTitle"), playerText); + int waitMore = pickOrder.IndexOf(PlayerControl.LocalPlayer.PlayerId); + + __instance.TeamTitle.text += string.Format(GetString("RoleDraft.TeamTitle2"), waitMore, (int)(maxTimer + 1 - timer)); + yield return null; + } + } + HudManager.Instance.FullScreen.color = Color.black; + __instance.FrontMost.gameObject.SetActive(true); + GameObject.Find("BackgroundLayer")?.SetActive(true); + if (AmongUsClient.Instance.AmHost) + { + assignRoleTargets(null); // Assign targets for Lawyer & Prosecutor + if (isGuesserGamemode) assignGuesserGamemode(); + 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 = cs(roleInfo.color, roleInfo.Name); + if (!CustomOptionHolder.draftModeShowRoles.GetBool() && !(playerId == PlayerControl.LocalPlayer.PlayerId)) + { + roleString = "RoleDraft.UnknownRole".Translate(); + } + else if (CustomOptionHolder.draftModeHideImpRoles.GetBool() && roleInfo.IsImpostor && !(playerId == PlayerControl.LocalPlayer.PlayerId)) + { + roleString = cs(Palette.ImpostorRed, "RoleDraft.ImpostorRole".Translate()); + } + else if (CustomOptionHolder.draftModeHideNeutralRoles.GetBool() && roleInfo.IsNeutral && !(playerId == PlayerControl.LocalPlayer.PlayerId)) + { + roleString = cs(Palette.Blue, "RoleDraft.NeutralRole".Translate()); + } + else if (CustomOptionHolder.draftModeHideCrewmateRoles.GetBool() && roleInfo.IsCrewmate && !(playerId == PlayerControl.LocalPlayer.PlayerId)) + { + roleString = cs(Palette.Blue, "RoleDraft.CrewmateRole".Translate()); + } + 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) { Error(e); } + } + + public static void sendPick(byte RoleId) + { + SoundEffectsManager.stop("timeMasterShield"); + var writer = StartRPC(PlayerControl.LocalPlayer, CustomRPC.DraftModePick); + writer.Write(PlayerControl.LocalPlayer.PlayerId); + writer.Write(RoleId); + writer.EndRPC(); + receivePick(PlayerControl.LocalPlayer.PlayerId, RoleId); + try + { + // destroy all the buttons: + foreach (var button in buttons) + { + button?.gameObject?.Destroy(); + } + buttons.Clear(); + } + catch (Exception e) { Message(e); } + } + + + public static void sendPickOrder() + { + pickOrder = PlayerControl.AllPlayerControls.ToArray().Select(x => x.PlayerId).OrderBy(_ => Guid.NewGuid()).ToList().ToList(); + var writer = StartRPC(PlayerControl.LocalPlayer, CustomRPC.DraftModePickOrder); + writer.Write((byte)pickOrder.Count); + foreach (var item in pickOrder) + { + writer.Write(item); + } + writer.EndRPC(); + } + + + public static void receivePickOrder(int amount, MessageReader reader) + { + pickOrder.Clear(); + for (int i = 0; i < amount; i++) + { + pickOrder.Add(reader.ReadByte()); + } + } + + private 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))] + private 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 12b67e3..26d9b59 100644 --- a/TheOtherRoles/Objects/Bomb.cs +++ b/TheOtherRoles/Objects/Bomb.cs @@ -109,7 +109,14 @@ public static void explode(Bomb b) GameHistory.OverrideDeathReasonAndKiller(PlayerControl.LocalPlayer, CustomDeathReason.Bomb, Terrorist.terrorist); } - SoundEffectsManager.playAtPosition("bombExplosion", position, range: Terrorist.hearRange); + try + { + SoundEffectsManager.playAtPosition("bombExplosion", position, maxDuration: 1.6f, range: Terrorist.hearRange); + } + catch (Exception e) + { + Warn($"Exception in Sound Effect for Bomb explosion: {e}"); + } } Terrorist.clearBomb(); diff --git a/TheOtherRoles/Objects/NinjaTrace.cs b/TheOtherRoles/Objects/NinjaTrace.cs index e6b222b..7a82dba 100644 --- a/TheOtherRoles/Objects/NinjaTrace.cs +++ b/TheOtherRoles/Objects/NinjaTrace.cs @@ -10,8 +10,6 @@ internal class NinjaTrace { public static List traces = new(); - private static Sprite TraceSprite; - private readonly GameObject trace; private float timeRemaining; @@ -25,7 +23,7 @@ public NinjaTrace(Vector2 p, float duration = 1f) trace.transform.localPosition = position; var traceRenderer = trace.AddComponent(); - traceRenderer.sprite = getTraceSprite(); + traceRenderer.sprite = new ResourceSprite("NinjaTraceW.png", 225f); timeRemaining = duration; @@ -64,12 +62,6 @@ public NinjaTrace(Vector2 p, float duration = 1f) trace.SetActive(true); traces.Add(this); } - public static Sprite getTraceSprite() - { - if (TraceSprite) return TraceSprite; - TraceSprite = UnityHelper.loadSpriteFromResources("TheOtherRoles.Resources.NinjaTraceW.png", 225f); - return TraceSprite; - } public static void clearTraces() { diff --git a/TheOtherRoles/Objects/Trap.cs b/TheOtherRoles/Objects/Trap.cs index 150b68a..2af403e 100644 --- a/TheOtherRoles/Objects/Trap.cs +++ b/TheOtherRoles/Objects/Trap.cs @@ -124,7 +124,7 @@ public static void triggerTrap(byte playerId, byte trapId) message = trap.trappedPlayer.Aggregate(message, (current, p) => current + Trapper.infoType switch { 0 => RoleInfo.GetRolesString(p, false, false, false) + "\n", - 1 when (isEvilNeutral(p) || p.isImpostor()) ^ Vortox.Reversal => "邪恶职业 \n", + 1 when (isEvilNeutral(p) || p.IsImpostor()) ^ Vortox.Reversal => "邪恶职业 \n", 1 => "善良职业 \n", _ => p.Data.PlayerName + "\n" }); diff --git a/TheOtherRoles/Options/CreateModOptions.cs b/TheOtherRoles/Options/CreateModOptions.cs index 32b4b9f..317ffb7 100644 --- a/TheOtherRoles/Options/CreateModOptions.cs +++ b/TheOtherRoles/Options/CreateModOptions.cs @@ -94,6 +94,7 @@ public static void Postfix(OptionsMenuBehaviour __instance) { enableSoundEffects.UpdateToggleText(!enableSoundEffects.onState, GetString("EnableSoundEffectsText")); ModOption.enableSoundEffects = Main.EnableSoundEffects.Value = enableSoundEffects.onState; + if (!ModOption.enableSoundEffects) SoundEffectsManager.stopAll(); }, nebulaTab, toggleButtonTemplate); //ToggleCursor diff --git a/TheOtherRoles/Options/CustomOptionHolder.cs b/TheOtherRoles/Options/CustomOptionHolder.cs index 45ca6e7..79c0d30 100644 --- a/TheOtherRoles/Options/CustomOptionHolder.cs +++ b/TheOtherRoles/Options/CustomOptionHolder.cs @@ -19,6 +19,13 @@ public class CustomOptionHolder public static string[] mapOptions = ["ExpandOptions", "CollapseOptions"]; public static CustomOption presetSelection; public static CustomOption anyPlayerCanStopStart; + 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 draftModeHideCrewmateRoles; public static CustomOption neutralRolesCountMin; public static CustomOption neutralRolesCountMax; public static CustomOption killerNeutralRolesCountMin; @@ -693,11 +700,20 @@ public static void Load() { vanillaSettings = Main.Instance.Config.Bind("Preset0", "VanillaOptions", ""); - // Role Options + //-------------------------- Role options 0 - 99 -------------------------- // presetSelection = Create(0, Types.General, cs(new Color32(204, 204, 0, 255), "presetSelection"), presets, null, true); anyPlayerCanStopStart = Create(3, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0), "anyPlayerCanStopStart"), false); + isDraftMode = Create(4, Types.General, cs(Color.yellow, "isDraftMode"), false, null, true); + draftModeAmountOfChoices = Create(5, Types.General, cs(Color.yellow, "draftModeAmountOfChoices"), 3f, 2f, 6f, 1f, isDraftMode, false); + draftModeTimeToChoose = Create(602, Types.General, cs(Color.yellow, "draftModeTimeToChoose"), 5f, 3f, 20f, 1f, isDraftMode, false); + draftModeShowRoles = Create(603, Types.General, cs(Color.yellow, "draftModeShowRoles"), false, isDraftMode, false); + draftModeHideImpRoles = Create(604, Types.General, cs(Color.yellow, "draftModeHideImpRoles"), false, draftModeShowRoles, false); + draftModeHideNeutralRoles = Create(605, Types.General, cs(Color.yellow, "draftModeHideNeutralRoles"), false, draftModeShowRoles, false); + draftModeHideCrewmateRoles = Create(606, Types.General, cs(Color.yellow, "draftModeHideCrewmateRoles"), false, draftModeShowRoles, false); + + neutralRolesCountMin = Create(8, Types.General, cs(new Color32(204, 204, 0, 255), "neutralRolesCountMin"), 2f, 0f, 15f, 1f, null, true); neutralRolesCountMax = Create(9, Types.General, cs(new Color32(204, 204, 0, 255), "neutralRolesCountMax"), 2f, 0f, 15f, 1f); killerNeutralRolesCountMin = Create(10, Types.General, cs(new Color32(204, 204, 0, 255), "killerNeutralRolesCountMin"), ratesRandom); @@ -705,101 +721,101 @@ public static void Load() modifiersCountMin = Create(12, Types.General, cs(new Color32(204, 204, 0, 255), "modifiersCountMin"), 15f, 0f, 30f, 1f); modifiersCountMax = Create(13, Types.General, cs(new Color32(204, 204, 0, 255), "modifiersCountMax"), 15f, 0f, 30f, 1f); - //-------------------------- Other options 1 - 599 -------------------------- // + //-------------------------- Other options 100 - 999 -------------------------- // //Global options - resteButtonCooldown = Create(20, Types.General, "resteButtonCooldown", 20f, 2.5f, 30f, 2.5f, null, true); - shieldFirstKill = Create(21, Types.General, "shieldFirstKill", false); - hidePlayerNames = Create(22, Types.General, "hidePlayerNames", false); - hideOutOfSightNametags = Create(23, Types.General, "hideOutOfSightNametags", true); - hideVentAnimOnShadows = Create(24, Types.General, "hideVentAnimOnShadows", false); - showButtonTarget = Create(25, Types.General, "showButtonTarget", true); - impostorSeeRoles = Create(26, Types.General, cs(Palette.ImpostorRed, "impostorSeeRoles"), false); - blockGameEnd = Create(27, Types.General, cs(Color.yellow, "blockGameEnd"), true); - randomLigherPlayer = Create(28, Types.General, "randomLigherPlayer", true); - allowModGuess = Create(29, Types.General, "allowModGuess", false); - randomGameStartPosition = Create(30, Types.General, "randomGameStartPosition", false); - randomGameStartToVents = Create(31, Types.General, "randomGameStartToVents", true, randomGameStartPosition); - ghostSpeed = Create(32, Types.General, "ghostSpeed", 1f, 0.75f, 5f, 0.125f); + resteButtonCooldown = Create(100, Types.General, "resteButtonCooldown", 20f, 2.5f, 30f, 2.5f, null, true); + shieldFirstKill = Create(101, Types.General, "shieldFirstKill", false); + hidePlayerNames = Create(102, Types.General, "hidePlayerNames", false); + hideOutOfSightNametags = Create(103, Types.General, "hideOutOfSightNametags", true); + hideVentAnimOnShadows = Create(104, Types.General, "hideVentAnimOnShadows", false); + showButtonTarget = Create(105, Types.General, "showButtonTarget", true); + impostorSeeRoles = Create(106, Types.General, cs(Palette.ImpostorRed, "impostorSeeRoles"), false); + blockGameEnd = Create(107, Types.General, cs(Color.yellow, "blockGameEnd"), true); + randomLigherPlayer = Create(108, Types.General, "randomLigherPlayer", true); + allowModGuess = Create(109, Types.General, "allowModGuess", false); + randomGameStartPosition = Create(110, Types.General, "randomGameStartPosition", false); + randomGameStartToVents = Create(111, Types.General, "randomGameStartToVents", true, randomGameStartPosition); + ghostSpeed = Create(112, Types.General, "ghostSpeed", 1f, 0.75f, 5f, 0.125f); //Meeting options - MeetingOptions = Create(100, Types.General, cs(new Color32(255, 85, 234, byte.MaxValue), "MeetingOptions"), false, null, true); - disableMeeting = Create(101, Types.General, "disableMeeting", false, MeetingOptions); - maxNumberOfMeetings = Create(102, Types.General, "maxNumberOfMeetings", 10, 0, 15, 1, MeetingOptions); - blockSkippingInEmergencyMeetings = Create(103, Types.General, "blockSkippingInEmergencyMeetings", false, MeetingOptions); - noVoteIsSelfVote = Create(104, Types.General, "noVoteIsSelfVote", false, blockSkippingInEmergencyMeetings); - guessReVote = Create(105, Types.General, "guessReVote", false, MeetingOptions); - guessExtendmeetingTime = Create(106, Types.General, "guessExtendmeetingTime", 15f, 0f, 60f, 5f, guessReVote); - exiledController = Create(107, Types.General, "exileController", false, MeetingOptions); - exiledReviveRole = Create(108, Types.General, "exiledReviveRole", ["optionOff", "Role", "Team"], exiledController); - exiledShowTeamNum = Create(109, Types.General, "exiledShowTeamNum", false, exiledController); + MeetingOptions = Create(200, Types.General, cs(new Color32(255, 85, 234, byte.MaxValue), "MeetingOptions"), false, null, true); + disableMeeting = Create(201, Types.General, "disableMeeting", false, MeetingOptions); + maxNumberOfMeetings = Create(202, Types.General, "maxNumberOfMeetings", 10, 0, 15, 1, MeetingOptions); + blockSkippingInEmergencyMeetings = Create(203, Types.General, "blockSkippingInEmergencyMeetings", false, MeetingOptions); + noVoteIsSelfVote = Create(204, Types.General, "noVoteIsSelfVote", false, blockSkippingInEmergencyMeetings); + guessReVote = Create(205, Types.General, "guessReVote", false, MeetingOptions); + guessExtendmeetingTime = Create(206, Types.General, "guessExtendmeetingTime", 15f, 0f, 60f, 5f, guessReVote); + exiledController = Create(207, Types.General, "exileController", false, MeetingOptions); + exiledReviveRole = Create(208, Types.General, "exiledReviveRole", ["optionOff", "Role", "Team"], exiledController); + exiledShowTeamNum = Create(209, Types.General, "exiledShowTeamNum", false, exiledController); //Task options - TaskOptions = Create(200, Types.General, cs(Palette.CrewmateBlue, "TaskOptions"), false, null, true); - WireTaskIsRandomOption = Create(201, Types.General, "WireTaskIsRandomOption", false, TaskOptions); - WireTaskNumOption = Create(202, Types.General, "WireTaskNumOption", 3f, 1f, 8f, 1f, WireTaskIsRandomOption); - transparentTasks = Create(203, Types.General, "transparentTasks", false, TaskOptions); - disableMedbayWalk = Create(204, Types.General, "disableMedbayWalk", false, TaskOptions); - allowParallelMedBayScans = Create(205, Types.General, "allowParallelMedBayScans", false, TaskOptions); - finishTasksBeforeHauntingOrZoomingOut = Create(206, Types.General, "finishTasksBeforeHauntingOrZoomingOut", false, TaskOptions); - disableTaskGameEnd = Create(207, Types.General, "disableTaskGameEnd", false, TaskOptions); + TaskOptions = Create(300, Types.General, cs(Palette.CrewmateBlue, "TaskOptions"), false, null, true); + WireTaskIsRandomOption = Create(301, Types.General, "WireTaskIsRandomOption", false, TaskOptions); + WireTaskNumOption = Create(302, Types.General, "WireTaskNumOption", 3f, 1f, 8f, 1f, WireTaskIsRandomOption); + transparentTasks = Create(303, Types.General, "transparentTasks", false, TaskOptions); + disableMedbayWalk = Create(304, Types.General, "disableMedbayWalk", false, TaskOptions); + allowParallelMedBayScans = Create(305, Types.General, "allowParallelMedBayScans", false, TaskOptions); + finishTasksBeforeHauntingOrZoomingOut = Create(306, Types.General, "finishTasksBeforeHauntingOrZoomingOut", false, TaskOptions); + disableTaskGameEnd = Create(307, Types.General, "disableTaskGameEnd", false, TaskOptions); //Sabotage options - SaboOptions = Create(300, Types.General, cs(Palette.ImpostorRed, "SaboOptions"), false, null, true); - disableSabotage = Create(301, Types.General, cs(Palette.ImpostorRed, "disableSabotage"), false, SaboOptions); - deadImpsBlockSabotage = Create(302, Types.General, cs(Palette.ImpostorRed, "deadImpsBlockSabotage"), false, SaboOptions); - enableCamoComms = Create(303, Types.General, cs(Palette.ImpostorRed, "enableCamoComms"), false, SaboOptions); - IsReactorDurationSetting = Create(310, Types.General, "IsReactorDurationSetting", false, SaboOptions); - SkeldReactorTimeLimit = Create(311, Types.General, "SkeldReactorTimeLimit", 30f, 0f, 30f, 2.5f, IsReactorDurationSetting); - SkeldLifeSuppTimeLimit = Create(312, Types.General, "SkeldLifeSuppTimeLimit", 30f, 0f, 30f, 2.5f, IsReactorDurationSetting); - MiraLifeSuppTimeLimit = Create(313, Types.General, "MiraLifeSuppTimeLimit", 30f, 0f, 45f, 2.5f, IsReactorDurationSetting); - MiraReactorTimeLimit = Create(314, Types.General, "MiraReactorTimeLimit", 30f, 0f, 45f, 2.5f, IsReactorDurationSetting); - PolusReactorTimeLimit = Create(315, Types.General, "PolusReactorTimeLimit", 60f, 0f, 60f, 2.5f, IsReactorDurationSetting); - AirshipReactorTimeLimit = Create(316, Types.General, "AirshipReactorTimeLimit", 75f, 0f, 90f, 2.5f, IsReactorDurationSetting); - FungleReactorTimeLimit = Create(317, Types.General, "FungleReactorTimeLimit", 45f, 0f, 60f, 2.5f, IsReactorDurationSetting); + SaboOptions = Create(400, Types.General, cs(Palette.ImpostorRed, "SaboOptions"), false, null, true); + disableSabotage = Create(401, Types.General, cs(Palette.ImpostorRed, "disableSabotage"), false, SaboOptions); + deadImpsBlockSabotage = Create(402, Types.General, cs(Palette.ImpostorRed, "deadImpsBlockSabotage"), false, SaboOptions); + enableCamoComms = Create(403, Types.General, cs(Palette.ImpostorRed, "enableCamoComms"), false, SaboOptions); + IsReactorDurationSetting = Create(410, Types.General, "IsReactorDurationSetting", false, SaboOptions); + SkeldReactorTimeLimit = Create(411, Types.General, "SkeldReactorTimeLimit", 30f, 0f, 30f, 2.5f, IsReactorDurationSetting); + SkeldLifeSuppTimeLimit = Create(412, Types.General, "SkeldLifeSuppTimeLimit", 30f, 0f, 30f, 2.5f, IsReactorDurationSetting); + MiraLifeSuppTimeLimit = Create(413, Types.General, "MiraLifeSuppTimeLimit", 30f, 0f, 45f, 2.5f, IsReactorDurationSetting); + MiraReactorTimeLimit = Create(414, Types.General, "MiraReactorTimeLimit", 30f, 0f, 45f, 2.5f, IsReactorDurationSetting); + PolusReactorTimeLimit = Create(415, Types.General, "PolusReactorTimeLimit", 60f, 0f, 60f, 2.5f, IsReactorDurationSetting); + AirshipReactorTimeLimit = Create(416, Types.General, "AirshipReactorTimeLimit", 75f, 0f, 90f, 2.5f, IsReactorDurationSetting); + FungleReactorTimeLimit = Create(417, Types.General, "FungleReactorTimeLimit", 45f, 0f, 60f, 2.5f, IsReactorDurationSetting); //Map options - MapOptions = Create(400, Types.General, cs(new Color32(223, 157, 192, byte.MaxValue), "MapOptions"), false, null, true); + MapOptions = Create(500, Types.General, cs(new Color32(223, 157, 192, byte.MaxValue), "MapOptions"), false, null, true); //Mira - enableMiraModify = Create(420, Types.General, cs(Color.yellow, "Mira"), false, MapOptions); - miraVitals = Create(421, Types.General, "miraVitals", false, enableMiraModify); + enableMiraModify = Create(520, Types.General, cs(Color.yellow, "Mira"), false, MapOptions); + miraVitals = Create(521, Types.General, "miraVitals", false, enableMiraModify); //Polus - enableBetterPolus = Create(430, Types.General, cs(Color.yellow, "Polus"), false, MapOptions); - movePolusVents = Create(431, Types.General, "movePolusVents", false, enableBetterPolus); - addPolusVents = Create(432, Types.General, "addPolusVents", false, enableBetterPolus); - movePolusVitals = Create(433, Types.General, "movePolusVitals", false, enableBetterPolus); - swapNavWifi = Create(434, Types.General, "swapNavWifi", false, enableBetterPolus); - moveColdTemp = Create(435, Types.General, "moveColdTemp", false, enableBetterPolus); + enableBetterPolus = Create(530, Types.General, cs(Color.yellow, "Polus"), false, MapOptions); + movePolusVents = Create(531, Types.General, "movePolusVents", false, enableBetterPolus); + addPolusVents = Create(532, Types.General, "addPolusVents", false, enableBetterPolus); + movePolusVitals = Create(533, Types.General, "movePolusVitals", false, enableBetterPolus); + swapNavWifi = Create(534, Types.General, "swapNavWifi", false, enableBetterPolus); + moveColdTemp = Create(535, Types.General, "moveColdTemp", false, enableBetterPolus); //AirShip - enableAirShipModify = Create(440, Types.General, cs(Color.yellow, "AirShip"), false, MapOptions); - airshipOptimize = Create(441, Types.General, "airshipOptimize", false, enableAirShipModify); - addAirShipVents = Create(442, Types.General, "addAirShipVents", false, enableAirShipModify); - airshipLadder = Create(443, Types.General, "airshipLadder", false, enableAirShipModify); + enableAirShipModify = Create(540, Types.General, cs(Color.yellow, "AirShip"), false, MapOptions); + airshipOptimize = Create(541, Types.General, "airshipOptimize", false, enableAirShipModify); + addAirShipVents = Create(542, Types.General, "addAirShipVents", false, enableAirShipModify); + airshipLadder = Create(543, Types.General, "airshipLadder", false, enableAirShipModify); //Fungle - enableFungleModify = Create(450, Types.General, cs(Color.yellow, "Fungle"), false, MapOptions); - fungleElectrical = Create(451, Types.General, "fungleElectrical", false, enableFungleModify); + enableFungleModify = Create(550, Types.General, cs(Color.yellow, "Fungle"), false, MapOptions); + fungleElectrical = Create(551, Types.General, "fungleElectrical", false, enableFungleModify); //dynamicMap options - dynamicMap = Create(470, Types.General, "dynamicMap", false, MapOptions, true); - dynamicMapEnableSkeld = Create(471, Types.General, "Skeld", rates, dynamicMap); - dynamicMapEnableMira = Create(472, Types.General, "Mira", rates, dynamicMap); - dynamicMapEnablePolus = Create(473, Types.General, "Polus", rates, dynamicMap); - dynamicMapEnableAirShip = Create(474, Types.General, "Airship", rates, dynamicMap); - dynamicMapEnableFungle = Create(475, Types.General, "Fungle", rates, dynamicMap); - dynamicMapEnableSubmerged = Create(476, Types.General, "Submerged", rates, dynamicMap); - dynamicMapSeparateSettings = Create(477, Types.General, "dynamicMapSeparateSettings", false, dynamicMap); + dynamicMap = Create(570, Types.General, "dynamicMap", false, MapOptions, true); + dynamicMapEnableSkeld = Create(571, Types.General, "Skeld", rates, dynamicMap); + dynamicMapEnableMira = Create(572, Types.General, "Mira", rates, dynamicMap); + dynamicMapEnablePolus = Create(573, Types.General, "Polus", rates, dynamicMap); + dynamicMapEnableAirShip = Create(574, Types.General, "Airship", rates, dynamicMap); + dynamicMapEnableFungle = Create(575, Types.General, "Fungle", rates, dynamicMap); + dynamicMapEnableSubmerged = Create(576, Types.General, "Submerged", rates, dynamicMap); + dynamicMapSeparateSettings = Create(577, Types.General, "dynamicMapSeparateSettings", false, dynamicMap); //Devices Option - DevicesOption = Create(500, Types.General, cs(new Color32(255, 50, 0, byte.MaxValue), "DevicesOption"), false, null, true); - restrictDevices = Create(501, Types.General, "restrictDevices", ["optionOff", "restrictDevices2", "restrictDevices3"], DevicesOption); - //restrictAdmin = Create(502, Types.General, "restrictAdmin", 30f, 0f, 600f, 5f, restrictDevices); - restrictCameras = Create(503, Types.General, "restrictCameras", 30f, 0f, 600f, 5f, restrictDevices); - restrictVents = Create(504, Types.General, "restrictVents", 30f, 0f, 600f, 5f, restrictDevices); - disableCamsRound1 = Create(505, Types.General, "disableCamsRound1", false, DevicesOption); - camsNightVision = Create(506, Types.General, "camsNightVision", false, DevicesOption); - camsNoNightVisionIfImpVision = Create(507, Types.General, "camsNoNightVisionIfImpVision", false, camsNightVision); - - debugMode = Create(900, Types.General, "debugMode", false, null, true); - disableGameEnd = Create(901, Types.General, "DisableGameEnd", false, debugMode); + DevicesOption = Create(600, Types.General, cs(new Color32(255, 50, 0, byte.MaxValue), "DevicesOption"), false, null, true); + restrictDevices = Create(601, Types.General, "restrictDevices", ["optionOff", "restrictDevices2", "restrictDevices3"], DevicesOption); + //restrictAdmin = Create(602, Types.General, "restrictAdmin", 30f, 0f, 600f, 5f, restrictDevices); + restrictCameras = Create(603, Types.General, "restrictCameras", 30f, 0f, 600f, 5f, restrictDevices); + restrictVents = Create(604, Types.General, "restrictVents", 30f, 0f, 600f, 5f, restrictDevices); + disableCamsRound1 = Create(605, Types.General, "disableCamsRound1", false, DevicesOption); + camsNightVision = Create(606, Types.General, "camsNightVision", false, DevicesOption); + camsNoNightVisionIfImpVision = Create(607, Types.General, "camsNoNightVisionIfImpVision", false, camsNightVision); + + debugMode = Create(950, Types.General, "debugMode", false, null, true); + disableGameEnd = Create(951, Types.General, "DisableGameEnd", false, debugMode); //-------------------------- Impostor Options 10000-19999 -------------------------- // diff --git a/TheOtherRoles/Patches/EndGamePatch.cs b/TheOtherRoles/Patches/EndGamePatch.cs index feae146..408cf76 100644 --- a/TheOtherRoles/Patches/EndGamePatch.cs +++ b/TheOtherRoles/Patches/EndGamePatch.cs @@ -277,6 +277,35 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ref En AdditionalTempData.winCondition = WinCondition.ExecutionerWin; } + // Akujo win + else if (akujoWin) + { + if (Akujo.honmeiOptimizeWin && !Akujo.existingWithKiller()) + { + AdditionalTempData.winCondition = WinCondition.AkujoTeamWin; + TempData.winners = new Il2CppSystem.Collections.Generic.List(); + foreach (PlayerControl p in PlayerControl.AllPlayerControls) + { + if (p == null) continue; + if (p == Akujo.akujo || p == Akujo.honmei) + TempData.winners.Add(new WinningPlayerData(p.Data)); + else if (Pursuer.Player.MContains(p) && !p.Data.IsDead) + TempData.winners.Add(new WinningPlayerData(p.Data)); + else if (Survivor.Player.MContains(p) && !p.Data.IsDead) + TempData.winners.Add(new WinningPlayerData(p.Data)); + else if (!notWinners.MContains(p) && !p.Data.Role.IsImpostor) + TempData.winners.Add(new WinningPlayerData(p.Data)); + } + } + else + { + AdditionalTempData.winCondition = WinCondition.AkujoSoloWin; + TempData.winners = new Il2CppSystem.Collections.Generic.List(); + TempData.winners.Add(new WinningPlayerData(Akujo.akujo.Data)); + TempData.winners.Add(new WinningPlayerData(Akujo.honmei.Data)); + } + } + // Lovers win conditions else if (loversWin) { @@ -290,11 +319,11 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ref En if (p == null) continue; if (p == Lovers.lover1 || p == Lovers.lover2) TempData.winners.Add(new WinningPlayerData(p.Data)); - else if (Pursuer.Player.Any(pc => pc == p) && Pursuer.Player.Any(pc => pc.IsAlive())) + else if (Pursuer.Player.MContains(p) && Pursuer.Player.Any(pc => pc.IsAlive())) TempData.winners.Add(new WinningPlayerData(p.Data)); - else if (Survivor.Player.Any(pc => pc == p) && Survivor.Player.Any(pc => pc.IsAlive())) + else if (Survivor.Player.MContains(p) && Survivor.Player.Any(pc => pc.IsAlive())) TempData.winners.Add(new WinningPlayerData(p.Data)); - else if (!notWinners.Contains(p) && !p.Data.Role.IsImpostor) + else if (!notWinners.MContains(p) && !p.IsImpostor()) TempData.winners.Add(new WinningPlayerData(p.Data)); } } @@ -406,35 +435,6 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ref En TempData.winners.Add(wpd); } - // Akujo win - else if (akujoWin) - { - if (Akujo.honmeiOptimizeWin && !Akujo.existingWithKiller()) - { - AdditionalTempData.winCondition = WinCondition.AkujoTeamWin; - TempData.winners = new Il2CppSystem.Collections.Generic.List(); - foreach (PlayerControl p in PlayerControl.AllPlayerControls) - { - if (p == null) continue; - if (p == Akujo.akujo || p == Akujo.honmei) - TempData.winners.Add(new WinningPlayerData(p.Data)); - else if (Pursuer.Player.Contains(p) && !p.Data.IsDead) - TempData.winners.Add(new WinningPlayerData(p.Data)); - else if (Survivor.Player.Contains(p) && !p.Data.IsDead) - TempData.winners.Add(new WinningPlayerData(p.Data)); - else if (!notWinners.Contains(p) && !p.Data.Role.IsImpostor) - TempData.winners.Add(new WinningPlayerData(p.Data)); - } - } - else - { - AdditionalTempData.winCondition = WinCondition.AkujoSoloWin; - TempData.winners = new Il2CppSystem.Collections.Generic.List(); - TempData.winners.Add(new WinningPlayerData(Akujo.akujo.Data)); - TempData.winners.Add(new WinningPlayerData(Akujo.honmei.Data)); - } - } - // Lawyer solo win else if (lawyerSoloWin) { diff --git a/TheOtherRoles/Patches/ExileControllerPatch.cs b/TheOtherRoles/Patches/ExileControllerPatch.cs index 08e84a0..fff6547 100644 --- a/TheOtherRoles/Patches/ExileControllerPatch.cs +++ b/TheOtherRoles/Patches/ExileControllerPatch.cs @@ -198,7 +198,7 @@ public static void Postfix(ExileController __instance, [HarmonyArgument(0)] ref if (CustomOptionHolder.exiledShowTeamNum.GetBool()) { - var Impostors = PlayerControl.AllPlayerControls.ToArray().Count(x => x.isImpostor() && x.IsAlive() && x.PlayerId != player?.PlayerId); + var Impostors = PlayerControl.AllPlayerControls.ToArray().Count(x => x.IsImpostor() && x.IsAlive() && x.PlayerId != player?.PlayerId); var Neutrals = PlayerControl.AllPlayerControls.ToArray().Count(x => x.isNeutral() && x.IsAlive() && x.PlayerId != player?.PlayerId); __instance.ImpostorText.text = $"\n{cs(getTeamColor(RoleType.Impostor), "伪装者阵营剩余 ") + Impostors}" + @@ -498,7 +498,7 @@ private static void WrapUpPostfix(GameData.PlayerInfo exiled) if (InfoSleuth.infoSleuth != null && InfoSleuth.target != null && InfoSleuth.infoSleuth == PlayerControl.LocalPlayer) { - var isNotCrew = (InfoSleuth.target.isNeutral() || InfoSleuth.target.isImpostor()) ^ Vortox.Reversal; + var isNotCrew = (InfoSleuth.target.isNeutral() || InfoSleuth.target.IsImpostor()) ^ Vortox.Reversal; var team = "的阵营是 " + getTeam(InfoSleuth.target); var info = InfoSleuth.infoType switch { @@ -526,11 +526,11 @@ static string getTeam(PlayerControl player) if (Vortox.Player.IsAlive()) { if (player.isCrew()) return rnd.Next(2) == 0 ? "NeutralRolesText".Translate() : "ImpostorRolesText".Translate(); - if (player.isNeutral() || player.isImpostor()) return "CrewmateRolesText".Translate(); + if (player.isNeutral() || player.IsImpostor()) return "CrewmateRolesText".Translate(); } return player.isNeutral() ? "NeutralRolesText".Translate() - : player.isImpostor() ? "ImpostorRolesText".Translate() + : player.IsImpostor() ? "ImpostorRolesText".Translate() : "CrewmateRolesText".Translate(); } } diff --git a/TheOtherRoles/Patches/HauntMenuPatch.cs b/TheOtherRoles/Patches/HauntMenuPatch.cs index 4fa78ac..3e65f9d 100644 --- a/TheOtherRoles/Patches/HauntMenuPatch.cs +++ b/TheOtherRoles/Patches/HauntMenuPatch.cs @@ -77,7 +77,7 @@ public static bool StartPrefix(HauntMenuMinigame __instance) public static void UpdatePostfix(HauntMenuMinigame __instance) { if (GameOptionsManager.Instance.currentGameOptions.GameMode != GameModes.Normal) return; - if (PlayerControl.LocalPlayer.isImpostor() && Vampire.vampire != PlayerControl.LocalPlayer) + if (PlayerControl.LocalPlayer.IsImpostor() && Vampire.vampire != PlayerControl.LocalPlayer) __instance.gameObject.transform.localPosition = new Vector3(-6f, -1.1f, __instance.gameObject.transform.localPosition.z); } diff --git a/TheOtherRoles/Patches/IntroPatch.cs b/TheOtherRoles/Patches/IntroPatch.cs index fdef1d9..33cd424 100644 --- a/TheOtherRoles/Patches/IntroPatch.cs +++ b/TheOtherRoles/Patches/IntroPatch.cs @@ -71,11 +71,7 @@ public static void Prefix(IntroCutscene __instance) // Add Electrical FungleAdditionalElectrical.CreateElectrical(); - // Force Reload of SoundEffectHolder - SoundEffectsManager.Load(); - // AntiTeleport set position - AntiTeleport.setPosition(); if (CustomOptionHolder.randomGameStartPosition.GetBool()) MapData.RandomSpawnPlayers(); @@ -202,6 +198,15 @@ public static void setupIntroTeamIcons(IntroCutscene __instance, ref List 0 && PlayerControl.AllPlayerControls.ToArray().ToList().Where(x => x.Data.Role.IsImpostor).Count() > 1) + { + // The local player always has to be the first one in the list (to be displayed in the center) + var fakeImpostorTeam = new List(); + fakeImpostorTeam.Add(PlayerControl.LocalPlayer); + yourTeam = fakeImpostorTeam; + } } public static void setupIntroTeam(IntroCutscene __instance, ref List yourTeam) diff --git a/TheOtherRoles/Patches/MainMenuPatch.cs b/TheOtherRoles/Patches/MainMenuPatch.cs index dae84b8..38059d7 100644 --- a/TheOtherRoles/Patches/MainMenuPatch.cs +++ b/TheOtherRoles/Patches/MainMenuPatch.cs @@ -15,6 +15,9 @@ public class MainMenuPatch 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; @@ -114,8 +117,12 @@ private static void Prefix(MainMenuManager __instance) 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 += ""; Announcement creditsAnnouncement = new() diff --git a/TheOtherRoles/Patches/MeetingHudPatch.cs b/TheOtherRoles/Patches/MeetingHudPatch.cs index 5beb69b..95ac770 100644 --- a/TheOtherRoles/Patches/MeetingHudPatch.cs +++ b/TheOtherRoles/Patches/MeetingHudPatch.cs @@ -921,6 +921,7 @@ public static void Postfix(MeetingHud __instance) HudManager.Instance.PlayerCam.Target = PlayerControl.LocalPlayer; PlayerControl.LocalPlayer.NetTransform.RpcSnapTo(Pelican.Player.transform.position); } + foreach (var p in Pelican.eatenPlayers) p.Die(DeathReason.Kill, true); Pelican.eatenPlayers = new(); } diff --git a/TheOtherRoles/Patches/PlayerControlPatch.cs b/TheOtherRoles/Patches/PlayerControlPatch.cs index d018455..31c72a8 100644 --- a/TheOtherRoles/Patches/PlayerControlPatch.cs +++ b/TheOtherRoles/Patches/PlayerControlPatch.cs @@ -140,8 +140,8 @@ public static void updatePlayerInfo() (local == PartTimer.target && p == PartTimer.partTimer) || (Akujo.knowsRoles && local == Akujo.akujo && (p == Akujo.honmei || Akujo.keeps.Any(x => x.PlayerId == p.PlayerId))) || - (ModOption.impostorSeeRoles && Spy.spy == null && PlayerControl.LocalPlayer.isImpostor() && - PlayerControl.LocalPlayer.IsAlive() && p.isImpostor() && p.IsAlive()); + (ModOption.impostorSeeRoles && Spy.spy == null && PlayerControl.LocalPlayer.IsImpostor() && + PlayerControl.LocalPlayer.IsAlive() && p.IsImpostor() && p.IsAlive()); bool reported = ((local == Slueth.slueth && Slueth.reported.Any(x => x.PlayerId == p.PlayerId)) || (local == Poucher.poucher && Poucher.killed.Any(x => x.PlayerId == p.PlayerId))) && p.IsDead(); @@ -822,7 +822,7 @@ private static void snitchTextUpdate() var local = PlayerControl.LocalPlayer; var isDead = local == Snitch.snitch || local.Data.IsDead; - var forImpTeam = local.isImpostor(); + var forImpTeam = local.IsImpostor(); var forKillerTeam = Snitch.Team == Snitch.includeNeutralTeam.KillNeutral && isKillerNeutral(local); var forEvilTeam = Snitch.Team == Snitch.includeNeutralTeam.EvilNeutral && isEvilNeutral(local); var forNeutraTeam = Snitch.Team == Snitch.includeNeutralTeam.AllNeutral && local.isNeutral(); @@ -910,7 +910,7 @@ private static void bountyHunterUpdate() BountyHunter.bountyUpdateTimer = BountyHunter.bountyDuration; var possibleTargets = new List(); foreach (PlayerControl p in PlayerControl.AllPlayerControls.ToArray().Where(x => x.IsAlive())) - if (p.IsAlive() && p != p.isImpostor(true) && (p != Mini.mini || Mini.isGrownUp()) && p != Lovers.otherLover(BountyHunter.bountyHunter)) + if (p.IsAlive() && p != p.IsImpostor(true) && (p != Mini.mini || Mini.isGrownUp()) && p != Lovers.otherLover(BountyHunter.bountyHunter)) possibleTargets.Add(p); if (possibleTargets.Count == 0) return; BountyHunter.bounty = possibleTargets[rnd.Next(0, possibleTargets.Count)]; @@ -2160,6 +2160,7 @@ public static void Postfix(PlayerControl __instance) } continue; } + foreach (var p in Pelican.eatenPlayers) p.Die(DeathReason.Kill, true); Pelican.eatenPlayers = new(); Pelican.PelicanDie(); diff --git a/TheOtherRoles/Patches/RoleAssignmentPatch.cs b/TheOtherRoles/Patches/RoleAssignmentPatch.cs index 01ee79d..ec5923c 100644 --- a/TheOtherRoles/Patches/RoleAssignmentPatch.cs +++ b/TheOtherRoles/Patches/RoleAssignmentPatch.cs @@ -49,7 +49,6 @@ internal class RoleManagerSelectRolesPatch private static int impValues; - //private static bool isEvilGuesser; private static readonly List> playerRoleMap = new(); public static bool isGuesserGamemode => ModOption.gameMode == CustomGamemodes.Guesser; @@ -60,7 +59,7 @@ public static void Postfix() AmongUsClient.Instance.FinishRpcImmediately(writer); RPCProcedure.resetVariables(); // Don't assign Roles in Hide N Seek - if (GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek) return; + if (GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek || RoleDraft.isEnabled) return; assignRoles(); } @@ -433,7 +432,7 @@ static List GetEnsuredRoles(Dictionary settings) => } } - private static void assignRoleTargets(RoleAssignmentData data) + public static void assignRoleTargets(RoleAssignmentData data) { // Set Lawyer or Prosecutor Target if (Lawyer.lawyer != null) @@ -493,7 +492,7 @@ private static void assignRoleTargets(RoleAssignmentData data) } } - private static void assignModifiers() + public static void assignModifiers() { var addMaxNum = Cursed.hideModifier ? 1 : 0; var modifierMin = CustomOptionHolder.modifiersCountMin.GetSelection(); @@ -626,7 +625,7 @@ private static void assignModifiers() assignModifiersToPlayers(chanceImpModifierToAssign, impPlayer, modifierCount); // Assign chance Imp modifier } - private static void assignGuesserGamemode() + public static void assignGuesserGamemode() { var impPlayer = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList(); var neutralPlayer = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList(); @@ -767,7 +766,7 @@ private static void assignModifiersToPlayers(List modifiers, List GuesserGM.isGuesser(p.PlayerId))) { GuesserList.Add(player); - if (!Specoality.IsGlobalModifier) GuesserList.RemoveAll(x => !x.isImpostor()); + if (!Specoality.IsGlobalModifier) GuesserList.RemoveAll(x => !x.IsImpostor()); } } else @@ -875,7 +874,7 @@ private static void assignModifiersToPlayers(List modifiers, List(playerList); - APlayers.RemoveAll(x => x.isImpostor()); + APlayers.RemoveAll(x => x.IsImpostor()); playerId = setModifierToRandomPlayer((byte)RoleId.Aftermath, APlayers); crewPlayer.RemoveAll(x => x.PlayerId == playerId); diff --git a/TheOtherRoles/Patches/UpdatePatch.cs b/TheOtherRoles/Patches/UpdatePatch.cs index 20c7b55..76d8cf9 100644 --- a/TheOtherRoles/Patches/UpdatePatch.cs +++ b/TheOtherRoles/Patches/UpdatePatch.cs @@ -4,6 +4,7 @@ using AmongUs.Data; using AmongUs.GameOptions; using InnerNet; +using Rewired; using TheOtherRoles.Buttons; using TheOtherRoles.Utilities; using UnityEngine; @@ -131,12 +132,12 @@ private static void setNameColors() setPlayerNameColor(WolfLord.Player, WolfLord.color); } - if (Grenadier.Player != null && ((localPlayer.isImpostor() && Grenadier.indicatorsMode) + if (Grenadier.Player != null && ((localPlayer.IsImpostor() && Grenadier.indicatorsMode) || localPlayer == Grenadier.Player || ShowGhostInfo)) { foreach (var p in Grenadier.controls) { - if (p != localPlayer && !p.isImpostor()) setPlayerNameColor(p, Color.black); + if (p != localPlayer && !p.IsImpostor()) setPlayerNameColor(p, Color.black); } } @@ -433,8 +434,17 @@ private static void updateVentButton(HudManager __instance) if ((Sheriff.handcuffedKnows.ContainsKey(PlayerControl.LocalPlayer.PlayerId) && Sheriff.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 (ReInput.players.GetPlayer(0).GetButtonDown(RewiredConsts.Action.UseVent) && + !PlayerControl.LocalPlayer.Data.Role.IsImpostor && PlayerControl.LocalPlayer.roleCanUseVents()) + { + __instance.ImpostorVentButton.DoClick(); + } + } private static void updateUseButton(HudManager __instance) diff --git a/TheOtherRoles/Patches/UsablesPatch.cs b/TheOtherRoles/Patches/UsablesPatch.cs index 368f25e..9126c9a 100644 --- a/TheOtherRoles/Patches/UsablesPatch.cs +++ b/TheOtherRoles/Patches/UsablesPatch.cs @@ -366,24 +366,6 @@ public static bool Prefix(ReportButton __instance) [HarmonyPatch(typeof(EmergencyMinigame), nameof(EmergencyMinigame.Update))] internal class EmergencyMinigameUpdatePatch { - private static void Postfix(EmergencyMinigame __instance) - { - if (!CanCallEmergency(out var statusText)) - { - UpdateEmergencyButton(__instance, statusText, false); - return; - } - - if (__instance.state == 1) - { - var localRemaining = PlayerControl.LocalPlayer.RemainingEmergencies; - var teamRemaining = Mathf.Max(0, maxNumberOfMeetings - meetingsCount); - var remaining = Mathf.Min(localRemaining, Mayor.mayor != null && Mayor.mayor == PlayerControl.LocalPlayer ? 1 : teamRemaining); - __instance.NumberText.text = string.Format(GetString("meetingCount"), localRemaining.ToString(), teamRemaining.ToString()); - UpdateEmergencyButton(__instance, string.Empty, remaining > 0); - } - } - private static bool CanCallEmergency(out string statusText) { statusText = string.Empty; @@ -430,11 +412,30 @@ private static bool CanCallEmergency(out string statusText) private static void UpdateEmergencyButton(EmergencyMinigame instance, string statusText, bool isActive) { instance.StatusText.text = statusText; - instance.NumberText.text = string.Empty; + instance.NumberText.text = isActive ? instance.NumberText.text : string.Empty; instance.ClosedLid.gameObject.SetActive(!isActive); instance.OpenLid.gameObject.SetActive(isActive); instance.ButtonActive = isActive; } + + private static void Postfix(EmergencyMinigame __instance) + { + if (!CanCallEmergency(out var statusText)) + { + UpdateEmergencyButton(__instance, statusText, false); + return; + } + + if (__instance.state == 1) + { + var localRemaining = PlayerControl.LocalPlayer.RemainingEmergencies; + var teamRemaining = Mathf.Max(0, maxNumberOfMeetings - meetingsCount); + var remaining = Mathf.Min(localRemaining, Mayor.mayor != null && Mayor.mayor == PlayerControl.LocalPlayer ? 1 : teamRemaining); + var text = string.Format(GetString("meetingCount"), localRemaining.ToString(), teamRemaining.ToString()); + __instance.NumberText.text = text; + UpdateEmergencyButton(__instance, text, remaining > 0); + } + } } [HarmonyPatch(typeof(Console), nameof(Console.CanUse))] diff --git a/TheOtherRoles/RPC.cs b/TheOtherRoles/RPC.cs index f730522..699d0a6 100644 --- a/TheOtherRoles/RPC.cs +++ b/TheOtherRoles/RPC.cs @@ -38,6 +38,8 @@ public enum CustomRPC DynamicMapOption, SetGameStarting, StopStart, + DraftModePickOrder, + DraftModePick, ShareGameMode = 95, UncheckedMurderPlayer, @@ -784,6 +786,7 @@ public static void turnToImpostor(byte targetId) { var player = playerById(targetId); erasePlayerRoles(player.PlayerId); + if (player == Cursed.cursed) Cursed.clearAndReload(); Helpers.turnToImpostor(player); } @@ -875,7 +878,7 @@ public static void shifterShift(byte targetId) Shifter.clearAndReload(); // Suicide (exile) when impostor or impostor variants - if ((target.isImpostor() || Shifter.isShiftNeutral(target)) && player.IsAlive()) + if ((target.IsImpostor() || Shifter.isShiftNeutral(target)) && player.IsAlive()) { Message($"Target Is Neutral: {Shifter.isShiftNeutral(target)}", "Shifter"); player.Exiled(); @@ -927,11 +930,11 @@ public static void grenadierFlash(bool clear = false) { if (PlayerControl.LocalPlayer.PlayerId == player.PlayerId) { - if (player.isImpostor() && !player.IsDead() && !MeetingHud.Instance) + if (player.IsImpostor() && !player.IsDead() && !MeetingHud.Instance) { Grenadier.showFlash(Grenadier.flash, Grenadier.duration, 0.24f); } - else if (!player.isImpostor() && !player.IsDead() && !MeetingHud.Instance) + else if (!player.IsImpostor() && !player.IsDead() && !MeetingHud.Instance) { Grenadier.showFlash(Grenadier.flash, Grenadier.duration, 1f); } @@ -1168,7 +1171,7 @@ public static void erasePlayerRoles(byte playerId, bool ignoreModifier = true) if (player == Jester.jester) Jester.clearAndReload(); if (player == Werewolf.werewolf) Werewolf.clearAndReload(); if (player == Miner.miner) Miner.clearAndReload(); - if (player == Pelican.Player) Pelican.PelicanDie(true); + if (player == Pelican.Player) { Pelican.PelicanDie(true); } if (player == Arsonist.arsonist) Arsonist.clearAndReload(); if (Guesser.isGuesser(player.PlayerId)) Guesser.clear(player.PlayerId); @@ -1196,7 +1199,6 @@ public static void erasePlayerRoles(byte playerId, bool ignoreModifier = true) if (player == PartTimer.partTimer) PartTimer.clearAndReload(); if (player == Vortox.Player) Vortox.ClearAndReload(); - if (player == Cursed.cursed) Cursed.clearAndReload(); if (player == Shifter.shifter) Shifter.clearAndReload(); Assassin.assassin.RemoveAll(x => x.PlayerId == player.PlayerId); @@ -2027,6 +2029,14 @@ private static bool Prefix([HarmonyArgument(0)] byte callId, [HarmonyArgument(1) RPCProcedure.setGhostRole(reader.ReadByte(), reader.ReadByte()); break; + case CustomRPC.DraftModePickOrder: + RoleDraft.receivePickOrder(reader.ReadByte(), reader); + break; + + case CustomRPC.DraftModePick: + RoleDraft.receivePick(reader.ReadByte(), reader.ReadByte()); + break; + case CustomRPC.VersionHandshake: byte major = reader.ReadByte(); byte minor = reader.ReadByte(); diff --git a/TheOtherRoles/Resources/SoundEffects/audio b/TheOtherRoles/Resources/SoundEffects/audio index b3fb5d0..d7d1373 100644 Binary files a/TheOtherRoles/Resources/SoundEffects/audio and b/TheOtherRoles/Resources/SoundEffects/audio differ diff --git a/TheOtherRoles/Resources/stringData.json b/TheOtherRoles/Resources/stringData.json index 9399a80..72cbcd8 100644 --- a/TheOtherRoles/Resources/stringData.json +++ b/TheOtherRoles/Resources/stringData.json @@ -248,11 +248,6 @@ "0": "Page 1: Prop Hunt Settings \n\n", "13": "第1页: 道具躲猫猫模式设置 \n\n" }, - "meetingCount": { - "0": "{0} and the ship has {1}", - "11": "{0}と船に{1}がいます", - "13": "{0}次,全船剩余{1}次" - }, "Random": { "0": "Random", "13": "随机" @@ -376,6 +371,34 @@ "0": "Maximum Modifiers", "13": "最大附加职业数" }, + "isDraftMode": { + "0": "isDraftMode", + "13": "轮抽选角模式" + }, + "draftModeAmountOfChoices": { + "0": "Max Amount Of Roles\nTo Choose From", + "13": "最大可选择的职业数量" + }, + "draftModeTimeToChoose": { + "0": "Time For Selection", + "13": "选择时间" + }, + "draftModeShowRoles": { + "0": "Show Picked Roles", + "13": "所有人可见选定职业" + }, + "draftModeHideImpRoles": { + "0": "Hide Impostor Roles", + "13": "隐藏伪装者阵营职业" + }, + "draftModeHideNeutralRoles": { + "0": "Hide Neutral Roles", + "13": "隐藏中立阵营职业" + }, + "draftModeHideCrewmateRoles": { + "0": "Hide Crewmate Roles", + "13": "隐藏船员阵营职业" + }, "MeetingOptions": { "13": "会议设置" }, @@ -2450,6 +2473,11 @@ "0": "Failed Murder Attempt on Shielded Player", "13": "尝试击杀被保护玩家" }, + "meetingCount": { + "0": "{0} and the ship has {1}", + "11": "{0}と船に{1}がいます", + "13": "{0}次,全船剩余{1}次" + }, "swapperMeetingButton": { "0": "The Swapper can't start an emergency meeting", "13": "换票师不可以发起紧急会议" @@ -2643,6 +2671,66 @@ "PartTimerWin": { "13": "打工仔跟随胜利" }, + "RoleDraft.FeedText": { + "0": "Player's Picks:\n\n", + "13": "已选择的玩家:\n\n" + }, + "RoleDraft.PlayerText": { + "0": "Player {0}", + "1": "Anonymous Player {1}", + "2": "Anonymous Player {2}", + "3": "Anonymous Player {3}", + "4": "Anonymous Player {4}", + "5": "Anonymous Player {5}", + "6": "Anonymous Player {6}", + "7": "Anonymous Player {7}", + "8": "Anonymous Player {8}", + "9": "Anonymous Player {9}", + "10": "Anonymous Player {10}", + "11": "Anonymous Player {11}", + "12": "Anonymous Player {12}", + "13": "玩家 {0}" + }, + "RoleDraft.TeamTitle": { + "0": "Welcome to the Role Draft!\n\n\n Currently Picking:\n\n{0}", + "1": "Welcome to the Role Draft!\n\n\n Currently Picking:\n\n{1}", + "2": "Welcome to the Role Draft!\n\n\n Currently Picking:\n\n{2}", + "3": "Welcome to the Role Draft!\n\n\n Currently Picking:\n\n{3}", + "4": "Welcome to the Role Draft!\n\n\n Currently Picking:\n\n{4}", + "5": "Welcome to the Role Draft!\n\n\n Currently Picking:\n\n{5}", + "6": "Welcome to the Role Draft!\n\n\n Currently Picking:\n\n{6}", + "7": "Welcome to the Role Draft!\n\n\n Currently Picking:\n\n{7}", + "8": "Welcome to the Role Draft!\n\n\n Currently Picking:\n\n{8}", + "9": "Welcome to the Role Draft!\n\n\n Currently Picking:\n\n{9}", + "10": "Welcome to the Role Draft!\n\n\n Currently Picking:\n\n{10}", + "11": "Welcome to the Role Draft!\n\n\n Currently Picking:\n\n{11}", + "12": "Welcome to the Role Draft!\n\n\n Currently Picking:\n\n{12}", + "13": "欢迎游玩轮抽模式!\n\n\n 当前正在选择的玩家:\n\n{0}" + }, + "RoleDraft.TeamTitle2": { + "0": "\n\n({0} rounds until your turn)\nRandom Selection In... {1}", + "13": "\n\n(在你前面还有 {0} 位玩家)\n选择中... {1}" + }, + "RoleDraft.UnknownRole": { + "0": "Unknown Role", + "13": "选择完毕! (隐藏)" + }, + "RoleDraft.You": { + "0": "You!", + "13": "请开始选择:" + }, + "RoleDraft.ImpostorRole": { + "0": "Impostor Role", + "13": "红狼阵营" + }, + "RoleDraft.NeutralRole": { + "0": "Neutral Role", + "13": "中立阵营" + }, + "RoleDraft.CrewmateRole": { + "0": "Crewmate Role", + "13": "船员阵营" + }, "colorSalmon": { "0": "Salmon", "13": "鲑鱼红" diff --git a/TheOtherRoles/Roles/Crewmate/Sheriff.cs b/TheOtherRoles/Roles/Crewmate/Sheriff.cs index 22e723f..fcf9178 100644 --- a/TheOtherRoles/Roles/Crewmate/Sheriff.cs +++ b/TheOtherRoles/Roles/Crewmate/Sheriff.cs @@ -73,7 +73,7 @@ public static void setHandcuffedKnows(bool active = true, byte playerId = byte.M public static void replaceCurrentSheriff() { - if (Deputy == null) return; + if (Deputy == null || Deputy?.CanMove == false) return; Player ??= new(); formerDeputy = Deputy; Player.Add(Deputy); diff --git a/TheOtherRoles/Roles/Ghost/Specter.cs b/TheOtherRoles/Roles/Ghost/Specter.cs index 07c190d..87c4b75 100644 --- a/TheOtherRoles/Roles/Ghost/Specter.cs +++ b/TheOtherRoles/Roles/Ghost/Specter.cs @@ -33,7 +33,7 @@ public static void TakeRole(byte targetId) RPCProcedure.erasePlayerRoles(local.PlayerId); var roleInfo = RoleInfo.getRoleInfoForPlayer(target).FirstOrDefault(x => x.roleType is not RoleType.Modifier and not RoleType.Ghost); - if (target.isImpostor()) turnToImpostor(local); + if (target.IsImpostor()) turnToImpostor(local); DeadBody[] array = Object.FindObjectsOfType(); for (var i = 0; i < array.Length; i++) diff --git a/TheOtherRoles/Roles/Guesser.cs b/TheOtherRoles/Roles/Guesser.cs index 8660455..63111d5 100644 --- a/TheOtherRoles/Roles/Guesser.cs +++ b/TheOtherRoles/Roles/Guesser.cs @@ -286,7 +286,7 @@ static void CreatePage(bool IsNext, MeetingHud __instance, Transform container) continue; case RoleId.Crewmate when !Assassin.evilGuesserCanGuessCrewmate && guesserRole == RoleId.Assassin: continue; - case RoleId.Spy when PlayerControl.LocalPlayer.isImpostor() && !HandleGuesser.evilGuesserCanGuessSpy: + case RoleId.Spy when PlayerControl.LocalPlayer.IsImpostor() && !HandleGuesser.evilGuesserCanGuessSpy: continue; case RoleId.Mayor when Mayor.Revealed: continue; diff --git a/TheOtherRoles/Roles/Impostor/WolfLord.cs b/TheOtherRoles/Roles/Impostor/WolfLord.cs index 747eb83..36f4ee9 100644 --- a/TheOtherRoles/Roles/Impostor/WolfLord.cs +++ b/TheOtherRoles/Roles/Impostor/WolfLord.cs @@ -124,7 +124,7 @@ private static void ButtonToggle(MeetingHud __instance) foreach (var pva in __instance.playerStates) { var player = playerById(pva.TargetPlayerId); - if (player.IsAlive() && player != Player && !player.isImpostor()) + if (player.IsAlive() && player != Player && !player.IsImpostor()) { GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; GameObject targetBox = Object.Instantiate(template, pva.transform); diff --git a/TheOtherRoles/Roles/Modifier/Cursed.cs b/TheOtherRoles/Roles/Modifier/Cursed.cs index 2b012e4..6bc449a 100644 --- a/TheOtherRoles/Roles/Modifier/Cursed.cs +++ b/TheOtherRoles/Roles/Modifier/Cursed.cs @@ -24,9 +24,9 @@ public static void Postfix(HudManager __instance) if (cursed.IsDead() || !InGame || cursed != PlayerControl.LocalPlayer) return; var allPlayers = PlayerControl.AllPlayerControls.ToList(); - var impostorCount = allPlayers.Count(x => x.isImpostor() && x.IsAlive()); + var impostorCount = allPlayers.Count(x => x.IsImpostor() && x.IsAlive()); - if (impostorCount >= allPlayers.Count(x => !x.isImpostor() && x.IsAlive())) + if (impostorCount >= allPlayers.Count(x => !x.IsImpostor() && x.IsAlive())) { turnToImpostorRPC(cursed); } diff --git a/TheOtherRoles/Roles/Neutral/Amnisiac.cs b/TheOtherRoles/Roles/Neutral/Amnisiac.cs index 3fa8397..3e7885c 100644 --- a/TheOtherRoles/Roles/Neutral/Amnisiac.cs +++ b/TheOtherRoles/Roles/Neutral/Amnisiac.cs @@ -38,7 +38,7 @@ public static void TakeRole(byte targetId, byte playerId) if (target == null || local == null) return; var targetInfo = RoleInfo.getRoleInfoForPlayer(target, false, false); var roleInfo = targetInfo.FirstOrDefault(); - if (target.isImpostor()) turnToImpostor(local); + if (target.IsImpostor()) turnToImpostor(local); switch (roleInfo!.roleId) { case RoleId.Impostor: diff --git a/TheOtherRoles/Roles/RoleInfo.cs b/TheOtherRoles/Roles/RoleInfo.cs index 45073d9..19fcb64 100644 --- a/TheOtherRoles/Roles/RoleInfo.cs +++ b/TheOtherRoles/Roles/RoleInfo.cs @@ -7,18 +7,34 @@ namespace TheOtherRoles.Roles; -public class RoleInfo(string name, Color color, RoleId roleId, RoleType roleType, bool isGuessable = false) +public class RoleInfo { public string Name => GetString(nameKey); public string IntroDescription => GetString(nameKey + "IntroDesc"); public string ShortDescription => GetString(nameKey + "ShortDesc"); public string FullDescription => GetString(nameKey + "FullDesc"); + public bool IsNeutral => roleType == RoleType.Neutral; + public bool IsImpostor => roleType == RoleType.Impostor; + public bool IsCrewmate => roleType == RoleType.Crewmate; + public bool IsModifier => roleType == RoleType.Modifier; + public static Dictionary RoleInfoById = new(); + + public Color color; + public RoleId roleId; + public RoleType roleType; + public bool isGuessable; + private readonly string nameKey; + + public RoleInfo(string name, Color color, RoleId roleId, RoleType roleType, bool isGuessable = false) + { + this.color = color; + this.roleId = roleId; + this.roleType = roleType; + this.isGuessable = isGuessable; + nameKey = name; + RoleInfoById.TryAdd(roleId, this); + } - public Color color = color; - public RoleId roleId = roleId; - public RoleType roleType = roleType; - public bool isGuessable = isGuessable; - private readonly string nameKey = name; public static RoleInfo impostor = new("Impostor", Palette.ImpostorRed, RoleId.Impostor, RoleType.Impostor); public static RoleInfo morphling = new("Morphling", Morphling.color, RoleId.Morphling, RoleType.Impostor); diff --git a/TheOtherRoles/SoundEffectsManager.cs b/TheOtherRoles/SoundEffectsManager.cs index c33d993..e2b7050 100644 --- a/TheOtherRoles/SoundEffectsManager.cs +++ b/TheOtherRoles/SoundEffectsManager.cs @@ -12,6 +12,7 @@ public static class SoundEffectsManager { private static Dictionary soundEffects = new(); + //private static List currentSources = new(); public static void Load() { @@ -42,36 +43,57 @@ public static AudioClip get(string path) { // 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"; return soundEffects.TryGetValue(path, out var returnValue) ? returnValue : null; } - public static void play(string path, float volume = 0.7f, bool loop = false) + public static AudioSource play(string path, float volume = 0.7f, bool loop = false, bool musicChannel = false) { - if (!ModOption.enableSoundEffects) return; + if (!ModOption.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 (!ModOption.enableSoundEffects || !Constants.ShouldPlaySfx()) return; AudioClip clipToPlay = get(path); + Message("play at position"); + if (clipToPlay == null) + { + Message("clip is null"); + return; + } AudioSource source = SoundManager.Instance.PlaySound(clipToPlay, false, 1f); + if (source == null) + { + Message("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()); @@ -82,18 +104,40 @@ public static void playAtPosition(string path, Vector2 position, float maxDurati source.volume = volume; } }))); + Message("end play at position"); } public static void stop(string path) { var soundToStop = get(path); if (soundToStop != null) - if (Constants.ShouldPlaySfx()) SoundManager.Instance.StopSound(soundToStop); + { + try + { + SoundManager.Instance?.StopSound(soundToStop); + } + catch (Exception e) { Warn($"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 { }*/ } -} \ No newline at end of file +} diff --git a/TheOtherRoles/TheOtherRoles.csproj b/TheOtherRoles/TheOtherRoles.csproj index 15ae91a..4684cf8 100644 --- a/TheOtherRoles/TheOtherRoles.csproj +++ b/TheOtherRoles/TheOtherRoles.csproj @@ -1,7 +1,7 @@  net6.0 - 1.1.2.7 + 1.1.3.0 TheOtherUs mxyx-club latest