Files
BetterAmongUs/Patches/RoleManagerPatch.cs
T
2024-09-08 15:21:36 -05:00

485 lines
19 KiB
C#

using AmongUs.GameOptions;
using HarmonyLib;
using Hazel;
namespace BetterAmongUs.Patches;
[HarmonyPatch(typeof(RoleManager))]
public class RoleManagerPatch
{
public static Dictionary<PlayerControl, RoleTypes> SetPlayerRole = []; // Player, Role
public static Dictionary<string, int> ImpostorMultiplier = []; // HashPuid, Multiplier
private static Random random = new Random();
static readonly Func<InnerNet.ClientData, bool> clientCheck = (clientData) =>
{
return clientData?.BetterData()?.IsVerifiedBetterUser != true;
};
// Better role algorithm
[HarmonyPatch(nameof(RoleManager.SelectRoles))]
[HarmonyPrefix]
public static bool RoleManager_Prefix(/*RoleManager __instance*/)
{
if (!GameStates.IsHideNSeek)
{
RegularBetterRoleAssignment();
}
else
{
HideAndSeekBetterRoleAssignment();
}
return false;
}
public static void RegularBetterRoleAssignment()
{
Logger.LogHeader($"Better Role Assignment Has Started", "RoleManager");
// Set roles up
foreach (var addplayer in Main.AllPlayerControls.Where(pc => !ImpostorMultiplier.ContainsKey(Utils.GetHashPuid(pc))))
ImpostorMultiplier[Utils.GetHashPuid(addplayer)] = 0;
int NumImpostors = GameOptionsManager.Instance.CurrentGameOptions.NumImpostors;
int NumPlayers = Main.AllPlayerControls.Length;
var impostorLimits = new Dictionary<int, int>
{
{ 3, 1 },
{ 5, 2 },
{ 7, 3 }
};
foreach (var limit in impostorLimits)
{
if (NumPlayers <= limit.Key)
{
NumImpostors = Math.Min(NumImpostors, limit.Value);
break;
}
}
List<PlayerControl> Impostors = [];
List<PlayerControl> Crewmates = [];
Dictionary<RoleTypes, int> ImpostorRoles = new() // Role, Amount
{
{ RoleTypes.Shapeshifter, 0 },
{ RoleTypes.Phantom, 0 }
};
Dictionary<RoleTypes, int> CrewmateRoles = new() // Role, Amount
{
{ RoleTypes.Engineer, 0 },
{ RoleTypes.Scientist, 0 },
{ RoleTypes.Tracker, 0 },
{ RoleTypes.Noisemaker, 0 }
};
List<RoleTypes> Roles = [.. ImpostorRoles.Keys, .. CrewmateRoles.Keys];
foreach (RoleTypes role in Roles)
{
if (IsImpostorRole(role))
ImpostorRoles[role] = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(role);
else
CrewmateRoles[role] = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(role);
}
#if DEBUG
// Override player role assignment
if (SetPlayerRole.Keys.Any())
{
foreach (var kvp in SetPlayerRole.Where(kvp => kvp.Key != null).OrderBy(kvp => kvp.Key.IsLocalPlayer() ? 0 : 1))
{
var player = kvp.Key;
var role = kvp.Value;
if (role is RoleTypes.Impostor)
{
if (Impostors.Count < NumImpostors)
{
Impostors.Add(player);
player.RpcSetRole(RoleTypes.Impostor);
Logger.LogPrivate($"Override Assigned {Utils.GetRoleName(RoleTypes.Impostor)} role to {player.Data.PlayerName}", "RoleManager");
}
else continue;
}
else if (role is RoleTypes.Crewmate)
{
Crewmates.Add(player);
player.RpcSetRole(RoleTypes.Crewmate);
Logger.LogPrivate($"Override Assigned {Utils.GetRoleName(RoleTypes.Crewmate)} role to {player.Data.PlayerName}", "RoleManager");
}
else
{
if (IsImpostorRole(role))
{
if (Impostors.Count < NumImpostors && ImpostorRoles[role] > 0)
{
ImpostorRoles[role]--;
Impostors.Add(player);
}
else continue;
}
else
{
if (CrewmateRoles[role] > 0)
{
CrewmateRoles[role]--;
Crewmates.Add(player);
}
else continue;
}
player.RpcSetRole(role);
// Desync role to other clients to prevent revealing the true role
if (BetterGameSettings.DesyncRoles.GetBool())
{
if (IsImpostorRole(role) && role is not RoleTypes.Phantom)
{
List<MessageWriter> messageWriter = AmongUsClient.Instance.StartRpcDesync(player.NetId, (byte)RpcCalls.SetRole, SendOption.None, player.GetClientId(), clientCheck);
messageWriter.ForEach(mW => mW.Write((ushort)RoleTypes.Impostor));
messageWriter.ForEach(mW => mW.Write(false));
AmongUsClient.Instance.FinishRpcDesync(messageWriter);
}
else if (role is not RoleTypes.Noisemaker)
{
List<MessageWriter> messageWriter = AmongUsClient.Instance.StartRpcDesync(player.NetId, (byte)RpcCalls.SetRole, SendOption.None, player.GetClientId(), clientCheck);
messageWriter.ForEach(mW => mW.Write((ushort)RoleTypes.Crewmate));
messageWriter.ForEach(mW => mW.Write(false));
AmongUsClient.Instance.FinishRpcDesync(messageWriter);
}
}
Logger.LogPrivate($"Override Assigned {Utils.GetRoleName(role)} role to {player.Data.PlayerName}", "RoleManager");
}
}
}
#endif
// Get players in random order
List<PlayerControl> players = Main.AllPlayerControls
.Where(player => !Impostors.Contains(player) && !Crewmates.Contains(player) && player.roleAssigned == false)
.ToList();
int n = players.Count;
while (n > 1)
{
n--;
int k = random.Next(n + 1);
PlayerControl value = players[k];
players[k] = players[n];
players[n] = value;
}
// Assign roles
foreach (PlayerControl pc in players)
{
if (pc == null || pc.roleAssigned == true) continue;
if (Impostors.Count < NumImpostors && RNG() > ImpostorMultiplier[Utils.GetHashPuid(pc)])
{
var impRoles = Shuffle(ImpostorRoles);
foreach (var kvp in impRoles)
{
if (RNG() <= GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(kvp.Key) && kvp.Value > 0)
{
ImpostorMultiplier[Utils.GetHashPuid(pc)] += 15;
ImpostorRoles[kvp.Key]--;
Impostors.Add(pc);
pc.RpcSetRole(kvp.Key);
// Desync role to other clients to prevent revealing the true role
if (BetterGameSettings.DesyncRoles.GetBool())
{
if (kvp.Key is not RoleTypes.Phantom)
{
List<MessageWriter> messageWriter = AmongUsClient.Instance.StartRpcDesync(pc.NetId, (byte)RpcCalls.SetRole, SendOption.None, pc.GetClientId(), clientCheck);
messageWriter.ForEach(mW => mW.Write((ushort)RoleTypes.Impostor));
messageWriter.ForEach(mW => mW.Write(false));
AmongUsClient.Instance.FinishRpcDesync(messageWriter);
}
}
Logger.LogPrivate($"Assigned {Utils.GetRoleName(kvp.Key)} role to {pc.Data.PlayerName}", "RoleManager");
break;
}
}
if (!Impostors.Contains(pc))
{
ImpostorMultiplier[Utils.GetHashPuid(pc)] += 15;
Impostors.Add(pc);
pc.RpcSetRole(RoleTypes.Impostor);
Logger.LogPrivate($"Assigned {Utils.GetRoleName(RoleTypes.Impostor)} role to {pc.Data.PlayerName}", "RoleManager");
}
}
else
{
var crewRoles = Shuffle(CrewmateRoles);
foreach (var kvp in crewRoles)
{
if (RNG() <= GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(kvp.Key) && kvp.Value > 0)
{
ImpostorMultiplier[Utils.GetHashPuid(pc)] = 0;
CrewmateRoles[kvp.Key]--;
Crewmates.Add(pc);
pc.RpcSetRole(kvp.Key);
// Desync role to other clients to prevent revealing the true role
if (BetterGameSettings.DesyncRoles.GetBool())
{
if (kvp.Key is not RoleTypes.Noisemaker)
{
List<MessageWriter> messageWriter = AmongUsClient.Instance.StartRpcDesync(pc.NetId, (byte)RpcCalls.SetRole, SendOption.None, pc.GetClientId(), clientCheck);
messageWriter.ForEach(mW => mW.Write((ushort)RoleTypes.Crewmate));
messageWriter.ForEach(mW => mW.Write(false));
AmongUsClient.Instance.FinishRpcDesync(messageWriter);
}
}
Logger.LogPrivate($"Assigned {Utils.GetRoleName(kvp.Key)} role to {pc.Data.PlayerName}", "RoleManager");
break;
}
}
if (!Crewmates.Contains(pc))
{
ImpostorMultiplier[Utils.GetHashPuid(pc)] = 0;
Crewmates.Add(pc);
pc.RpcSetRole(RoleTypes.Crewmate);
Logger.LogPrivate($"Assigned {Utils.GetRoleName(RoleTypes.Crewmate)} role to {pc.Data.PlayerName}", "RoleManager");
}
}
}
SetPlayerRole.Clear();
_ = new LateTask(() =>
{
RPC.SyncAllNames(false, true, Main.BetterHost.Value);
}, 1f, "RoleManager SyncAllNames");
Logger.LogHeader($"Better Role Assignment Has Finished", "RoleManager");
}
public static void HideAndSeekBetterRoleAssignment()
{
Logger.LogHeader($"Better Role Assignment Has Started", "RoleManager");
int NumImpostors = BetterGameSettings.HideAndSeekImpNum.GetInt();
if (NumImpostors > Main.AllPlayerControls.Length)
NumImpostors = Main.AllPlayerControls.Length;
List<PlayerControl> Impostors = [];
List<PlayerControl> Crewmates = [];
// Set imp from settings
int[] betterImpostorSettings =
[
GameOptionsManager.Instance.currentHideNSeekGameOptions.ImpostorPlayerID,
BetterGameSettingsTemp.HideAndSeekImp2.GetValue(),
BetterGameSettingsTemp.HideAndSeekImp3.GetValue(),
BetterGameSettingsTemp.HideAndSeekImp4.GetValue(),
BetterGameSettingsTemp.HideAndSeekImp5.GetValue()
];
for (int i = 0; i < NumImpostors; i++)
{
int tempSetImpostor = betterImpostorSettings[i];
if (tempSetImpostor >= 0)
{
var player = Utils.PlayerFromPlayerId(tempSetImpostor);
if (player != null)
{
if (Impostors.Count < NumImpostors)
{
Impostors.Add(player);
Logger.LogPrivate($"Settings Assigned {Utils.GetRoleName(RoleTypes.Impostor)} role to {player.Data.PlayerName}", "RoleManager");
}
}
}
}
#if DEBUG
// Override player role assignment
if (SetPlayerRole.Keys.Any())
{
foreach (var kvp in SetPlayerRole.Where(kvp => kvp.Key != null).OrderBy(kvp => kvp.Key.IsLocalPlayer() ? 0 : 1))
{
var player = kvp.Key;
var role = kvp.Value;
if (role is RoleTypes.Impostor)
{
if (Impostors.Count < NumImpostors)
{
Impostors.Add(player);
Logger.LogPrivate($"Override Assigned {Utils.GetRoleName(RoleTypes.Impostor)} role to {player.Data.PlayerName}", "RoleManager");
}
else continue;
}
else if (role is RoleTypes.Engineer)
{
Crewmates.Add(player);
Logger.LogPrivate($"Override Assigned {Utils.GetRoleName(RoleTypes.Engineer)} role to {player.Data.PlayerName}", "RoleManager");
}
}
}
#endif
// Get players in random order
List<PlayerControl> players = Main.AllPlayerControls
.Where(player => !Impostors.Contains(player) && !Crewmates.Contains(player) && player.roleAssigned == false)
.ToList();
int n = players.Count;
while (n > 1)
{
n--;
int k = random.Next(n + 1);
PlayerControl value = players[k];
players[k] = players[n];
players[n] = value;
}
// Assign roles
foreach (PlayerControl pc in players)
{
if (pc == null || pc.roleAssigned == true) continue;
if (Impostors.Count < NumImpostors)
{
if (!Crewmates.Contains(pc))
{
Impostors.Add(pc);
Logger.LogPrivate($"Assigned {Utils.GetRoleName(RoleTypes.Impostor)} role to {pc.Data.PlayerName}", "RoleManager");
}
}
else
{
if (!Impostors.Contains(pc))
{
Crewmates.Add(pc);
Logger.LogPrivate($"Assigned {Utils.GetRoleName(RoleTypes.Engineer)} role to {pc.Data.PlayerName}", "RoleManager");
}
}
}
foreach (var player in Impostors)
{
player.RpcSetRole(RoleTypes.Impostor);
}
foreach (var player in Crewmates)
{
player.RpcSetRole(RoleTypes.Engineer);
}
_ = new LateTask(() =>
{
RPC.SyncAllNames(false, true, Main.BetterHost.Value);
}, 1f, "RoleManager SyncAllNames");
Logger.LogHeader($"Better Role Assignment Has Finished", "RoleManager");
}
[HarmonyPatch(nameof(RoleManager.AssignRoleOnDeath))]
[HarmonyPrefix]
public static bool AssignRoleOnDeath_Prefix(/*RoleManager __instance*/ [HarmonyArgument(0)] PlayerControl player)
{
Dictionary<RoleTypes, int> GhostRoles = new() // Role, Amount
{
{ RoleTypes.GuardianAngel, 0 },
};
List<RoleTypes> Roles = [.. GhostRoles.Keys];
foreach (RoleTypes role in Roles)
{
GhostRoles[role] = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(role);
}
foreach (var allDeadPlayers in Main.AllPlayerControls.Where(pc => !pc.IsAlive()))
{
for (int i = 0; i < Roles.Count; i++)
{
if (allDeadPlayers.Is(Roles[i]))
{
GhostRoles[Roles[i]]--;
}
}
}
var ghostRoles = Shuffle(GhostRoles);
foreach (var kvp in ghostRoles)
{
if (player.IsImpostorTeam() && kvp.Key is RoleTypes.GuardianAngel) continue;
if (kvp.Value > 0 && RNG() <= GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(kvp.Key))
{
player.RpcSetRole(kvp.Key);
// Desync role to other clients to prevent revealing the true role
if (BetterGameSettings.DesyncRoles.GetBool())
{
List<MessageWriter> messageWriter = AmongUsClient.Instance.StartRpcDesync(player.NetId, (byte)RpcCalls.SetRole, SendOption.None, player.GetClientId(), clientCheck);
messageWriter.ForEach(mW => mW.Write((ushort)player.Data.Role.DefaultGhostRole));
messageWriter.ForEach(mW => mW.Write(false));
AmongUsClient.Instance.FinishRpcDesync(messageWriter);
}
return false;
}
}
player.RpcSetRole(player.Data.Role.DefaultGhostRole);
return false;
}
private static bool IsImpostorRole(RoleTypes role) => role is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom;
private static Dictionary<TKey, TValue> Shuffle<TKey, TValue>(Dictionary<TKey, TValue> dictionary)
{
List<TKey> keys = dictionary.Keys.ToList();
// Fisher-Yates shuffle algorithm
for (int i = keys.Count - 1; i > 0; i--)
{
int j = random.Next(0, i + 1);
TKey temp = keys[i];
keys[i] = keys[j];
keys[j] = temp;
}
// Rebuild dictionary with shuffled keys
Dictionary<TKey, TValue> shuffledDictionary = new Dictionary<TKey, TValue>();
foreach (var key in keys)
{
shuffledDictionary[key] = dictionary[key];
}
return shuffledDictionary;
}
public static int RNG()
{
switch (BetterGameSettings.RoleRandomizer.GetValue())
{
case 1:
return UnityEngine.Random.Range(0, 100);
default:
Random Random = new Random();
return Random.Next(0, 100);
}
}
}