diff --git a/Modules/CustomRoleSelector.cs b/Modules/CustomRoleSelector.cs index 728c3ae99..66e62b4ee 100644 --- a/Modules/CustomRoleSelector.cs +++ b/Modules/CustomRoleSelector.cs @@ -135,6 +135,13 @@ internal static class CustomRoleSelector else roles[RoleAssignType.Crewmate].Add(info); } + if (optImpNum >= 2 && roles[RoleAssignType.Impostor].FindFirst(x => x.Role == CustomRoles.Loner, out var lonerInfo) && lonerInfo.SpawnChance > rd.Next(100)) + { + finalRolesList.Add(CustomRoles.Loner); + readyImpNum += 2; + readyRoleNum++; + } + loversData.OneIsImp &= roles[RoleAssignType.Impostor].Count(x => x.SpawnChance == 100) < optImpNum; if (loversData.Spawning) @@ -986,7 +993,8 @@ internal static class CustomRoleSelector private class RoleAssignInfo(CustomRoles role, int spawnChance, int maxCount) { public CustomRoles Role => role; - public int SpawnChance => spawnChance; + public int SpawnChance { get; set; } = spawnChance; + public int MaxCount => maxCount; public int AssignedCount { get; set; } public RoleOptionType OptionType { get; } = role.GetRoleOptionType(); diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 16e870442..84e5f6da7 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -483,6 +483,31 @@ internal static class ChatCommands // --------------------------------------------------------------------------------------------------------------------------------------------- + private static void SelectCommand(PlayerControl player, string text, string[] args) + { + if (Starspawn.IsDayBreak) return; + + if (!AmongUsClient.Instance.AmHost) + { + RequestCommandProcessingFromHost(nameof(SelectCommand), text); + return; + } + + if (!Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState state) || state.IsDead || state.Role is not Loner loner || loner.Done) return; + if (args.Length < 3 || !GuessManager.MsgToPlayerAndRole(text[7..], out byte targetId, out CustomRoles pickedRole, out _) || targetId == player.PlayerId) return; + if (!pickedRole.IsImpostor() || pickedRole.IsVanilla() || CustomRoleSelector.RoleResult.ContainsValue(pickedRole) || pickedRole.GetMode() == 0) return; + if (!Main.PlayerStates.TryGetValue(targetId, out PlayerState ts) || ts.IsDead) return; + + if (!player.IsLocalPlayer()) ChatManager.SendPreviousMessagesToAll(); + + loner.PickedPlayer = targetId; + loner.PickedRole = pickedRole; + + Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("Loner.Picked"), targetId.ColoredPlayerName(), pickedRole.ToColoredString())); + + MeetingManager.SendCommandUsedMessage(args[0]); + } + private static void ReviveCommand(PlayerControl player, string text, string[] args) { if ((!Options.NoGameEnd.GetBool() && !player.FriendCode.GetDevUser().up) || args.Length < 2 || !byte.TryParse(args[1], out byte targetId)) return; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 072126064..00e0b3a0c 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3482,6 +3482,10 @@ "OccultistActionSwitchMode.Vanish": "Vanish", "OccultistReviving": "Reviving {0}....", "OccultistRevived": "{0} has been revived!", + "Loner": "Loner", + "LonerInfo": "Select Your Partner", + "LonerInfoLong": "(Impostors):\\nAs the Loner, you can freely select your impostor partner using your shapeshift menu in a meeting or the /select {id} {role} command (replace {id} with the player's ID, and {role} with the name of the role you want them to get). If you use the shapeshift menu, it will assign them a random enabled impostor role. The role change takes effect after the meeting.", + "Loner.Picked": "<#00ff00>✓ <#00ffa5>You've picked {0} to be your partner! Their role will be {1}.", "Thanos": "Thanos", "ThanosInfo": "Collect Infinity Stones", "ThanosInfoLong": "(Neutrals):\\nAs Thanos, your job is to collect all infinity stones and/or kill everyone to win. Random players (you don't know who) in the game have infinity stones, kill them to take it. Shift/Vanish/Pet to use the selected infinity stone. Each one has a different effect.\\n\\nSpace Stone: Your next kill will not leave a body behind and ignore all shields\\nMind Stone: Makes you permanently immune to controlling abilities\\nReality Stone: Gives you a shield.\\nPower Stone: Your kill cooldown will be significantly lower after your next kill\\nTime Stone: Slows down everyone except you\\nSoul Stone: Gives you an arrow to someone holding an infinity stone", diff --git a/Roles/Impostor/Loner.cs b/Roles/Impostor/Loner.cs index c3d04bab0..6369fcb9a 100644 --- a/Roles/Impostor/Loner.cs +++ b/Roles/Impostor/Loner.cs @@ -13,10 +13,11 @@ public class Loner : RoleBase public byte PickedPlayer; public CustomRoles PickedRole; + public bool Done; public override void SetupCustomOption() { - StartSetup(655300); + StartSetup(655400); } public override void Init() @@ -28,17 +29,30 @@ public class Loner : RoleBase { On = true; PickedPlayer = byte.MaxValue; - PickedRole = default; + PickedRole = CustomRoles.Crewmate; + Done = false; } public override void OnMeetingShapeshift(PlayerControl shapeshifter, PlayerControl target) { if (Starspawn.IsDayBreak) return; - if (shapeshifter == null || target == null || shapeshifter.PlayerId == target.PlayerId || PickedPlayer != byte.MaxValue) return; + if (shapeshifter == null || target == null || shapeshifter.PlayerId == target.PlayerId || Done) return; PickedPlayer = target.PlayerId; PickedRole = Enum.GetValues().Where(x => x.IsImpostor() && !x.IsVanilla() && !CustomRoleSelector.RoleResult.ContainsValue(x) && x.GetMode() != 0).RandomElement(); Utils.SendMessage("\n", shapeshifter.PlayerId, string.Format(Translator.GetString("Loner.Picked"), PickedPlayer.ColoredPlayerName(), PickedRole.ToColoredString())); } + + public override void AfterMeetingTasks() + { + var pc = PickedPlayer.GetPlayer(); + + if (!Done && PickedPlayer != byte.MaxValue && PickedRole != CustomRoles.Crewmate && pc != null) + { + Done = true; + pc.RpcSetCustomRole(PickedRole); + pc.RpcChangeRoleBasis(PickedRole); + } + } } \ No newline at end of file