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