Files

855 lines
31 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Data;
using AmongUs.GameOptions;
using Hazel;
using UnityEngine;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using InnerNet;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System;
using System.Security.Cryptography;
using System.Text;
using AmongUs.InnerNet.GameDataMessages;
namespace AmongUsRevamped;
public static class Utils
{
private static readonly DateTime Epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static readonly DateTime StartTime = DateTime.UtcNow;
private static readonly long EpochStartSeconds = (long)(StartTime - Epoch).TotalSeconds;
private static readonly Stopwatch Stopwatch = Stopwatch.StartNew();
public static long TimeStamp => EpochStartSeconds + (long)Stopwatch.Elapsed.TotalSeconds;
public static int allAlivePlayersCount => AllAlivePlayerControls.Count();
public static int AliveCrewmates => AllAlivePlayerControls.Count(pc => !pc.Data.Role.IsImpostor);
public static int AliveImpostors => AllAlivePlayerControls.Count(pc => pc.Data.Role.IsImpostor || pc.isNew);
public static bool IsLobby => AmongUsClient.Instance.GameState == InnerNetClient.GameStates.Joined;
public static bool InGame => AmongUsClient.Instance && AmongUsClient.Instance.GameState == InnerNetClient.GameStates.Started;
public static bool isHideNSeek => GameOptionsManager.Instance.CurrentGameOptions.GameMode == GameModes.HideNSeek;
public static bool IsOnlineGame => AmongUsClient.Instance.NetworkMode == NetworkModes.OnlineGame;
public static bool IsShip => ShipStatus.Instance != null;
public static bool CanMove => PlayerControl.LocalPlayer?.CanMove is true;
public static bool IsDead => PlayerControl.LocalPlayer?.Data?.IsDead is true;
public static bool IsFreePlay => AmongUsClient.Instance.NetworkMode == NetworkModes.FreePlay;
public static bool IsMeeting => InGame && (MeetingHud.Instance);
public static bool HandlingGameEnd;
public static byte CustomGameOverReason;
public static bool CanCallMeetings;
public static bool HasMurdered;
public static string ColorString(Color32 color, string str) => $"<#{color.r:x2}{color.g:x2}{color.b:x2}{color.a:x2}>{str}</color>";
public static string ColorToHex(Color32 color) => $"#{color.r:x2}{color.g:x2}{color.b:x2}{color.a:x2}";
public static byte GetActiveMapId() => GameOptionsManager.Instance.CurrentGameOptions.MapId;
public static int CheckAccessLevel(string friendCode)
{
if (friendCode == "") return 0;
var vipFilePath = @$"{BanManager.DataPath}/AUR-DATA/VIP.txt";
var vipFriendCodes = File.ReadAllLines(vipFilePath);
var moderatorFilePath = @$"{BanManager.DataPath}/AUR-DATA/Moderator.txt";
var moderatorFriendCodes = File.ReadAllLines(moderatorFilePath);
var adminFilePath = @$"{BanManager.DataPath}/AUR-DATA/Admin.txt";
var adminFriendCodes = File.ReadAllLines(adminFilePath);
if (adminFriendCodes.Any(code => code.Contains(friendCode))) return 3;
if (moderatorFriendCodes.Any(code => code.Contains(friendCode))) return 2;
if (vipFriendCodes.Any(code => code.Contains(friendCode))) return 1;
return 0;
}
public static string GetTabName(TabGroup tab)
{
switch (tab)
{
case TabGroup.SystemSettings:
return Translator.Get("systemSettings");
case TabGroup.ModerationSettings:
return Translator.Get("moderationSettings");
case TabGroup.ModSettings:
return Translator.Get("gameplaySettings");
case TabGroup.GamemodeSettings:
return Translator.Get("gamemodeSettings");
default:
return "";
}
}
public static bool IsCustomOption(NumberOption option)
{
return option.GetComponent<OptionItem>() != null;
}
public static void DestroyTranslator(this GameObject obj)
{
var translator = obj.GetComponent<TextTranslatorTMP>();
if (translator != null)
{
Object.Destroy(translator);
}
}
public static void DestroyTranslator(this MonoBehaviour obj) => obj.gameObject.DestroyTranslator();
public static void CustomSettingsChangeMessageLogic(this NotificationPopper notificationPopper, OptionItem optionItem, string text, bool playSound)
{
if (notificationPopper.lastMessageKey == 10000 + optionItem.Id && notificationPopper.activeMessages.Count > 0)
{
notificationPopper.activeMessages[notificationPopper.activeMessages.Count - 1].UpdateMessage(text);
}
else
{
notificationPopper.lastMessageKey = 10000 + optionItem.Id;
LobbyNotificationMessage settingmessage = Object.Instantiate(notificationPopper.notificationMessageOrigin, Vector3.zero, Quaternion.identity, notificationPopper.transform);
settingmessage.transform.localPosition = new Vector3(0f, 0f, -2f);
settingmessage.SetUp(text, notificationPopper.settingsChangeSprite, notificationPopper.settingsChangeColor, new Action(() =>
{
notificationPopper.OnMessageDestroy(settingmessage);
}));
notificationPopper.ShiftMessages();
notificationPopper.AddMessageToQueue(settingmessage);
}
if (playSound)
{
SoundManager.Instance.PlaySoundImmediate(notificationPopper.settingsChangeSound, false, 1f, 1f, null);
}
}
public static string GetOptionNameSCM(this OptionItem optionItem)
{
if (optionItem.Name == "Enable")
{
int id = optionItem.Id;
while (id % 10 != 0)
--id;
var optionItem2 = OptionItem.AllOptions.FirstOrDefault(opt => opt.Id == id);
return optionItem2 != null ? optionItem2.GetName() : optionItem.GetName();
}
else
return optionItem.GetName();
}
public static string GetRegionName(IRegionInfo region = null)
{
region ??= ServerManager.Instance.CurrentRegion;
string name = region.Name;
// Joining games shows incorrect regions
if (!AmongUsClient.Instance.AmHost)
{
name = "";
return name;
}
if (AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame)
{
name = "Server: Local Game";
return name;
}
if (region.PingServer.EndsWith("among.us", StringComparison.Ordinal))
{
// Official servers
name = name switch
{
"North America" => "Server: NA",
"Europe" => "Server: EU",
"Asia" => "Server: AS",
_ => name
};
return name;
}
string ip = region.Servers.FirstOrDefault()?.Ip ?? string.Empty;
if (ip.Contains("aumods.us", StringComparison.Ordinal) || ip.Contains("duikbo.at", StringComparison.Ordinal))
{
// Modded Servers
if (ip.Contains("au-eu"))
name = "Server: MEU";
else if (ip.Contains("au-as"))
name = "Server: MAS";
else
name = "Server: MNA";
return name;
}
if (name.Contains("Niko", StringComparison.OrdinalIgnoreCase))
name = name.Replace("233(", "-").TrimEnd(')');
return name;
}
public static ClientData GetClientById(int id)
{
try { return AmongUsClient.Instance.allClients.ToArray().FirstOrDefault(cd => cd.Id == id); }
catch { return null; }
}
public static void ClearLeftoverData()
{
Logger.Info(" Cleared Leftover Data", "Utils");
RpcSetTasksPatch.GlobalTaskIds = null;
HandlingGameEnd = false;
OnGameJoinedPatch.AutoStartCheck = false;
Main.GameTimer = 0f;
Main.AfkTimer = 0f;
FixedUpdate.Position.Clear();
NormalGameEndChecker.canUpdateWinnerText = true;
MurderPlayerPatch.misfireCount.Clear();
MurderPlayerPatch.killCount.Clear();
HasMurdered = false;
LateTask.Tasks.RemoveAll(x => x.name != "RehostManager");
NormalGameEndChecker.ImpCheckComplete = false;
CreateOptionsPickerPatch.SetDleks2 = false;
CanCallMeetings = true;
PlayerControlCompleteTaskPatch.playerTasksCompleted.Clear();
PlayerControlCompleteTaskPatch.tasksPerPlayer.Clear();
PlayerControlCompleteTaskPatch.tasksInitiated = false;
PlayerControlCompleteTaskPatch.ignoredCompletedTasks = 0;
PlayerControlCompleteTaskPatch.ignoredTasks = 0;
OnPlayerJoinedPatch.JoinNum = 0;
PlayerControlSetRolePatch.ProcessedPlayers.Clear();
PlayerControlSetRolePatch.Seekers.Clear();
PlayerControlSetRolePatch.FirstAssign = true;
PlayerControlSetRolePatch.manualSeekers.Clear();
OnGameStartPatch.PastStartScreen = false;
FixedUpdate.BadMovementTimer.Clear();
skeldVotes = 0;
miraVotes = 0;
polusVotes = 0;
airshipVotes = 0;
fungleVotes = 0;
dleksVotes = 0;
HasVoted.Clear();
MapVoteActive = false;
standardVotes = 0;
noKillCooldownVotes = 0;
speedrunVotes = 0;
shiftAndSeekVotes = 0;
poofAndSeekVotes = 0;
ModeVoteActive = false;
}
public static PlayerControl[] AllAlivePlayerControls
{
get
{
int count = PlayerControl.AllPlayerControls.Count;
var result = new PlayerControl[count];
var i = 0;
foreach (PlayerControl pc in PlayerControl.AllPlayerControls)
{
if (pc == null || pc.Data == null || pc.PlayerId >= 254 || pc.Data.IsDead || (pc.Data.Disconnected)) continue;
result[i++] = pc;
}
if (i == 0) return [];
Array.Resize(ref result, i);
return result;
}
}
public static bool IsActive(SystemTypes type)
{
try
{
if (Utils.IsLobby || !ShipStatus.Instance || !ShipStatus.Instance.Systems.TryGetValue(type, out ISystemType systemType)) return false;
int mapId = Main.NormalOptions.MapId;
switch (type)
{
case SystemTypes.Electrical:
{
if (mapId == 5) return false;
var switchSystem = systemType.CastFast<SwitchSystem>();
return switchSystem is { IsActive: true };
}
case SystemTypes.Reactor:
{
switch (mapId)
{
case 2:
return false;
case 4:
var heliSabotageSystem = systemType.CastFast<HeliSabotageSystem>();
return heliSabotageSystem != null && heliSabotageSystem.IsActive;
default:
var reactorSystemType = systemType.CastFast<ReactorSystemType>();
return reactorSystemType is { IsActive: true };
}
}
case SystemTypes.Laboratory:
{
if (mapId != 2) return false;
var reactorSystemType = systemType.CastFast<ReactorSystemType>();
return reactorSystemType is { IsActive: true };
}
case SystemTypes.LifeSupp:
{
if (mapId is 2 or 4 or 5) return false;
var lifeSuppSystemType = systemType.CastFast<LifeSuppSystemType>();
return lifeSuppSystemType is { IsActive: true };
}
case SystemTypes.Comms:
{
if (mapId is 1 or 5)
{
var hqHudSystemType = systemType.TryCast<HqHudSystemType>();
return hqHudSystemType != null && hqHudSystemType.IsActive;
}
var hudOverrideSystemType = systemType.CastFast<HudOverrideSystemType>();
return hudOverrideSystemType is { IsActive: true };
}
case SystemTypes.HeliSabotage:
{
if (mapId != 4) return false;
var heliSabotageSystem = systemType.CastFast<HeliSabotageSystem>();
return heliSabotageSystem != null && heliSabotageSystem.IsActive;
}
case SystemTypes.MushroomMixupSabotage:
{
if (mapId != 5) return false;
var mushroomMixupSabotageSystem = systemType.CastFast<MushroomMixupSabotageSystem>();
return mushroomMixupSabotageSystem != null && mushroomMixupSabotageSystem.IsActive;
}
default:
return false;
}
}
catch (Exception e)
{
Logger.Exception(e, "IsActive");
return false;
}
}
public static void ShowLastResult(byte playerId = byte.MaxValue)
{
if (InGame)
{
Logger.SendInGame($"Hi, you're currently in-game. Let's use this command afterwards");
return;
}
if (string.IsNullOrEmpty(NormalGameEndChecker.LastWinReason))
{
Logger.SendInGame($"Your command was canceled due to not having the required info");
return;
}
PlayerControl.LocalPlayer.RpcSendChat($"{NormalGameEndChecker.LastWinReason}");
}
public static bool TryGetColorId(string input, out byte colorId)
{
colorId = 0;
if (Enum.TryParse<Main.ColorToString>(input, true, out var color))
{
colorId = (byte)color;
return true;
}
return false;
}
public static void ChatCommand(ChatController __instance, string msg, string msg2, bool multi)
{
if (OnGameJoinedPatch.WaitingForChat) return;
OnGameJoinedPatch.WaitingForChat = true;
__instance.freeChatField.textArea.Clear();
__instance.freeChatField.textArea.SetText(string.Empty);
PlayerControl.LocalPlayer.RpcSendChat($"{msg}");
new LateTask(() =>
{
if (multi && msg2 != "")
{
PlayerControl.LocalPlayer.RpcSendChat($"{msg2}");
}
if (!multi)
{
OnGameJoinedPatch.WaitingForChat = false;
}
}, 2.2f, "ChatCommand1");
if (!multi || msg2 == "") return;
new LateTask(() =>
{
OnGameJoinedPatch.WaitingForChat = false;
}, 4.4f, "ChatCommand2");
}
public static void ModeratorChatCommand(string msg, string msg2, bool multi)
{
if (OnGameJoinedPatch.WaitingForChat) return;
OnGameJoinedPatch.WaitingForChat = true;
new LateTask(() =>
{
PlayerControl.LocalPlayer.RpcSendChat($"{msg}");
}, 2.2f, "ModeratorChatCommand1");
new LateTask(() =>
{
if (multi && msg2 != "")
{
PlayerControl.LocalPlayer.RpcSendChat($"{msg2}");
}
if (!multi)
{
OnGameJoinedPatch.WaitingForChat = false;
}
}, 4.4f, "ModeratorChatCommand2");
if (!multi || msg2 == "") return;
new LateTask(() =>
{
OnGameJoinedPatch.WaitingForChat = false;
}, 6.6f, "ModeratorChatCommand3");
}
// 0 = no winner. 1 = solo winner. 2 = add winner.
public static void CustomWinnerEndGame(PlayerControl winner, int winnerType)
{
HandlingGameEnd = true;
MessageWriter writer = AmongUsClient.Instance.StartEndGame();
if (winnerType == 0)
{
foreach (PlayerControl pc in PlayerControl.AllPlayerControls)
{
CustomGameOverReason = (byte)GameOverReason.ImpostorsByVote;
pc.RpcSetRole(AmongUs.GameOptions.RoleTypes.CrewmateGhost, false);
}
}
if (winnerType == 1)
{
foreach (PlayerControl pc in PlayerControl.AllPlayerControls)
{
CustomGameOverReason = (byte)GameOverReason.ImpostorsByVote;
if (pc != winner)
{
pc.RpcSetRole(AmongUs.GameOptions.RoleTypes.CrewmateGhost, false);
}
else
{
pc.RpcSetRole(AmongUs.GameOptions.RoleTypes.ImpostorGhost, false);
}
}
}
new LateTask(() =>
{
ContinueEndGame((byte)CustomGameOverReason);
}, 1f, "CustomWinnerEndGame");
}
public static void ContinueEndGame(byte gameOverReason)
{
MessageWriter writer = AmongUsClient.Instance.StartEndGame();
writer.Write(gameOverReason);
AmongUsClient.Instance.FinishEndGame(writer);
HandlingGameEnd = false;
Logger.Info($"{gameOverReason}", "ContinueEndGame");
}
public static void DumpLog()
{
string t = DateTime.Now.ToString("yyyy-MM-dd_HH.mm.ss");
#if ANDROID
var f = $"{BanManager.DataPath}/AUR-Logs/{t}";
#else
string f = $"{Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)}/AUR-logs/";
#endif
string filename = $"{f}AUR-{Main.ModVersion}-{t}.log";
if (!Directory.Exists(f)) Directory.CreateDirectory(f);
FileInfo file = new(@$"{Environment.CurrentDirectory}/BepInEx/LogOutput.log");
file.CopyTo(@filename);
if (PlayerControl.LocalPlayer != null)
{
HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, $"/Dump command activated\n\nFile: AUR-{Main.ModVersion}-{t}");
#if !ANDROID
ProcessStartInfo psi = new("Explorer.exe") { Arguments = "/e,/select," + @filename.Replace("/", "\\") };
Process.Start(psi);
#endif
}
}
private readonly static Dictionary<string, Sprite> CachedSprites = [];
public static Sprite LoadSprite(string path, float pixelsPerUnit = 1f)
{
try
{
if (CachedSprites.TryGetValue(path + pixelsPerUnit, out var sprite)) return sprite;
Texture2D texture = LoadTextureFromResources(path);
sprite = Sprite.Create(texture, new(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), pixelsPerUnit);
sprite.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontSaveInEditor;
return CachedSprites[path + pixelsPerUnit] = sprite;
}
catch
{
Logger.Error($"Failed to read Texture: {path}", "LoadSprite");
}
return null;
}
private static unsafe Texture2D LoadTextureFromResources(string path)
{
try
{
Texture2D texture = new(2, 2, TextureFormat.ARGB32, true);
Assembly assembly = Assembly.GetExecutingAssembly();
Stream stream = assembly.GetManifestResourceStream(path);
var length = stream.Length;
var byteTexture = new Il2CppStructArray<byte>(length);
stream.Read(new Span<byte>(IntPtr.Add(byteTexture.Pointer, IntPtr.Size * 4).ToPointer(), (int)length));
ImageConversion.LoadImage(texture, byteTexture, false);
return texture;
}
catch
{
Logger.Error($"Failed to read Texture: {path}", "LoadTextureFromResources");
}
return null;
}
public static void SetChatVisible()
{
PlayerControl.LocalPlayer.CmdReportDeadBody(null);
MeetingHud.Instance.RpcClose();
}
public static MethodBase GetStateMachineMoveNext<T>(string methodName)
{
var typeName = typeof(T).FullName;
var stateMachine = typeof(T).GetNestedTypes().FirstOrDefault(x => x.Name.Contains(methodName));
if (stateMachine == null)
{
Logger.Error($"Failed to find {methodName} state machine for {typeName}", "GetStateMachineMoveNext");
return null;
}
var moveNext = AccessTools.Method(stateMachine, "MoveNext");
if (moveNext == null)
{
Logger.Error($"Failed to find MoveNext method for {typeName}.{methodName}", "GetStateMachineMoveNext");
return null;
}
// Logger.Info($"Found {methodName}.MoveNext", "GetStateMachineMoveNext");
return moveNext;
}
public static readonly Dictionary<byte, string> StoredRoleText = new();
public static string GetRoleText(PlayerControl p)
{
if (p == null) return "";
if (p.Data.RoleType == RoleTypes.Impostor || p.Data.RoleType == RoleTypes.Shapeshifter || p.Data.RoleType == RoleTypes.Phantom || p.Data.RoleType == RoleTypes.Viper || p.Data.RoleType == RoleTypes.ImpostorGhost)
{
return $"<color=#ff1919>{p.Data.RoleType}</color>";
}
else return $"<color=#8cffff>{p.Data.RoleType}</color>";
}
public static int skeldVotes;
public static int miraVotes;
public static int polusVotes;
public static int airshipVotes;
public static int fungleVotes;
public static int dleksVotes;
public static bool MapVoteActive;
public static readonly List<byte> HasVoted = [];
public static void MapVote(bool moderator)
{
if (MapVoteActive || ModeVoteActive) return;
MapVoteActive = true;
string voteMessage = "A map vote is active!\n\n";
if (!Options.CustomizeMapVote.GetBool()) voteMessage += "A: Skeld\nB: Mira\nC: Polus\nD: Airship\nE: Fungle\nF: Dleks\n";
if (Options.IncludeSkeld.GetBool()) voteMessage += "A: Skeld\n";
if (Options.IncludeMira.GetBool()) voteMessage += "B: Mira\n";
if (Options.IncludePolus.GetBool()) voteMessage += "C: Polus\n";
if (Options.IncludeAirship.GetBool()) voteMessage += "D: Airship\n";
if (Options.IncludeFungle.GetBool()) voteMessage += "E: Fungle\n";
if (Options.IncludeDleks.GetBool()) voteMessage += "F: Dleks\n";
voteMessage += "\nType /vote 'letter' to vote.";
if (moderator) Utils.ModeratorChatCommand(voteMessage, "", false);
else Utils.ChatCommand(DestroyableSingleton<HudManager>.Instance.Chat, voteMessage, "", false);
new LateTask(() =>
{
int highest = -1;
string winner = "";
byte mapId = 0;
bool tie = false;
void Check(int votes, string name, byte id)
{
if (votes > highest)
{
highest = votes;
winner = name;
mapId = id;
tie = false;
}
else if (votes == highest)
{
tie = true;
}
}
if (!Options.CustomizeMapVote.GetBool())
{
Check(skeldVotes, "Skeld", 0);
Check(miraVotes, "Mira", 1);
Check(polusVotes, "Polus", 2);
Check(airshipVotes, "Airship", 4);
Check(fungleVotes, "Fungle", 5);
Check(dleksVotes, "Dleks", 3);
}
else
{
if (Options.IncludeSkeld.GetBool()) Check(skeldVotes, "Skeld", 0);
if (Options.IncludeMira.GetBool()) Check(miraVotes, "Mira", 1);
if (Options.IncludePolus.GetBool()) Check(polusVotes, "Polus", 2);
if (Options.IncludeAirship.GetBool()) Check(airshipVotes, "Airship", 4);
if (Options.IncludeFungle.GetBool()) Check(fungleVotes, "Fungle", 5);
if (Options.IncludeDleks.GetBool()) Check(dleksVotes, "Dleks", 3);
}
string voteResults = "";
if (!Options.CustomizeMapVote.GetBool())
{
voteResults += $"Skeld: {skeldVotes}\n";
voteResults += $"Mira: {miraVotes}\n";
voteResults += $"Polus: {polusVotes}\n";
voteResults += $"Airship: {airshipVotes}\n";
voteResults += $"Fungle: {fungleVotes}\n";
voteResults += $"Dleks: {dleksVotes}\n";
}
else
{
if (Options.IncludeSkeld.GetBool()) voteResults += $"Skeld: {skeldVotes}\n";
if (Options.IncludeMira.GetBool()) voteResults += $"Mira: {miraVotes}\n";
if (Options.IncludePolus.GetBool()) voteResults += $"Polus: {polusVotes}\n";
if (Options.IncludeAirship.GetBool()) voteResults += $"Airship: {airshipVotes}\n";
if (Options.IncludeFungle.GetBool()) voteResults += $"Fungle: {fungleVotes}\n";
if (Options.IncludeDleks.GetBool()) voteResults += $"Dleks: {dleksVotes}\n";
}
voteResults = voteResults.TrimEnd();
if (highest <= 0)
{
ModeratorChatCommand("Lol, no one voted. Hooray for democracy?", "", false);
}
else if (tie)
{
ModeratorChatCommand($"Map vote tied. No new map selected.\n\n{voteResults}", "", false);
}
else
{
ModeratorChatCommand(SendChatPatch.ConvertNum($"Winner: {winner} ({highest})\n\n{voteResults}"), "", false);
if (winner == "Dleks")
{
CreateOptionsPickerPatch.ApplyDleks(Utils.isHideNSeek);
}
else
{
CreateOptionsPickerPatch.ClearDleks();
if (!Utils.isHideNSeek) Main.NormalOptions.MapId = mapId;
else Main.HideNSeekOptions.MapId = mapId;
}
}
skeldVotes = 0;
miraVotes = 0;
polusVotes = 0;
airshipVotes = 0;
fungleVotes = 0;
dleksVotes = 0;
HasVoted.Clear();
MapVoteActive = false;
}, Options.MapVoteDuration.GetInt(), "MapPoll");
}
public static int standardVotes;
public static int noKillCooldownVotes;
public static int speedrunVotes;
public static int shiftAndSeekVotes;
public static int poofAndSeekVotes;
public static bool ModeVoteActive;
public static readonly List<byte> HasModeVoted = [];
public static void ModeVote(bool moderator)
{
if (ModeVoteActive || MapVoteActive) return;
ModeVoteActive = true;
string voteMessage = "A Gamemode vote is active!\n\n";
if (!Options.CustomizeModeVote.GetBool()) voteMessage += "A: None\nB: No Kill Cooldown\nC: Speedrun\nD: SnS\nE: PnS\n";
if (Options.IncludeStandard.GetBool()) voteMessage += "A: None\n";
if (Options.IncludeNoKillCooldown.GetBool()) voteMessage += "B: No Kill Cooldown\n";
if (Options.IncludeSpeedrun.GetBool()) voteMessage += "C: Speedrun\n";
if (Options.IncludeShiftAndSeek.GetBool()) voteMessage += "D: SnS\n";
if (Options.IncludePoofAndSeek.GetBool()) voteMessage += "E: PnS\n";
voteMessage += "\nType /vote 'letter' to vote.";
if (moderator) Utils.ModeratorChatCommand(voteMessage, "", false);
else Utils.ChatCommand(DestroyableSingleton<HudManager>.Instance.Chat, voteMessage, "", false);
new LateTask(() =>
{
int highest = -1;
string winner = "";
byte modeId = 0;
bool tie = false;
void Check(int votes, string name, byte id)
{
if (votes > highest)
{
highest = votes;
winner = name;
modeId = id;
tie = false;
}
else if (votes == highest)
{
tie = true;
}
}
if (!Options.CustomizeModeVote.GetBool())
{
Check(standardVotes, "None", 0);
Check(noKillCooldownVotes, "0 Kill Cooldown", 0);
Check(speedrunVotes, "Speedrun", 2);
Check(shiftAndSeekVotes, "Shift and Seek", 1);
Check(poofAndSeekVotes, "Poof and Seek", 3);
}
else
{
if (Options.IncludeStandard.GetBool()) Check(standardVotes, "None", 0);
if (Options.IncludeNoKillCooldown.GetBool()) Check(noKillCooldownVotes, "0 Kill Cooldown", 0);
if (Options.IncludeSpeedrun.GetBool()) Check(speedrunVotes, "Speedrun", 2);
if (Options.IncludeShiftAndSeek.GetBool()) Check(shiftAndSeekVotes, "Shift and Seek", 1);
if (Options.IncludePoofAndSeek.GetBool()) Check(poofAndSeekVotes, "Poof and Seek", 3);
}
string voteResults = "";
if (!Options.CustomizeModeVote.GetBool())
{
voteResults += $"Standard: {standardVotes}\n";
voteResults += $"No Kill Cooldown: {noKillCooldownVotes}\n";
voteResults += $"Speedrun: {speedrunVotes}\n";
voteResults += $"Shift and Seek: {shiftAndSeekVotes}\n";
voteResults += $"Poof and Seek: {poofAndSeekVotes}\n";
}
else
{
if (Options.IncludeStandard.GetBool()) voteResults += $"Standard: {standardVotes}\n";
if (Options.IncludeNoKillCooldown.GetBool()) voteResults += $"No Kill Cooldown: {noKillCooldownVotes}\n";
if (Options.IncludeSpeedrun.GetBool()) voteResults += $"Speedrun: {speedrunVotes}\n";
if (Options.IncludeShiftAndSeek.GetBool()) voteResults += $"Shift and Seek: {shiftAndSeekVotes}\n";
if (Options.IncludePoofAndSeek.GetBool()) voteResults += $"Poof and Seek: {poofAndSeekVotes}\n";
}
voteResults = voteResults.TrimEnd();
if (highest <= 0)
{
ModeratorChatCommand("We have a winner! Just kidding. No one voted.", "", false);
}
else if (tie)
{
ModeratorChatCommand($"Gamemode vote tied. No new mode selected.\n\n{voteResults}", "", false);
}
else
{
ModeratorChatCommand(SendChatPatch.ConvertNum($"Winner: {winner} ({highest})\n\n{voteResults}"), "", false);
Options.Gamemode.SetValue(modeId);
if (winner == "0 Kill Cooldown")
{
Main.NormalOptions.KillCooldown = 0.001f;
}
}
standardVotes = 0;
noKillCooldownVotes = 0;
speedrunVotes = 0;
shiftAndSeekVotes = 0;
poofAndSeekVotes = 0;
HasModeVoted.Clear();
ModeVoteActive = false;
}, Options.ModeVoteDuration.GetInt(), "ModePoll");
}
// As of v2026.8.18, Innersloth bans the host for hacking if the start game button is pressed when any player is not Serialized (or something similar).
// CheckValidData serves as an emergency blockage to prevent this and stop host from being kicked.
public static bool CheckValidData()
{
if (!AmongUsClient.Instance.AmHost) return true;
foreach (var p in PlayerControl.AllPlayerControls)
{
if (p == null || !p.hasBeenSerialized)
{
return false;
}
}
return true;
}
}