mirror of
https://github.com/ApeMV/AmongUsRevamped.git
synced 2026-10-19 15:51:59 +00:00
855 lines
31 KiB
C#
855 lines
31 KiB
C#
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;
|
||
}
|
||
} |