]> git.deb.at Git - rhonda/theotherroles.git/commitdiff
Version v2.6.3
authorthunderstorm584 <80977292+thunderstorm584@users.noreply.github.com>
Thu, 13 May 2021 16:25:46 +0000 (18:25 +0200)
committerthunderstorm584 <80977292+thunderstorm584@users.noreply.github.com>
Thu, 13 May 2021 16:25:46 +0000 (18:25 +0200)
README.md
Source Code/CredentialsPatch.cs
Source Code/CustomHats.cs
Source Code/CustomOptions.cs
Source Code/Main.cs
Source Code/ModUpdater.cs
Source Code/PlayerControlPatch.cs
Source Code/Resources/Banner.png [new file with mode: 0644]
Source Code/RoleAssignmentPatch.cs
Source Code/TheOtherRoles.csproj

index 1a893101f7b42805292d96b9efe1e781b6071a6d..949dce451e5377a331f5a3997bf9c8ab63b84fe0 100644 (file)
--- a/README.md
+++ b/README.md
@@ -33,6 +33,7 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein
 # Releases\r
 | Among Us - Version| Mod Version | Link |\r
 |----------|-------------|-----------------|\r
+| 2021.5.10s| v2.6.3| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.3/TheOtherRoles.zip)\r
 | 2021.5.10s| v2.6.2| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.2/TheOtherRoles.zip)\r
 | 2021.4.14s| v2.6.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.1/TheOtherRoles.zip)\r
 | 2021.4.14s| v2.6.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.0/TheOtherRoles.zip)\r
@@ -61,6 +62,10 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein
 <details>\r
   <summary>Click to show the Changelog</summary>\r
 \r
+**Version 2.6.3**\r
+- Changed the role limits options to allow for minimum and maximum bounds\r
+- Changed the role assignment to be more random when assigning roles (previously assigned the neutral roles before assigning the crewmate roles)\r
+\r
 **Version 2.6.2**\r
 - The Other Roles now supports the new Among Us version **2021.5.10s**\r
 - Added a chat command to kick players as the host of a lobby (/kick playerName)\r
@@ -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:\r
 - First all roles that are set to 100% are being assigned to arbitrary players\r
 - 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.\r
-- 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.\r
+- 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.\r
 \r
 **Example:**\\r
 Settings: 2 special Crewmate roles, Snitch: 100%, Hacker: 10%, Tracker: 30%\\r
index df950a2b8e5c0a515512bede13d6c7af83040c4b..a7e9e687f78aa898bf6a5621503e07c3db89e162 100644 (file)
@@ -11,39 +11,72 @@ namespace TheOtherRoles
     [HarmonyPatch]
     public static class CredentialsPatch {
         public static string fullCredentials = 
-$@"<color=#FCCE03FF>TheOtherRoles</color> v{TheOtherRolesPlugin.Version.ToString()}:
-- Modded by <color=#FCCE03FF>Eisbison</color>,
-  <color=#FFEB91FF>Thunderstorm584</color> and <color=#FFEB91FF>EndOfFile</color>
-- Balanced with <color=#FFEB91FF>Dhalucard</color>
-- Button design by <color=#FFEB91FF>Bavari</color>";
+$@"<size=130%><color=#ff351f>TheOtherRoles</color></size> v{TheOtherRolesPlugin.Version.ToString()}
+<size=80%>Modded by <color=#FCCE03FF>Eisbison</color>,
+<color=#FCCE03FF>Thunderstorm584</color> & <color=#FCCE03FF>EndOfFile</color>
+Balanced with <color=#FCCE03FF>Dhalucard</color>
+Button design by <color=#FCCE03FF>Bavari</color></size>";
+
+    public static string mainMenuCredentials = 
+$@"Modded by <color=#FCCE03FF>Eisbison</color>, <color=#FCCE03FF>Thunderstorm584</color> & <color=#FCCE03FF>EndOfFile</color>
+Balanced with <color=#FCCE03FF>Dhalucard</color>      Design by <color=#FCCE03FF>Bavari</color>";
 
         [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<TMPro.TextMeshPro>(__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<TMPro.TextMeshPro>(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 = "<color=#FCCE03FF>TheOtherRoles</color>\nModded by <color=#FCCE03FF>Eisbison</color>\n" + __instance.text.text;
-                    __instance.transform.localPosition = new Vector3(2.583f, 2.675f, __instance.transform.localPosition.z);
+                    __instance.text.text = $"<size=130%><color=#ff351f>TheOtherRoles</color></size> 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<SpriteRenderer>();
+                renderer.sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Banner.png", 300f);                                
+            }
+        }
     }
 }
index 3a74d8d339beef2a9b885dc644eed7924842c516..8345329598eac8fa881d5ba9242d408c1cabb6c2 100644 (file)
@@ -1,4 +1,3 @@
-extern alias Il2CppNewtonsoft;
 using System;
 using BepInEx;
 using BepInEx.Configuration;
@@ -19,8 +18,8 @@ using System.Runtime.CompilerServices;
 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 @@ namespace TheOtherRoles {
         public static Material hatShader;
 
         public static Dictionary<string, HatExtension> CustomHatRegistry = new Dictionary<string, HatExtension>();
+        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 @@ namespace TheOtherRoles {
             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 @@ namespace TheOtherRoles {
         private static List<CustomHat> createCustomHatDetails(string[] hats, bool fromDisk = false) {
             Dictionary<string, CustomHat> fronts = new Dictionary<string, CustomHat>();
             Dictionary<string, string> backs = new Dictionary<string, string>();
+            Dictionary<string, string> flips = new Dictionary<string, string>();
+            Dictionary<string, string> backflips = new Dictionary<string, string>();
             Dictionary<string, string> climbs = new Dictionary<string, string>();
 
             for (int i = 0; i < hats.Length; i++) {
@@ -68,11 +74,15 @@ namespace TheOtherRoles {
                 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 @@ namespace TheOtherRoles {
 
             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 @@ namespace TheOtherRoles {
             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 @@ namespace TheOtherRoles {
                 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 @@ namespace TheOtherRoles {
             }
         }
 
+        [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 @@ namespace TheOtherRoles {
                     List<System.Tuple<HatBehaviour, HatExtension>> 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 @@ namespace TheOtherRoles {
                         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 @@ namespace TheOtherRoles {
                         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 @@ namespace TheOtherRoles {
             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;
         }
index 9fa79860144acc0db40db2c3a6fb6e00cadab2cc..1d26fd815d1ebb157212503f9f101143fadb28d7 100644 (file)
@@ -12,14 +12,15 @@ using static TheOtherRoles.TheOtherRoles;
 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 @@ namespace TheOtherRoles {
             // 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 @@ namespace TheOtherRoles {
         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) {
index 3420c3d88e8543c6122d30844226dcd11b412a09..0fe7b20efed1ff7d4419b369eaa468d7d469a803 100644 (file)
@@ -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);
index 102ff0aed6496c40258d5d7677d421c8d7248b1d..bbb54e6a5e39cacaeb774bbc8426de93282fdf84 100644 (file)
@@ -1,4 +1,3 @@
-extern alias Il2CppNewtonsoft;
 using System;
 using BepInEx;
 using BepInEx.Configuration;
@@ -22,8 +21,8 @@ using System.Runtime.CompilerServices;
 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 {
index f0a736ffc076e3faa5ee276cdb60c74df7134ae6..4aad0dd07dd407a2919c0d0deb9f420f1a42abc8 100644 (file)
@@ -365,23 +365,32 @@ namespace TheOtherRoles {
                 string roleNames = String.Join(", ", RoleInfo.getRoleInfoForPlayer(p).Select(x => Helpers.cs(x.color, x.name)).ToArray());
                 string taskInfo = tasksTotal > 0 ? $"<color=#FAD934FF>({tasksCompleted}/{tasksTotal})</color>" : "";
                 
-                string info = "";
+                string playerInfoText = "";
+                string meetingInfoText ="";
                 if (p == PlayerControl.LocalPlayer) {
-                    info = $"{roleNames}";
+                    playerInfoText = $"{roleNames}";
                     if (DestroyableSingleton<TaskPanelBehaviour>.InstanceExists) {
                         TMPro.TextMeshPro tabText = DestroyableSingleton<TaskPanelBehaviour>.Instance.tab.transform.FindChild("TabText_TMP").GetComponent<TMPro.TextMeshPro>();
                         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 (file)
index 0000000..73d63de
Binary files /dev/null and b/Source Code/Resources/Banner.png differ
index 34a4dbb193e31f7528ba0032c91e58335695748a..51aea509548839ec8ee77a25ba83500614748d63 100644 (file)
@@ -13,45 +13,52 @@ namespace TheOtherRoles
     class SetInfectedPatch
     {
 
-        private static void setRoleToRandomPlayer(byte roleId, List<PlayerControl> 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<GameData.PlayerInfo> 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<PlayerControl> crewmates = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList();
             crewmates.RemoveAll(x => x.Data.IsImpostor);
             List<PlayerControl> 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<byte, int> impSettings = new Dictionary<byte, int>();
             Dictionary<byte, int> neutralSettings = new Dictionary<byte, int>();
             Dictionary<byte, int> crewSettings = new Dictionary<byte, int>();
@@ -81,159 +88,190 @@ namespace TheOtherRoles
             crewSettings.Add((byte)RoleId.Hacker, CustomOptionHolder.hackerSpawnRate.getSelection());
             crewSettings.Add((byte)RoleId.Tracker, CustomOptionHolder.trackerSpawnRate.getSelection());
             crewSettings.Add((byte)RoleId.Snitch, CustomOptionHolder.snitchSpawnRate.getSelection());
-            if (impostors.Count > 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<byte> crewTickets = new List<byte>();
-            List<byte> neutralTickets = new List<byte>();
-            List<byte> impTickets = new List<byte>();
-
-            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<byte> ensuredCrewmateRoles = data.crewSettings.Where(x => x.Value == 10).Select(x => x.Key).ToList();
+            List<byte> ensuredNeutralRoles = data.neutralSettings.Where(x => x.Value == 10).Select(x => x.Key).ToList();
+            List<byte> 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<RoleType, List<byte>> rolesToAssign = new Dictionary<RoleType, List<byte>>();
+                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<byte> crewmateTickets = data.crewSettings.Where(x => x.Value > 0 && x.Value < 10).Select(x => Enumerable.Repeat(x.Key, x.Value)).SelectMany(x => x).ToList();
+            List<byte> neutralTickets = data.neutralSettings.Where(x => x.Value > 0 && x.Value < 10).Select(x => Enumerable.Repeat(x.Key, x.Value)).SelectMany(x => x).ToList();
+            List<byte> 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<RoleType, List<byte>> rolesToAssign = new Dictionary<RoleType, List<byte>>();
+                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<PlayerControl> 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<PlayerControl> crewmates {get;set;}
+            public List<PlayerControl> impostors {get;set;}
+            public Dictionary<byte, int> impSettings = new Dictionary<byte, int>();
+            public Dictionary<byte, int> neutralSettings = new Dictionary<byte, int>();
+            public Dictionary<byte, int> crewSettings = new Dictionary<byte, int>();
+            public int maxCrewmateRoles {get;set;}
+            public int maxNeutralRoles {get;set;}
+            public int maxImpostorRoles {get;set;}
+        }
+        
+        private enum RoleType {
+            Crewmate = 0,
+            Neutral = 1,
+            Impostor = 2
+        }
+
     }
 }
index 0e55cd61c5cac455d146c7ee52190e23731d6019..110f4a91508417768ba1276a6c9976b498ae1133 100644 (file)
@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
     <PropertyGroup>
         <TargetFramework>netstandard2.1</TargetFramework>
-        <Version>2.6.2</Version>
+        <Version>2.6.3</Version>
         <Description>TheOtherRoles</Description>
         <Authors>Eisbison</Authors>
     </PropertyGroup>
         <Message Text="Second occurrence" />
         <Copy SourceFiles="$(ProjectDir)\bin\$(Configuration)\netstandard2.1\TheOtherRoles.dll" DestinationFolder="$(AmongUs)/BepInEx/plugins/" />
     </Target>
-
-    <!-- il2cpp newtonsoft -->
-       <ItemGroup>
-               <Reference Include="Newtonsoft.Json, Version=3.7.1.6">
-                       <HintPath>$(AmongUs)\BepInEx\unhollowed\Newtonsoft.Json.dll</HintPath>
-                       <Aliases>Il2CppNewtonsoft</Aliases>
-               </Reference>
-       </ItemGroup>
-    <PropertyGroup>
-        <NoWarn>$(NoWarn);MSB3243</NoWarn>
-    </PropertyGroup>
 </Project>
\ No newline at end of file