private static void assignRoleTargets(RoleAssignmentData data)
 {
     // Set Lawyer Target
     if (Lawyer.lawyer != null)
     {
         var possibleTargets = new List <PlayerControl>();
         foreach (PlayerControl p in PlayerControl.AllPlayerControls)
         {
             if (!p.Data.IsDead && !p.Data.Disconnected && !p.isLovers() && (p.Data.Role.IsImpostor || p == Jackal.jackal))
             {
                 possibleTargets.Add(p);
             }
         }
         if (possibleTargets.Count == 0)
         {
             MessageWriter w = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.LawyerPromotesToPursuer, Hazel.SendOption.Reliable, -1);
             AmongUsClient.Instance.FinishRpcImmediately(w);
             RPCProcedure.lawyerPromotesToPursuer();
         }
         else
         {
             var           target = possibleTargets[TheOtherRoles.rnd.Next(0, possibleTargets.Count)];
             MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.LawyerSetTarget, Hazel.SendOption.Reliable, -1);
             writer.Write(target.PlayerId);
             AmongUsClient.Instance.FinishRpcImmediately(writer);
             RPCProcedure.lawyerSetTarget(target.PlayerId);
         }
     }
 }
        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 (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 (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--;
                }
            }
        }
        private static void assignRoleModifiers(RoleAssignmentData data)
        {
            // Madmate
            for (int i = 0; i < CustomOptionHolder.madmateSpawnRate.count; i++)
            {
                if (rnd.Next(1, 100) <= CustomOptionHolder.madmateSpawnRate.rate * 10)
                {
                    var candidates = Madmate.candidates;
                    if (candidates.Count <= 0)
                    {
                        break;
                    }

                    if (Madmate.madmateType == Madmate.MadmateType.Simple)
                    {
                        if (data.maxCrewmateRoles <= 0)
                        {
                            break;
                        }
                        setModifierToRandomPlayer((byte)ModifierType.Madmate, Madmate.candidates);
                        data.maxCrewmateRoles--;
                    }
                    else
                    {
                        setModifierToRandomPlayer((byte)ModifierType.Madmate, Madmate.candidates);
                    }
                }
            }
            // Munou
            for (int i = 0; i < CustomOptionHolder.munouSpawnRate.count; i++)
            {
                if (rnd.Next(1, 100) <= CustomOptionHolder.munouSpawnRate.rate * 10)
                {
                    var candidates = Munou.candidates;
                    if (candidates.Count <= 0)
                    {
                        break;
                    }

                    if (Munou.munouType == Munou.MunouType.Simple)
                    {
                        if (data.maxCrewmateRoles <= 0)
                        {
                            break;
                        }
                        setModifierToRandomPlayer((byte)ModifierType.Munou, Munou.candidates);
                        data.maxCrewmateRoles--;
                    }
                    else
                    {
                        setModifierToRandomPlayer((byte)ModifierType.Munou, Munou.candidates);
                    }
                }
            }
            // AntiTeleport
            for (int i = 0; i < CustomOptionHolder.antiTeleportSpawnRate.count; i++)
            {
                if (rnd.Next(1, 100) <= CustomOptionHolder.antiTeleportSpawnRate.rate * 10)
                {
                    var candidates = AntiTeleport.candidates;
                    if (candidates.Count <= 0)
                    {
                        break;
                    }
                    setModifierToRandomPlayer((byte)ModifierType.AntiTeleport, AntiTeleport.candidates);
                }
            }
            // Mini
            for (int i = 0; i < CustomOptionHolder.miniSpawnRate.count; i++)
            {
                if (rnd.Next(1, 100) <= CustomOptionHolder.miniSpawnRate.rate * 10)
                {
                    var candidates = Mini.candidates;
                    if (candidates.Count <= 0)
                    {
                        break;
                    }
                    setModifierToRandomPlayer((byte)ModifierType.Mini, Mini.candidates);
                }
            }
        }
        private static void assignChanceRoles(RoleAssignmentData data)
        {
            blockedAssignments = 0;

            // 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.rate > 0 && x.Value.rate < 10).Select(x => Enumerable.Repeat(x.Key, x.Value.rate * x.Value.count)).SelectMany(x => x).ToList();
            List <byte> neutralTickets  = data.neutralSettings.Where(x => x.Value.rate > 0 && x.Value.rate < 10).Select(x => Enumerable.Repeat(x.Key, x.Value.rate * x.Value.count)).SelectMany(x => x).ToList();
            List <byte> impostorTickets = data.impSettings.Where(x => x.Value.rate > 0 && x.Value.rate < 10).Select(x => Enumerable.Repeat(x.Key, x.Value.rate * x.Value.count)).SelectMany(x => x).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 && impostorTickets.Count > 0) ||
                (data.crewmates.Count > 0 && (
                     (data.maxCrewmateRoles > 0 && crewmateTickets.Count > 0) ||
                     (data.maxNeutralRoles > 0 && neutralTickets.Count > 0)
                     )))
            {
                Dictionary <TeamType, List <byte> > rolesToAssign = new Dictionary <TeamType, List <byte> >();
                if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0 && crewmateTickets.Count > 0)
                {
                    rolesToAssign.Add(TeamType.Crewmate, crewmateTickets);
                }
                if (data.crewmates.Count > 0 && data.maxNeutralRoles > 0 && neutralTickets.Count > 0)
                {
                    rolesToAssign.Add(TeamType.Neutral, neutralTickets);
                }
                if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && impostorTickets.Count > 0)
                {
                    rolesToAssign.Add(TeamType.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 == TeamType.Crewmate || roleType == TeamType.Neutral ? data.crewmates : data.impostors;
                var index    = rnd.Next(0, rolesToAssign[roleType].Count);
                var roleId   = rolesToAssign[roleType][index];
                var player   = setRoleToRandomPlayer(rolesToAssign[roleType][index], players);
                if (player == byte.MaxValue && blockedAssignments < maxBlocks)
                {
                    blockedAssignments++;
                    continue;
                }
                blockedAssignments = 0;

                rolesToAssign[roleType].RemoveAll(x => x == roleId);

                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);
                    }
                }

                // Adjust the role limit
                switch (roleType)
                {
                case TeamType.Crewmate: data.maxCrewmateRoles--; break;

                case TeamType.Neutral: data.maxNeutralRoles--; break;

                case TeamType.Impostor: data.maxImpostorRoles--; break;
                }
            }
        }
        private static void assignEnsuredRoles(RoleAssignmentData data)
        {
            blockedAssignments = 0;

            // Get all roles where the chance to occur is set to 100%
            List <byte> ensuredCrewmateRoles = data.crewSettings.Where(x => x.Value.rate == 10).Select(x => Enumerable.Repeat(x.Key, x.Value.count)).SelectMany(x => x).ToList();
            List <byte> ensuredNeutralRoles  = data.neutralSettings.Where(x => x.Value.rate == 10).Select(x => Enumerable.Repeat(x.Key, x.Value.count)).SelectMany(x => x).ToList();
            List <byte> ensuredImpostorRoles = data.impSettings.Where(x => x.Value.rate == 10).Select(x => Enumerable.Repeat(x.Key, x.Value.count)).SelectMany(x => x).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 <TeamType, List <byte> > rolesToAssign = new Dictionary <TeamType, List <byte> >();
                if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0 && ensuredCrewmateRoles.Count > 0)
                {
                    rolesToAssign.Add(TeamType.Crewmate, ensuredCrewmateRoles);
                }
                if (data.crewmates.Count > 0 && data.maxNeutralRoles > 0 && ensuredNeutralRoles.Count > 0)
                {
                    rolesToAssign.Add(TeamType.Neutral, ensuredNeutralRoles);
                }
                if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && ensuredImpostorRoles.Count > 0)
                {
                    rolesToAssign.Add(TeamType.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 == TeamType.Crewmate || roleType == TeamType.Neutral ? data.crewmates : data.impostors;
                var index    = rnd.Next(0, rolesToAssign[roleType].Count);
                var roleId   = rolesToAssign[roleType][index];
                var player   = setRoleToRandomPlayer(rolesToAssign[roleType][index], players);
                if (player == byte.MaxValue && blockedAssignments < maxBlocks)
                {
                    blockedAssignments++;
                    continue;
                }
                blockedAssignments = 0;

                rolesToAssign[roleType].RemoveAt(index);

                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, 0);
                        }
                        if (data.neutralSettings.ContainsKey(blockedRoleId))
                        {
                            data.neutralSettings[blockedRoleId] = (0, 0);
                        }
                        if (data.crewSettings.ContainsKey(blockedRoleId))
                        {
                            data.crewSettings[blockedRoleId] = (0, 0);
                        }
                        // Remove blocked roles even if the chance was 100%
                        foreach (var ensuredRolesList in rolesToAssign.Values)
                        {
                            ensuredRolesList.RemoveAll(x => x == blockedRoleId);
                        }
                    }
                }

                // Adjust the role limit
                switch (roleType)
                {
                case TeamType.Crewmate: data.maxCrewmateRoles--; break;

                case TeamType.Neutral: data.maxNeutralRoles--; break;

                case TeamType.Impostor: data.maxImpostorRoles--; break;
                }
            }
        }
        private static void selectFactionForFactionIndependentRoles(RoleAssignmentData data)
        {
            // Assign Guesser (chance to be impostor based on setting)
            bool isEvilGuesser = (rnd.Next(1, 101) <= CustomOptionHolder.guesserIsImpGuesserRate.getSelection() * 10);

            if (CustomOptionHolder.guesserSpawnBothRate.getSelection() > 0)
            {
                if (rnd.Next(1, 101) <= CustomOptionHolder.guesserSpawnRate.getSelection() * 10)
                {
                    if (isEvilGuesser)
                    {
                        if (data.impostors.Count > 0 && data.maxImpostorRoles > 0)
                        {
                            byte evilGuesser = setRoleToRandomPlayer((byte)RoleType.EvilGuesser, data.impostors);
                            data.impostors.ToList().RemoveAll(x => x.PlayerId == evilGuesser);
                            data.maxImpostorRoles--;
                            data.crewSettings.Add((byte)RoleType.NiceGuesser, (CustomOptionHolder.guesserSpawnBothRate.getSelection(), 1));
                        }
                    }
                    else if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0)
                    {
                        byte niceGuesser = setRoleToRandomPlayer((byte)RoleType.NiceGuesser, data.crewmates);
                        data.crewmates.ToList().RemoveAll(x => x.PlayerId == niceGuesser);
                        data.maxCrewmateRoles--;
                        data.impSettings.Add((byte)RoleType.EvilGuesser, (CustomOptionHolder.guesserSpawnBothRate.getSelection(), 1));
                    }
                }
            }
            else
            {
                if (isEvilGuesser)
                {
                    data.impSettings.Add((byte)RoleType.EvilGuesser, (CustomOptionHolder.guesserSpawnRate.getSelection(), 1));
                }
                else
                {
                    data.crewSettings.Add((byte)RoleType.NiceGuesser, (CustomOptionHolder.guesserSpawnRate.getSelection(), 1));
                }
            }

            // Assign Swapper (chance to be impostor based on setting)
            if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && rnd.Next(1, 101) <= CustomOptionHolder.swapperIsImpRate.getSelection() * 10)
            {
                data.impSettings.Add((byte)RoleType.Swapper, (CustomOptionHolder.swapperSpawnRate.getSelection(), 1));
            }
            else if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0)
            {
                data.crewSettings.Add((byte)RoleType.Swapper, (CustomOptionHolder.swapperSpawnRate.getSelection(), 1));
            }

            // Assign Shifter (chance to be neutral based on setting)
            bool shifterIsNeutral = false;

            if (data.crewmates.Count > 0 && data.maxNeutralRoles > 0 && rnd.Next(1, 101) <= CustomOptionHolder.shifterIsNeutralRate.getSelection() * 10)
            {
                data.neutralSettings.Add((byte)RoleType.Shifter, (CustomOptionHolder.shifterSpawnRate.getSelection(), 1));
                shifterIsNeutral = true;
            }
            else if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0)
            {
                data.crewSettings.Add((byte)RoleType.Shifter, (CustomOptionHolder.shifterSpawnRate.getSelection(), 1));
                shifterIsNeutral = false;
            }

            // Assign any dual role types
            foreach (var option in CustomDualRoleOption.dualRoles)
            {
                if (option.count <= 0 || !option.roleEnabled)
                {
                    continue;
                }

                int niceCount = 0;
                int evilCount = 0;
                while (niceCount + evilCount < option.count)
                {
                    if (option.assignEqually)
                    {
                        niceCount++;
                        evilCount++;
                    }
                    else
                    {
                        bool isEvil = rnd.Next(1, 101) <= option.impChance * 10;
                        if (isEvil)
                        {
                            evilCount++;
                        }
                        else
                        {
                            niceCount++;
                        }
                    }
                }

                if (niceCount > 0)
                {
                    data.crewSettings.Add((byte)option.roleType, (option.rate, niceCount));
                }

                if (evilCount > 0)
                {
                    data.impSettings.Add((byte)option.roleType, (option.rate, evilCount));
                }
            }

            MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetShifterType, Hazel.SendOption.Reliable, -1);

            writer.Write(shifterIsNeutral);
            AmongUsClient.Instance.FinishRpcImmediately(writer);
            RPCProcedure.setShifterType(shifterIsNeutral);
        }
        private static void assignSpecialRoles(RoleAssignmentData data)
        {
            // Assign GM
            if (CustomOptionHolder.gmEnabled.getBool() == true)
            {
                byte gmID = 0;

                if (CustomOptionHolder.gmIsHost.getBool() == true)
                {
                    PlayerControl host = AmongUsClient.Instance?.GetHost().Character;
                    gmID = setRoleToHost((byte)RoleType.GM, host);

                    // First, remove the GM from role selection.
                    data.crewmates.RemoveAll(x => x.PlayerId == host.PlayerId);
                    data.impostors.RemoveAll(x => x.PlayerId == host.PlayerId);
                }
                else
                {
                    gmID = setRoleToRandomPlayer((byte)RoleType.GM, data.crewmates);
                }

                PlayerControl p = PlayerControl.AllPlayerControls.ToArray().ToList().Find(x => x.PlayerId == gmID);

                if (p != null && CustomOptionHolder.gmDiesAtStart.getBool())
                {
                    p.Exiled();
                }
            }

            // Assign Lovers
            for (int i = 0; i < CustomOptionHolder.loversNumCouples.getFloat(); i++)
            {
                var singleCrew = data.crewmates.FindAll(x => !x.isLovers());
                var singleImps = data.impostors.FindAll(x => !x.isLovers());

                bool isOnlyRole = !CustomOptionHolder.loversCanHaveAnotherRole.getBool();
                if (rnd.Next(1, 101) <= CustomOptionHolder.loversSpawnRate.getSelection() * 10)
                {
                    int lover1      = -1;
                    int lover2      = -1;
                    int lover1Index = -1;
                    int lover2Index = -1;
                    if (singleImps.Count > 0 && singleCrew.Count > 0 && (!isOnlyRole || (data.maxCrewmateRoles > 0 && data.maxImpostorRoles > 0)) && rnd.Next(1, 101) <= CustomOptionHolder.loversImpLoverRate.getSelection() * 10)
                    {
                        lover1Index = rnd.Next(0, singleImps.Count);
                        lover1      = singleImps[lover1Index].PlayerId;

                        lover2Index = rnd.Next(0, singleCrew.Count);
                        lover2      = singleCrew[lover2Index].PlayerId;

                        if (isOnlyRole)
                        {
                            data.maxImpostorRoles--;
                            data.maxCrewmateRoles--;

                            data.impostors.RemoveAll(x => x.PlayerId == lover1);
                            data.crewmates.RemoveAll(x => x.PlayerId == lover2);
                        }
                    }

                    else if (singleCrew.Count >= 2 && (isOnlyRole || data.maxCrewmateRoles >= 2))
                    {
                        lover1Index = rnd.Next(0, singleCrew.Count);
                        while (lover2Index == lover1Index || lover2Index < 0)
                        {
                            lover2Index = rnd.Next(0, singleCrew.Count);
                        }

                        lover1 = singleCrew[lover1Index].PlayerId;
                        lover2 = singleCrew[lover2Index].PlayerId;

                        if (isOnlyRole)
                        {
                            data.maxCrewmateRoles -= 2;
                            data.crewmates.RemoveAll(x => x.PlayerId == lover1);
                            data.crewmates.RemoveAll(x => x.PlayerId == lover2);
                        }
                    }

                    if (lover1 >= 0 && lover2 >= 0)
                    {
                        MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetLovers, Hazel.SendOption.Reliable, -1);
                        writer.Write((byte)lover1);
                        writer.Write((byte)lover2);
                        AmongUsClient.Instance.FinishRpcImmediately(writer);
                        RPCProcedure.setLovers((byte)lover1, (byte)lover2);
                    }
                }
            }

            // Assign Mafia
            if (data.impostors.Count >= 3 && data.maxImpostorRoles >= 3 && (rnd.Next(1, 101) <= CustomOptionHolder.mafiaSpawnRate.getSelection() * 10))
            {
                setRoleToRandomPlayer((byte)RoleType.Godfather, data.impostors);
                setRoleToRandomPlayer((byte)RoleType.Janitor, data.impostors);
                setRoleToRandomPlayer((byte)RoleType.Mafioso, data.impostors);
                data.maxImpostorRoles -= 3;
            }

            // Assign Bomber
            if (data.impostors.Count >= 2 && data.maxImpostorRoles >= 2 && (rnd.Next(1, 101) <= CustomOptionHolder.bomberSpawnRate.getSelection() * 10))
            {
                setRoleToRandomPlayer((byte)RoleType.BomberA, data.impostors);
                setRoleToRandomPlayer((byte)RoleType.BomberB, data.impostors);
                data.maxImpostorRoles -= 2;
            }

            // Assign Mimic
            if (data.impostors.Count >= 2 && data.maxImpostorRoles >= 2 && (rnd.Next(1, 101) <= CustomOptionHolder.mimicSpawnRate.getSelection() * 10))
            {
                setRoleToRandomPlayer((byte)RoleType.MimicK, data.impostors);
                setRoleToRandomPlayer((byte)RoleType.MimicA, data.impostors);
                data.maxImpostorRoles -= 2;
            }
        }