From d64de1094ed8ae7642ea398e4dfdf8b607cc938b Mon Sep 17 00:00:00 2001 From: thunderstorm584 <80977292+thunderstorm584@users.noreply.github.com> Date: Thu, 13 May 2021 18:25:46 +0200 Subject: [PATCH] Version v2.6.3 --- README.md | 7 +- Source Code/CredentialsPatch.cs | 65 ++++-- Source Code/CustomHats.cs | 97 +++++++-- Source Code/CustomOptions.cs | 55 ++++- Source Code/Main.cs | 2 +- Source Code/ModUpdater.cs | 5 +- Source Code/PlayerControlPatch.cs | 31 ++- Source Code/Resources/Banner.png | Bin 0 -> 28723 bytes Source Code/RoleAssignmentPatch.cs | 336 ++++++++++++++++------------- Source Code/TheOtherRoles.csproj | 13 +- 10 files changed, 394 insertions(+), 217 deletions(-) create mode 100644 Source Code/Resources/Banner.png diff --git a/README.md b/README.md index 1a893101f..949dce451 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein # Releases | Among Us - Version| Mod Version | Link | |----------|-------------|-----------------| +| 2021.5.10s| v2.6.3| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.3/TheOtherRoles.zip) | 2021.5.10s| v2.6.2| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.2/TheOtherRoles.zip) | 2021.4.14s| v2.6.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.1/TheOtherRoles.zip) | 2021.4.14s| v2.6.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.0/TheOtherRoles.zip) @@ -61,6 +62,10 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein
Click to show the Changelog +**Version 2.6.3** +- Changed the role limits options to allow for minimum and maximum bounds +- Changed the role assignment to be more random when assigning roles (previously assigned the neutral roles before assigning the crewmate roles) + **Version 2.6.2** - The Other Roles now supports the new Among Us version **2021.5.10s** - Added a chat command to kick players as the host of a lobby (/kick playerName) @@ -376,7 +381,7 @@ First you need to choose how many special roles of each kind (Impostor/Neutral/C The count you set will only be reached, if there are enough Crewmates/Impostors in the game and if enough roles are set to be in the game (i.e. they are set to > 0%). The roles are then being distributed as follows: - First all roles that are set to 100% are being assigned to arbitrary players - After that each role that has 10%-90% selected adds 1-9 tickets to a ticket pool (there exists a ticket pool for Crewmates, Neutrals and Impostors). Then the roles will be selected randomly from the pools as long it's possible (until the selected number is reached, until there are no more Crewmates/Impostors or until there are no more tickets). If a role is selected from the pool, obviously all the tickets of that role are being removed. -- The Mafia, Lovers and Child are being selected independently (without using the ticket system) according to the spawn chance you selected. After that all Neutral roles are being selected, then all Crewmate roles and in the very end all Impostor roles. +- The Mafia, Lovers and Child are being selected independently (without using the ticket system) according to the spawn chance you selected. After that the Crewmate, Neutral and Impostor roles are selected and assigned in a random order. **Example:**\ Settings: 2 special Crewmate roles, Snitch: 100%, Hacker: 10%, Tracker: 30%\ diff --git a/Source Code/CredentialsPatch.cs b/Source Code/CredentialsPatch.cs index df950a2b8..a7e9e687f 100644 --- a/Source Code/CredentialsPatch.cs +++ b/Source Code/CredentialsPatch.cs @@ -11,39 +11,72 @@ namespace TheOtherRoles [HarmonyPatch] public static class CredentialsPatch { public static string fullCredentials = -$@"TheOtherRoles v{TheOtherRolesPlugin.Version.ToString()}: -- Modded by Eisbison, - Thunderstorm584 and EndOfFile -- Balanced with Dhalucard -- Button design by Bavari"; +$@"TheOtherRoles v{TheOtherRolesPlugin.Version.ToString()} +Modded by Eisbison, +Thunderstorm584 & EndOfFile +Balanced with Dhalucard +Button design by Bavari"; + + public static string mainMenuCredentials = +$@"Modded by Eisbison, Thunderstorm584 & EndOfFile +Balanced with Dhalucard Design by Bavari"; [HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))] private static class VersionShowerPatch { static void Postfix(VersionShower __instance) { - string spacer = new String('\n', 8); + var amongUsLogo = GameObject.Find("bannerLogo_AmongUs"); + if (amongUsLogo == null) return; + + var credentials = UnityEngine.Object.Instantiate(__instance.text); + credentials.transform.position = new Vector3(0, 0.1f, 0); + credentials.SetText(mainMenuCredentials); + credentials.alignment = TMPro.TextAlignmentOptions.Center; + credentials.fontSize *= 0.75f; + + var version = UnityEngine.Object.Instantiate(credentials); + version.transform.position = new Vector3(0, -0.25f, 0); + version.SetText($"v{TheOtherRolesPlugin.Version.ToString()}"); - if (__instance.text.text.Contains(spacer)) - __instance.text.text = __instance.text.text + "\n" + fullCredentials; - else - __instance.text.text = __instance.text.text + spacer + fullCredentials; - __instance.text.alignment = TMPro.TextAlignmentOptions.TopLeft; + credentials.transform.SetParent(amongUsLogo.transform); + version.transform.SetParent(amongUsLogo.transform); } } [HarmonyPatch(typeof(PingTracker), nameof(PingTracker.Update))] private static class PingTrackerPatch { - static void Postfix(PingTracker __instance) - { + static void Postfix(PingTracker __instance){ + __instance.text.alignment = TMPro.TextAlignmentOptions.TopRight; if (AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Started) { - __instance.text.text = "TheOtherRoles\nModded by Eisbison\n" + __instance.text.text; - __instance.transform.localPosition = new Vector3(2.583f, 2.675f, __instance.transform.localPosition.z); + __instance.text.text = $"TheOtherRoles v{TheOtherRolesPlugin.Version.ToString()}\n" + __instance.text.text; + if (PlayerControl.LocalPlayer.Data.IsDead) { + __instance.transform.localPosition = new Vector3(3.45f, 2.675f, __instance.transform.localPosition.z); + } else { + __instance.transform.localPosition = new Vector3(4.2f, 2.675f, __instance.transform.localPosition.z); + } } else { __instance.text.text = $"{fullCredentials}\n{__instance.text.text}"; - __instance.transform.localPosition = new Vector3(1.25f, 2.675f, __instance.transform.localPosition.z); + __instance.transform.localPosition = new Vector3(3.5f, 2.675f, __instance.transform.localPosition.z); } } } + + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] + private static class LogoPatch + { + static void Postfix(PingTracker __instance) { + var amongUsLogo = GameObject.Find("bannerLogo_AmongUs"); + if (amongUsLogo != null) { + amongUsLogo.transform.localScale *= 0.6f; + amongUsLogo.transform.position += Vector3.up * 0.25f; + } + + var torLogo = new GameObject("bannerLogo_TOR"); + torLogo.transform.position = Vector3.up; + var renderer = torLogo.AddComponent(); + renderer.sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Banner.png", 300f); + } + } } } diff --git a/Source Code/CustomHats.cs b/Source Code/CustomHats.cs index 3a74d8d33..834532959 100644 --- a/Source Code/CustomHats.cs +++ b/Source Code/CustomHats.cs @@ -1,4 +1,3 @@ -extern alias Il2CppNewtonsoft; using System; using BepInEx; using BepInEx.Configuration; @@ -19,8 +18,8 @@ using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Security.Cryptography; -using Il2CppNewtonsoft::Newtonsoft.Json.Linq; -using Il2CppNewtonsoft::Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; namespace TheOtherRoles { [HarmonyPatch] @@ -29,11 +28,14 @@ public class CustomHats { public static Material hatShader; public static Dictionary CustomHatRegistry = new Dictionary(); + public static HatExtension TestExt = null; public class HatExtension { public string author { get; set;} public string package { get; set;} public string condition { get; set;} + public Sprite FlipImage { get; set;} + public Sprite BackFlipImage { get; set;} public bool isUnlocked() { if (condition == null || condition.ToLower() == "none") @@ -48,6 +50,8 @@ public class CustomHat { public string condition { get; set;} public string name { get; set;} public string resource { get; set;} + public string flipresource { get; set;} + public string backflipresource { get; set;} public string backresource { get; set;} public string climbresource { get; set;} public bool bounce { get; set;} @@ -58,6 +62,8 @@ public class CustomHat { private static List createCustomHatDetails(string[] hats, bool fromDisk = false) { Dictionary fronts = new Dictionary(); Dictionary backs = new Dictionary(); + Dictionary flips = new Dictionary(); + Dictionary backflips = new Dictionary(); Dictionary climbs = new Dictionary(); for (int i = 0; i < hats.Length; i++) { @@ -68,11 +74,15 @@ private static List createCustomHatDetails(string[] hats, bool fromDi for (int j = 1; j < p.Length; j++) options.Add(p[j]); - if (options.Contains("climb")) + if (options.Contains("back") && options.Contains("flip")) + backflips.Add(p[0], hats[i]); + else if (options.Contains("climb")) climbs.Add(p[0], hats[i]); - if (options.Contains("back")) + else if (options.Contains("back")) backs.Add(p[0], hats[i]); - if (!options.Contains("back") && !options.Contains("climb")) { + else if (options.Contains("flip")) + flips.Add(p[0], hats[i]); + else { CustomHat custom = new CustomHat { resource = hats[i] }; custom.name = p[0].Replace('-', ' '); custom.bounce = options.Contains("bounce"); @@ -87,13 +97,19 @@ private static List createCustomHatDetails(string[] hats, bool fromDi foreach (string k in fronts.Keys) { CustomHat hat = fronts[k]; - string br, cr; + string br, cr, fr, bfr; backs.TryGetValue(k, out br); climbs.TryGetValue(k, out cr); + flips.TryGetValue(k, out fr); + backflips.TryGetValue(k, out bfr); if (br != null) hat.backresource = br; if (cr != null) hat.climbresource = cr; + if (fr != null) + hat.flipresource = fr; + if (bfr != null) + hat.backflipresource = bfr; if (hat.backresource != null) hat.behind = true; @@ -136,12 +152,20 @@ private static HatBehaviour CreateHatBehaviour(CustomHat ch, bool fromDisk = fal if (ch.adaptive && hatShader != null) hat.AltShader = hatShader; - if (!testOnly) { - HatExtension extend = new HatExtension(); - extend.author = ch.author != null ? ch.author : "Unknown"; - extend.package = ch.package != null ? ch.package : "Misc."; - extend.condition = ch.condition != null ? ch.condition : "none"; + HatExtension extend = new HatExtension(); + extend.author = ch.author != null ? ch.author : "Unknown"; + extend.package = ch.package != null ? ch.package : "Misc."; + extend.condition = ch.condition != null ? ch.condition : "none"; + if (ch.flipresource != null) + extend.FlipImage = CreateHatSprite(ch.flipresource, fromDisk); + if (ch.backflipresource != null) + extend.BackFlipImage = CreateHatSprite(ch.backflipresource, fromDisk); + + if (testOnly) { + TestExt = extend; + TestExt.condition = hat.name; + } else { CustomHatRegistry.Add(hat.name, extend); } @@ -155,6 +179,10 @@ private static HatBehaviour CreateHatBehaviour(CustomHatLoader.CustomHatOnline c chd.backresource = filePath + chd.backresource; if (chd.climbresource != null) chd.climbresource = filePath + chd.climbresource; + if (chd.flipresource != null) + chd.flipresource = filePath + chd.flipresource; + if (chd.backflipresource != null) + chd.backflipresource = filePath + chd.backflipresource; return CreateHatBehaviour(chd, true); } @@ -188,6 +216,32 @@ where r.StartsWith(hatres) && r.EndsWith(".png") } } + [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.HandleAnimation))] + private static class PlayerPhysicsHandleAnimationPatch { + private static void Postfix(PlayerPhysics __instance) { + AnimationClip currentAnimation = __instance.Animator.GetCurrentAnimation(); + if (currentAnimation == __instance.ClimbAnim || currentAnimation == __instance.ClimbDownAnim) return; + HatParent hp = __instance.myPlayer.HatRenderer; + if (hp.Hat == null) return; + HatExtension extend = hp.Hat.getHatExtension(); + if (extend == null) return; + if (extend.FlipImage != null) { + if (__instance.rend.flipX) { + hp.FrontLayer.sprite = extend.FlipImage; + } else { + hp.FrontLayer.sprite = hp.Hat.MainImage; + } + } + if (extend.BackFlipImage != null) { + if (__instance.rend.flipX) { + hp.BackLayer.sprite = extend.BackFlipImage; + } else { + hp.BackLayer.sprite = hp.Hat.BackImage; + } + } + } + } + [HarmonyPatch(typeof(HatParent), nameof(HatParent.SetHat), new System.Type[] { typeof(uint), typeof(int) })] private static class HatParentSetHatPatch { static void Postfix(HatParent __instance, [HarmonyArgument(0)]uint hatId, [HarmonyArgument(1)]int color) { @@ -315,7 +369,10 @@ public static bool Prefix(HatsTab __instance) { List> value = packages[key]; YOffset = createHatPackage(value, key, YOffset, __instance); } - __instance.scroller.YBounds.max = YOffset * -0.875f; // probably needs to fix up the entire messed math to solve this correctly + + // __instance.scroller.YBounds.max = -(__instance.YStart - (float)(unlockedHats.Length / this.NumPerRow) * this.YOffset) - 3f; + // __instance.scroller.YBounds.max = YOffset * -0.875f; // probably needs to fix up the entire messed math to solve this correctly + __instance.scroller.YBounds.max = -(YOffset + 4.1f); return false; } } @@ -399,6 +456,11 @@ public static async Task FetchHats() { info.reshashb = current["reshashb"]?.ToString(); info.climbresource = sanitizeResourcePath(current["climbresource"]?.ToString()); info.reshashc = current["reshashc"]?.ToString(); + info.flipresource = sanitizeResourcePath(current["flipresource"]?.ToString()); + info.reshashf = current["reshashf"]?.ToString(); + info.backflipresource = sanitizeResourcePath(current["backflipresource"]?.ToString()); + info.reshashbf = current["reshashbf"]?.ToString(); + info.author = current["author"]?.ToString(); info.package = current["package"]?.ToString(); info.condition = current["condition"]?.ToString(); @@ -420,6 +482,10 @@ public static async Task FetchHats() { markedfordownload.Add(data.backresource); if (data.climbresource != null && doesResourceRequireDownload(filePath + data.climbresource, data.reshashc, md5)) markedfordownload.Add(data.climbresource); + if (data.flipresource != null && doesResourceRequireDownload(filePath + data.flipresource, data.reshashf, md5)) + markedfordownload.Add(data.flipresource); + if (data.backflipresource != null && doesResourceRequireDownload(filePath + data.backflipresource, data.reshashbf, md5)) + markedfordownload.Add(data.backflipresource); } foreach(var file in markedfordownload) { @@ -455,11 +521,16 @@ public class CustomHatOnline : CustomHats.CustomHat { public string reshasha { get; set;} public string reshashb { get; set;} public string reshashc { get; set;} + public string reshashf { get; set;} + public string reshashbf { get; set;} } } public static class CustomHatExtensions { public static CustomHats.HatExtension getHatExtension(this HatBehaviour hat) { CustomHats.HatExtension ret = null; + if (CustomHats.TestExt != null && CustomHats.TestExt.condition.Equals(hat.name)) { + return CustomHats.TestExt; + } CustomHats.CustomHatRegistry.TryGetValue(hat.name, out ret); return ret; } diff --git a/Source Code/CustomOptions.cs b/Source Code/CustomOptions.cs index 9fa798601..1d26fd815 100644 --- a/Source Code/CustomOptions.cs +++ b/Source Code/CustomOptions.cs @@ -12,14 +12,15 @@ namespace TheOtherRoles { public class CustomOptionHolder { public static string[] rates = new string[]{"0%", "10%", "20%", "30%", "40%", "50%", "60%", "70%", "80%", "90%", "100%"}; - public static string[] crewmateAndNeutralRoleCaps = new string[]{"0", "0-1", "1", "1-2", "2", "2-3", "3", "3-4", "4", "4-5", "5", "5-6", "6", "6-7", "7", "7-8", "8", "8-9", "9", "9-10", "10", "10-11", "11", "11-12", "12", "12-13", "13", "13-14", "14", "14-15", "15"}; - public static string[] impostorRoleCaps = new string[]{"0", "0-1", "1", "1-2", "2", "2-3", "3"}; public static string[] presets = new string[]{"Preset 1", "Preset 2", "Preset 3", "Preset 4", "Preset 5"}; public static CustomOption presetSelection; - public static CustomOption crewmateRolesCount; - public static CustomOption neutralRolesCount; - public static CustomOption impostorRolesCount; + public static CustomOption crewmateRolesCountMin; + public static CustomOption crewmateRolesCountMax; + public static CustomOption neutralRolesCountMin; + public static CustomOption neutralRolesCountMax; + public static CustomOption impostorRolesCountMin; + public static CustomOption impostorRolesCountMax; public static CustomOption mafiaSpawnRate; public static CustomOption janitorCooldown; @@ -160,9 +161,13 @@ public static void Load() { // Role Options presetSelection = CustomOption.Create(0, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Preset"), presets, null, true); - crewmateRolesCount = CustomOption.Create(1, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Number Of Crewmate Roles"), crewmateAndNeutralRoleCaps, null, true); - neutralRolesCount = CustomOption.Create(7, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Number Of Neutral Roles"), crewmateAndNeutralRoleCaps); - impostorRolesCount = CustomOption.Create(2, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Number Of Impostor Roles"), impostorRoleCaps); + // Using new id's for the options to not break compatibilty with older versions + crewmateRolesCountMin = CustomOption.Create(300, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Minimum Crewmate Roles"), 0f, 0f, 15f, 1f, null, true); + crewmateRolesCountMax = CustomOption.Create(301, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Crewmate Roles"), 0f, 0f, 15f, 1f); + neutralRolesCountMin = CustomOption.Create(302, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Minimum Neutral Roles"), 0f, 0f, 15f, 1f); + neutralRolesCountMax = CustomOption.Create(303, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Neutral Roles"), 0f, 0f, 15f, 1f); + impostorRolesCountMin = CustomOption.Create(304, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Minimum Impostor Roles"), 0f, 0f, 3f, 1f); + impostorRolesCountMax = CustomOption.Create(305, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Impostor Roles"), 0f, 0f, 3f, 1f); mafiaSpawnRate = CustomOption.Create(10, cs(Janitor.color, "Mafia"), rates, null, true); janitorCooldown = CustomOption.Create(11, "Janitor Cooldown", 30f, 10f, 60f, 2.5f, mafiaSpawnRate); @@ -545,9 +550,37 @@ private static IEnumerable TargetMethods() { private static void Postfix(ref string __result) { StringBuilder sb = new StringBuilder(__result); - foreach (CustomOption option in CustomOption.options) - if (option.parent == null) - sb.AppendLine($"{option.name}: {option.selections[option.selection].ToString()}"); + foreach (CustomOption option in CustomOption.options) { + if (option.parent == null) { + if (option == CustomOptionHolder.crewmateRolesCountMin) { + var optionName = CustomOptionHolder.cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Crewmate Roles"); + var min = CustomOptionHolder.crewmateRolesCountMin.getSelection(); + var max = CustomOptionHolder.crewmateRolesCountMax.getSelection(); + if (min > max) min = max; + var optionValue = (min == max) ? $"{max}" : $"{min} - {max}"; + sb.AppendLine($"{optionName}: {optionValue}"); + } else if (option == CustomOptionHolder.neutralRolesCountMin) { + var optionName = CustomOptionHolder.cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Neutral Roles"); + var min = CustomOptionHolder.neutralRolesCountMin.getSelection(); + var max = CustomOptionHolder.neutralRolesCountMax.getSelection(); + if (min > max) min = max; + var optionValue = (min == max) ? $"{max}" : $"{min} - {max}"; + sb.AppendLine($"{optionName}: {optionValue}"); + } else if (option == CustomOptionHolder.impostorRolesCountMin) { + var optionName = CustomOptionHolder.cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Impostor Roles"); + var min = CustomOptionHolder.impostorRolesCountMin.getSelection(); + var max = CustomOptionHolder.impostorRolesCountMax.getSelection(); + if (min > max) min = max; + var optionValue = (min == max) ? $"{max}" : $"{min} - {max}"; + sb.AppendLine($"{optionName}: {optionValue}"); + } else if ((option == CustomOptionHolder.crewmateRolesCountMax) || (option == CustomOptionHolder.neutralRolesCountMax) || (option == CustomOptionHolder.impostorRolesCountMax)) { + continue; + } else { + sb.AppendLine($"{option.name}: {option.selections[option.selection].ToString()}"); + } + + } + } CustomOption parent = null; foreach (CustomOption option in CustomOption.options) if (option.parent != null) { diff --git a/Source Code/Main.cs b/Source Code/Main.cs index 3420c3d88..0fe7b20ef 100644 --- a/Source Code/Main.cs +++ b/Source Code/Main.cs @@ -19,7 +19,7 @@ namespace TheOtherRoles public class TheOtherRolesPlugin : BasePlugin { public const string Id = "me.eisbison.theotherroles"; - public const string VersionString = "2.6.2"; + public const string VersionString = "2.6.3"; public static System.Version Version = System.Version.Parse(VersionString); public Harmony Harmony { get; } = new Harmony(Id); diff --git a/Source Code/ModUpdater.cs b/Source Code/ModUpdater.cs index 102ff0aed..bbb54e6a5 100644 --- a/Source Code/ModUpdater.cs +++ b/Source Code/ModUpdater.cs @@ -1,4 +1,3 @@ -extern alias Il2CppNewtonsoft; using System; using BepInEx; using BepInEx.Configuration; @@ -22,8 +21,8 @@ using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Security.Cryptography; -using Il2CppNewtonsoft::Newtonsoft.Json.Linq; -using Il2CppNewtonsoft::Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; using Twitch; namespace TheOtherRoles { diff --git a/Source Code/PlayerControlPatch.cs b/Source Code/PlayerControlPatch.cs index f0a736ffc..4aad0dd07 100644 --- a/Source Code/PlayerControlPatch.cs +++ b/Source Code/PlayerControlPatch.cs @@ -365,23 +365,32 @@ public static void updatePlayerInfo() { string roleNames = String.Join(", ", RoleInfo.getRoleInfoForPlayer(p).Select(x => Helpers.cs(x.color, x.name)).ToArray()); string taskInfo = tasksTotal > 0 ? $"({tasksCompleted}/{tasksTotal})" : ""; - string info = ""; + string playerInfoText = ""; + string meetingInfoText =""; if (p == PlayerControl.LocalPlayer) { - info = $"{roleNames}"; + playerInfoText = $"{roleNames}"; if (DestroyableSingleton.InstanceExists) { TMPro.TextMeshPro tabText = DestroyableSingleton.Instance.tab.transform.FindChild("TabText_TMP").GetComponent(); tabText.SetText($"Tasks {taskInfo}"); } - } else if (MapOptions.ghostsSeeRoles && MapOptions.ghostsSeeTasks) - info = $"{roleNames} {taskInfo}".Trim(); - else if (MapOptions.ghostsSeeTasks) - info = $"{taskInfo}".Trim(); - else if (MapOptions.ghostsSeeRoles) - info = $"{roleNames}"; - - playerInfo.text = info; + meetingInfoText = $"{roleNames} {taskInfo}".Trim(); + } + else if (MapOptions.ghostsSeeRoles && MapOptions.ghostsSeeTasks) { + playerInfoText = $"{roleNames} {taskInfo}".Trim(); + meetingInfoText = playerInfoText; + } + else if (MapOptions.ghostsSeeTasks) { + playerInfoText = $"{taskInfo}".Trim(); + meetingInfoText = playerInfoText; + } + else if (MapOptions.ghostsSeeRoles) { + playerInfoText = $"{roleNames}"; + meetingInfoText = playerInfoText; + } + + playerInfo.text = playerInfoText; playerInfo.gameObject.SetActive(p.Visible); - if (meetingInfo != null) meetingInfo.text = MeetingHud.Instance.state == MeetingHud.VoteStates.Results ? "" : info; + if (meetingInfo != null) meetingInfo.text = MeetingHud.Instance.state == MeetingHud.VoteStates.Results ? "" : meetingInfoText; } } diff --git a/Source Code/Resources/Banner.png b/Source Code/Resources/Banner.png new file mode 100644 index 0000000000000000000000000000000000000000..73d63decd86cc70f6929dd6d07ea14856d153fb2 GIT binary patch literal 28723 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 playerList) { - var index = rnd.Next(0, playerList.Count); - byte playerId = playerList[index].PlayerId; - playerList.RemoveAt(index); - - setRoleToPlayer(roleId, playerId); - } - - private static void setRoleToPlayer(byte roleId, byte playerId) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetRole, Hazel.SendOption.Reliable, -1); - writer.Write(roleId); - writer.Write(playerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.setRole(roleId, playerId); - } - public static void Postfix([HarmonyArgument(0)]Il2CppReferenceArray infected) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ResetVaribles, Hazel.SendOption.Reliable, -1); AmongUsClient.Instance.FinishRpcImmediately(writer); RPCProcedure.resetVariables(); + assignRoles(); + } + + private static void assignRoles() { + var data = getRoleAssignmentData(); + assignSpecialRoles(data); // Assign special roles like mafia and lovers first as they assign a role to multiple players and the chances are independent of the ticket system + assignEnsuredRoles(data); // Assign roles that should always be in the game next + assignChanceRoles(data); // Assign roles that may or may not be in the game last + } + + private static RoleAssignmentData getRoleAssignmentData() { + // Get the players that we want to assign the roles to. Crewmate and Neutral roles are assigned to natural crewmates. Impostor roles to impostors. List crewmates = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList(); crewmates.RemoveAll(x => x.Data.IsImpostor); List impostors = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList(); impostors.RemoveAll(x => !x.Data.IsImpostor); - float crewCountSettings = (float)CustomOptionHolder.crewmateRolesCount.getSelection() / 2; - float neutralCountSettings = (float)CustomOptionHolder.neutralRolesCount.getSelection() / 2; - float impCountSettings = (float)CustomOptionHolder.impostorRolesCount.getSelection() / 2; + var crewmateMin = CustomOptionHolder.crewmateRolesCountMin.getSelection(); + var crewmateMax = CustomOptionHolder.crewmateRolesCountMax.getSelection(); + var neutralMin = CustomOptionHolder.neutralRolesCountMin.getSelection(); + var neutralMax = CustomOptionHolder.neutralRolesCountMax.getSelection(); + var impostorMin = CustomOptionHolder.impostorRolesCountMin.getSelection(); + var impostorMax = CustomOptionHolder.impostorRolesCountMax.getSelection(); + + // Make sure min is less or equal to max + if (crewmateMin > crewmateMax) crewmateMin = crewmateMax; + if (neutralMin > neutralMax) neutralMin = neutralMax; + if (impostorMin > impostorMax) impostorMin = impostorMax; - if (crewCountSettings % 1 == 0.5f) crewCountSettings += 0.5f * (rnd.Next(2) * 2 - 1); - if (neutralCountSettings % 1 == 0.5f) neutralCountSettings += 0.5f * (rnd.Next(2) * 2 - 1); - if (impCountSettings % 1 == 0.5f) impCountSettings += 0.5f * (rnd.Next(2) * 2 - 1); + // Get the maximum allowed count of each role type based on the minimum and maximum option + int crewCountSettings = rnd.Next(crewmateMin, crewmateMax + 1); + int neutralCountSettings = rnd.Next(neutralMin, neutralMax + 1); + int impCountSettings = rnd.Next(impostorMin, impostorMax + 1); - int maxCrewmateRoles = Mathf.Min(crewmates.Count, Mathf.RoundToInt(crewCountSettings)); - int maxNeutralRoles = Mathf.Min(crewmates.Count, Mathf.RoundToInt(neutralCountSettings)); - int maxImpostorRoles = Mathf.Min(impostors.Count, Mathf.RoundToInt(impCountSettings)); + // Potentially lower the actual maximum to the assignable players + int maxCrewmateRoles = Mathf.Min(crewmates.Count, crewCountSettings); + int maxNeutralRoles = Mathf.Min(crewmates.Count, neutralCountSettings); + int maxImpostorRoles = Mathf.Min(impostors.Count, impCountSettings); + // Fill in the lists with the roles that should be assigned to players. Note that the special roles (like Mafia or Lovers) are NOT included in these lists Dictionary impSettings = new Dictionary(); Dictionary neutralSettings = new Dictionary(); Dictionary crewSettings = new Dictionary(); @@ -81,159 +88,190 @@ public static void Postfix([HarmonyArgument(0)]Il2CppReferenceArray 1) // Spy is useless with less than 2 Impostors + if (impostors.Count > 1) { + // Only add Spy if more than 1 impostor as the spy role is otherwise useless crewSettings.Add((byte)RoleId.Spy, CustomOptionHolder.spySpawnRate.getSelection()); + } crewSettings.Add((byte)RoleId.SecurityGuard, CustomOptionHolder.securityGuardSpawnRate.getSelection()); - // Set special roles - if (impostors.Count >= 3 && maxImpostorRoles >= 3 && (rnd.Next(1, 101) <= CustomOptionHolder.mafiaSpawnRate.getSelection() * 10)) { - setRoleToRandomPlayer((byte)RoleId.Godfather, impostors); - setRoleToRandomPlayer((byte)RoleId.Janitor, impostors); - setRoleToRandomPlayer((byte)RoleId.Mafioso, impostors); - maxImpostorRoles -= 3; + return new RoleAssignmentData { + crewmates = crewmates, + impostors = impostors, + crewSettings = crewSettings, + neutralSettings = neutralSettings, + impSettings = impSettings, + maxCrewmateRoles = maxCrewmateRoles, + maxNeutralRoles = maxNeutralRoles, + maxImpostorRoles = maxImpostorRoles + }; + } + + private static void assignSpecialRoles(RoleAssignmentData data) { + // Assign Mafia + if (data.impostors.Count >= 3 && data.maxImpostorRoles >= 3 && (rnd.Next(1, 101) <= CustomOptionHolder.mafiaSpawnRate.getSelection() * 10)) { + setRoleToRandomPlayer((byte)RoleId.Godfather, data.impostors); + setRoleToRandomPlayer((byte)RoleId.Janitor, data.impostors); + setRoleToRandomPlayer((byte)RoleId.Mafioso, data.impostors); + data.maxImpostorRoles -= 3; } + // Assign Lovers if (rnd.Next(1, 101) <= CustomOptionHolder.loversSpawnRate.getSelection() * 10) { - if (impostors.Count > 0 && crewmates.Count > 0 && maxCrewmateRoles > 0 && maxImpostorRoles > 0 && rnd.Next(1, 101) <= CustomOptionHolder.loversImpLoverRate.getSelection() * 10) { - setRoleToRandomPlayer((byte)RoleId.Lover1, impostors); - setRoleToRandomPlayer((byte)RoleId.Lover2, crewmates); - maxCrewmateRoles--; - maxImpostorRoles--; - } else if (crewmates.Count >= 2 && maxCrewmateRoles >= 2) { - setRoleToRandomPlayer((byte)RoleId.Lover1, crewmates); - setRoleToRandomPlayer((byte)RoleId.Lover2, crewmates); - maxCrewmateRoles -= 2; + if (data.impostors.Count > 0 && data.crewmates.Count > 0 && data.maxCrewmateRoles > 0 && data.maxImpostorRoles > 0 && rnd.Next(1, 101) <= CustomOptionHolder.loversImpLoverRate.getSelection() * 10) { + setRoleToRandomPlayer((byte)RoleId.Lover1, data.impostors); + setRoleToRandomPlayer((byte)RoleId.Lover2, data.crewmates); + data.maxCrewmateRoles--; + data.maxImpostorRoles--; + } else if (data.crewmates.Count >= 2 && data.maxCrewmateRoles >= 2) { + setRoleToRandomPlayer((byte)RoleId.Lover1, data.crewmates); + setRoleToRandomPlayer((byte)RoleId.Lover2, data.crewmates); + data.maxCrewmateRoles -= 2; } } + // Assign Child if (rnd.Next(1, 101) <= CustomOptionHolder.childSpawnRate.getSelection() * 10) { - if (impostors.Count > 0 && maxImpostorRoles > 0 && rnd.Next(1, 101) <= 33) { - setRoleToRandomPlayer((byte)RoleId.Child, impostors); - maxImpostorRoles--; - } else if (crewmates.Count > 0 && maxCrewmateRoles > 0) { - setRoleToRandomPlayer((byte)RoleId.Child, crewmates); - maxCrewmateRoles--; + if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && rnd.Next(1, 101) <= 33) { + setRoleToRandomPlayer((byte)RoleId.Child, data.impostors); + data.maxImpostorRoles--; + } else if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) { + setRoleToRandomPlayer((byte)RoleId.Child, data.crewmates); + data.maxCrewmateRoles--; } } + } - // Set tickets and always active roles - - List crewTickets = new List(); - List neutralTickets = new List(); - List impTickets = new List(); - - for (int i = 0; i < neutralSettings.Count; i++) { - var entry = neutralSettings.ElementAt(i); - if (entry.Value == 0) { // Never - } else if (entry.Value == 10) { // Always - if (crewmates.Count > 0 && maxNeutralRoles > 0) { - setRoleToRandomPlayer(entry.Key, crewmates); - maxNeutralRoles--; - if (CustomOptionHolder.blockedRolePairings.ContainsKey(entry.Key)) { - foreach(var blockedRoleId in CustomOptionHolder.blockedRolePairings[entry.Key]) { - if(impSettings.ContainsKey(blockedRoleId)) impSettings[blockedRoleId] = 0; - if (neutralSettings.ContainsKey(blockedRoleId)) neutralSettings[blockedRoleId] = 0; - if(crewSettings.ContainsKey(blockedRoleId)) crewSettings[blockedRoleId] = 0; - } - } - } - } else { // Other - for (int j = 0; j < entry.Value; j++) neutralTickets.Add(entry.Key); - } - } + private static void assignEnsuredRoles(RoleAssignmentData data) { + // Get all roles where the chance to occur is set to 100% + List ensuredCrewmateRoles = data.crewSettings.Where(x => x.Value == 10).Select(x => x.Key).ToList(); + List ensuredNeutralRoles = data.neutralSettings.Where(x => x.Value == 10).Select(x => x.Key).ToList(); + List ensuredImpostorRoles = data.impSettings.Where(x => x.Value == 10).Select(x => x.Key).ToList(); + + // Assign roles until we run out of either players we can assign roles to or run out of roles we can assign to players + while ( + (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && ensuredImpostorRoles.Count > 0) || + (data.crewmates.Count > 0 && ( + (data.maxCrewmateRoles > 0 && ensuredCrewmateRoles.Count > 0) || + (data.maxNeutralRoles > 0 && ensuredNeutralRoles.Count > 0) + ))) { + + Dictionary> rolesToAssign = new Dictionary>(); + if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0 && ensuredCrewmateRoles.Count > 0) rolesToAssign.Add(RoleType.Crewmate, ensuredCrewmateRoles); + if (data.crewmates.Count > 0 && data.maxNeutralRoles > 0 && ensuredNeutralRoles.Count > 0) rolesToAssign.Add(RoleType.Neutral, ensuredNeutralRoles); + if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && ensuredImpostorRoles.Count > 0) rolesToAssign.Add(RoleType.Impostor, ensuredImpostorRoles); + + // Randomly select a pool of roles to assign a role from next (Crewmate role, Neutral role or Impostor role) + // then select one of the roles from the selected pool to a player + // and remove the role (and any potentially blocked role pairings) from the pool(s) + var roleType = rolesToAssign.Keys.ElementAt(rnd.Next(0, rolesToAssign.Keys.Count())); + var players = roleType == RoleType.Crewmate || roleType == RoleType.Neutral ? data.crewmates : data.impostors; + var index = rnd.Next(0, rolesToAssign[roleType].Count); + var roleId = rolesToAssign[roleType][index]; + setRoleToRandomPlayer(rolesToAssign[roleType][index], players); + rolesToAssign[roleType].RemoveAt(index); - for (int i = 0; i < crewSettings.Count; i++) { - var entry = crewSettings.ElementAt(i); - if (entry.Value == 0) { // Never - } else if (entry.Value == 10) { // Always - if (crewmates.Count > 0 && maxCrewmateRoles > 0) { - setRoleToRandomPlayer(entry.Key, crewmates); - maxCrewmateRoles--; - if(CustomOptionHolder.blockedRolePairings.ContainsKey(entry.Key)) { - foreach(var blockedRoleId in CustomOptionHolder.blockedRolePairings[entry.Key]) { - if (impSettings.ContainsKey(blockedRoleId)) impSettings[blockedRoleId] = 0; - if (neutralSettings.ContainsKey(blockedRoleId)) neutralSettings[blockedRoleId] = 0; - if (crewSettings.ContainsKey(blockedRoleId)) crewSettings[blockedRoleId] = 0; - } + if (CustomOptionHolder.blockedRolePairings.ContainsKey(roleId)) { + foreach(var blockedRoleId in CustomOptionHolder.blockedRolePairings[roleId]) { + // Set chance for the blocked roles to 0 for chances less than 100% + if (data.impSettings.ContainsKey(blockedRoleId)) data.impSettings[blockedRoleId] = 0; + if (data.neutralSettings.ContainsKey(blockedRoleId)) data.neutralSettings[blockedRoleId] = 0; + if (data.crewSettings.ContainsKey(blockedRoleId)) data.crewSettings[blockedRoleId] = 0; + // Remove blocked roles even if the chance was 100% + foreach(var ensuredRolesList in rolesToAssign.Values) { + ensuredRolesList.RemoveAll(x => x == blockedRoleId); } } - } else { // Other - for (int j = 0; j < entry.Value; j++) crewTickets.Add(entry.Key); } - } - for (int i = 0; i < impSettings.Count; i++) { - var entry = impSettings.ElementAt(i); - if (entry.Value == 0) { // Never - } else if (entry.Value == 10) { // Always - if (impostors.Count > 0 && maxImpostorRoles > 0) { - setRoleToRandomPlayer(entry.Key, impostors); - if(CustomOptionHolder.blockedRolePairings.ContainsKey(entry.Key)) { - foreach(var blockedRoleId in CustomOptionHolder.blockedRolePairings[entry.Key]) { - if (impSettings.ContainsKey(blockedRoleId)) impSettings[blockedRoleId] = 0; - if (neutralSettings.ContainsKey(blockedRoleId)) neutralSettings[blockedRoleId] = 0; - if (crewSettings.ContainsKey(blockedRoleId)) crewSettings[blockedRoleId] = 0; - } - } - maxImpostorRoles--; - } - } else { // Other - for (int j = 0; j < entry.Value; j++) impTickets.Add(entry.Key); + // Adjust the role limit + switch (roleType) { + case RoleType.Crewmate: data.maxCrewmateRoles--; break; + case RoleType.Neutral: data.maxNeutralRoles--;break; + case RoleType.Impostor: data.maxImpostorRoles--;break; } } + } - // Set solo player roles + + private static void assignChanceRoles(RoleAssignmentData data) { + // Get all roles where the chance to occur is set grater than 0% but not 100% and build a ticket pool based on their weight + List crewmateTickets = data.crewSettings.Where(x => x.Value > 0 && x.Value < 10).Select(x => Enumerable.Repeat(x.Key, x.Value)).SelectMany(x => x).ToList(); + List neutralTickets = data.neutralSettings.Where(x => x.Value > 0 && x.Value < 10).Select(x => Enumerable.Repeat(x.Key, x.Value)).SelectMany(x => x).ToList(); + List impostorTickets = data.impSettings.Where(x => x.Value > 0 && x.Value < 10).Select(x => Enumerable.Repeat(x.Key, x.Value)).SelectMany(x => x).ToList(); - for (int i = 0; i < maxNeutralRoles; i++) { - if (neutralTickets.Count > 0 && crewmates.Count > 0) { - var index = rnd.Next(0, neutralTickets.Count); - byte roleId = neutralTickets[index]; - neutralTickets.RemoveAll(x => x == roleId); - setRoleToRandomPlayer(roleId, crewmates); + // Assign roles until we run out of either players we can assign roles to or run out of roles we can assign to players + while ( + (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && impostorTickets.Count > 0) || + (data.crewmates.Count > 0 && ( + (data.maxCrewmateRoles > 0 && crewmateTickets.Count > 0) || + (data.maxNeutralRoles > 0 && neutralTickets.Count > 0) + ))) { + + Dictionary> rolesToAssign = new Dictionary>(); + if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0 && crewmateTickets.Count > 0) rolesToAssign.Add(RoleType.Crewmate, crewmateTickets); + if (data.crewmates.Count > 0 && data.maxNeutralRoles > 0 && neutralTickets.Count > 0) rolesToAssign.Add(RoleType.Neutral, neutralTickets); + if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && impostorTickets.Count > 0) rolesToAssign.Add(RoleType.Impostor, impostorTickets); + + // Randomly select a pool of role tickets to assign a role from next (Crewmate role, Neutral role or Impostor role) + // then select one of the roles from the selected pool to a player + // and remove all tickets of this role (and any potentially blocked role pairings) from the pool(s) + var roleType = rolesToAssign.Keys.ElementAt(rnd.Next(0, rolesToAssign.Keys.Count())); + var players = roleType == RoleType.Crewmate || roleType == RoleType.Neutral ? data.crewmates : data.impostors; + var index = rnd.Next(0, rolesToAssign[roleType].Count); + var roleId = rolesToAssign[roleType][index]; + setRoleToRandomPlayer(rolesToAssign[roleType][index], players); + rolesToAssign[roleType].RemoveAll(x => x == roleId); - if (CustomOptionHolder.blockedRolePairings.ContainsKey(roleId)) { - foreach(var blockedRoleId in CustomOptionHolder.blockedRolePairings[roleId]) { - crewTickets.RemoveAll(x => x == blockedRoleId); - neutralTickets.RemoveAll(x => x == blockedRoleId); - impTickets.RemoveAll(x => x == blockedRoleId); - } + if (CustomOptionHolder.blockedRolePairings.ContainsKey(roleId)) { + foreach(var blockedRoleId in CustomOptionHolder.blockedRolePairings[roleId]) { + // Remove tickets of blocked roles from all pools + crewmateTickets.RemoveAll(x => x == blockedRoleId); + neutralTickets.RemoveAll(x => x == blockedRoleId); + impostorTickets.RemoveAll(x => x == blockedRoleId); } } - } - for (int i = 0; i < maxCrewmateRoles; i++) { - if (crewTickets.Count > 0 && crewmates.Count > 0) { - var index = rnd.Next(0, crewTickets.Count); - byte roleId = crewTickets[index]; - crewTickets.RemoveAll(x => x == roleId); - setRoleToRandomPlayer(roleId, crewmates); - - if (CustomOptionHolder.blockedRolePairings.ContainsKey(roleId)) { - foreach(var blockedRoleId in CustomOptionHolder.blockedRolePairings[roleId]) { - crewTickets.RemoveAll(x => x == blockedRoleId); - neutralTickets.RemoveAll(x => x == blockedRoleId); - impTickets.RemoveAll(x => x == blockedRoleId); - } - } + // Adjust the role limit + switch (roleType) { + case RoleType.Crewmate: data.maxCrewmateRoles--; break; + case RoleType.Neutral: data.maxNeutralRoles--;break; + case RoleType.Impostor: data.maxImpostorRoles--;break; } } + } - for (int i = 0; i < maxImpostorRoles; i++) { - if (impTickets.Count > 0 && impostors.Count > 0) { - var index = rnd.Next(0, impTickets.Count); - byte roleId = impTickets[index]; - impTickets.RemoveAll(x => x == roleId); - setRoleToRandomPlayer(roleId, impostors); - - if (CustomOptionHolder.blockedRolePairings.ContainsKey(roleId)) { - foreach(var blockedRoleId in CustomOptionHolder.blockedRolePairings[roleId]) { - crewTickets.RemoveAll(x => x == blockedRoleId); - neutralTickets.RemoveAll(x => x == blockedRoleId); - impTickets.RemoveAll(x => x == blockedRoleId); - } - } - } - } + private static void setRoleToRandomPlayer(byte roleId, List playerList) { + var index = rnd.Next(0, playerList.Count); + byte playerId = playerList[index].PlayerId; + playerList.RemoveAt(index); + + setRoleToPlayer(roleId, playerId); } + + private static void setRoleToPlayer(byte roleId, byte playerId) { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetRole, Hazel.SendOption.Reliable, -1); + writer.Write(roleId); + writer.Write(playerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.setRole(roleId, playerId); + } + + private class RoleAssignmentData { + public List crewmates {get;set;} + public List impostors {get;set;} + public Dictionary impSettings = new Dictionary(); + public Dictionary neutralSettings = new Dictionary(); + public Dictionary crewSettings = new Dictionary(); + public int maxCrewmateRoles {get;set;} + public int maxNeutralRoles {get;set;} + public int maxImpostorRoles {get;set;} + } + + private enum RoleType { + Crewmate = 0, + Neutral = 1, + Impostor = 2 + } + } } diff --git a/Source Code/TheOtherRoles.csproj b/Source Code/TheOtherRoles.csproj index 0e55cd61c..110f4a915 100644 --- a/Source Code/TheOtherRoles.csproj +++ b/Source Code/TheOtherRoles.csproj @@ -1,7 +1,7 @@ netstandard2.1 - 2.6.2 + 2.6.3 TheOtherRoles Eisbison @@ -27,15 +27,4 @@ - - - - - $(AmongUs)\BepInEx\unhollowed\Newtonsoft.Json.dll - Il2CppNewtonsoft - - - - $(NoWarn);MSB3243 - \ No newline at end of file