diff --git a/src/BAUPlugin.cs b/src/BAUPlugin.cs index c3deb46..3699aaf 100644 --- a/src/BAUPlugin.cs +++ b/src/BAUPlugin.cs @@ -5,6 +5,7 @@ using BepInEx.Unity.IL2CPP; using BetterAmongUs.Attributes; using BetterAmongUs.Data; using BetterAmongUs.Data.Json; +using BetterAmongUs.Enums; using BetterAmongUs.Helpers; using BetterAmongUs.Modules; using BetterAmongUs.Network; @@ -16,17 +17,15 @@ using UnityEngine; namespace BetterAmongUs; -internal enum ReleaseTypes : int -{ - Release, - Beta, - Dev, -} - [BepInPlugin(ModInfo.PLUGIN_GUID, ModInfo.PLUGIN_NAME, ModInfo.PLUGIN_VERSION)] -[BepInProcess("Among Us.exe")] +[BepInProcess(ModInfo.AmongUs.PROCESS_NAME)] internal class BAUPlugin : BasePlugin { + /// + /// Gets the formatted version text for display. + /// + /// Whether to use newline separation for additional info. + /// Formatted version string. internal static string GetVersionText(bool newLine = false) { string text = string.Empty; @@ -54,24 +53,62 @@ internal class BAUPlugin : BasePlugin return text; } + /// + /// Gets the Harmony instance used for patching. + /// internal static Harmony Harmony { get; } = new Harmony(ModInfo.PLUGIN_GUID); + /// + /// Gets the BetterAmongUs version string. + /// internal static string BetterAmongUsVersion => ModInfo.PLUGIN_VERSION; + + /// + /// Gets the application version string. + /// internal static string AppVersion => Application.version; + + /// + /// Gets the Among Us version string from reference data. + /// internal static string AmongUsVersion => ReferenceDataManager.Instance.Refdata.userFacingVersion; + /// + /// Gets platform-specific data. + /// internal static PlatformSpecificData PlatformData => Constants.GetPlatformData(); + /// + /// Gets the list of supported Among Us versions. + /// internal static string[] SupportedAmongUsVersions = [ "2025.11.18", ]; + /// + /// Gets the list of all PlayerControl instances. + /// internal static List AllPlayerControls = []; + + /// + /// Gets the list of all alive PlayerControl instances. + /// internal static List AllAlivePlayerControls => AllPlayerControls.Where(pc => pc.IsAlive()).ToList(); + + /// + /// Gets all DeadBody objects in the scene. + /// internal static DeadBody[] AllDeadBodys => UnityEngine.Object.FindObjectsOfType().ToArray(); + + /// + /// Gets all Vent objects in the scene. + /// internal static Vent[] AllVents => UnityEngine.Object.FindObjectsOfType(); + /// + /// Gets the BepInEx logger instance. + /// internal static ManualLogSource? Logger; public override void Load() @@ -114,6 +151,9 @@ internal class BAUPlugin : BasePlugin } } + /// + /// Sets up the console window for logging. + /// private static void SetupConsole() { ConsoleManager.CreateConsole(); @@ -128,6 +168,9 @@ internal class BAUPlugin : BasePlugin ConsoleManager.ConsoleStream.WriteLine($".--------------------------------------------------------------------------------.\r\n| ____ _ _ _ _ _ |\r\n| | __ ) ___| |_| |_ ___ _ __ / \\ _ __ ___ ___ _ __ __ _ | | | |___ |\r\n| | _ \\ / _ \\ __| __/ _ \\ '__| / _ \\ | '_ ` _ \\ / _ \\| '_ \\ / _` | | | | / __||\r\n| | |_) | __/ |_| || __/ | / ___ \\| | | | | | (_) | | | | (_| | | |_| \\__ \\|\r\n| |____/ \\___|\\__|\\__\\___|_| /_/ \\_\\_| |_| |_|\\___/|_| |_|\\__, | \\___/|___/|\r\n| |___/ |\r\n'--------------------------------------------------------------------------------'"); } + /// + /// Registers all MonoBehaviour classes for IL2CPP injection. + /// private static void RegisterAllMonoBehavioursInAssembly() { var assembly = Assembly.GetExecutingAssembly(); @@ -149,19 +192,74 @@ internal class BAUPlugin : BasePlugin } } + /// + /// Gets or sets the configuration entry for private only lobby setting. + /// internal static ConfigEntry? PrivateOnlyLobby { get; private set; } + + /// + /// Gets or sets the configuration entry for anti-cheat setting. + /// internal static ConfigEntry? AntiCheat { get; private set; } + + /// + /// Gets or sets the configuration entry for sending Better RPC setting. + /// internal static ConfigEntry? SendBetterRpc { get; private set; } + + /// + /// Gets or sets the configuration entry for better notifications setting. + /// internal static ConfigEntry? BetterNotifications { get; private set; } + + /// + /// Gets or sets the configuration entry for force own language setting. + /// internal static ConfigEntry? ForceOwnLanguage { get; private set; } + + /// + /// Gets or sets the configuration entry for chat dark mode setting. + /// internal static ConfigEntry? ChatDarkMode { get; private set; } + + /// + /// Gets or sets the configuration entry for chat in gameplay setting. + /// internal static ConfigEntry? ChatInGameplay { get; private set; } + + /// + /// Gets or sets the configuration entry for lobby player info setting. + /// internal static ConfigEntry? LobbyPlayerInfo { get; private set; } + + /// + /// Gets or sets the configuration entry for disable lobby theme setting. + /// internal static ConfigEntry? DisableLobbyTheme { get; private set; } + + /// + /// Gets or sets the configuration entry for unlock FPS setting. + /// internal static ConfigEntry? UnlockFPS { get; private set; } + + /// + /// Gets or sets the configuration entry for show FPS setting. + /// internal static ConfigEntry? ShowFPS { get; private set; } + + /// + /// Gets or sets the configuration entry for command prefix setting. + /// internal static ConfigEntry? CommandPrefix { get; set; } + + /// + /// Gets or sets the configuration entry for favorite color setting. + /// internal static ConfigEntry? FavoriteColor { get; set; } + + /// + /// Loads configuration options from BepInEx config file. + /// private void LoadOptions() { PrivateOnlyLobby = Config.Bind("Mod", "PrivateOnlyLobby", false); @@ -179,6 +277,15 @@ internal class BAUPlugin : BasePlugin FavoriteColor = Config.Bind("Mod", "FavoriteColor", -1); } + /// + /// Gets the persistent data path for Among Us. + /// + /// The persistent data path string. internal static string GetDataPathToAmongUs() => Application.persistentDataPath; + + /// + /// Gets the game installation path for Among Us. + /// + /// The game installation path string. internal static string GetGamePathToAmongUs() => Path.GetDirectoryName(Application.dataPath) ?? Application.dataPath; -} +} \ No newline at end of file diff --git a/src/BetterAmongUs.csproj b/src/BetterAmongUs.csproj index 817b69d..3f783fb 100644 --- a/src/BetterAmongUs.csproj +++ b/src/BetterAmongUs.csproj @@ -9,11 +9,7 @@ - + @@ -64,23 +60,15 @@ - + - + - + - + diff --git a/src/Commands/Arguments/BaseArgument.cs b/src/Commands/Arguments/BaseArgument.cs index 22c66a9..65a6895 100644 --- a/src/Commands/Arguments/BaseArgument.cs +++ b/src/Commands/Arguments/BaseArgument.cs @@ -1,11 +1,40 @@ namespace BetterAmongUs.Commands.Arguments; +/// +/// Abstract base class for command arguments in BetterAmongUs. +/// +/// The command this argument belongs to. +/// Information about the argument. internal abstract class BaseArgument(BaseCommand? command, string argInfo) { + /// + /// Gets the command this argument belongs to. + /// internal BaseCommand? Command { get; } = command; + + /// + /// Gets information about the argument. + /// internal string ArgInfo { get; } = argInfo; + + /// + /// Gets or sets the current argument value. + /// internal string Arg { get; set; } = string.Empty; + + /// + /// Gets the argument suggestions for auto-completion. + /// protected virtual string[] ArgSuggestions => GetArgSuggestions.Invoke(); + + /// + /// Gets or sets the function that provides argument suggestions. + /// internal Func GetArgSuggestions { get; set; } = () => { return []; }; + + /// + /// Gets the closest suggestion for the current argument value. + /// + /// The closest matching suggestion, or empty string if none found. internal string GetClosestSuggestion() => ArgSuggestions.FirstOrDefault(name => name.StartsWith(Arg, StringComparison.OrdinalIgnoreCase)) ?? string.Empty; -} +} \ No newline at end of file diff --git a/src/Commands/Arguments/BoolArgument.cs b/src/Commands/Arguments/BoolArgument.cs index f2d4d77..8e3c1af 100644 --- a/src/Commands/Arguments/BoolArgument.cs +++ b/src/Commands/Arguments/BoolArgument.cs @@ -1,8 +1,28 @@ namespace BetterAmongUs.Commands.Arguments; +/// +/// Represents a boolean command argument. +/// +/// The command this argument belongs to. +/// Information about the argument (default: "{bool}"). internal sealed class BoolArgument(BaseCommand? command, string argInfo = "{bool}") : BaseArgument(command, argInfo) { + /// + /// Gets the boolean argument suggestions for auto-completion. + /// protected override string[] ArgSuggestions => ["true", "false"]; + + /// + /// Parses the argument string to a nullable boolean value. + /// + /// + /// True if the argument is "true", + /// False if the argument is "false" or empty, + /// Null if the argument is invalid. + /// + /// + /// Displays an error message if the argument is invalid. + /// internal bool? GetBool() { if (Arg.ToLower() is "true") diff --git a/src/Commands/Arguments/PlayerArgument.cs b/src/Commands/Arguments/PlayerArgument.cs index 5245b6e..43f61ca 100644 --- a/src/Commands/Arguments/PlayerArgument.cs +++ b/src/Commands/Arguments/PlayerArgument.cs @@ -2,9 +2,29 @@ namespace BetterAmongUs.Commands.Arguments; +/// +/// Represents a player command argument. +/// +/// The command this argument belongs to. +/// Information about the argument (default: "{player}"). internal sealed class PlayerArgument(BaseCommand? command, string argInfo = "{player}") : BaseArgument(command, argInfo) { + /// + /// Gets the player argument suggestions for auto-completion. + /// + /// + /// Suggestions are ordered with the local player first, then other players. + /// Spaces in player names are replaced with underscores for command parsing. + /// protected override string[] ArgSuggestions => BAUPlugin.AllPlayerControls.OrderBy(pc => pc.IsLocalPlayer() ? 0 : 1).Select(pc => pc.Data.PlayerName.Replace(' ', '_')).ToArray(); + + /// + /// Attempts to get a PlayerControl instance based on the argument value. + /// + /// + /// The PlayerControl if found; otherwise, null. + /// Displays an error message if the player is not found. + /// internal PlayerControl? TryGetTarget() { var player = BAUPlugin.AllPlayerControls.FirstOrDefault(pc => pc.Data.PlayerName.ToLower().Replace(' ', '_') == Arg.ToLower()); diff --git a/src/Commands/Arguments/StringArgument.cs b/src/Commands/Arguments/StringArgument.cs index e131dea..4cd095e 100644 --- a/src/Commands/Arguments/StringArgument.cs +++ b/src/Commands/Arguments/StringArgument.cs @@ -1,5 +1,12 @@ namespace BetterAmongUs.Commands.Arguments; +/// +/// Represents a string command argument. +/// +/// The command this argument belongs to. +/// Information about the argument (default: "{String}"). internal sealed class StringArgument(BaseCommand? command, string argInfo = "{String}") : BaseArgument(command, argInfo) { + // This class inherits the basic string argument functionality from BaseArgument + // and can be extended with string-specific parsing or validation if needed. } \ No newline at end of file diff --git a/src/Commands/BaseCommand.cs b/src/Commands/BaseCommand.cs index 37da32d..d56bd16 100644 --- a/src/Commands/BaseCommand.cs +++ b/src/Commands/BaseCommand.cs @@ -5,30 +5,90 @@ using BetterAmongUs.Helpers; namespace BetterAmongUs.Commands; +/// +/// Abstract base class for all commands in BetterAmongUs. +/// internal abstract class BaseCommand { + /// + /// Gets an array of all registered commands. + /// internal static readonly BaseCommand?[] allCommands = [.. RegisterCommandAttribute.Instances]; + + /// + /// Gets the type of the command. + /// internal virtual CommandType Type => CommandType.Normal; + + /// + /// Gets all names for this command (including short names). + /// internal string[] Names => ShortNames.Concat(new[] { Name }).ToArray(); + + /// + /// Gets the primary name of the command. + /// internal abstract string Name { get; } + + /// + /// Gets the short names (aliases) for the command. + /// internal virtual string[] ShortNames => []; + + /// + /// Gets the description of the command. + /// internal abstract string Description { get; } + + /// + /// Gets or sets the arguments for this command. + /// internal BaseArgument[] Arguments { get; set; } = []; + + /// + /// Gets whether this command sets a chat timer. + /// internal virtual bool SetChatTimer { get; set; } = false; + + /// + /// Determines whether this command should be shown in command lists. + /// + /// True if the command should be shown; otherwise, false. internal virtual bool ShowCommand() => true; + + /// + /// Determines whether this command should be shown in suggestions. + /// + /// True if the command should be shown in suggestions; otherwise, false. internal virtual bool ShowSuggestion() => ShowCommand(); + + /// + /// Executes the command. + /// internal abstract void Run(); + /// + /// Formats and optionally displays command result text. + /// + /// The result text to display. + /// If true, only returns the formatted string without displaying it. + /// The formatted result text. internal static string CommandResultText(string text, bool onlyGetStr = false) { if (!onlyGetStr) Utils.AddChatPrivate(text); return text; } + /// + /// Formats and optionally displays command error text. + /// + /// The error text to display. + /// If true, only returns the formatted string without displaying it. + /// The formatted error text. internal static string CommandErrorText(string error, bool onlyGetStr = false) { string er = "Error:"; if (!onlyGetStr) Utils.AddChatPrivate($"{er}\n{error}"); return $"{er}\n{error}"; } -} +} \ No newline at end of file diff --git a/src/Data/BetterDataManager.cs b/src/Data/BetterDataManager.cs index a52487a..35db576 100644 --- a/src/Data/BetterDataManager.cs +++ b/src/Data/BetterDataManager.cs @@ -3,23 +3,79 @@ using BetterAmongUs.Helpers; namespace BetterAmongUs.Data; +/// +/// Manages data storage, settings, and ban lists for the BetterAmongUs mod. +/// internal static class BetterDataManager { + /// + /// The main data file containing outfit presets and cheat detection data. + /// internal static BetterDataFile BetterDataFile = new(); + + /// + /// The game settings file with compressed storage. + /// internal static BetterGameSettingsFile BetterGameSettingsFile = new(); + /// + /// Legacy data file path (BetterData.json). + /// internal static string dataPathOLD = GetFilePath("BetterData"); + + /// + /// Current data file path (BetterDataV2.json). + /// internal static string dataPath = GetFilePath("BetterDataV2"); + + /// + /// Root directory for BetterAmongUs data. + /// internal static string filePathFolder = Path.Combine(BAUPlugin.GetGamePathToAmongUs(), $"Better_Data"); + + /// + /// Directory for save information files. + /// internal static string filePathFolderSaveInfo = Path.Combine(filePathFolder, $"SaveInfo"); + + /// + /// Directory for settings files. + /// internal static string filePathFolderSettings = Path.Combine(filePathFolder, $"Settings"); + + /// + /// Directory for game replay files. + /// internal static string filePathFolderReplays = Path.Combine(filePathFolder, $"Replays"); + + /// + /// Legacy settings file path. + /// internal static string SettingsFileOld = Path.Combine(filePathFolderSettings, "Preset.json"); + + /// + /// Current compressed settings file path. + /// internal static string SettingsFile = Path.Combine(filePathFolderSettings, "Settings.dat"); + + /// + /// File containing banned player identifiers. + /// internal static string banPlayerListFile = Path.Combine(filePathFolderSaveInfo, "BanPlayerList.txt"); + + /// + /// File containing banned player names. + /// internal static string banNameListFile = Path.Combine(filePathFolderSaveInfo, "BanNameList.txt"); + + /// + /// File containing banned words/patterns. + /// internal static string banWordListFile = Path.Combine(filePathFolderSaveInfo, "BanWordList.txt"); + /// + /// Array of file paths that should be checked during initialization. + /// private static string[] Paths => [ banPlayerListFile, @@ -27,11 +83,19 @@ internal static class BetterDataManager banWordListFile ]; + /// + /// Gets a file path by name in the Among Us data directory. + /// + /// The name of the file (without extension). + /// The full file path with .json extension. internal static string GetFilePath(string name) { return Path.Combine(BAUPlugin.GetDataPathToAmongUs(), $"{name}.json"); } + /// + /// Initializes the data manager, loading files and ensuring required directories exist. + /// internal static void Init() { BetterDataFile.Init(); @@ -52,12 +116,23 @@ internal static class BetterDataManager } } + /// + /// Saves a setting with the specified ID. + /// + /// The setting identifier. + /// The setting value to save. internal static void SaveSetting(int id, object? input) { BetterGameSettingsFile.Settings[id] = input; BetterGameSettingsFile.Save(); } + /// + /// Checks if a setting can be loaded as the specified type. + /// + /// The type to check against. + /// The setting identifier. + /// True if the setting exists and can be cast to type T, false otherwise. internal static bool CanLoadSetting(int id) { if (BetterGameSettingsFile.Settings.TryGetValue(id, out var value)) @@ -71,6 +146,13 @@ internal static class BetterDataManager return false; } + /// + /// Loads a setting with the specified ID, returning a default value if not found. + /// + /// The type of the setting value. + /// The setting identifier. + /// The default value to return and save if the setting doesn't exist. + /// The setting value or the default value if not found. internal static T? LoadSetting(int id, T? Default = default) { if (BetterGameSettingsFile.Settings.TryGetValue(id, out var value)) @@ -85,6 +167,11 @@ internal static class BetterDataManager return Default; } + /// + /// Adds a player to the ban list by friend code and/or hashed PUID. + /// + /// The player's friend code (optional). + /// The player's hashed PUID (optional). internal static void AddToBanList(string friendCode = "", string hashPUID = "") { if (!string.IsNullOrEmpty(friendCode) || !string.IsNullOrEmpty(hashPUID)) @@ -115,6 +202,11 @@ internal static class BetterDataManager } } + /// + /// Removes a player from all cheat detection lists by identifier. + /// + /// The player identifier (name, hashPUID, or friend code). + /// True if the player was found and removed, false otherwise. internal static bool RemovePlayer(string identifier) { identifier = identifier.Replace(' ', '_'); @@ -161,6 +253,9 @@ internal static class BetterDataManager return didFind; } + /// + /// Clears all cheat detection data from all categories. + /// internal static void ClearCheatData() { BetterDataFile.CheatData.Clear(); @@ -169,4 +264,4 @@ internal static class BetterDataManager BetterDataFile.KNData.Clear(); BetterDataFile.Save(); } -} +} \ No newline at end of file diff --git a/src/Data/Json/AbstractJsonFile.cs b/src/Data/Json/AbstractJsonFile.cs index 3d2d3c5..3021772 100644 --- a/src/Data/Json/AbstractJsonFile.cs +++ b/src/Data/Json/AbstractJsonFile.cs @@ -5,11 +5,20 @@ using System.Text.Json.Serialization; namespace BetterAmongUs.Data.Json; +/// +/// Abstract base class for JSON file handling with serialization and deserialization capabilities. +/// internal abstract class AbstractJsonFile { + /// + /// Gets the file path where the JSON data will be stored. + /// internal abstract string FilePath { get; } private bool _hasInit; + /// + /// Gets the JSON serializer options used for serialization and deserialization. + /// protected virtual JsonSerializerOptions SerializerOptions { get; } = new() { WriteIndented = true, @@ -18,6 +27,9 @@ internal abstract class AbstractJsonFile PropertyNameCaseInsensitive = true, }; + /// + /// Initializes the JSON file, creating it if it doesn't exist or loading existing data. + /// internal virtual void Init() { if (_hasInit) return; @@ -33,6 +45,10 @@ internal abstract class AbstractJsonFile Save(); } + /// + /// Loads data from the JSON file into the current instance. + /// + /// True if loading was successful, false otherwise. protected virtual bool Load() { try @@ -74,6 +90,10 @@ internal abstract class AbstractJsonFile } } + /// + /// Attempts to read content from the file, returning an empty string on failure. + /// + /// The file content or an empty string if reading fails. private string TryReadFromFile() { try @@ -86,11 +106,19 @@ internal abstract class AbstractJsonFile } } + /// + /// Reads the content from the JSON file. + /// + /// The content of the JSON file. protected virtual string ReadFromFile() { return File.ReadAllText(FilePath); } + /// + /// Saves the current instance's data to the JSON file. + /// + /// True if saving was successful, false otherwise. internal virtual bool Save() { try @@ -107,11 +135,19 @@ internal abstract class AbstractJsonFile return true; } + /// + /// Writes the JSON string to the file. + /// + /// The JSON string to write. protected virtual void WriteToFile(string json) { File.WriteAllText(FilePath, json); } + /// + /// Checks if the JSON file exists and contains valid data. + /// + /// True if the file exists and contains valid JSON data, false otherwise. private bool CheckFile() { var directory = Path.GetDirectoryName(FilePath); @@ -154,4 +190,4 @@ internal abstract class AbstractJsonFile return true; } -} +} \ No newline at end of file diff --git a/src/Data/Json/BetterDataFile.cs b/src/Data/Json/BetterDataFile.cs index c04c866..7aefef4 100644 --- a/src/Data/Json/BetterDataFile.cs +++ b/src/Data/Json/BetterDataFile.cs @@ -3,10 +3,20 @@ using System.Text.Json.Serialization; namespace BetterAmongUs.Data.Json; +/// +/// Represents the main data file for BetterAmongUs, containing outfit presets and cheat detection data. +/// internal sealed class BetterDataFile : AbstractJsonFile { + /// + /// Gets the file path for the BetterAmongUs data file. + /// internal override string FilePath => BetterDataManager.dataPath; + /// + /// Loads the data file and performs post-load processing. + /// + /// True if loading was successful, false otherwise. protected override bool Load() { var success = base.Load(); @@ -18,14 +28,28 @@ internal sealed class BetterDataFile : AbstractJsonFile return success; } + /// + /// Saves the data file, updating the combined cheat data before saving. + /// + /// True if saving was successful, false otherwise. internal override bool Save() { AllCheatData = [.. CheatData, .. SickoData, .. AUMData, .. KNData]; return base.Save(); } + /// + /// Checks if player data matches any known cheat entries. + /// + /// The player information to check. + /// True if the player matches a cheat entry, false otherwise. internal bool CheckPlayerData(NetworkedPlayerInfo data) => CheckPlayerDataWithReason(data).check; + /// + /// Checks if player data matches any known cheat entries and provides a reason if found. + /// + /// The player information to check. + /// A tuple containing whether a match was found and the reason for the match. internal (bool check, string reason) CheckPlayerDataWithReason(NetworkedPlayerInfo data) { foreach (var info in AllCheatData) @@ -40,23 +64,44 @@ internal sealed class BetterDataFile : AbstractJsonFile return (false, ""); } + /// + /// Gets or sets the combined collection of all cheat detection data. + /// internal HashSet AllCheatData { get; set; } = []; + /// + /// Gets or sets the index of the currently selected outfit preset. + /// [JsonPropertyName("selectedOutfitPreset")] public int SelectedOutfitPreset { get; set; } = 0; + /// + /// Gets or sets the collection of outfit presets. + /// [JsonPropertyName("outfitData")] public HashSet OutfitData { get; set; } = [new(), new(), new(), new(), new(), new()]; + /// + /// Gets or sets the collection of known cheat user data. + /// [JsonPropertyName("cheatData")] public HashSet CheatData { get; set; } = []; + /// + /// Gets or sets the collection of Sicko cheat user data. + /// [JsonPropertyName("sickoData")] public HashSet SickoData { get; set; } = []; + /// + /// Gets or sets the collection of AUM cheat user data. + /// [JsonPropertyName("aumData")] public HashSet AUMData { get; set; } = []; + /// + /// Gets or sets the collection of KN cheat user data. + /// [JsonPropertyName("knData")] public HashSet KNData { get; set; } = []; } \ No newline at end of file diff --git a/src/Data/Json/BetterGameSettingsFile.cs b/src/Data/Json/BetterGameSettingsFile.cs index 6c06b48..a4f9d1b 100644 --- a/src/Data/Json/BetterGameSettingsFile.cs +++ b/src/Data/Json/BetterGameSettingsFile.cs @@ -6,10 +6,20 @@ using System.Text.Json.Serialization; namespace BetterAmongUs.Data.Json; +/// +/// Represents a compressed JSON file for storing game settings with GZIP compression. +/// internal sealed class BetterGameSettingsFile : AbstractJsonFile { + /// + /// Gets the file path for the game settings file. + /// internal override string FilePath => BetterDataManager.SettingsFile; + /// + /// Loads the settings file and converts JSON elements to their appropriate types. + /// + /// True if loading was successful, false otherwise. protected override bool Load() { var success = base.Load(); @@ -42,6 +52,10 @@ internal sealed class BetterGameSettingsFile : AbstractJsonFile return success; } + /// + /// Writes JSON data to the file with GZIP compression and base64 encoding. + /// + /// The JSON string to write to the file. protected override void WriteToFile(string json) { var jsonDoc = JsonDocument.Parse(json); @@ -64,6 +78,10 @@ internal sealed class BetterGameSettingsFile : AbstractJsonFile File.WriteAllText(FilePath, Convert.ToBase64String(ms.ToArray())); } + /// + /// Reads compressed JSON data from the file and decompresses it. + /// + /// The decompressed JSON string. protected override string ReadFromFile() { byte[] compressedBytes = Convert.FromBase64String(File.ReadAllText(FilePath).Trim()); @@ -95,6 +113,9 @@ internal sealed class BetterGameSettingsFile : AbstractJsonFile return JsonSerializer.Serialize(new { Settings = resultDict }); } + /// + /// Gets the dictionary of game settings with integer keys and various value types. + /// [JsonPropertyName(nameof(Settings))] - public Dictionary Settings { get; set; } = []; + public Dictionary Settings { get; private set; } = []; } \ No newline at end of file diff --git a/src/Data/Json/OutfitData.cs b/src/Data/Json/OutfitData.cs index cbb98f5..67125af 100644 --- a/src/Data/Json/OutfitData.cs +++ b/src/Data/Json/OutfitData.cs @@ -2,19 +2,55 @@ namespace BetterAmongUs.Data.Json; +/// +/// Represents outfit data including hat, pet, skin, visor, and nameplate information. +/// [Serializable] internal sealed class OutfitData { + /// + /// The ID of the hat. + /// public string HatId = HatData.EmptyId; + + /// + /// The ID of the pet. + /// public string PetId = PetData.EmptyId; + + /// + /// The ID of the skin. + /// public string SkinId = SkinData.EmptyId; + + /// + /// The ID of the visor. + /// public string VisorId = VisorData.EmptyId; + + /// + /// The ID of the nameplate. + /// public string NamePlateId = NamePlateData.EmptyId; + /// + /// Gets the outfit data for the currently selected preset. + /// + /// The outfit data for the current preset. internal static OutfitData GetOutfitData() => BetterDataManager.BetterDataFile.OutfitData.ElementAt(BetterDataManager.BetterDataFile.SelectedOutfitPreset); + + /// + /// Gets the outfit data for a specific preset index. + /// + /// The index of the preset to retrieve. + /// The outfit data for the specified preset. internal static OutfitData GetOutfitData(int index) => BetterDataManager.BetterDataFile.OutfitData.ElementAt(index); private static bool ignoreChange; + + /// + /// Initializes the outfit data system and sets up change listeners. + /// internal static void Init() { FindPreset(); @@ -33,6 +69,9 @@ internal sealed class OutfitData DataManager.Player.Customization.OnNamePlateChanged += Save; } + /// + /// Finds the preset that matches the current player customization and sets it as selected. + /// internal static void FindPreset() { var collection = BetterDataManager.BetterDataFile.OutfitData; @@ -56,6 +95,9 @@ internal sealed class OutfitData BetterDataManager.BetterDataFile.Save(); } + /// + /// Validates the outfit data by ensuring all IDs correspond to unlocked items. + /// private void Validate() { if (!HatManager.Instance.GetUnlockedHats().Any(item => item.ProductId == HatId)) @@ -70,6 +112,10 @@ internal sealed class OutfitData NamePlateId = NamePlateData.EmptyId; } + /// + /// Loads the outfit data into the player's customization and invokes a callback. + /// + /// The callback to invoke after loading the outfit. internal void Load(Action callback) { Validate(); @@ -86,6 +132,9 @@ internal sealed class OutfitData BetterDataManager.BetterDataFile.Save(); } + /// + /// Updates the outfit data with the current player customization values. + /// internal void LoadToData() { HatId = DataManager.Player.Customization.Hat; @@ -94,4 +143,4 @@ internal sealed class OutfitData VisorId = DataManager.Player.Customization.Visor; NamePlateId = DataManager.Player.Customization.NamePlate; } -} +} \ No newline at end of file diff --git a/src/Data/Replay/Events/AppearReplayEvent.cs b/src/Data/Replay/Events/AppearReplayEvent.cs index ca357fd..a995bf9 100644 --- a/src/Data/Replay/Events/AppearReplayEvent.cs +++ b/src/Data/Replay/Events/AppearReplayEvent.cs @@ -1,5 +1,6 @@ using AmongUs.GameOptions; using BetterAmongUs.Helpers; +using BetterAmongUs.Interfaces; using System.Text.Json.Serialization; namespace BetterAmongUs.Data.Replay.Events; diff --git a/src/Data/Replay/Events/IReplayEvent.cs b/src/Data/Replay/Events/IReplayEvent.cs deleted file mode 100644 index accc0f2..0000000 --- a/src/Data/Replay/Events/IReplayEvent.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace BetterAmongUs.Data.Replay.Events; - -public interface IReplayEvent -{ - [JsonIgnore] - string Id { get; } - void Play(); -} - -public interface IReplayEvent : IReplayEvent -{ - T? EventData { get; set; } -} diff --git a/src/Data/Replay/Events/MurderReplayEvent.cs b/src/Data/Replay/Events/MurderReplayEvent.cs index b237029..9e2de03 100644 --- a/src/Data/Replay/Events/MurderReplayEvent.cs +++ b/src/Data/Replay/Events/MurderReplayEvent.cs @@ -1,4 +1,5 @@ using BetterAmongUs.Helpers; +using BetterAmongUs.Interfaces; using System.Text.Json.Serialization; namespace BetterAmongUs.Data.Replay.Events; diff --git a/src/Data/Replay/Events/ProtectReplayEvent.cs b/src/Data/Replay/Events/ProtectReplayEvent.cs index 9d681e8..f94a68d 100644 --- a/src/Data/Replay/Events/ProtectReplayEvent.cs +++ b/src/Data/Replay/Events/ProtectReplayEvent.cs @@ -1,5 +1,6 @@ using AmongUs.GameOptions; using BetterAmongUs.Helpers; +using BetterAmongUs.Interfaces; using System.Text.Json.Serialization; namespace BetterAmongUs.Data.Replay.Events; diff --git a/src/Data/Replay/Events/ProtectedReplayEvent.cs b/src/Data/Replay/Events/ProtectedReplayEvent.cs index 7c5b935..15beb16 100644 --- a/src/Data/Replay/Events/ProtectedReplayEvent.cs +++ b/src/Data/Replay/Events/ProtectedReplayEvent.cs @@ -1,4 +1,5 @@ using BetterAmongUs.Helpers; +using BetterAmongUs.Interfaces; using System.Text.Json.Serialization; namespace BetterAmongUs.Data.Replay.Events; diff --git a/src/Data/Replay/Events/ShapeshiftReplayEvent.cs b/src/Data/Replay/Events/ShapeshiftReplayEvent.cs index 0d2a13e..4d44ae8 100644 --- a/src/Data/Replay/Events/ShapeshiftReplayEvent.cs +++ b/src/Data/Replay/Events/ShapeshiftReplayEvent.cs @@ -1,5 +1,6 @@ using AmongUs.GameOptions; using BetterAmongUs.Helpers; +using BetterAmongUs.Interfaces; using System.Text.Json.Serialization; namespace BetterAmongUs.Data.Replay.Events; diff --git a/src/Data/Replay/Events/StartMeetingReplayEvent.cs b/src/Data/Replay/Events/StartMeetingReplayEvent.cs index d700ac3..01ecbd7 100644 --- a/src/Data/Replay/Events/StartMeetingReplayEvent.cs +++ b/src/Data/Replay/Events/StartMeetingReplayEvent.cs @@ -1,4 +1,6 @@ -namespace BetterAmongUs.Data.Replay.Events; +using BetterAmongUs.Interfaces; + +namespace BetterAmongUs.Data.Replay.Events; internal sealed class StartMeetingReplayEvent : IReplayEvent<(int playerId, int targetId)> { diff --git a/src/Data/Replay/Events/UpdateSystemReplayEvent.cs b/src/Data/Replay/Events/UpdateSystemReplayEvent.cs index ec20500..3f6b5f3 100644 --- a/src/Data/Replay/Events/UpdateSystemReplayEvent.cs +++ b/src/Data/Replay/Events/UpdateSystemReplayEvent.cs @@ -1,4 +1,5 @@ using BetterAmongUs.Helpers; +using BetterAmongUs.Interfaces; using System.Text.Json.Serialization; namespace BetterAmongUs.Data.Replay.Events; diff --git a/src/Data/Replay/Events/VanishReplayEvent.cs b/src/Data/Replay/Events/VanishReplayEvent.cs index 11f6256..94b4955 100644 --- a/src/Data/Replay/Events/VanishReplayEvent.cs +++ b/src/Data/Replay/Events/VanishReplayEvent.cs @@ -1,5 +1,6 @@ using AmongUs.GameOptions; using BetterAmongUs.Helpers; +using BetterAmongUs.Interfaces; using System.Text.Json.Serialization; namespace BetterAmongUs.Data.Replay.Events; diff --git a/src/Data/Replay/Events/VentReplayEvent.cs b/src/Data/Replay/Events/VentReplayEvent.cs index 227a191..594cd48 100644 --- a/src/Data/Replay/Events/VentReplayEvent.cs +++ b/src/Data/Replay/Events/VentReplayEvent.cs @@ -1,4 +1,5 @@ using BetterAmongUs.Helpers; +using BetterAmongUs.Interfaces; using System.Text.Json.Serialization; namespace BetterAmongUs.Data.Replay.Events; diff --git a/src/Data/Replay/PlayerReplayData.cs b/src/Data/Replay/PlayerReplayData.cs index 7457866..1f4d88e 100644 --- a/src/Data/Replay/PlayerReplayData.cs +++ b/src/Data/Replay/PlayerReplayData.cs @@ -3,12 +3,34 @@ using UnityEngine; namespace BetterAmongUs.Data.Replay; +/// +/// Represents player data for replay functionality. +/// [Serializable] internal class PlayerReplayData { + /// + /// Gets or sets the player's ID. + /// public int PlayerId; + + /// + /// Gets or sets the player's name. + /// public string PlayerName = ""; + + /// + /// Gets or sets the player's role type. + /// public RoleTypes Role; + + /// + /// Gets or sets the buffer of movement data containing timestamps and positions. + /// public (float timeStamp, Vector2 pos)[] MovementDataBuffer = []; + + /// + /// Gets or sets the player's cosmetic data. + /// public (int colorId, string skinId, string visorId, string petId, string namePlateId) CosmeticData = new(); -} +} \ No newline at end of file diff --git a/src/Data/Replay/Replay.cs b/src/Data/Replay/Replay.cs index da2c18f..7422dc5 100644 --- a/src/Data/Replay/Replay.cs +++ b/src/Data/Replay/Replay.cs @@ -1,14 +1,34 @@ -using BetterAmongUs.Data.Replay.Events; +using BetterAmongUs.Interfaces; namespace BetterAmongUs.Data.Replay; +/// +/// Represents a replay of a game session. +/// [Serializable] internal class Replay { + /// + /// Gets or sets the map ID for the replay. + /// public int MapId; + + /// + /// Gets or sets the list of player data for the replay. + /// public List PlayerData = []; + + /// + /// Gets or sets the dictionary of replay events keyed by timestamp. + /// public Dictionary> Events = []; + /// + /// Loads the replay and initializes the game session. + /// + /// + /// Sets up the map and host game, then creates player instances. + /// public void Load() { AmongUsClient.Instance.TutorialMapId = MapId; @@ -16,18 +36,38 @@ internal class Replay CreatePlayers(); } + /// + /// Creates player instances for the replay. + /// + /// + /// This method is currently a placeholder and needs implementation. + /// private void CreatePlayers() { - + // TODO: Implement player creation logic } + /// + /// Updates player movement data at the specified timestamp. + /// + /// The timestamp to update movement for. + /// + /// This method is currently a placeholder and needs implementation. + /// public void UpdateMovement(float timeStamp) { - + // TODO: Implement movement update logic } + /// + /// Updates replay events at the specified timestamp. + /// + /// The timestamp to update events for. + /// + /// This method is currently a placeholder and needs implementation. + /// public void UpdateEvents(float timeStamp) { - + // TODO: Implement event update logic } -} +} \ No newline at end of file diff --git a/src/Data/Replay/ReplayEventConverter.cs b/src/Data/Replay/ReplayEventConverter.cs deleted file mode 100644 index 8e9bc41..0000000 --- a/src/Data/Replay/ReplayEventConverter.cs +++ /dev/null @@ -1,47 +0,0 @@ -using BetterAmongUs.Data.Replay.Events; -using System.Reflection; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace BetterAmongUs.Data.Replay; - -public class ReplayEventConverter : JsonConverter -{ - private static readonly Dictionary _eventTypes; - - static ReplayEventConverter() - { - _eventTypes = Assembly.GetExecutingAssembly() - .GetTypes() - .Where(t => t.IsClass && !t.IsAbstract && t.GetInterfaces() - .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IReplayEvent<>))) - .ToDictionary(t => - { - var instance = (IReplayEvent)Activator.CreateInstance(t)!; - return instance.Id; - }); - } - - public override IReplayEvent Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - using JsonDocument doc = JsonDocument.ParseValue(ref reader); - var root = doc.RootElement; - string id = root.GetProperty("Id").GetString()!; - - if (_eventTypes.TryGetValue(id, out Type? eventType)) - { - return (IReplayEvent)JsonSerializer.Deserialize( - root.GetRawText(), - eventType, - options - )!; - } - - throw new JsonException($"Unknown event type: {id}"); - } - - public override void Write(Utf8JsonWriter writer, IReplayEvent value, JsonSerializerOptions options) - { - JsonSerializer.Serialize(writer, (object)value, options); - } -} diff --git a/src/Data/SharedSecretExchange.cs b/src/Data/SharedSecretExchange.cs index 82c5f4f..eea7937 100644 --- a/src/Data/SharedSecretExchange.cs +++ b/src/Data/SharedSecretExchange.cs @@ -3,6 +3,9 @@ using System.Security.Cryptography; namespace BetterAmongUs.Data; +/// +/// Handles secure key exchange using Elliptic Curve Diffie-Hellman (ECDH) for establishing shared secrets. +/// internal sealed class SharedSecretExchange { private readonly ECDiffieHellman dh; @@ -10,6 +13,9 @@ internal sealed class SharedSecretExchange private int tempKey; private byte[] sharedSecret = []; + /// + /// Initializes a new instance of the SharedSecretExchange class with a new ECDH key pair. + /// internal SharedSecretExchange() { dh = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256); @@ -18,18 +24,35 @@ internal sealed class SharedSecretExchange tempKey = random.Next(); } + /// + /// Gets the public key for key exchange. + /// + /// The public key in SubjectPublicKeyInfo format. internal byte[] GetPublicKey() { return publicKey; } + /// + /// Gets the temporary key used for initial communication. + /// + /// A random integer used as a temporary key. internal int GetTempKey() { return tempKey; } + /// + /// Gets the established shared secret. + /// + /// The shared secret byte array. internal byte[] GetSharedSecret() => sharedSecret; + /// + /// Generates a shared secret using another party's public key. + /// + /// The other party's public key in SubjectPublicKeyInfo format. + /// The shared secret byte array, or an empty array if generation fails. internal byte[] GenerateSharedSecret(byte[] otherPartyPublicKey) { if (sharedSecret.Length > 0) return sharedSecret; @@ -50,6 +73,10 @@ internal sealed class SharedSecretExchange } } + /// + /// Gets a numeric hash of the shared secret for verification purposes. + /// + /// A 32-bit integer hash of the shared secret, or 0 if no shared secret exists. internal int GetSharedSecretHash() { if (sharedSecret.Length == 0) @@ -61,7 +88,14 @@ internal sealed class SharedSecretExchange return Math.Abs(numericHash); } + /// + /// Gets a value indicating whether the key exchange data has been cleared for security. + /// internal bool HasBeenCleared { get; private set; } + + /// + /// Clears all sensitive key exchange data for security purposes. + /// internal void ClearData() { if (HasBeenCleared) return; diff --git a/src/Enums/CommandType.cs b/src/Enums/CommandType.cs index ac8cd5e..02da728 100644 --- a/src/Enums/CommandType.cs +++ b/src/Enums/CommandType.cs @@ -1,6 +1,12 @@ namespace BetterAmongUs.Enums; +/// +/// Defines the types of commands available in the BetterAmongUs system. +/// internal enum CommandType { + /// + /// Standard command type. + /// Normal, -} +} \ No newline at end of file diff --git a/src/Enums/CustomRPC.cs b/src/Enums/CustomRPC.cs index 8b06d54..b3acff2 100644 --- a/src/Enums/CustomRPC.cs +++ b/src/Enums/CustomRPC.cs @@ -1,16 +1,49 @@ namespace BetterAmongUs.Enums; +/// +/// Defines custom Remote Procedure Call (RPC) identifiers used in BetterAmongUs for cheat detection and communication. +/// internal enum CustomRPC : int { // Cheat RPC's + /// + /// RPC identifier for Sicko cheat detection. + /// Sicko = 420, // Results in 164 + + /// + /// RPC identifier for AUM (Among Us Menu) cheat detection. + /// AUM = 42069, // Results in 85 + + /// + /// RPC identifier for AUM chat communication. + /// AUMChat = 101, + + /// + /// RPC identifier for KillNetwork cheat detection. + /// KillNetwork = 250, + + /// + /// RPC identifier for KillNetwork chat communication. + /// KillNetworkChat = 119, - //Better Among Us + + + // Better Among Us + /// + /// Legacy RPC for BetterAmongUs checks (currently unused). + /// LegacyBetterCheck = 150, // Unused + /// + /// RPC for sending a shared secret to another player. + /// SendSecretToPlayer, + /// + /// RPC for checking the hash of a shared secret received from another player. + /// CheckSecretHashFromPlayer, } \ No newline at end of file diff --git a/src/Enums/HandlerFlag.cs b/src/Enums/HandlerFlag.cs index 9016d61..9b1b31c 100644 --- a/src/Enums/HandlerFlag.cs +++ b/src/Enums/HandlerFlag.cs @@ -1,11 +1,37 @@ namespace BetterAmongUs.Enums; +/// +/// Defines handler flags used to control the processing flow of network messages and cheat detection. +/// internal enum HandlerFlag { + /// + /// Indicates the message should be handled normally. + /// Handle, + + /// + /// Indicates the message should be checked for cheat RPC signatures. + /// CheatRpcCheck, + + /// + /// Indicates that cheat detection processing should be cancelled. + /// AntiCheatCancel, + + /// + /// Indicates the message should be processed by the anti-cheat system. + /// AntiCheat, + + /// + /// Indicates the host is using BetterAmongUs. + /// BetterHost, + + /// + /// Indicates the message contains GameData tag information. + /// HandleGameDataTag -} +} \ No newline at end of file diff --git a/src/Enums/NewsTypes.cs b/src/Enums/NewsTypes.cs index fad12de..a27619b 100644 --- a/src/Enums/NewsTypes.cs +++ b/src/Enums/NewsTypes.cs @@ -1,7 +1,17 @@ namespace BetterAmongUs.Enums; +/// +/// Defines the types of news items displayed in BetterAmongUs. +/// internal enum NewsTypes { + /// + /// No news type specified. + /// None, + + /// + /// BetterAmongUs-specific news. + /// BAU, -} +} \ No newline at end of file diff --git a/src/Enums/ReleaseTypes.cs b/src/Enums/ReleaseTypes.cs new file mode 100644 index 0000000..e6bcb06 --- /dev/null +++ b/src/Enums/ReleaseTypes.cs @@ -0,0 +1,22 @@ +namespace BetterAmongUs.Enums; + +/// +/// Defines the release types or build channels for BetterAmongUs. +/// +internal enum ReleaseTypes : int +{ + /// + /// Stable release version. + /// + Release, + + /// + /// Beta testing version. + /// + Beta, + + /// + /// Development/experimental version. + /// + Dev, +} \ No newline at end of file diff --git a/src/Helpers/CastHelper.cs b/src/Helpers/CastHelper.cs index 7aeda54..3ce9c0f 100644 --- a/src/Helpers/CastHelper.cs +++ b/src/Helpers/CastHelper.cs @@ -1,9 +1,25 @@ namespace BetterAmongUs.Helpers; +/// +/// Provides extension methods for safe type casting operations. +/// internal static class CastHelper { + /// + /// Checks if an object can be cast to the specified type. + /// + /// The target type to check. + /// The object to check. + /// True if the object can be cast to type T, false otherwise. internal static bool TryCast(this object obj) => obj is T; + /// + /// Attempts to cast an object to the specified reference type. + /// + /// The target reference type. + /// The object to cast. + /// The cast object if successful, or null if not. + /// True if the cast was successful, false otherwise. internal static bool TryCast(this object obj, out T? item) where T : class { if (obj != null && obj is T casted) @@ -17,4 +33,4 @@ internal static class CastHelper return false; } } -} +} \ No newline at end of file diff --git a/src/Helpers/Colors.cs b/src/Helpers/Colors.cs index 2ea1257..2b97e4b 100644 --- a/src/Helpers/Colors.cs +++ b/src/Helpers/Colors.cs @@ -2,6 +2,9 @@ namespace BetterAmongUs.Helpers; +/// +/// Provides extension methods and utilities for working with colors in Unity. +/// internal static class Colors { // Convert a string to a colored string using a Unity Color object @@ -70,6 +73,11 @@ internal static class Colors return new Color32(r, g, b, 255); } + /// + /// Converts a Unity Color object to a Color32 object. + /// + /// The Color object to convert. + /// A Color32 object representing the input color. internal static Color32 ColorToColor32(this Color color) { return new Color32( @@ -80,6 +88,14 @@ internal static class Colors ); } + /// + /// Linearly interpolates between multiple colors based on a value within a specified range. + /// + /// The array of colors to interpolate between. + /// The minimum and maximum range for the interpolation value. + /// The interpolation value within the specified range. + /// Whether to reverse the color array order. + /// The interpolated color. internal static Color LerpColor(this Color[] colors, (float min, float max) lerpRange, float t, bool reverse = false) { float normalizedT = Mathf.InverseLerp(lerpRange.min, lerpRange.max, t); @@ -104,6 +120,13 @@ internal static class Colors return Color.Lerp(colors[segmentIndex], colors[segmentIndex + 1], segmentT); } + /// + /// Standard crewmate blue color. + /// internal static readonly Color CrewmateBlue = new Color32(140, byte.MaxValue, byte.MaxValue, byte.MaxValue); + + /// + /// Standard impostor red color. + /// internal static readonly Color ImpostorRed = new Color32(byte.MaxValue, 25, 25, byte.MaxValue); } \ No newline at end of file diff --git a/src/Helpers/EnumHelper.cs b/src/Helpers/EnumHelper.cs index b7bdcaa..87a73e2 100644 --- a/src/Helpers/EnumHelper.cs +++ b/src/Helpers/EnumHelper.cs @@ -1,27 +1,32 @@ namespace BetterAmongUs.Helpers; +/// +/// Provides utility methods for working with enumerations. +/// internal static class EnumHelper { /// - /// Get all values of enum + /// Gets all values of an enumeration type. /// - /// Type of enum to be obtained - /// All values of T + /// The enumeration type. + /// An array containing all values of the enumeration type, or null if the type is not an enum. internal static T[]? GetAllValues() where T : Enum => Enum.GetValues(typeof(T)) as T[]; + /// - /// Get all names in enum + /// Gets all names of an enumeration type. /// - /// Type of enum to be obtained - /// All values of T + /// The enumeration type. + /// An array containing all names of the enumeration type. internal static string[] GetAllNames() where T : Enum => Enum.GetNames(typeof(T)); - /// - /// Splits all values in the enum to equal segments + /// Splits all values in the enumeration into equal-sized chunks. /// - /// Type of enum to be obtained - /// The number of elements each chunk should contain. - /// A list of arrays, each containing up to elements of the enum type. + /// The enumeration type. + /// The number of elements each chunk should contain. + /// Whether to randomize the order of values before chunking. + /// Optional predicate to exclude certain enumeration values. + /// A list of arrays, each containing up to chunkSize elements of the enumeration type. internal static List Achunk(int chunkSize, bool shuffle = false, Func? exclude = null) where TEnum : Enum { List chunkedList = []; @@ -38,6 +43,5 @@ internal static class EnumHelper } return chunkedList; - } -} +} \ No newline at end of file diff --git a/src/Helpers/GamePrefabHelper.cs b/src/Helpers/GamePrefabHelper.cs index e924950..78ae880 100644 --- a/src/Helpers/GamePrefabHelper.cs +++ b/src/Helpers/GamePrefabHelper.cs @@ -3,8 +3,16 @@ using UnityEngine; namespace BetterAmongUs.Helpers; +/// +/// Provides helper methods for working with Unity game prefabs in an Il2Cpp environment. +/// internal static class GamePrefabHelper { + /// + /// Retrieves a Unity prefab by its name from the loaded resources. + /// + /// The name of the prefab to find. + /// The Unity Object if found, or null if not found. internal static UnityEngine.Object? GetPrefabByName(string objectName) { UnityEngine.Object[] allObjects = Resources.FindObjectsOfTypeAll(Il2CppType.Of()); @@ -17,4 +25,4 @@ internal static class GamePrefabHelper return null; } -} +} \ No newline at end of file diff --git a/src/Helpers/Il2CppExtensions.cs b/src/Helpers/Il2CppExtensions.cs index 05a0820..4c3b4cb 100644 --- a/src/Helpers/Il2CppExtensions.cs +++ b/src/Helpers/Il2CppExtensions.cs @@ -2,9 +2,17 @@ namespace BetterAmongUs.Helpers; +/// +/// Provides extension methods for working with Il2Cpp collections in a LINQ-like manner. +/// internal static class Il2CppExtensions { - // For Il2CppSystem.Collections.Generic.IEnumerable + /// + /// Performs the specified action on each element of an Il2Cpp IEnumerable. + /// + /// The type of elements in the collection. + /// The source collection. + /// The action to perform on each element. internal static void ForEachIl2Cpp(this Il2CppSystem.Collections.Generic.IEnumerable source, Action action) { if (source == null || action == null) return; @@ -16,6 +24,13 @@ internal static class Il2CppExtensions } } + /// + /// Returns the first element that satisfies a condition, or a default value if no such element is found. + /// + /// The type of elements in the collection. + /// The source collection. + /// A function to test each element for a condition. + /// The first element that satisfies the condition, or default(T) if no such element is found. internal static T? FirstOrDefaultIl2Cpp(this Il2CppSystem.Collections.Generic.IEnumerable source, Func predicate) { if (source == null || predicate == null) return default; @@ -31,7 +46,12 @@ internal static class Il2CppExtensions return default; } - // For Il2CppSystem.Collections.Generic.List - MORE EFFICIENT! + /// + /// Performs the specified action on each element of an Il2Cpp List (more efficient than IEnumerable version). + /// + /// The type of elements in the collection. + /// The source list. + /// The action to perform on each element. internal static void ForEachIl2Cpp(this Il2CppSystem.Collections.Generic.List source, Action action) { if (source == null || action == null) return; @@ -43,6 +63,13 @@ internal static class Il2CppExtensions } } + /// + /// Returns the first element of an Il2Cpp List that satisfies a condition, or a default value if no such element is found. + /// + /// The type of elements in the collection. + /// The source list. + /// A function to test each element for a condition. + /// The first element that satisfies the condition, or default(T) if no such element is found. internal static T? FirstOrDefaultIl2Cpp(this Il2CppSystem.Collections.Generic.List source, Func predicate) { if (source == null || predicate == null) return default; @@ -58,6 +85,13 @@ internal static class Il2CppExtensions return default; } + /// + /// Determines whether any element of an Il2Cpp List satisfies a condition. + /// + /// The type of elements in the collection. + /// The source list. + /// A function to test each element for a condition. + /// true if any elements in the source list pass the test in the predicate; otherwise, false. public static bool AnyIl2Cpp(this Il2CppSystem.Collections.Generic.List list, Func predicate) { for (int i = 0; i < list.Count; i++) @@ -67,6 +101,13 @@ internal static class Il2CppExtensions return false; } + /// + /// Returns a number that represents how many elements in the Il2Cpp List satisfy a condition. + /// + /// The type of elements in the collection. + /// The source list. + /// A function to test each element for a condition. + /// A number that represents how many elements in the list satisfy the condition. internal static int CountIl2Cpp(this Il2CppSystem.Collections.Generic.List source, Func? predicate = null) { if (source == null) return 0; @@ -80,6 +121,13 @@ internal static class Il2CppExtensions return count; } + /// + /// Filters an Il2Cpp List based on a predicate. + /// + /// The type of elements in the collection. + /// The source list. + /// A function to test each element for a condition. + /// A List that contains elements from the input list that satisfy the condition. internal static List WhereIl2Cpp(this Il2CppSystem.Collections.Generic.List source, Func predicate) { if (source == null || predicate == null) return []; @@ -94,6 +142,13 @@ internal static class Il2CppExtensions return result; } + /// + /// Determines whether all elements of an Il2Cpp List satisfy a condition. + /// + /// The type of elements in the collection. + /// The source list. + /// A function to test each element for a condition. + /// true if every element of the source list passes the test in the predicate, or if the list is empty; otherwise, false. internal static bool AllIl2Cpp(this Il2CppSystem.Collections.Generic.List source, Func predicate) { if (source == null || predicate == null) return false; @@ -106,6 +161,13 @@ internal static class Il2CppExtensions return true; } + /// + /// Determines whether an Il2Cpp List contains a specific Il2Cpp object. + /// + /// The type of elements in the collection. + /// The source list. + /// The value to locate in the list. + /// true if the list contains an element that equals the specified value; otherwise, false. internal static bool ContainsIl2Cpp(this Il2CppSystem.Collections.Generic.List source, T value) where T : Il2CppObjectBase { if (source == null) return false; @@ -118,6 +180,14 @@ internal static class Il2CppExtensions return false; } + /// + /// Projects each element of an Il2Cpp List into a new form. + /// + /// The type of elements in the source list. + /// The type of the value returned by the selector function. + /// The source list. + /// A transform function to apply to each element. + /// A List whose elements are the result of invoking the transform function on each element of source. internal static List SelectIl2Cpp(this Il2CppSystem.Collections.Generic.List source, Func selector) { if (source == null || selector == null) return []; @@ -129,4 +199,4 @@ internal static class Il2CppExtensions } return result; } -} +} \ No newline at end of file diff --git a/src/Helpers/InnerNetClientHelper.cs b/src/Helpers/InnerNetClientHelper.cs index cb89170..305235a 100644 --- a/src/Helpers/InnerNetClientHelper.cs +++ b/src/Helpers/InnerNetClientHelper.cs @@ -5,8 +5,16 @@ using System.Globalization; namespace BetterAmongUs.Helpers; +/// +/// Provides helper methods for working with InnerNet messaging, RPC handling, and message serialization. +/// internal static class InnerNetClientHelper { + /// + /// Broadcasts an RPC message to all clients with optional reliability. + /// + /// The RPC message to broadcast. + /// Whether to use reliable or unreliable transmission. internal static void BroadcastRpc(this BaseRpcMessage rpcMessage, bool reliable = true) { if (rpcMessage.TryCast(out var data)) @@ -18,6 +26,10 @@ internal static class InnerNetClientHelper } } + /// + /// Broadcasts a game data message to all clients using reliable transmission. + /// + /// The game data message to broadcast. internal static void BroadcastData(this BaseGameDataMessage rpcMessage) { if (rpcMessage.TryCast(out var data)) @@ -30,6 +42,8 @@ internal static class InnerNetClientHelper /// Writes an array of boolean values to a MessageWriter in a packed format to save space. /// Each byte stores up to 8 boolean values as bits. /// + /// The MessageWriter to write to. + /// The boolean values to write. internal static void WriteBooleans(this MessageWriter writer, IEnumerable boolsEnumerable) { bool[] bools = boolsEnumerable.ToArray(); @@ -59,6 +73,8 @@ internal static class InnerNetClientHelper /// /// Reads an array of boolean values from a MessageReader that were previously packed using WriteBooleans. /// + /// The MessageReader to read from. + /// An array of boolean values. internal static bool[] ReadBooleans(this MessageReader reader) { int length = reader.ReadInt32(); @@ -85,6 +101,8 @@ internal static class InnerNetClientHelper /// /// Writes an array of bytes to a MessageWriter in a packed format, combining two bytes into one to save space. /// + /// The MessageWriter to write to. + /// The byte values to write. internal static void WriteBytes(this MessageWriter writer, IEnumerable bytesEnumerable) { byte[] bytes = bytesEnumerable.ToArray(); @@ -96,6 +114,8 @@ internal static class InnerNetClientHelper /// /// Reads an array of bytes from a MessageReader that were previously packed using WritePackedBytes. /// + /// The MessageReader to read from. + /// An array of bytes. internal static byte[] ReadBytes(this MessageReader reader) { int count = reader.ReadInt32(); @@ -106,6 +126,8 @@ internal static class InnerNetClientHelper /// /// Writes an array of bytes to a MessageWriter in a packed format, combining two bytes into one to save space. /// + /// The MessageWriter to write to. + /// The byte values to write. internal static void WritePackedBytes(this MessageWriter writer, IEnumerable bytesEnumerable) { byte[] bytes = bytesEnumerable.ToArray(); @@ -124,6 +146,8 @@ internal static class InnerNetClientHelper /// /// Reads an array of bytes from a MessageReader that were previously packed using WritePackedBytes. /// + /// The MessageReader to read from. + /// An array of bytes. internal static byte[] ReadPackedBytes(this MessageReader reader) { int count = reader.ReadByte(); @@ -144,6 +168,8 @@ internal static class InnerNetClientHelper /// Writes a floating-point value to a MessageWriter in a packed format to save space. /// Handles negative values, integers, and decimal numbers efficiently. /// + /// The MessageWriter to write to. + /// The float value to write. internal static void WritePacked(this MessageWriter writer, float value) { bool isNegative = value < 0; @@ -222,6 +248,8 @@ internal static class InnerNetClientHelper /// Reads a floating-point value from a MessageReader that was previously packed using WritePacked. /// Handles negative values, integers, and decimal numbers efficiently. /// + /// The MessageReader to read from. + /// The unpacked float value. internal static float ReadPackedSingle(this MessageReader reader) { var flags = reader.ReadBooleans(); @@ -259,27 +287,74 @@ internal static class InnerNetClientHelper } } + /// + /// Writes a player's ID to a MessageWriter, using 255 for null players. + /// + /// The MessageWriter to write to. + /// The player whose ID to write. internal static void WritePlayerId(this MessageWriter writer, PlayerControl player) => writer.Write(player?.PlayerId ?? 255); + /// + /// Reads a player ID from a MessageReader and returns the corresponding PlayerControl. + /// + /// The MessageReader to read from. + /// The PlayerControl or null if not found. internal static PlayerControl? ReadPlayerId(this MessageReader reader) => Utils.PlayerFromPlayerId(reader.ReadByte()); + /// + /// Writes a NetworkedPlayerInfo's player ID to a MessageWriter, using 255 for null data. + /// + /// The MessageWriter to write to. + /// The NetworkedPlayerInfo whose ID to write. internal static void WritePlayerDataId(this MessageWriter writer, NetworkedPlayerInfo data) => writer.Write(data?.PlayerId ?? 255); + /// + /// Reads a player ID from a MessageReader and returns the corresponding NetworkedPlayerInfo. + /// + /// The MessageReader to read from. + /// The NetworkedPlayerInfo or null if not found. internal static NetworkedPlayerInfo? ReadPlayerDataId(this MessageReader reader) => Utils.PlayerDataFromPlayerId(reader.ReadByte()); + /// + /// Writes a DeadBody's parent ID to a MessageWriter, using 255 for null bodies. + /// + /// The MessageWriter to write to. + /// The DeadBody whose parent ID to write. internal static void WriteDeadBodyId(this MessageWriter writer, DeadBody body) => writer.Write(body?.ParentId ?? 255); + /// + /// Reads a DeadBody ID from a MessageReader and returns the corresponding DeadBody. + /// + /// The MessageReader to read from. + /// The DeadBody or null if not found. internal static DeadBody? ReadDeadBodyId(this MessageReader reader) => BAUPlugin.AllDeadBodys.FirstOrDefault(deadbody => deadbody.ParentId == reader.ReadByte()); + /// + /// Writes a Vent's ID to a MessageWriter, using -1 for null vents. + /// + /// The MessageWriter to write to. + /// The Vent whose ID to write. internal static void WriteVentId(this MessageWriter writer, Vent vent) => writer.Write(vent?.Id ?? -1); + /// + /// Reads a Vent ID from a MessageReader and returns the corresponding Vent. + /// + /// The MessageReader to read from. + /// The Vent or null if not found. internal static Vent? ReadVentId(this MessageReader reader) => BAUPlugin.AllVents.FirstOrDefault(vent => vent.Id == reader.ReadInt32()); /// /// Converts a MessageWriter to a MessageReader. /// + /// The MessageWriter to convert. + /// A MessageReader containing the writer's data. internal static MessageReader ToReader(this MessageWriter writer) => MessageReader.Get(writer.ToByteArray(false)); + /// + /// Converts a MessageWriter into multiple MessageReaders for each contained message. + /// + /// The MessageWriter to convert. + /// An array of MessageReaders. internal static MessageReader[] ToReaders(this MessageWriter writer) { var reader = writer.ToReader(); @@ -293,6 +368,11 @@ internal static class InnerNetClientHelper return [.. readers]; } + /// + /// Converts a MessageReader into multiple MessageReaders for each contained message. + /// + /// The MessageReader to convert. + /// An array of MessageReaders. internal static MessageReader[] ToReaders(this MessageReader reader) { List readers = []; @@ -305,6 +385,11 @@ internal static class InnerNetClientHelper return [.. readers]; } + /// + /// Converts a MessageReader into multiple MessageReaders with new buffers for each message. + /// + /// The MessageReader to convert. + /// An array of MessageReaders with new buffers. internal static MessageReader[] ToReadersNewBuffer(this MessageReader reader) { List readers = []; @@ -317,6 +402,13 @@ internal static class InnerNetClientHelper return [.. readers]; } + /// + /// Writes multiple MessageReaders to a MessageWriter. + /// + /// The MessageWriter to write to. + /// The MessageReaders to write. + /// Whether to clear the writer before writing. + /// The MessageWriter for method chaining. internal static MessageWriter WriteReaders(this MessageWriter writer, IEnumerable readers, bool clear = false) { if (clear) writer.Clear(writer.SendOption); @@ -331,6 +423,11 @@ internal static class InnerNetClientHelper return writer; } + /// + /// Creates a copy of a MessageWriter. + /// + /// The MessageWriter to copy. + /// A new MessageWriter with the same content. internal static MessageWriter Copy(this MessageWriter writer) { var newWriter = MessageWriter.Get(writer.SendOption); @@ -346,6 +443,7 @@ internal static class InnerNetClientHelper /// The RPC call ID. /// The send option for the RPC. /// The client ID to ignore. Default is -1, which means no client is ignored. + /// Optional function to filter which clients receive the RPC. /// A list of MessageWriter instances for the RPC calls. /// /// @@ -354,11 +452,6 @@ internal static class InnerNetClientHelper /// AmongUsClient.Instance.FinishRpcDesync(messageWriter); /// /// - /* - List messageWriter = AmongUsClient.Instance.StartRpcDesync(PlayerNetId, (byte)RpcCalls, SendOption, ClientId); - messageWriter.ForEach(mW => mW.Write("RPC TEST")); - AmongUsClient.Instance.FinishRpcDesync(messageWriter); - */ internal static List StartRpcDesync(this InnerNetClient client, uint playerNetId, byte callId, SendOption option, int ignoreClientId = -1, Func? clientCheck = null) { List messageWriters = []; @@ -381,8 +474,11 @@ internal static class InnerNetClientHelper return messageWriters; } - - + /// + /// Completes and sends the RPC desynchronization messages. + /// + /// The InnerNetClient instance. + /// The list of MessageWriters to finish and send. internal static void FinishRpcDesync(this InnerNetClient client, List messageWriters) { foreach (var msg in messageWriters) @@ -393,4 +489,4 @@ internal static class InnerNetClientHelper msg.Recycle(); } } -} +} \ No newline at end of file diff --git a/src/Helpers/LateTask.cs b/src/Helpers/LateTask.cs index f47cff6..53a0389 100644 --- a/src/Helpers/LateTask.cs +++ b/src/Helpers/LateTask.cs @@ -1,5 +1,8 @@ namespace BetterAmongUs.Helpers; +/// +/// Represents a delayed task that executes after a specified time interval. +/// internal sealed class LateTask { private readonly Action _action; @@ -9,6 +12,13 @@ internal sealed class LateTask private static readonly List Tasks = new(); + /// + /// Initializes a new instance of the LateTask class. + /// + /// The action to execute after the delay. + /// The delay in seconds before executing the action. + /// The name of the task for logging purposes. + /// Whether to log task completion. private LateTask(Action action, float delay, string name = "Unnamed Task", bool shouldLog = true) { _action = action ?? throw new ArgumentNullException(nameof(action)); @@ -19,6 +29,11 @@ internal sealed class LateTask Tasks.Add(this); } + /// + /// Updates the task timer and executes the action if the delay has elapsed. + /// + /// The time in seconds since the last update. + /// True if the task has completed and should be removed, false otherwise. private bool Update(float deltaTime) { _remainingTime -= deltaTime; @@ -44,11 +59,22 @@ internal sealed class LateTask } } + /// + /// Schedules a new delayed task. + /// + /// The action to execute after the delay. + /// The delay in seconds before executing the action. + /// The name of the task for logging purposes. + /// Whether to log task completion. internal static void Schedule(Action action, float delay, string name = "Unnamed Task", bool shouldLog = true) { _ = new LateTask(action, delay, name, shouldLog); } + /// + /// Updates all scheduled tasks with the elapsed time. + /// + /// The time in seconds since the last update. internal static void UpdateAll(float deltaTime) { if (Tasks.Count == 0) @@ -70,10 +96,16 @@ internal sealed class LateTask } } + /// + /// Cancels and removes all scheduled tasks. + /// public static void CancelAll() { Tasks.Clear(); } + /// + /// Gets the number of currently active tasks. + /// public static int ActiveTaskCount => Tasks.Count; } \ No newline at end of file diff --git a/src/Helpers/Logger.cs b/src/Helpers/Logger.cs index d1eaefd..8d21849 100644 --- a/src/Helpers/Logger.cs +++ b/src/Helpers/Logger.cs @@ -1,5 +1,4 @@ - -using BepInEx; +using BepInEx; using BepInEx.Logging; using BetterAmongUs.Data; using BetterAmongUs.Modules; @@ -8,8 +7,19 @@ using System.Runtime.CompilerServices; namespace BetterAmongUs.Helpers; +/// +/// Provides logging utilities for BetterAmongUs with various log levels and destinations. +/// internal static class Logger_ { + /// + /// Logs a message with specified parameters. + /// + /// The message to log. + /// The log tag/category. + /// Whether to output to console. + /// The console color for the message. + /// Whether to log only when the client is host. internal static void Log(string info, string tag = "Log", bool logConsole = true, ConsoleColor color = ConsoleColor.White, bool hostOnly = false) { try @@ -29,6 +39,16 @@ internal static class Logger_ } catch { } } + + /// + /// Logs method calls with detailed information about the caller. + /// + /// Additional information about the method call. + /// The type containing the method (optional). + /// Whether to log only when the client is host. + /// Automatically populated: path of the file containing the caller. + /// Automatically populated: line number of the caller. + /// Automatically populated: name of the calling member. internal static void LogMethod( string info = "", Type? runtimeType = null, @@ -51,6 +71,15 @@ internal static class Logger_ Log(logMessage, "MethodLog", hostOnly); } + /// + /// Logs private method calls with detailed information, using encrypted storage. + /// + /// Additional information about the method call. + /// The type containing the method (optional). + /// Whether to log only when the client is host. + /// Automatically populated: path of the file containing the caller. + /// Automatically populated: line number of the caller. + /// Automatically populated: name of the calling member. internal static void LogMethodPrivate( string info = "", Type? runtimeType = null, @@ -73,17 +102,65 @@ internal static class Logger_ LogPrivate(logMessage, "MethodLog", hostOnly); } + /// + /// Logs a header message with visual formatting. + /// + /// The header text. + /// The log tag/category. + /// Whether to log only when the client is host. + /// Whether to output to console. internal static void LogHeader(string info, string tag = "LogHeader", bool hostOnly = false, bool logConsole = true) => Log($" >-------------- {info} --------------<", tag, hostOnly: hostOnly, logConsole: logConsole); + + /// + /// Logs cheat detection messages with green console color. + /// + /// The cheat detection message. + /// The log tag/category. + /// Whether to log only when the client is host. + /// Whether to output to console. internal static void LogCheat(string info, string tag = "AntiCheat", bool hostOnly = false, bool logConsole = true) => Log(info, tag, color: ConsoleColor.Green, hostOnly: hostOnly, logConsole: logConsole); + + /// + /// Logs error messages with red console color. + /// + /// The error message. + /// The log tag/category. + /// Whether to log only when the client is host. + /// Whether to output to console. internal static void Error(string info, string tag = "Error", bool hostOnly = false, bool logConsole = true) => Log(info, tag, color: ConsoleColor.Red, hostOnly: hostOnly, logConsole: logConsole); + + /// + /// Logs exception details with red console color. + /// + /// The exception to log. + /// The log tag/category. + /// Whether to log only when the client is host. + /// Whether to output to console. internal static void Error(Exception ex, string tag = "Error", bool hostOnly = false, bool logConsole = true) => Log(ex.ToString(), tag, color: ConsoleColor.Red, hostOnly: hostOnly, logConsole: logConsole); + + /// + /// Logs warning messages with yellow console color. + /// + /// The warning message. + /// The log tag/category. + /// Whether to log only when the client is host. + /// Whether to output to console. internal static void Warning(string info, string tag = "Warning", bool hostOnly = false, bool logConsole = true) => Log(info, tag, color: ConsoleColor.Yellow, hostOnly: hostOnly, logConsole: logConsole); + + /// + /// Logs a test message for debugging purposes. + /// internal static void Test() { Log("------------------> TEST <------------------", "TEST"); InGame("TEST"); } - // Log in game join msg + + /// + /// Logs a message in-game via the disconnect message notifier. + /// + /// The message to display in-game. + /// Whether to log only when the client is host. internal static void InGame(string info, bool hostOnly = false) { if (hostOnly && !GameState.IsHost) return; @@ -92,9 +169,12 @@ internal static class Logger_ Log(info, "InGame", hostOnly: hostOnly); } - // ------------- Private Log ------------- - // Logs that can only be accessed when dumped - + /// + /// Logs a private message with encryption for sensitive data. + /// + /// The sensitive message to log. + /// The log tag/category. + /// Whether to log only when the client is host. internal static void LogPrivate(string info, string tag = "Log", bool hostOnly = false) { try @@ -110,9 +190,21 @@ internal static class Logger_ } } +/// +/// Custom log listener for BepInEx that forwards logs to the BetterAmongUs logging system. +/// internal class CustomLogListener : ILogListener { + /// + /// Gets or sets the log levels to filter. + /// public LogLevel LogLevelFilter { get; set; } = LogLevel.Info | LogLevel.Warning | LogLevel.Error; + + /// + /// Handles log events from BepInEx. + /// + /// The event sender. + /// The log event arguments. public void LogEvent(object sender, LogEventArgs eventArgs) { if (eventArgs.Source.SourceName.ToLower().Contains("unity") @@ -132,12 +224,13 @@ internal class CustomLogListener : ILogListener } } - public void Dispose() - { - } - - public void Flush() - { - } -} + /// + /// Disposes the log listener. + /// + public void Dispose() { } + /// + /// Flushes the log listener. + /// + public void Flush() { } +} \ No newline at end of file diff --git a/src/Helpers/ObjectHelper.cs b/src/Helpers/ObjectHelper.cs index a2a6166..f5ebfa6 100644 --- a/src/Helpers/ObjectHelper.cs +++ b/src/Helpers/ObjectHelper.cs @@ -2,11 +2,15 @@ namespace BetterAmongUs.Helpers; +/// +/// Provides helper methods for working with Unity GameObjects, components, and visual elements. +/// internal static class ObjectHelper { /// /// Destroys a GameObject if it is not null. /// + /// The GameObject to destroy. internal static void DestroyObj(this GameObject obj) { if (obj != null) @@ -18,16 +22,19 @@ internal static class ObjectHelper /// /// Destroys the GameObject associated with a MonoBehaviour if it is not null. /// + /// The MonoBehaviour whose GameObject should be destroyed. internal static void DestroyObj(this MonoBehaviour mono) => mono?.gameObject?.DestroyObj(); /// /// Destroys a MonoBehaviour component if it is not null. /// + /// The MonoBehaviour to destroy. internal static void DestroyMono(this MonoBehaviour mono) => UnityEngine.Object.Destroy(mono); /// /// Destroys all TextTranslatorTMP components in the children of a GameObject. /// + /// The GameObject to search for TextTranslatorTMP components. internal static void DestroyTextTranslators(this GameObject obj) { var translators = obj.GetComponentsInChildren(); @@ -43,8 +50,14 @@ internal static class ObjectHelper /// /// Destroys all TextTranslatorTMP components in the children of a MonoBehaviour's GameObject. /// + /// The MonoBehaviour whose GameObject should be searched. internal static void DestroyTextTranslators(this MonoBehaviour mono) => mono.gameObject.DestroyTextTranslators(); + /// + /// Sets the color of all SpriteRenderer components in the children of a GameObject. + /// + /// The GameObject to modify. + /// The color to apply to all sprites. internal static void SetSpriteColors(this GameObject go, Color color) { var sprites = go.GetComponentsInChildren(true); @@ -54,6 +67,11 @@ internal static class ObjectHelper } } + /// + /// Applies a custom action to all SpriteRenderer components in the children of a GameObject. + /// + /// The GameObject to modify. + /// The action to apply to each SpriteRenderer. internal static void SetSpriteColors(this GameObject go, Action setSprite) { var sprites = go.GetComponentsInChildren(true); @@ -63,16 +81,34 @@ internal static class ObjectHelper } } + /// + /// Sets UI colors for all SpriteRenderer components while avoiding specific GameObject names. + /// + /// The GameObject to modify. + /// Names of GameObjects to skip when applying colors. internal static void SetUIColors(this GameObject go, params string[] avoidGoName) { go.SetUIColors(null, null, avoidGoName); } + /// + /// Sets UI colors for SpriteRenderer components with an optional check function. + /// + /// The GameObject to modify. + /// Optional function to determine if a SpriteRenderer should be modified. + /// Names of GameObjects to skip when applying colors. internal static void SetUIColors(this GameObject go, Func? check = null, params string[] avoidGoName) { go.SetUIColors(null, check, avoidGoName); } + /// + /// Sets UI colors for SpriteRenderer components with specific color and filtering options. + /// + /// The GameObject to modify. + /// The color to apply (defaults to green if null). + /// Optional function to determine if a SpriteRenderer should be modified. + /// Names of GameObjects to skip when applying colors. internal static void SetUIColors(this GameObject go, Color? color = null, Func? check = null, params string[] avoidGoName) { var sprites = go.GetComponentsInChildren(true); @@ -84,12 +120,22 @@ internal static class ObjectHelper } } + /// + /// Adds a color tint to a SpriteRenderer using a blend formula. + /// + /// The SpriteRenderer to modify. + /// The color to blend (defaults to green if null). internal static void AddColor(SpriteRenderer sprite, Color? color = null) { color ??= Color.green; sprite.color = (sprite.color * 0.6f) + ((Color)color * 0.5f); } + /// + /// Sets the layer for a GameObject and all its children. + /// + /// The GameObject to modify. + /// The name of the layer to set. internal static void SetLayers(this GameObject go, string layerName) { int layer = LayerMask.NameToLayer(layerName); @@ -101,4 +147,4 @@ internal static class ObjectHelper child.gameObject.layer = layer; } } -} +} \ No newline at end of file diff --git a/src/Helpers/PlayerControlHelper.cs b/src/Helpers/PlayerControlHelper.cs index 55c7fff..64d48c8 100644 --- a/src/Helpers/PlayerControlHelper.cs +++ b/src/Helpers/PlayerControlHelper.cs @@ -2,7 +2,6 @@ using BetterAmongUs.Data; using BetterAmongUs.Modules; using BetterAmongUs.Mono; -using BetterAmongUs.Network; using BetterAmongUs.Patches.Gameplay.Player; using BetterAmongUs.Patches.Gameplay.UI.Settings; using InnerNet; @@ -10,9 +9,16 @@ using UnityEngine; namespace BetterAmongUs.Helpers; +/// +/// Provides extension methods and utilities for working with PlayerControl instances. +/// static class PlayerControlHelper { - // Get players client + /// + /// Gets the ClientData associated with a player. + /// + /// The player to get client data for. + /// The ClientData if found, null otherwise. internal static ClientData? GetClient(this PlayerControl player) { if (AmongUsClient.Instance?.allClients == null || player == null) @@ -25,9 +31,19 @@ static class PlayerControlHelper } return null; } - // Get players client id + + /// + /// Gets the client ID of a player. + /// + /// The player to get the client ID for. + /// The client ID, or -1 if not found. internal static int GetClientId(this PlayerControl player) => player?.GetClient()?.Id ?? -1; - // Get player name with outfit color + + /// + /// Gets the player's name with color formatting based on their outfit color. + /// + /// The player to get the name for. + /// The colored player name string. internal static string GetPlayerNameAndColor(this PlayerControl player) { if (player?.Data == null) return string.Empty; @@ -42,7 +58,11 @@ static class PlayerControlHelper } } - // Check if players character has been created and received from the Host + /// + /// Checks if a player's character data has been fully loaded and received from the host. + /// + /// The player to check. + /// True if player data is complete, false otherwise. internal static bool DataIsCollected(this PlayerControl player) { if (player == null) return false; @@ -66,7 +86,15 @@ static class PlayerControlHelper return true; } - // Kick player + /// + /// Kicks a player from the game with optional ban and anti-cheat features. + /// + /// The player to kick. + /// Whether to ban the player. + /// Custom reason message for the kick. + /// Whether this is an anti-cheat related kick. + /// Whether to bypass the data collection check. + /// Whether to force a ban regardless of settings. internal static void Kick(this PlayerControl player, bool ban = false, string setReasonInfo = "", bool AntiCheatBan = false, bool bypassDataCheck = false, bool forceBan = false) { var Ban = ban || forceBan; @@ -95,7 +123,13 @@ static class PlayerControlHelper player.BetterData().AntiCheatInfo.BannedByAntiCheat = AntiCheatBan; } - // Set color outline on player + + /// + /// Sets the outline effect on a player's character. + /// + /// The player to modify. + /// Whether to enable the outline. + /// The outline color (optional). internal static void SetOutline(this PlayerControl player, bool active, Color? color = null) { player.cosmetics.currentBodySprite.BodySprite.material.SetFloat("_Outline", active ? 1 : 0); @@ -114,7 +148,13 @@ static class PlayerControlHelper } } } - // Set color outline on player + + /// + /// Sets the outline effect on a player's character using a hex color string. + /// + /// The player to modify. + /// Whether to enable the outline. + /// The hex color string for the outline. internal static void SetOutlineByHex(this PlayerControl player, bool active, string hexColor = "") { Color color = Utils.HexToColor32(hexColor); @@ -133,21 +173,18 @@ static class PlayerControlHelper } } - // Exile player - internal static void RpcExile(this PlayerControl player) - { - if (player == null) return; - RPC.RpcExile(player); - } - // Check if player is selecting room to spawn in, for Airship - internal static bool IsInRoomSelect(this PlayerControl player) - { - if (player == null) return false; - return GameState.AirshipIsActive && Vector2.Distance(player.GetTruePosition(), new(-25, 40)) < 5f; - } - // Check if player controller is self client + /// + /// Checks if a player is the local player. + /// + /// The player to check. + /// True if the player is the local player. internal static bool IsLocalPlayer(this PlayerControl player) => player != null && PlayerControl.LocalPlayer != null && player == PlayerControl.LocalPlayer; - // Get vent Id that the player is in. + + /// + /// Gets the ID of the vent the player is currently in. + /// + /// The player to check. + /// The vent ID, or -1 if not in a vent. internal static int GetPlayerVentId(this PlayerControl player) { if (player == null) return -1; @@ -158,14 +195,33 @@ static class PlayerControlHelper return ventilationSystem.PlayersInsideVents.TryGetValue(player.PlayerId, out var playerIdVentId) ? playerIdVentId : -1; } - // Get true position - internal static Vector2 GetCustomPosition(this PlayerControl player) => new(player.transform.position.x, player.transform.position.y); - // Check if player is alive - internal static bool IsAlive(this PlayerControl player) => player?.Data != null && !player.Data.IsDead; - // Check if player is in a vent - internal static bool IsInVent(this PlayerControl player) => player != null && (player.inVent || player.walkingToVent || player.MyPhysics?.Animations?.IsPlayingEnterVentAnimation() == true); - // Check if player role name + /// + /// Gets the custom position of a player. + /// + /// The player to get the position for. + /// The player's position as a Vector2. + internal static Vector2 GetCustomPosition(this PlayerControl player) => new(player.transform.position.x, player.transform.position.y); + + /// + /// Checks if a player is alive. + /// + /// The player to check. + /// True if the player is alive. + internal static bool IsAlive(this PlayerControl player) => player?.Data != null && !player.Data.IsDead; + + /// + /// Checks if a player is in a vent. + /// + /// The player to check. + /// True if the player is in a vent. + internal static bool IsInVent(this PlayerControl player) => player != null && (player.inVent || player.walkingToVent || player.MyPhysics?.Animations?.IsPlayingEnterVentAnimation() == true); + + /// + /// Gets the role name of a player. + /// + /// The player to get the role name for. + /// The role name string. internal static string GetRoleName(this PlayerControl player) { if (!player.IsAlive() && !player.IsGhostRole()) @@ -180,9 +236,19 @@ static class PlayerControlHelper return string.Empty; } - // Check if player is Shapeshifting + + /// + /// Checks if a player is currently shapeshifting. + /// + /// The player to check. + /// True if the player is shapeshifting. internal static bool IsInShapeshift(this PlayerControl player) => player != null && (player.shapeshiftTargetPlayerId > -1 || player.shapeshifting) && !player.waitingForShapeshiftResponse; - // Check if player is in vanish as Phantom + + /// + /// Checks if a player is in vanish mode as a Phantom. + /// + /// The player to check. + /// True if the player is in vanish mode. internal static bool IsInVanish(this PlayerControl player) { if (player != null && player.Data.Role is PhantomRole phantomRole) @@ -191,63 +257,140 @@ static class PlayerControlHelper } return false; } - // Get hex color for team + + /// + /// Gets the hex color code for a player's team. + /// + /// The player to get team color for. + /// The hex color string. internal static string GetTeamHexColor(this PlayerControl player) => player.Data.GetTeamHexColor(); + + /// + /// Gets the hex color code for a player data's team. + /// + /// The player data to get team color for. + /// The hex color string. internal static string GetTeamHexColor(this NetworkedPlayerInfo data) { if (data == null) return "#ffffff"; if (data.IsImpostorTeam()) { - return "#f00202"; + return Colors.ImpostorRed.ColorToHex(); } else { - return "#8cffff"; + return Colors.CrewmateBlue.ColorToHex(); } } - // Check if player is role type + /// + /// Checks if a player has a specific role type. + /// + /// The player to check. + /// The role type to check for. + /// True if the player has the specified role. internal static bool Is(this PlayerControl player, RoleTypes role) => player?.Data?.RoleType == role; - // Check if player is Ghost role type + + /// + /// Checks if a player has a ghost role. + /// + /// The player to check. + /// True if the player has a ghost role. internal static bool IsGhostRole(this PlayerControl player) => player?.Data?.RoleType is RoleTypes.GuardianAngel; - // Check if player is on imposter team + + /// + /// Checks if a player is on the impostor team. + /// + /// The player to check. + /// True if the player is on the impostor team. internal static bool IsImpostorTeam(this PlayerControl player) => player?.Data?.IsImpostorTeam() == true; + + /// + /// Checks if player data indicates the player is on the impostor team. + /// + /// The player data to check. + /// True if the player is on the impostor team. internal static bool IsImpostorTeam(this NetworkedPlayerInfo data) => data?.RoleType is RoleTypes.Impostor or RoleTypes.ImpostorGhost or RoleTypes.Shapeshifter or RoleTypes.Phantom or RoleTypes.Viper; - // Check if player is a imposter teammate + + /// + /// Checks if a player is an impostor teammate of the local player. + /// + /// The player to check. + /// True if the player is an impostor teammate. internal static bool IsImpostorTeammate(this PlayerControl player) => player != null && PlayerControl.LocalPlayer != null && (player.IsLocalPlayer() && PlayerControl.LocalPlayer.IsImpostorTeam() || PlayerControl.LocalPlayer.IsImpostorTeam() && player.IsImpostorTeam()); - // Check if player is in the Anti-Cheat list + + /// + /// Checks if a player is listed as a cheater in the anti-cheat database. + /// + /// The player to check. + /// True if the player is listed as a cheater. internal static bool IsCheater(this PlayerControl player) => BetterDataManager.BetterDataFile?.CheckPlayerData(player.Data) == true; - // Check if player is in the Anti-Cheat list + + /// + /// Checks if player data is listed as a cheater in the anti-cheat database. + /// + /// The player data to check. + /// True if the player data is listed as a cheater. internal static bool IsCheater(this NetworkedPlayerInfo data) => BetterDataManager.BetterDataFile?.CheckPlayerData(data) == true; - // Check if player is the host + + /// + /// Checks if a player is the game host. + /// + /// The player to check. + /// True if the player is the host. internal static bool IsHost(this PlayerControl player) => player?.Data != null && GameData.Instance?.GetHost() == player.Data; - // Get players HashPuid + /// + /// Gets the hashed PUID of a player. + /// + /// The player to get the hashed PUID for. + /// The hashed PUID string. internal static string GetHashPuid(this PlayerControl player) { return player.Data.GetHashPuid() ?? ""; } + + /// + /// Gets the hashed PUID from player data. + /// + /// The player data to get the hashed PUID for. + /// The hashed PUID string. internal static string GetHashPuid(this NetworkedPlayerInfo data) { if (data?.Puid == null) return ""; return Utils.GetHashStr(data.Puid); } - // Get players Friendcode + + /// + /// Gets the hashed friend code of a player. + /// + /// The player to get the hashed friend code for. + /// The hashed friend code string. internal static string GetHashFriendcode(this PlayerControl player) { return player.Data.GetHashFriendcode() ?? ""; } + + /// + /// Gets the hashed friend code from player data. + /// + /// The player data to get the hashed friend code for. + /// The hashed friend code string. internal static string GetHashFriendcode(this NetworkedPlayerInfo data) { if (data?.FriendCode == null) return ""; return Utils.GetHashStr(data.FriendCode); } - // Report player + /// + /// Reports a player with a specified reason. + /// + /// The player to report. + /// The reason for the report. internal static void ReportPlayer(this PlayerControl player, ReportReasons reason = ReportReasons.None) { if (player?.GetClient() != null) @@ -258,4 +401,4 @@ static class PlayerControlHelper } } } -} +} \ No newline at end of file diff --git a/src/Helpers/RoleHelper.cs b/src/Helpers/RoleHelper.cs index 746d0fb..c854788 100644 --- a/src/Helpers/RoleHelper.cs +++ b/src/Helpers/RoleHelper.cs @@ -2,6 +2,9 @@ namespace BetterAmongUs.Helpers; +/// +/// Provides helper methods for working with Among Us roles and their properties. +/// internal static class RoleHelper { private static readonly Lazy> roleLookup = @@ -15,15 +18,30 @@ internal static class RoleHelper return dict; }); + /// + /// Gets the RoleBehaviour associated with a RoleTypes enum value. + /// + /// The role type to look up. + /// The RoleBehaviour if found, null otherwise. internal static RoleBehaviour? GetBehaviour(this RoleTypes role) { var lookup = roleLookup.Value; return lookup.TryGetValue(role, out var behaviour) ? behaviour : null; } + /// + /// Determines whether a role type belongs to the impostor team. + /// + /// The role type to check. + /// True if the role is part of the impostor team, false otherwise. internal static bool IsImpostorRole(RoleTypes role) => role.GetBehaviour().TeamType is RoleTeamTypes.Impostor; + /// + /// Gets the display name of a role type. + /// + /// The role type to get the name for. + /// The display name of the role, or "???" if not found. internal static string GetRoleName(this RoleTypes role) { if (role is RoleTypes.ImpostorGhost) @@ -38,6 +56,11 @@ internal static class RoleHelper return role.GetBehaviour()?.NiceName ?? "???"; } + /// + /// Gets the hexadecimal color code associated with a role type. + /// + /// The role type to get the color for. + /// The hexadecimal color string, or empty string if not found. internal static string GetRoleHex(this RoleTypes role) { if (RoleColor.TryGetValue(role, out var color)) @@ -48,20 +71,23 @@ internal static class RoleHelper return string.Empty; } + /// + /// Dictionary mapping role types to their hexadecimal color codes. + /// internal static Dictionary RoleColor => new() { - { RoleTypes.CrewmateGhost, "#8cffff" }, + { RoleTypes.CrewmateGhost, Colors.CrewmateBlue.ColorToHex() }, { RoleTypes.GuardianAngel, "#8cffff" }, - { RoleTypes.Crewmate, "#8cffff" }, + { RoleTypes.Crewmate, Colors.CrewmateBlue.ColorToHex() }, { RoleTypes.Scientist, "#00d9d9" }, { RoleTypes.Engineer, "#8f8f8f" }, { RoleTypes.Noisemaker, "#fc7c7c" }, { RoleTypes.Tracker, "#59f002" }, { RoleTypes.Detective, "#0027FF" }, - { RoleTypes.ImpostorGhost, "#f00202" }, - { RoleTypes.Impostor, "#f00202" }, + { RoleTypes.ImpostorGhost, Colors.ImpostorRed.ColorToHex() }, + { RoleTypes.Impostor, Colors.ImpostorRed.ColorToHex() }, { RoleTypes.Shapeshifter, "#f06102" }, { RoleTypes.Phantom, "#d100b9" }, { RoleTypes.Viper, "#367400" } }; -} +} \ No newline at end of file diff --git a/src/Helpers/ShuffleHelper.cs b/src/Helpers/ShuffleHelper.cs index f777671..6f88c51 100644 --- a/src/Helpers/ShuffleHelper.cs +++ b/src/Helpers/ShuffleHelper.cs @@ -1,14 +1,17 @@ namespace BetterAmongUs.Helpers; +/// +/// Provides extension methods for shuffling collections. +/// internal static class ShuffleListExtension { /// - /// Shuffles all elements in a collection randomly + /// Shuffles all elements in a collection randomly using the Fisher-Yates algorithm. /// - /// The type of the collection - /// The collection to be shuffled - /// An instance of a randomizer algorithm - /// The shuffled collection + /// The type of the collection elements. + /// The collection to be shuffled. + /// An instance of a randomizer algorithm. + /// The shuffled collection. internal static IEnumerable Shuffle(this IEnumerable collection, Random random) { var list = collection.ToList(); @@ -21,4 +24,4 @@ internal static class ShuffleListExtension } return list; } -} +} \ No newline at end of file diff --git a/src/Helpers/Utils.cs b/src/Helpers/Utils.cs index 2fa9842..6b77f51 100644 --- a/src/Helpers/Utils.cs +++ b/src/Helpers/Utils.cs @@ -7,17 +7,30 @@ using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using UnityEngine; -using UnityEngine.Networking; namespace BetterAmongUs.Helpers; +/// +/// Provides utility methods for string manipulation, network operations, player lookups, and game utilities. +/// internal static class Utils { internal static Dictionary CachedSprites = []; // String extensions and formatting + /// + /// Wraps a string in size HTML tags. + /// + /// The string to format. + /// The size percentage. + /// The formatted string with size tags. internal static string Size(this string str, float size) => $"{str}"; + /// + /// Removes size HTML tags from a string. + /// + /// The text to clean. + /// The text without size tags. internal static string RemoveSizeHtmlText(string text) { text = Regex.Replace(text, "]*>", "", RegexOptions.IgnoreCase); @@ -28,6 +41,11 @@ internal static class Utils return text; } + /// + /// Formats information from a StringBuilder, cleaning HTML and formatting separators. + /// + /// The StringBuilder containing the text to format. + /// The formatted string. internal static string FormatInfo(StringBuilder source) { if (source.Length == 0) return string.Empty; @@ -43,6 +61,11 @@ internal static class Utils return sb.ToString().TrimEnd(" - ".ToCharArray()); } + /// + /// Removes all HTML tags and formatting from a string. + /// + /// The text to clean. + /// The plain text without HTML. internal static string RemoveHtmlText(string text) { text = Regex.Replace(text, "<[^>]*>", ""); @@ -53,6 +76,11 @@ internal static class Utils return text; } + /// + /// Checks if a string contains HTML or special formatting. + /// + /// The text to check. + /// True if the text contains HTML or formatting, false otherwise. internal static bool IsHtmlText(string text) { return Regex.IsMatch(text, "<[^>]*>") || @@ -61,53 +89,70 @@ internal static class Utils text.Contains("\r"); } - // Network utilities - internal static bool IsInternetAvailable() - { - if (Application.internetReachability == NetworkReachability.NotReachable) - return false; - - UnityWebRequest? www = null; - try - { - www = UnityWebRequest.Get("http://clients3.google.com/generate_204"); - www.SendWebRequest(); - while (!www.isDone) { } - return www.result == UnityWebRequest.Result.Success && www.responseCode == 204; - } - catch - { - return false; - } - finally - { - www?.Dispose(); - } - } - // Player lookup methods + /// + /// Gets ClientData from a client ID. + /// + /// The client ID to look up. + /// The ClientData if found, null otherwise. internal static ClientData? ClientFromClientId(int clientId) => AmongUsClient.Instance.allClients.FirstOrDefaultIl2Cpp(cd => cd.Id == clientId); + /// + /// Gets NetworkedPlayerInfo from a player ID. + /// + /// The player ID to look up. + /// The NetworkedPlayerInfo if found, null otherwise. internal static NetworkedPlayerInfo? PlayerDataFromPlayerId(int playerId) => GameData.Instance.AllPlayers.FirstOrDefaultIl2Cpp(data => data.PlayerId == playerId); + /// + /// Gets NetworkedPlayerInfo from a client ID. + /// + /// The client ID to look up. + /// The NetworkedPlayerInfo if found, null otherwise. internal static NetworkedPlayerInfo? PlayerDataFromClientId(int clientId) => GameData.Instance.AllPlayers.FirstOrDefaultIl2Cpp(data => data.ClientId == clientId); + /// + /// Gets NetworkedPlayerInfo from a friend code. + /// + /// The friend code to look up. + /// The NetworkedPlayerInfo if found, null otherwise. internal static NetworkedPlayerInfo? PlayerDataFromFriendCode(string friendCode) => GameData.Instance.AllPlayers.FirstOrDefaultIl2Cpp(data => data.FriendCode == friendCode); + /// + /// Gets PlayerControl from a player ID. + /// + /// The player ID to look up. + /// The PlayerControl if found, null otherwise. internal static PlayerControl? PlayerFromPlayerId(int playerId) => BAUPlugin.AllPlayerControls.FirstOrDefault(player => player.PlayerId == playerId); + /// + /// Gets PlayerControl from a client ID. + /// + /// The client ID to look up. + /// The PlayerControl if found, null otherwise. internal static PlayerControl? PlayerFromClientId(int clientId) => BAUPlugin.AllPlayerControls.FirstOrDefault(player => player.GetClientId() == clientId); + /// + /// Gets PlayerControl from a network ID. + /// + /// The network ID to look up. + /// The PlayerControl if found, null otherwise. internal static PlayerControl? PlayerFromNetId(uint netId) => BAUPlugin.AllPlayerControls.FirstOrDefault(player => player.NetId == netId); // Chat functionality + /// + /// Adds a private chat message with custom formatting options. + /// + /// The message text. + /// Optional override for the sender name. + /// Whether to align the message to the right side. internal static void AddChatPrivate(string text, string overrideName = "", bool setRight = false) { if (!GameState.IsInGame) return; @@ -163,20 +208,40 @@ internal static class Utils } // System type checks + /// + /// Checks if a SystemTypes value represents a sabotage type. + /// + /// The SystemTypes value to check. + /// True if the system type is a sabotage, false otherwise. internal static bool SystemTypeIsSabotage(SystemTypes type) => type is SystemTypes.Reactor or SystemTypes.Laboratory or SystemTypes.Comms or SystemTypes.LifeSupp or SystemTypes.MushroomMixupSabotage or SystemTypes.HeliSabotage or SystemTypes.Electrical; + /// + /// Checks if an integer value represents a sabotage SystemTypes. + /// + /// The integer value to check. + /// True if the value represents a sabotage system type. internal static bool SystemTypeIsSabotage(int typeNum) => SystemTypeIsSabotage((SystemTypes)typeNum); // Hashing utilities + /// + /// Gets the hashed PUID of a player. + /// + /// The player to get the hashed PUID for. + /// The hashed PUID string. internal static string GetHashPuid(PlayerControl player) { return player?.Data?.Puid == null ? "" : GetHashStr(player.Data.Puid); } + /// + /// Computes a truncated SHA256 hash of a string. + /// + /// The string to hash. + /// A 9-character hash (first 5 + last 4 characters of SHA256). internal static string GetHashStr(this string str) { if (string.IsNullOrEmpty(str)) return ""; @@ -187,6 +252,11 @@ internal static class Utils return sha256Hash[..5] + sha256Hash[^4..]; } + /// + /// Computes a 16-bit hash of a string using SHA256. + /// + /// The string to hash. + /// A 16-bit hash value. internal static ushort GetHashUInt16(string input) { if (string.IsNullOrEmpty(input)) return 0; @@ -194,13 +264,28 @@ internal static class Utils } // Color utilities + /// + /// Gets the hexadecimal color code for a role team. + /// + /// The role team type. + /// The hex color string for the team. internal static string GetTeamHexColor(RoleTeamTypes team) { return team == RoleTeamTypes.Impostor ? "#f00202" : "#8cffff"; } + /// + /// Converts a Color32 to a hexadecimal string. + /// + /// The Color32 to convert. + /// The hexadecimal color string. internal static string Color32ToHex(Color32 color) => $"#{color.r:X2}{color.g:X2}{color.b:X2}{255:X2}"; + /// + /// Converts a hexadecimal string to a Color32. + /// + /// The hexadecimal color string. + /// The Color32 representation. internal static Color HexToColor32(string hex) { if (hex.StartsWith("#")) @@ -215,6 +300,14 @@ internal static class Utils return new Color32(r, g, b, 255); } + /// + /// Linearly interpolates between multiple colors based on a value within a range. + /// + /// The array of colors to interpolate between. + /// The minimum and maximum range for interpolation. + /// The interpolation value within the range. + /// Whether to reverse the color array order. + /// The interpolated color. internal static Color LerpColor(Color[] colors, (float min, float max) lerpRange, float t, bool reverse = false) { float normalizedT = Mathf.InverseLerp(lerpRange.min, lerpRange.max, t); @@ -240,6 +333,10 @@ internal static class Utils } // Network and UI operations + /// + /// Disconnects the player's account from online services. + /// + /// Whether this is due to an API error. internal static void DisconnectAccountFromOnline(bool apiError = false) { if (GameState.IsInGame) @@ -256,6 +353,12 @@ internal static class Utils } } + /// + /// Shows a settings change notification in the HUD. + /// + /// The notification ID for deduplication. + /// The notification text. + /// Whether to play the notification sound. internal static void SettingsChangeNotifier(int id, string text, bool playSound = true) { var notifier = HudManager.Instance.Notifier; @@ -290,6 +393,11 @@ internal static class Utils } } + /// + /// Disconnects the local player from the game with an optional reason message. + /// + /// The reason for disconnection. + /// Whether to show the reason in a popup. internal static void DisconnectSelf(string reason, bool showReason = true) { AmongUsClient.Instance.ExitGame(0); @@ -309,6 +417,11 @@ internal static class Utils }, 0.2f, "DisconnectSelf 1"); } + /// + /// Shows a popup message using the DisconnectPopup. + /// + /// The text to display. + /// Whether to enable word wrapping. internal static void ShowPopUp(string text, bool enableWordWrapping = false) { DisconnectPopup.Instance.gameObject.SetActive(true); @@ -317,6 +430,12 @@ internal static class Utils } // Resource loading + /// + /// Loads a sprite from an embedded resource with caching. + /// + /// The resource path. + /// The pixels per unit for the sprite. + /// The loaded sprite, or null if loading fails. internal static Sprite? LoadSprite(string path, float pixelsPerUnit = 1f) { try @@ -341,6 +460,11 @@ internal static class Utils } } + /// + /// Loads a Texture2D from an embedded resource. + /// + /// The resource path. + /// The loaded texture, or null if loading fails. internal static Texture2D? LoadTextureFromResources(string path) { try @@ -367,12 +491,24 @@ internal static class Utils } // Platform utilities + /// + /// Gets the platform name for a player. + /// + /// The player to check. + /// Whether to include the platform tag (PC, Console, Mobile). + /// The platform name string. internal static string GetPlatformName(PlayerControl player, bool useTag = false) { if (player?.GetClient()?.PlatformData == null) return string.Empty; return GetPlatformName(player.GetClient().PlatformData.Platform, useTag); } + /// + /// Gets the platform name from a Platforms enum value. + /// + /// The platform enum value. + /// Whether to include the platform tag. + /// The platform name string. internal static string GetPlatformName(Platforms platform, bool useTag = false) { var (platformName, tag) = platform switch diff --git a/src/Interfaces/IReplayEvent.cs b/src/Interfaces/IReplayEvent.cs new file mode 100644 index 0000000..3861795 --- /dev/null +++ b/src/Interfaces/IReplayEvent.cs @@ -0,0 +1,32 @@ +using System.Text.Json.Serialization; + +namespace BetterAmongUs.Interfaces; + +/// +/// Interface for replay events in BetterAmongUs. +/// +public interface IReplayEvent +{ + /// + /// Gets the unique identifier for the replay event. + /// + [JsonIgnore] + string Id { get; } + + /// + /// Plays or executes the replay event. + /// + void Play(); +} + +/// +/// Generic interface for replay events with associated data. +/// +/// The type of data associated with the event. +public interface IReplayEvent : IReplayEvent +{ + /// + /// Gets or sets the event data. + /// + T? EventData { get; set; } +} \ No newline at end of file diff --git a/src/Managers/BetterNotificationManager.cs b/src/Managers/BetterNotificationManager.cs index 42db8d6..70cb289 100644 --- a/src/Managers/BetterNotificationManager.cs +++ b/src/Managers/BetterNotificationManager.cs @@ -9,6 +9,9 @@ using UnityEngine; namespace BetterAmongUs.Managers; +/// +/// Manages in-game notifications for BetterAmongUs, including cheat detection alerts and system messages. +/// internal static class BetterNotificationManager { internal static GameObject? BAUNotificationManagerObj; @@ -19,6 +22,11 @@ internal static class BetterNotificationManager private static Camera? localCamera; internal static bool Notifying = false; + /// + /// Displays a notification message in-game. + /// + /// The text to display in the notification. + /// The duration in seconds to show the notification. internal static void Notify(string text, float Time = 5f) { if (!BAUPlugin.BetterNotifications.Value) return; @@ -42,6 +50,15 @@ internal static class BetterNotificationManager } } + /// + /// Handles cheat detection notifications and actions. + /// + /// The player who was detected cheating. + /// The reason for the cheat detection. + /// Optional custom text to replace the default detection message. + /// Whether to kick the detected player. + /// Whether to force a ban regardless of settings. + /// True if the cheat detection was handled, false otherwise. internal static bool NotifyCheat(PlayerControl player, string reason, string newText = "", bool kickPlayer = true, bool forceBan = false) { if (player.IsCheater() || player?.Data == null) return false; @@ -97,7 +114,9 @@ internal static class BetterNotificationManager return true; } - + /// + /// Updates the notification manager each frame. + /// internal static void Update() { if (BAUNotificationManagerObj != null) @@ -134,6 +153,9 @@ internal static class BetterNotificationManager } } + /// + /// Checks and processes queued notifications. + /// private static void CheckNotifyQueue() { if (NotifyQueue.Any()) @@ -144,4 +166,4 @@ internal static class BetterNotificationManager NotifyQueue.Remove(key); } } -} +} \ No newline at end of file diff --git a/src/Managers/CustomLoadingBarManager.cs b/src/Managers/CustomLoadingBarManager.cs index 3bedeae..8204106 100644 --- a/src/Managers/CustomLoadingBarManager.cs +++ b/src/Managers/CustomLoadingBarManager.cs @@ -1,18 +1,33 @@ namespace BetterAmongUs.Managers; +/// +/// Manages custom loading bar functionality for BetterAmongUs. +/// internal static class CustomLoadingBarManager { + /// + /// Gets the current loading bar instance. + /// internal static AmongUsLoadingBar? LoadingBar => LoadingBarManager.Instance?.loadingBar; + /// + /// Toggles the loading bar visibility. + /// + /// True to show the loading bar, false to hide it. internal static void ToggleLoadingBar(bool on) { LoadingBarManager.Instance.loadingBar.gameObject.SetActive(on); } + /// + /// Sets the loading bar progress percentage and text. + /// + /// The progress percentage (0-100). + /// The text to display on the loading bar. internal static void SetLoadingPercent(float percent, string loadText) { var loadingBar = LoadingBarManager.Instance.loadingBar; loadingBar.SetLoadingPercent(percent, StringNames.None); loadingBar.loadingText.SetText(loadText); } -} +} \ No newline at end of file diff --git a/src/Managers/NetworkManager.cs b/src/Managers/NetworkManager.cs index 1e085b4..49013c0 100644 --- a/src/Managers/NetworkManager.cs +++ b/src/Managers/NetworkManager.cs @@ -15,11 +15,20 @@ using UnityEngine; namespace BetterAmongUs.Managers; +/// +/// Manages network communication, RPC handling, and anti-cheat measures for BetterAmongUs. +/// internal static class NetworkManager { + /// + /// Gets the InnerNetClient instance from AmongUsClient. + /// internal static InnerNetClient? InnerNetClient => AmongUsClient.Instance; - // Send non-immediate RPC to game server + /// + /// Sends a message to the game server. + /// + /// The MessageWriter containing the data to send. internal static void SendToServer(MessageWriter writer) { try @@ -47,7 +56,11 @@ internal static class NetworkManager } } - // Read non-immediate RPC + /// + /// Processes and forwards messages from a MessageWriter. + /// + /// The MessageWriter containing messages to process. + /// The send option for the messages. internal static void StreamlineMessage(MessageWriter writer, SendOption sendOption) { if (!InnerNetClient.InOnlineScene) @@ -68,7 +81,11 @@ internal static class NetworkManager } } - // Read data from non-immediate RPC + /// + /// Reads data from a MessageReader and processes it based on the send option. + /// + /// The MessageReader containing data to read. + /// The send option for the data. internal static void ReadData(MessageReader reader, SendOption sendOption) { var typeFlag = reader.Tag; @@ -93,7 +110,13 @@ internal static class NetworkManager } } - // Read rpc data from non-immediate RPC + /// + /// Reads RPC data from a MessageReader. + /// + /// The MessageReader containing RPC data. + /// The message flag. + /// The target client ID. + /// An RPCData structure containing the parsed RPC information. internal static RPCData ReadRpc(MessageReader reader, byte flag, int targetId) { var netId = reader.ReadPackedUInt32(); @@ -102,7 +125,10 @@ internal static class NetworkManager return new RPCData(AmongUsClient.Instance.FindObjectByNetId(netId), (SendOption)flag, targetId, (RpcCalls)calledId, reader); } - // Handle received rpcs from server + /// + /// Handles GameData messages from the server. + /// + /// The MessageReader containing GameData messages. public static void HandleGameData(MessageReader parentReader) { try @@ -123,7 +149,12 @@ internal static class NetworkManager } } - // Handle rpc data + /// + /// Internal coroutine to handle different types of GameData messages. + /// + /// The MessageReader containing the GameData. + /// The message number for tracking. + /// An IEnumerator for the coroutine. private static IEnumerator HandleGameDataInner(MessageReader reader, int msgNum) { int attemptCount = 0; @@ -168,6 +199,9 @@ internal static class NetworkManager yield break; } + /// + /// Handles object deserialization from a MessageReader. + /// private static IEnumerator HandleObjectDeserialization(MessageReader reader, int msgNum, int initialAttemptCount) { int attemptCount = initialAttemptCount; @@ -207,6 +241,9 @@ internal static class NetworkManager } } + /// + /// Handles RPC calls from a MessageReader. + /// private static IEnumerator HandleRpcCall(MessageReader reader, int msgNum, int initialAttemptCount) { int attemptCount = initialAttemptCount; @@ -272,6 +309,9 @@ internal static class NetworkManager } } + /// + /// Handles object destruction messages. + /// private static IEnumerator HandleObjectDestruction(MessageReader reader) { try @@ -293,6 +333,9 @@ internal static class NetworkManager yield break; } + /// + /// Handles client ready status messages. + /// private static IEnumerator HandleClientReady(MessageReader reader) { try @@ -310,6 +353,9 @@ internal static class NetworkManager yield break; } + /// + /// Handles scene change messages. + /// private static void HandleSceneChange(MessageReader reader) { int clientId = reader.ReadPackedInt32(); @@ -327,6 +373,9 @@ internal static class NetworkManager } } + /// + /// Handles Xbox XUID declaration messages (currently disabled). + /// private static IEnumerator HandleXboxDeclareXuid(MessageReader reader) { /* @@ -347,12 +396,18 @@ internal static class NetworkManager yield break; } + /// + /// Handles invalid message tags. + /// private static void HandleInvalidTag(MessageReader reader) { Logger_.Warning($"Bad tag {reader.Tag} at {reader.Offset}+{reader.Position}={reader.Length}: " + string.Join(" ", reader.Buffer.Take(128)), "NetworkManager"); reader.Recycle(); } + /// + /// Handles InnerNetObject RPCs with anti-cheat checks. + /// private static bool HandleInnerNetObject(InnerNetObject netObj, byte callId, MessageReader reader) { if (netObj is PlayerControl player) @@ -373,6 +428,9 @@ internal static class NetworkManager return true; } + /// + /// Processes RPCs for a player with anti-cheat validation. + /// private static bool PlayerRpc(PlayerControl player, byte callId, MessageReader reader) { if (player.BetterData() != null) @@ -401,7 +459,7 @@ internal static class NetworkManager [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.UpdateSystem), typeof(SystemTypes), typeof(PlayerControl), typeof(MessageReader))] internal static class MessageReaderUpdateSystemPatch { - internal static bool Prefix(/*ShipStatus __instance,*/ [HarmonyArgument(0)] SystemTypes systemType, [HarmonyArgument(1)] PlayerControl player, [HarmonyArgument(2)] MessageReader reader) + internal static bool Prefix([HarmonyArgument(0)] SystemTypes systemType, [HarmonyArgument(1)] PlayerControl player, [HarmonyArgument(2)] MessageReader reader) { player.BetterData().AntiCheatInfo.RPCSentPS++; if (player.BetterData().AntiCheatInfo.RPCSentPS >= ExtendedAntiCheatInfo.MAX_RPC_SENT) diff --git a/src/Managers/PrivateOnlyLobbyManager.cs b/src/Managers/PrivateOnlyLobbyManager.cs index bb25676..00ddb1f 100644 --- a/src/Managers/PrivateOnlyLobbyManager.cs +++ b/src/Managers/PrivateOnlyLobbyManager.cs @@ -4,6 +4,9 @@ using HarmonyLib; namespace BetterAmongUs.Managers; +/// +/// Manages lobby-specific behaviors for private lobbies +/// [HarmonyPatch] internal static class PrivateOnlyLobbyManager { @@ -17,4 +20,4 @@ internal static class PrivateOnlyLobbyManager __instance.RpcSetPet(PetData.EmptyId); } } -} +} \ No newline at end of file diff --git a/src/Managers/UpdateManager.cs b/src/Managers/UpdateManager.cs index f59f640..b709e1b 100644 --- a/src/Managers/UpdateManager.cs +++ b/src/Managers/UpdateManager.cs @@ -9,13 +9,26 @@ using UnityEngine; namespace BetterAmongUs.Managers; +/// +/// Manages update functionality for BetterAmongUs, including download and installation. +/// internal sealed class UpdateManager : MonoBehaviour { private bool AmUpdateing; + /// + /// Gets the singleton instance of the UpdateManager. + /// internal static UpdateManager? Instance { get; private set; } + + /// + /// Gets whether the application is waiting for a restart after an update. + /// internal static bool WaitForRestart { get; private set; } + /// + /// Initializes the UpdateManager singleton. + /// internal static void Init() { var obj = new GameObject("UpdateManager(BAU)") { hideFlags = HideFlags.HideAndDontSave }; @@ -23,6 +36,9 @@ internal sealed class UpdateManager : MonoBehaviour Instance = obj.AddComponent(); } + /// + /// Called when the main menu is loaded to set up update UI elements. + /// internal void OnMainMenu() { var doNotPress = FindObjectOfType(true); @@ -57,6 +73,9 @@ internal sealed class UpdateManager : MonoBehaviour } } + /// + /// Unity Start method called when the component is initialized. + /// private void Start() { var oldDll = Assembly.GetExecutingAssembly().Location + ".old"; @@ -69,6 +88,11 @@ internal sealed class UpdateManager : MonoBehaviour private GameObject? mainMenu; private GameObject? ambience; + /// + /// Coroutine that handles the download process when the update button is pressed. + /// + /// The DoNotPressButton that was clicked. + /// An IEnumerator for the coroutine. [HideFromIl2Cpp] private IEnumerator CoPressDownload(DoNotPressButton button) { @@ -109,4 +133,4 @@ internal sealed class UpdateManager : MonoBehaviour AmUpdateing = false; } -} +} \ No newline at end of file diff --git a/src/ModInfo.cs b/src/ModInfo.cs index 96f163d..8e8ad00 100644 --- a/src/ModInfo.cs +++ b/src/ModInfo.cs @@ -1,22 +1,73 @@ -using System.Reflection; +using BetterAmongUs.Enums; +using System.Reflection; namespace BetterAmongUs; +/// +/// Contains metadata and constants for the BetterAmongUs mod. +/// internal static class ModInfo { + /// + /// Gets the release type of the current build. + /// internal static readonly ReleaseTypes ReleaseBuildType = ReleaseTypes.Dev; + + /// + /// Gets the Git commit hash from assembly metadata. + /// public static string CommitHash = GetAssemblyMetadata("CommitHash"); + + /// + /// Gets the build date from assembly metadata. + /// public static string BuildDate = GetAssemblyMetadata("BuildDate"); + /// + /// The beta number for beta releases. + /// internal const string BETA_NUM = "0"; + + /// + /// The hotfix number for hotfix releases. + /// internal const string HOTFIX_NUM = "0"; + + /// + /// Indicates whether this is a hotfix release. + /// internal const bool IS_HOTFIX = false; + + /// + /// The name of BAU. + /// internal const string PLUGIN_NAME = "BetterAmongUs"; + + /// + /// The GUID (Globally Unique Identifier) of BAU. + /// internal const string PLUGIN_GUID = "com.d1gq.betteramongus"; + + /// + /// The version of BAU. + /// internal const string PLUGIN_VERSION = "1.3.2"; + + /// + /// The GitHub repository URL for BAU. + /// internal const string GITHUB = "https://github.com/D1GQ/BetterAmongUs"; + + /// + /// The Discord invite URL for BAU. + /// internal const string DISCORD = "https://discord.gg/vjYrXpzNAn"; + /// + /// Retrieves metadata from the assembly attributes. + /// + /// The metadata key to retrieve. + /// The metadata value, or an empty string if not found. private static string GetAssemblyMetadata(string key) { var attribute = Assembly.GetExecutingAssembly() @@ -25,4 +76,15 @@ internal static class ModInfo return attribute?.Value ?? string.Empty; } + + /// + /// Contains constants for Among Us. + /// + internal static class AmongUs + { + /// + /// The process name of the Among Us executable. + /// + internal const string PROCESS_NAME = "Among Us.exe"; + } } \ No newline at end of file diff --git a/src/Modules/AntiCheat/BetterAntiCheat.cs b/src/Modules/AntiCheat/BetterAntiCheat.cs index 1bd4ddd..cf3a3e5 100644 --- a/src/Modules/AntiCheat/BetterAntiCheat.cs +++ b/src/Modules/AntiCheat/BetterAntiCheat.cs @@ -11,10 +11,19 @@ using InnerNet; namespace BetterAmongUs.Modules.AntiCheat; +/// +/// Provides anti-cheat functionality for detecting and handling cheating behaviors. +/// internal static class BetterAntiCheat { + /// + /// Gets whether anti-cheat is enabled for the current player. + /// internal static bool IsEnabled => PlayerControl.LocalPlayer?.Data?.IsIncomplete == false; + /// + /// Updates anti-cheat checks for all players in the game. + /// internal static void Update() { if (GameState.IsHost && GameState.IsInGame) @@ -49,9 +58,15 @@ internal static class BetterAntiCheat } } + /// + /// Harmony patch for PlatformSpecificData to detect platform spoofing. + /// [HarmonyPatch(typeof(PlatformSpecificData))] class PlatformSpecificDataPatch { + /// + /// Postfix patch to validate platform data after deserialization. + /// [HarmonyPatch(nameof(PlatformSpecificData.Deserialize))] [HarmonyPostfix] internal static void Deserialize_Postfix(PlatformSpecificData __instance) @@ -110,7 +125,12 @@ internal static class BetterAntiCheat } } - // Handle RPC before anti cheat detection + /// + /// Handles RPCs before anti-cheat detection checks. + /// + /// The player who sent the RPC. + /// The RPC call ID. + /// The MessageReader containing RPC data. internal static void HandleCheatRPCBeforeCheck(PlayerControl player, byte callId, MessageReader oldReader) { if (!IsEnabled) return; @@ -120,7 +140,12 @@ internal static class BetterAntiCheat reader.Recycle(); } - // Check and notify for invalid rpcs + /// + /// Checks and notifies for invalid RPCs. + /// + /// The player who sent the RPC. + /// The RPC call ID. + /// The MessageReader containing RPC data. internal static void CheckRPC(PlayerControl player, byte callId, MessageReader oldReader) { if (player == null || player?.Data == null) return; @@ -132,7 +157,13 @@ internal static class BetterAntiCheat reader.Recycle(); } - // Check notify and cancel out request for invalid rpcs + /// + /// Checks, notifies, and cancels invalid RPCs. + /// + /// The player who sent the RPC. + /// The RPC call ID. + /// The MessageReader containing RPC data. + /// True if the RPC should be processed, false if it should be canceled. internal static bool CheckCancelRPC(PlayerControl player, byte callId, MessageReader oldReader) { try @@ -242,7 +273,12 @@ internal static class BetterAntiCheat } } - // Handle RPC received from players + /// + /// Handles RPCs received from players. + /// + /// The player who sent the RPC. + /// The RPC call ID. + /// The MessageReader containing RPC data. internal static void HandleRPC(PlayerControl player, byte callId, MessageReader oldReader) { if (player == null || player?.Data == null || player.IsLocalPlayer()) return; @@ -252,7 +288,13 @@ internal static class BetterAntiCheat reader.Recycle(); } - // Check game states when sabotaging + /// + /// Checks game states when sabotaging systems. + /// + /// The player attempting the sabotage. + /// The system type being updated. + /// The MessageReader containing system update data. + /// True if the sabotage should be allowed, false otherwise. internal static bool RpcUpdateSystemCheck(PlayerControl player, SystemTypes systemType, MessageReader oldReader) { if (Utils.SystemTypeIsSabotage(systemType) || systemType is SystemTypes.Doors) @@ -275,7 +317,11 @@ internal static class BetterAntiCheat return notCanceled; } - // Check if RPC is known + /// + /// Checks if an RPC ID is from a known/trusted RPC enumeration. + /// + /// The RPC ID to check. + /// True if the RPC ID is from a known enumeration, false otherwise. private static bool TrustedRPCs(int RPCId) { foreach (RpcCalls rpc in Enum.GetValues(typeof(RpcCalls))) @@ -287,4 +333,4 @@ internal static class BetterAntiCheat return false; } -} +} \ No newline at end of file diff --git a/src/Modules/AntiCheat/RPCHandler.cs b/src/Modules/AntiCheat/RPCHandler.cs index 0d23f31..252f991 100644 --- a/src/Modules/AntiCheat/RPCHandler.cs +++ b/src/Modules/AntiCheat/RPCHandler.cs @@ -8,25 +8,104 @@ using UnityEngine; namespace BetterAmongUs.Modules.AntiCheat; +/// +/// Abstract base class for handling RPC (Remote Procedure Call) messages with anti-cheat functionality. +/// internal abstract class RPCHandler { + /// + /// Array of all registered RPC handler instances. + /// internal static readonly RPCHandler?[] allHandlers = [.. RegisterRPCHandlerAttribute.Instances]; + + /// + /// Gets the InnerNetClient instance. + /// internal InnerNetClient innerNetClient => AmongUsClient.Instance; + + /// + /// Gets the RPC call ID that this handler responds to. + /// internal virtual byte CallId => byte.MaxValue; + + /// + /// Gets the GameData tag that this handler responds to. + /// internal virtual byte GameDataTag => byte.MaxValue; + + /// + /// Gets or sets whether this handler should process messages from the local player. + /// internal virtual bool LocalHandling { get; set; } + + /// + /// Gets whether to cancel RPC handling when not the host. + /// protected static bool CancelAsHost => !GameState.IsHost; + /// + /// Checks if two positions are within a specified range. + /// + /// First position. + /// Second position. + /// Maximum allowed distance. + /// True if positions are within range, false otherwise. internal static bool CheckRange(Vector2 pos1, Vector2 pos2, float range) => Vector2.Distance(pos1, pos2) <= range; + + /// + /// Handles normal RPC messages. + /// + /// The player who sent the RPC. + /// The MessageReader containing RPC data. internal virtual void Handle(PlayerControl? sender, MessageReader reader) { } + + /// + /// Handles GameData messages. + /// + /// The MessageReader containing GameData. internal virtual void HandleGameData(MessageReader reader) { } + + /// + /// Handles RPC messages for cheat detection checks. + /// + /// The player who sent the RPC. + /// The MessageReader containing RPC data. internal virtual void HandleCheatRpcCheck(PlayerControl? sender, MessageReader reader) { } + + /// + /// Handles anti-cheat cancellation checks for RPC messages. + /// + /// The player who sent the RPC. + /// The MessageReader containing RPC data. + /// True if the RPC should be processed, false if it should be canceled. internal virtual bool HandleAntiCheatCancel(PlayerControl? sender, MessageReader reader) => true; + + /// + /// Handles anti-cheat analysis for RPC messages. + /// + /// The player who sent the RPC. + /// The MessageReader containing RPC data. internal virtual void HandleAntiCheat(PlayerControl? sender, MessageReader reader) { } + + /// + /// Handles RPC messages with BetterAmongUs-specific logic. + /// + /// The player who sent the RPC. + /// The MessageReader containing RPC data. + /// True if the RPC should be processed, false if it should be canceled. internal virtual bool BetterHandle(PlayerControl? sender, MessageReader reader) => true; protected static PlayerControl? catchedSender; protected static HandlerFlag catchedHandlerFlag = HandlerFlag.Handle; + + /// + /// Routes RPC messages to the appropriate handler based on the call ID and handler flag. + /// + /// The RPC call ID. + /// The player who sent the RPC. + /// The MessageReader containing RPC data. + /// The type of handling to perform. + /// True if the RPC was handled successfully and should continue, false if canceled. internal static bool HandleRPC(byte calledId, PlayerControl? sender, MessageReader reader, HandlerFlag handlerFlag) { catchedSender = sender; @@ -80,6 +159,11 @@ internal abstract class RPCHandler return !cancel; } + /// + /// Logs RPC information for debugging and anti-cheat tracking. + /// + /// Additional information about the RPC. + /// Optional player to log information for. internal void LogRpcInfo(string info, PlayerControl? player = null) { string Name = Enum.GetName((RpcCalls)CallId) ?? Enum.GetName((CustomRPC)CallId) ?? $"Unregistered({CallId})"; @@ -87,12 +171,20 @@ internal abstract class RPCHandler Logger_.LogCheat($"{catchedSender?.BetterData()?.RealName ?? player.BetterData()?.RealName ?? string.Empty} {Name}: {info}"); } + /// + /// Gets formatted text for invalid action RPC notifications. + /// + /// A formatted string for displaying invalid action RPC messages. internal string GetFormatActionText() { string Name = Enum.GetName((RpcCalls)CallId) ?? Enum.GetName((CustomRPC)CallId) ?? $"Unregistered({CallId})"; return string.Format(Translator.GetString("AntiCheat.InvalidActionRPC"), Name); } + /// + /// Gets formatted text for invalid set RPC notifications. + /// + /// A formatted string for displaying invalid set RPC messages. internal string GetFormatSetText() { string Name = Enum.GetName((RpcCalls)CallId) ?? Enum.GetName((CustomRPC)CallId) ?? $"Unregistered({CallId})"; diff --git a/src/Modules/AntiCheat/RPCHandlers/MurderPlayerHandler.cs b/src/Modules/AntiCheat/RPCHandlers/MurderPlayerHandler.cs index 3d14f76..ede05d2 100644 --- a/src/Modules/AntiCheat/RPCHandlers/MurderPlayerHandler.cs +++ b/src/Modules/AntiCheat/RPCHandlers/MurderPlayerHandler.cs @@ -1,6 +1,6 @@ using AmongUs.GameOptions; -using BetterAmongUs.Helpers; using BetterAmongUs.Attributes; +using BetterAmongUs.Helpers; using BetterAmongUs.Managers; using BetterAmongUs.Mono; using Hazel; @@ -24,7 +24,7 @@ internal sealed class MurderPlayerHandler : RPCHandler { target.BetterData().AntiCheatInfo.TimesAttemptedKilled++; - if (target.BetterData().AntiCheatInfo.TimesAttemptedKilled >= 5 && !target.IsAlive()) + if (target.BetterData().AntiCheatInfo.TimesAttemptedKilled >= 10 && !target.IsAlive()) { if (BetterNotificationManager.NotifyCheat(player, string.Format(Translator.GetString("AntiCheat.InvalidAction"), Translator.GetString("AntiCheat.TryBanExploit")))) { diff --git a/src/Modules/ClientOptionItem.cs b/src/Modules/ClientOptionItem.cs index 801e4ae..b90a352 100644 --- a/src/Modules/ClientOptionItem.cs +++ b/src/Modules/ClientOptionItem.cs @@ -4,14 +4,37 @@ using Object = UnityEngine.Object; namespace BetterAmongUs.Modules; +/// +/// Represents a customizable client option item that can be toggled in the options menu. +/// internal sealed class ClientOptionItem { - internal ConfigEntry? Config; - internal ToggleButtonBehaviour ToggleButton; + /// + /// Gets or sets the configuration entry associated with this option. + /// + internal ConfigEntry? Config { get; set; } + + /// + /// Gets or sets the toggle button behavior for this option. + /// + internal ToggleButtonBehaviour ToggleButton { get; set; } + + /// + /// Gets or sets the custom background for BetterAmongUs options. + /// + internal static SpriteRenderer? CustomBackground { get; set; } - internal static SpriteRenderer? CustomBackground; private static int numOptions = 0; + /// + /// Initializes a new instance of the ClientOptionItem class. + /// + /// The display name of the option. + /// The configuration entry for this option. + /// The options menu behavior instance. + /// Additional action to perform on click. + /// Function to check if the toggle can be interacted with. + /// Whether this is a toggle option or a button. internal ClientOptionItem(string name, ConfigEntry? config, OptionsMenuBehaviour optionsMenuBehaviour, Action? additionalOnClickAction = null, Func? toggleCheck = null, bool IsToggle = true) { try @@ -20,7 +43,7 @@ internal sealed class ClientOptionItem var mouseMoveToggle = optionsMenuBehaviour.DisableMouseMovement; - // 1つ目のボタンの生成時に背景も生成 + // Initialize custom background on first creation if (CustomBackground == null) { numOptions = 0; @@ -30,6 +53,7 @@ internal sealed class ClientOptionItem CustomBackground.transform.localPosition += Vector3.back * 8; CustomBackground.gameObject.SetActive(false); + // Create back button var closeButton = Object.Instantiate(mouseMoveToggle, CustomBackground.transform); closeButton.transform.localPosition = new(1.3f, -2.3f, -6f); closeButton.name = "Back"; @@ -42,6 +66,7 @@ internal sealed class ClientOptionItem CustomBackground.gameObject.SetActive(false); })); + // Find existing UI elements var selectableButtons = optionsMenuBehaviour.ControllerSelectable; PassiveButton? leaveButton = null; PassiveButton? returnButton = null; @@ -54,8 +79,9 @@ internal sealed class ClientOptionItem else if (button.name == "ReturnToGameButton") returnButton = button.GetComponent(); } - var generalTab = mouseMoveToggle.transform.parent.parent.parent; + // Create main mod options button + var generalTab = mouseMoveToggle.transform.parent.parent.parent; var modOptionsButton = Object.Instantiate(mouseMoveToggle, generalTab); modOptionsButton.transform.localPosition = leaveButton?.transform?.localPosition ?? new(0f, -2.4f, 1f); modOptionsButton.name = "Better Options"; @@ -68,12 +94,14 @@ internal sealed class ClientOptionItem CustomBackground.gameObject.SetActive(true); })); + // Reposition existing buttons if (leaveButton != null) leaveButton.transform.localPosition = new(-1.35f, -2.411f, -1f); if (returnButton != null) returnButton.transform.localPosition = new(1.35f, -2.411f, -1f); } + // Create this specific option button ToggleButton = Object.Instantiate(mouseMoveToggle, CustomBackground.transform); ToggleButton.transform.localPosition = new Vector3( numOptions % 2 == 0 ? -1.3f : 1.3f, @@ -86,6 +114,7 @@ internal sealed class ClientOptionItem var passiveButton = ToggleButton.GetComponent(); passiveButton.OnClick = new(); + // Handle non-toggle button if (IsToggle == false) { ToggleButton.Text.text = name; @@ -94,7 +123,7 @@ internal sealed class ClientOptionItem passiveButton.OnClick.AddListener(new Action(() => { - if (toggleCheck() == false) + if (toggleCheck?.Invoke() == false) { return; } @@ -105,9 +134,10 @@ internal sealed class ClientOptionItem return; } + // Handle toggle button passiveButton.OnClick.AddListener(new Action(() => { - if (toggleCheck() == false) + if (toggleCheck?.Invoke() == false) { return; } @@ -116,11 +146,25 @@ internal sealed class ClientOptionItem UpdateToggle(); additionalOnClickAction?.Invoke(); })); + UpdateToggle(); } - finally { numOptions++; } + finally + { + numOptions++; + } } + /// + /// Factory method to create a new ClientOptionItem. + /// + /// The display name of the option. + /// The configuration entry for this option. + /// The options menu behavior instance. + /// Additional action to perform on click. + /// Function to check if the toggle can be interacted with. + /// Whether this is a toggle option or a button. + /// A new ClientOptionItem instance. internal static ClientOptionItem Create(string name, ConfigEntry? config, OptionsMenuBehaviour optionsMenuBehaviour, Action? additionalOnClickAction = null, Func? toggleCheck = null, bool IsToggle = true) { toggleCheck ??= () => true; @@ -128,12 +172,20 @@ internal sealed class ClientOptionItem return new ClientOptionItem(name, config, optionsMenuBehaviour, additionalOnClickAction, toggleCheck, IsToggle); } + /// + /// Updates the visual state of the toggle button based on the configuration value. + /// internal void UpdateToggle() { if (ToggleButton == null) return; - var color = Config != null && Config.Value ? new Color32(0, 150, 0, byte.MaxValue) : new Color32(77, 77, 77, byte.MaxValue); - var textColor = Config != null && Config.Value ? new Color(1f, 1f, 1f, 1f) : new Color(1f, 1f, 1f, 0.5f); + var color = Config != null && Config.Value ? + new Color32(0, 150, 0, byte.MaxValue) : + new Color32(77, 77, 77, byte.MaxValue); + var textColor = Config != null && Config.Value ? + new Color(1f, 1f, 1f, 1f) : + new Color(1f, 1f, 1f, 0.5f); + ToggleButton.Background.color = color; ToggleButton.Rollover?.ChangeOutColor(color); ToggleButton.Text.color = textColor; diff --git a/src/Modules/Encryptor.cs b/src/Modules/Encryptor.cs index c49cfc3..ea241e9 100644 --- a/src/Modules/Encryptor.cs +++ b/src/Modules/Encryptor.cs @@ -3,28 +3,39 @@ using System.Text; namespace BetterAmongUs.Modules; +/// +/// Provides AES encryption and decryption utilities for sensitive data. +/// internal static class Encryptor { private static readonly byte[] Key = Encoding.UTF8.GetBytes("0123456789ABCDEF0123456789ABCDEF"); private static readonly byte[] IV = Encoding.UTF8.GetBytes("ABCDEF0123456789"); + /// + /// Encrypts a plain text string using AES encryption. + /// + /// The plain text string to encrypt. + /// A base64-encoded string containing the encrypted data. internal static string Encrypt(string input) { - using (Aes aes = Aes.Create()) - { - aes.Key = Key; - aes.IV = IV; + using Aes aes = Aes.Create(); + aes.Key = Key; + aes.IV = IV; - using MemoryStream memoryStream = new(); - using CryptoStream cryptoStream = new(memoryStream, aes.CreateEncryptor(), CryptoStreamMode.Write); - using (StreamWriter streamWriter = new(cryptoStream)) - { - streamWriter.Write(input); - } - return Convert.ToBase64String(memoryStream.ToArray()); + using MemoryStream memoryStream = new(); + using CryptoStream cryptoStream = new(memoryStream, aes.CreateEncryptor(), CryptoStreamMode.Write); + using (StreamWriter streamWriter = new(cryptoStream)) + { + streamWriter.Write(input); } + return Convert.ToBase64String(memoryStream.ToArray()); } + /// + /// Decrypts an AES-encrypted base64 string back to plain text. + /// + /// The base64-encoded encrypted string. + /// The decrypted plain text string. internal static string Decrypt(string input) { using Aes aes = Aes.Create(); @@ -36,4 +47,4 @@ internal static class Encryptor using StreamReader streamReader = new(cryptoStream); return streamReader.ReadToEnd(); } -} +} \ No newline at end of file diff --git a/src/Modules/GameState.cs b/src/Modules/GameState.cs index 3558e40..53dd370 100644 --- a/src/Modules/GameState.cs +++ b/src/Modules/GameState.cs @@ -2,19 +2,67 @@ using AmongUs.GameOptions; namespace BetterAmongUs.Modules; +/// +/// Provides static properties to check various game states and conditions. +/// internal static class GameState { /**********Check Game Status***********/ + /// + /// Gets whether any players exist in the game. + /// internal static bool InGame => BAUPlugin.AllPlayerControls.Any(); + + /// + /// Gets whether the current game mode is Normal or NormalFools. + /// internal static bool IsNormalGame => GameOptionsManager.Instance.CurrentGameOptions.GameMode is GameModes.Normal or GameModes.NormalFools; + + /// + /// Gets whether the current game mode is HideNSeek or SeekFools. + /// internal static bool IsHideNSeek => GameOptionsManager.Instance != null && GameOptionsManager.Instance.CurrentGameOptions.GameMode is GameModes.HideNSeek or GameModes.SeekFools; + + /// + /// Gets whether the Skeld map is currently active. + /// internal static bool SkeldIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Skeld; + + /// + /// Gets whether the MiraHQ map is currently active. + /// internal static bool MiraHQIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.MiraHQ; + + /// + /// Gets whether the Polus map is currently active. + /// internal static bool PolusIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Polus; + + /// + /// Gets whether the Dleks map is currently active. + /// internal static bool DleksIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Dleks; + + /// + /// Gets whether the Airship map is currently active. + /// internal static bool AirshipIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Airship; + + /// + /// Gets whether the Fungle map is currently active. + /// internal static bool FungleIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Fungle; + + /// + /// Gets the ID of the currently active map. + /// internal static byte GetActiveMapId => GameOptionsManager.Instance.CurrentGameOptions.MapId; + + /// + /// Checks whether a specific system type is currently active on the map. + /// + /// The system type to check. + /// True if the system is active, false otherwise. internal static bool IsSystemActive(SystemTypes type) { if (IsHideNSeek || !ShipStatus.Instance.Systems.TryGetValue(type, out var system)) @@ -37,6 +85,11 @@ internal static class GameState _ => false }; } + + /// + /// Checks if any critical (death-causing) sabotage is currently active. + /// + /// True if a critical sabotage is active, false otherwise. internal static bool IsCriticalSabotageActive() { var deathSabotages = new[] @@ -49,6 +102,11 @@ internal static class GameState return deathSabotages.Any(IsSystemActive); } + + /// + /// Checks if any non-critical sabotage is currently active. + /// + /// True if a non-critical sabotage is active, false otherwise. internal static bool IsNoneCriticalSabotageActive() { var noneDeathSabotages = new[] @@ -60,6 +118,11 @@ internal static class GameState return noneDeathSabotages.Any(IsSystemActive); } + + /// + /// Checks if any sabotage (critical or non-critical) is currently active. + /// + /// True if any sabotage is active, false otherwise. internal static bool IsAnySabotageActive() { var allSabotages = new[] @@ -75,13 +138,45 @@ internal static class GameState return allSabotages.Any(IsSystemActive); } + + /// + /// Gets whether the player is in a game. + /// internal static bool IsInGame => InGame; + + /// + /// Gets whether the player is in the lobby (joined but game not started). + /// internal static bool IsLobby => AmongUsClient.Instance?.GameState == InnerNet.InnerNetClient.GameStates.Joined; + + /// + /// Gets whether the intro cutscene is currently playing. + /// internal static bool IsInIntro => IntroCutscene.Instance != null; + + /// + /// Gets whether gameplay is currently active (not in lobby, intro, or ended). + /// internal static bool IsInGamePlay => InGame && IsShip && !IsLobby && !IsInIntro || IsFreePlay; + + /// + /// Gets whether the game has ended. + /// internal static bool IsEnded => AmongUsClient.Instance?.GameState == InnerNet.InnerNetClient.GameStates.Ended; + + /// + /// Gets whether the player is not joined to any game. + /// internal static bool IsNotJoined => AmongUsClient.Instance?.GameState == InnerNet.InnerNetClient.GameStates.NotJoined; + + /// + /// Gets whether the current game is an online game. + /// internal static bool IsOnlineGame => AmongUsClient.Instance?.NetworkMode == NetworkModes.OnlineGame; + + /// + /// Gets whether the player is connected to a vanilla (official) server. + /// internal static bool IsVanillaServer { get @@ -92,17 +187,69 @@ internal static class GameState return region == "North America" || region == "Europe" || region == "Asia"; } } + + /// + /// Gets whether the current game is a local game. + /// internal static bool IsLocalGame => AmongUsClient.Instance?.NetworkMode == NetworkModes.LocalGame; + + /// + /// Gets whether the current game is in free play mode. + /// internal static bool IsFreePlay => AmongUsClient.Instance?.NetworkMode == NetworkModes.FreePlay; + + /// + /// Gets whether the player is in a task (not in a meeting). + /// internal static bool IsInTask => InGame && MeetingHud.Instance == null; + + /// + /// Gets whether a meeting is currently active. + /// internal static bool IsMeeting => InGame && MeetingHud.Instance != null; + + /// + /// Gets whether voting is currently in progress during a meeting. + /// internal static bool IsVoting => IsMeeting && MeetingHud.Instance?.state is MeetingHud.VoteStates.Voted or MeetingHud.VoteStates.NotVoted; + + /// + /// Gets whether the meeting is proceeding to results. + /// internal static bool IsProceeding => IsMeeting && MeetingHud.Instance?.state is MeetingHud.VoteStates.Proceeding; + + /// + /// Gets whether a player is being exiled. + /// internal static bool IsExilling => ExileController.Instance != null && !(AirshipIsActive && Minigame.Instance != null && Minigame.Instance.isActiveAndEnabled); + + /// + /// Gets whether the game start countdown is active. + /// internal static bool IsCountDown => GameStartManager.InstanceExists && GameStartManager.Instance.startState == GameStartManager.StartingStates.Countdown; + + /// + /// Gets whether a ship (map) is currently loaded. + /// internal static bool IsShip => ShipStatus.Instance != null; + + /// + /// Gets whether the local player is the host. + /// internal static bool IsHost => AmongUsClient.Instance != null && AmongUsClient.Instance.AmHost; + + /// + /// Gets whether the lobby is private-only (requires specific settings). + /// internal static bool IsPrivateOnlyLobby => (BAUPlugin.PrivateOnlyLobby.Value || AmongUsClient.Instance.AmLocalHost) && IsHost; + + /// + /// Gets whether the local player can move. + /// internal static bool IsCanMove => PlayerControl.LocalPlayer?.CanMove is true; + + /// + /// Gets whether the local player is dead. + /// internal static bool IsDead => PlayerControl.LocalPlayer?.Data?.IsDead is true; -} +} \ No newline at end of file diff --git a/src/Modules/HandshakeHandler.cs b/src/Modules/HandshakeHandler.cs index ae6b00e..70f55a2 100644 --- a/src/Modules/HandshakeHandler.cs +++ b/src/Modules/HandshakeHandler.cs @@ -10,6 +10,9 @@ using UnityEngine; namespace BetterAmongUs.Modules; +/// +/// Handles the secure handshake process between BetterAmongUs clients using Diffie-Hellman key exchange. +/// internal sealed class HandshakeHandler { [HideFromIl2Cpp] @@ -21,11 +24,17 @@ internal sealed class HandshakeHandler [HideFromIl2Cpp] private ExtendedPlayerInfo extendedData { get; } + /// + /// Initiates the wait period before sending the secret to another player. + /// internal void WaitSendSecretToPlayer() { extendedData.StartCoroutine(CoWaitSendSecretToPlayer()); } + /// + /// Coroutine that waits for player initialization before sending the secret. + /// private IEnumerator CoWaitSendSecretToPlayer() { if (!BAUPlugin.SendBetterRpc.Value) yield break; @@ -40,6 +49,9 @@ internal sealed class HandshakeHandler SendSecretToPlayer(); } + /// + /// Resends the secret to the player if not already verified. + /// internal void ResendSecretToPlayer() { if (!BAUPlugin.SendBetterRpc.Value) return; @@ -49,6 +61,9 @@ internal sealed class HandshakeHandler SendSecretToPlayer(); } + /// + /// Sends the local client's public key and temporary key to another player. + /// // Local client sends to client private void SendSecretToPlayer() { @@ -62,6 +77,10 @@ internal sealed class HandshakeHandler AmongUsClient.Instance.FinishRpcImmediately(writer); } + /// + /// Handles receiving a secret from another player and generates a shared secret. + /// + /// MessageReader containing the sender's public key and temporary key. // Client receives from local client internal void HandleSecretFromSender(MessageReader reader) { @@ -84,6 +103,11 @@ internal sealed class HandshakeHandler ResendSecretToPlayer(); } + /// + /// Sends the hash of the generated shared secret back to the original sender for verification. + /// + /// The temporary key received from the sender. + /// The client ID of the sender. // Client sends back to local client private void SendSecretHashToSender(int tempKey, int senderClientId) { @@ -98,6 +122,10 @@ internal sealed class HandshakeHandler AmongUsClient.Instance.FinishRpcImmediately(writer); } + /// + /// Handles receiving a secret hash from another player for verification. + /// + /// MessageReader containing the temporary key and hash. internal void HandleSecretHashFromPlayer(MessageReader reader) { int tempKey = reader.ReadInt32(); @@ -106,6 +134,9 @@ internal sealed class HandshakeHandler TryHandlePendingVerificationData(); } + /// + /// Attempts to verify pending handshake data if all required information is available. + /// internal void TryHandlePendingVerificationData() { if (_pendingVerificationData?.tempKey == null || _pendingVerificationData?.receivedHash == null) return; @@ -139,5 +170,9 @@ internal sealed class HandshakeHandler private (int tempKey, int receivedHash)? _pendingVerificationData = null; private bool HasSendSharedSecret { get; set; } + + /// + /// Gets the SharedSecretExchange instance for secure key exchange. + /// internal SharedSecretExchange SharedSecret { get; set; } = new(); -} +} \ No newline at end of file diff --git a/src/Modules/ModNews.cs b/src/Modules/ModNews.cs index 78a4638..1448b49 100644 --- a/src/Modules/ModNews.cs +++ b/src/Modules/ModNews.cs @@ -7,19 +7,66 @@ using UnityEngine; namespace BetterAmongUs.Modules; +/// +/// Represents a mod-specific news item with localization support. +/// internal sealed class ModNews { + /// + /// Gets the type of news item. + /// internal NewsTypes NewsType { get; } + + /// + /// Gets the unique identifier number for this news item. + /// internal int Number { get; } + + /// + /// Gets the title of the news item. + /// internal string Title { get; } + + /// + /// Gets the subtitle of the news item. + /// internal string SubTitle { get; } + + /// + /// Gets the short title for display in lists. + /// internal string ShortTitle { get; } + + /// + /// Gets or sets the dictionary of localized content by language ID. + /// public Dictionary Contents { get; set; } = []; + + /// + /// Gets the publication date of the news item. + /// internal string Date { get; } + /// + /// Gets the list of raw news data waiting to be processed. + /// internal static List NewsDataToProcess { get; } = []; + + /// + /// Gets the list of all processed mod news items. + /// internal static List AllModNews { get; } = []; + /// + /// Initializes a new instance of the ModNews class. + /// + /// The type of news item. + /// The unique identifier number. + /// The title of the news. + /// The subtitle of the news. + /// The short title for lists. + /// Localized content by language ID. + /// The publication date. internal ModNews(NewsTypes type, int number, string title, string subTitle, string shortTitle, Dictionary contents, string date) { NewsType = type; @@ -33,6 +80,10 @@ internal sealed class ModNews AllModNews.Add(this); } + /// + /// Converts this ModNews instance to an AmongUs Announcement object. + /// + /// An Announcement object ready for display in-game. internal Announcement ToAnnouncement() { var announcement = new Announcement @@ -60,6 +111,10 @@ internal sealed class ModNews } private static int _nextAnnouncementNumber; + + /// + /// Processes all queued mod news data and creates ModNews instances. + /// internal static void ProcessModNewsFiles() { AllModNews.Clear(); @@ -72,6 +127,11 @@ internal sealed class ModNews } } + /// + /// Parses raw news data and creates a ModNews instance. + /// + /// The raw news data configuration. + /// The announcement number to assign. private static void ParseModNewsContent(NewsData config, int nextAnnouncementNumber) { var type = (NewsTypes)config.Type; diff --git a/src/Modules/MonoExtension.cs b/src/Modules/MonoExtension.cs index cd86b19..a57f020 100644 --- a/src/Modules/MonoExtension.cs +++ b/src/Modules/MonoExtension.cs @@ -5,15 +5,31 @@ using UnityEngine; namespace BetterAmongUs.Modules; +/// +/// Interface for MonoBehavior extensions in BetterAmongUs. +/// internal interface IMonoExtension { + /// + /// Gets or sets the base MonoBehavior this extension is attached to. + /// MonoBehaviour? BaseMono { get; set; } } +/// +/// Generic interface for MonoBehavior extensions with specific base type. +/// +/// The type of MonoBehavior this extension attaches to. internal interface IMonoExtension : IMonoExtension where T : MonoBehaviour { + /// + /// Gets or sets the base MonoBehavior of type T. + /// new T? BaseMono { get; set; } + /// + /// Explicit interface implementation for non-generic BaseMono. + /// MonoBehaviour? IMonoExtension.BaseMono { get => BaseMono; @@ -21,10 +37,19 @@ internal interface IMonoExtension : IMonoExtension where T : MonoBehaviour } } +/// +/// Manages MonoBehavior extensions and their lifecycle in BetterAmongUs. +/// internal static class MonoExtensionManager { private static readonly Dictionary> _extensionsByBaseType = []; + /// + /// Gets an extension of type T attached to a MonoBehavior. + /// + /// The type of extension to retrieve. + /// The base MonoBehavior. + /// The extension instance, or null if not found. internal static T? Get(MonoBehaviour mono) where T : class, IMonoExtension { if (mono == null) return null; @@ -43,11 +68,21 @@ internal static class MonoExtensionManager return null; } + /// + /// Runs a callback when a MonoBehavior extension becomes available. + /// + /// The type of extension to wait for. + /// The base MonoBehavior. + /// Function to retrieve the extension. + /// Callback to execute when extension is available. internal static void RunWhenNotNull(MonoBehaviour mono, Func getExtension, Action callback) where T : class, IMonoExtension { mono.StartCoroutine(CoWaitForExtension(getExtension, callback)); } + /// + /// Coroutine that waits for an extension to become available. + /// private static IEnumerator CoWaitForExtension(Func getExtension, Action callback) where T : class, IMonoExtension { T? extension; @@ -58,6 +93,9 @@ internal static class MonoExtensionManager callback(extension); } + /// + /// Cleans up all registered extensions with null references. + /// internal static void CleanAll() { foreach (var kvp in _extensionsByBaseType.ToArray()) @@ -78,6 +116,11 @@ internal static class MonoExtensionManager } } + /// + /// Registers a MonoBehavior extension with the manager. + /// + /// The extension to register. + /// True if registration was successful, false otherwise. internal static bool RegisterExtension(this IMonoExtension extension) { // Try to find IMonoExtension implementation @@ -135,6 +178,10 @@ internal static class MonoExtensionManager return true; } + /// + /// Unregisters a MonoBehavior extension from the manager. + /// + /// The extension to unregister. internal static void UnregisterExtension(this IMonoExtension extension) { if (extension.BaseMono == null) return; @@ -157,9 +204,19 @@ internal static class MonoExtensionManager } } + /// + /// Represents a pairing between a base MonoBehavior and its extension. + /// private struct ExtensionPair { + /// + /// The base MonoBehavior. + /// internal MonoBehaviour? Base; + + /// + /// The extension attached to the base. + /// internal IMonoExtension? Extension; } } \ No newline at end of file diff --git a/src/Modules/OptionItems/NoneOption/OptionDividerItem.cs b/src/Modules/OptionItems/NoneOption/OptionDividerItem.cs index d64cf3d..113deca 100644 --- a/src/Modules/OptionItems/NoneOption/OptionDividerItem.cs +++ b/src/Modules/OptionItems/NoneOption/OptionDividerItem.cs @@ -3,12 +3,33 @@ using UnityEngine; namespace BetterAmongUs.Modules.OptionItems.NoneOption; +/// +/// Represents a visual divider item used to separate option groups in the UI. +/// internal sealed class OptionDividerItem : OptionItem { + /// + /// Gets whether this option item loads values from persistent storage. + /// internal override bool CanLoad => false; + + /// + /// Gets whether this item represents a configurable option (false for dividers). + /// internal override bool IsOption => false; + + /// + /// Gets or sets the spacing distances above and below the divider. + /// internal (float top, float bottom) Distance { get; set; } + /// + /// Creates a new divider option item. + /// + /// The tab this divider belongs to. + /// Spacing distance above the divider. + /// Spacing distance below the divider. + /// A new OptionDividerItem instance. internal static OptionDividerItem Create(OptionTab tab, float topDistance = 0.26f, float bottomDistance = 0.50f) { var Item = new OptionDividerItem @@ -21,6 +42,9 @@ internal sealed class OptionDividerItem : OptionItem return Item; } + /// + /// Creates the UI behavior for this divider item. + /// private void CreateBehavior() { if (!GameSettingMenu.Instance) return; @@ -37,8 +61,13 @@ internal sealed class OptionDividerItem : OptionItem Tab.Children.Add(this); } + /// + /// Gets the string representation of the divider value (not applicable). + /// + /// NotImplementedException as dividers don't have values. + /// Always thrown since dividers have no values. internal sealed override string ValueAsString() { throw new NotImplementedException(); } -} +} \ No newline at end of file diff --git a/src/Modules/OptionItems/NoneOption/OptionHeaderItem.cs b/src/Modules/OptionItems/NoneOption/OptionHeaderItem.cs index eecf561..17cc438 100644 --- a/src/Modules/OptionItems/NoneOption/OptionHeaderItem.cs +++ b/src/Modules/OptionItems/NoneOption/OptionHeaderItem.cs @@ -2,13 +2,36 @@ namespace BetterAmongUs.Modules.OptionItems.NoneOption; +/// +/// Represents a header item used to group options with a title in the UI. +/// internal sealed class OptionHeaderItem : OptionItem { private CategoryHeaderMasked? categoryHeaderMasked; + + /// + /// Gets whether this option item loads values from persistent storage. + /// internal override bool CanLoad => false; + + /// + /// Gets whether this item represents a configurable option (false for headers). + /// internal override bool IsOption => false; + + /// + /// Gets or sets the spacing distances above and below the header. + /// internal (float top, float bottom) Distance { get; set; } + /// + /// Creates a new header option item. + /// + /// The tab this header belongs to. + /// The translation key for the header title. + /// Spacing distance above the header. + /// Spacing distance below the header. + /// A new OptionHeaderItem instance. internal static OptionHeaderItem Create(OptionTab tab, string tranStr, float topDistance = 0.35f, float bottomDistance = 0.80f) { var Item = new OptionHeaderItem @@ -22,6 +45,9 @@ internal sealed class OptionHeaderItem : OptionItem return Item; } + /// + /// Creates the UI behavior for this header item. + /// private void CreateBehavior() { if (!GameSettingMenu.Instance) return; @@ -44,6 +70,10 @@ internal sealed class OptionHeaderItem : OptionItem Tab.Children.Add(this); } + /// + /// Updates the visual appearance of the header. + /// + /// Whether to update the parent tab visuals as well. internal sealed override void UpdateVisuals(bool updateTabVisuals = true) { if (categoryHeaderMasked != null) @@ -52,8 +82,13 @@ internal sealed class OptionHeaderItem : OptionItem } } + /// + /// Gets the string representation of the header value (not applicable). + /// + /// NotImplementedException as headers don't have values. + /// Always thrown since headers have no values. internal sealed override string ValueAsString() { throw new NotImplementedException(); } -} +} \ No newline at end of file diff --git a/src/Modules/OptionItems/NoneOption/OptionTitleItem.cs b/src/Modules/OptionItems/NoneOption/OptionTitleItem.cs index 5e6c9e1..83f136f 100644 --- a/src/Modules/OptionItems/NoneOption/OptionTitleItem.cs +++ b/src/Modules/OptionItems/NoneOption/OptionTitleItem.cs @@ -3,12 +3,33 @@ using UnityEngine; namespace BetterAmongUs.Modules.OptionItems.NoneOption; +/// +/// Represents a title item used to group options with a title in the UI. +/// internal sealed class OptionTitleItem : OptionItem { private ToggleOption? optionBehaviour; + + /// + /// Gets a value indicating whether this option item can be loaded. + /// + /// Always returns false for title items since they are non-interactive. internal override bool CanLoad => false; + + /// + /// Gets or sets the distance values for spacing around the title. + /// + /// A tuple containing top and bottom distance values. internal (float top, float bottom) Distance { get; set; } + /// + /// Creates a new title option item. + /// + /// The tab where this title item will be placed. + /// The translation string for the title text. + /// The top spacing distance (default: 0.15f). + /// The bottom spacing distance (default: 0.50f). + /// A new instance. internal static OptionTitleItem Create(OptionTab tab, string tranStr, float topDistance = 0.15f, float bottomDistance = 0.50f) { var Item = new OptionTitleItem @@ -22,6 +43,12 @@ internal sealed class OptionTitleItem : OptionItem return Item; } + /// + /// Creates the visual behavior for the title item. + /// + /// + /// This method sets up the UI elements, positioning, and styling for the title. + /// private void CreateBehavior() { if (!GameSettingMenu.Instance) return; @@ -52,6 +79,10 @@ internal sealed class OptionTitleItem : OptionItem Tab.Children.Add(this); } + /// + /// Updates the visual representation of the title item. + /// + /// Whether to update tab visuals (unused for title items). internal sealed override void UpdateVisuals(bool updateTabVisuals = true) { if (optionBehaviour != null) @@ -60,8 +91,13 @@ internal sealed class OptionTitleItem : OptionItem } } + /// + /// Gets the string representation of the title value (not applicable). + /// + /// NotImplementedException as headers don't have values. + /// Always thrown since headers have no values. internal sealed override string ValueAsString() { throw new NotImplementedException(); } -} +} \ No newline at end of file diff --git a/src/Modules/OptionItems/OptionCheckboxItem.cs b/src/Modules/OptionItems/OptionCheckboxItem.cs index e72fde7..7ddf681 100644 --- a/src/Modules/OptionItems/OptionCheckboxItem.cs +++ b/src/Modules/OptionItems/OptionCheckboxItem.cs @@ -3,10 +3,25 @@ using UnityEngine; namespace BetterAmongUs.Modules.OptionItems; +/// +/// Represents a checkbox option item that can be toggled on or off. +/// internal sealed class OptionCheckboxItem : OptionItem { + /// + /// Gets whether child options should be shown, based on the checkbox value. + /// internal sealed override bool ShowChildren => base.ShowChildren && Value; + /// + /// Creates a new checkbox option item or returns an existing one with the same ID. + /// + /// The unique identifier for this option. + /// The tab this option belongs to. + /// The translation key for the option name. + /// The default value for the checkbox. + /// Optional parent option for hierarchical organization. + /// A new or existing OptionCheckboxItem instance. internal static OptionCheckboxItem Create(int id, OptionTab tab, string tranStr, bool defaultValue, OptionItem? parent = null) { if (GetOptionById(id) is OptionCheckboxItem checkboxItem) @@ -32,6 +47,9 @@ internal sealed class OptionCheckboxItem : OptionItem return Item; } + /// + /// Creates the UI behavior for this checkbox option. + /// protected sealed override void CreateBehavior() { TryLoad(); @@ -48,6 +66,9 @@ internal sealed class OptionCheckboxItem : OptionItem SetOptionVisuals(); } + /// + /// Sets up the specific behavior for the ToggleOption component. + /// protected sealed override void SetupOptionBehavior() { if (Option is ToggleOption toggleOption) @@ -61,6 +82,10 @@ internal sealed class OptionCheckboxItem : OptionItem } } + /// + /// Updates the visual appearance of the checkbox based on its current value. + /// + /// Whether to update the parent tab visuals as well. internal sealed override void UpdateVisuals(bool updateTabVisuals = true) { if (Option is ToggleOption toggleOption) @@ -74,6 +99,10 @@ internal sealed class OptionCheckboxItem : OptionItem } } + /// + /// Gets the string representation of the checkbox value with color formatting. + /// + /// A colored string indicating "On" (green) or "Off" (red). internal sealed override string ValueAsString() { Color color = Value ? Color.green : Color.red; @@ -81,6 +110,16 @@ internal sealed class OptionCheckboxItem : OptionItem return $"{@bool}"; } + /// + /// Gets the boolean value of this checkbox option. + /// + /// The current boolean value. internal sealed override bool GetBool() => GetValue(); + + /// + /// Checks if the checkbox value matches a specific boolean. + /// + /// The boolean value to compare against. + /// True if the checkbox value matches, false otherwise. internal sealed override bool Is(bool @bool) => @bool == GetBool(); -} +} \ No newline at end of file diff --git a/src/Modules/OptionItems/OptionFloatItem.cs b/src/Modules/OptionItems/OptionFloatItem.cs index 2fb3a9a..cdc001e 100644 --- a/src/Modules/OptionItems/OptionFloatItem.cs +++ b/src/Modules/OptionItems/OptionFloatItem.cs @@ -3,14 +3,48 @@ using UnityEngine; namespace BetterAmongUs.Modules.OptionItems; +/// +/// Represents a floating-point number option item with adjustable value range. +/// internal class OptionFloatItem : OptionItem { + /// + /// Gets whether child options should be shown, based on whether the value is greater than zero. + /// internal override bool ShowChildren => base.ShowChildren && Value > 0f; + + /// + /// Gets or sets the valid range for this float option. + /// protected FloatRange? Range { get; set; } + + /// + /// Gets or sets the increment/decrement step size for this option. + /// protected float Increment { get; set; } + + /// + /// Gets or sets whether this option can represent infinite values (value ≤ 0). + /// protected bool CanBeInfinite { get; set; } + + /// + /// Gets or sets the prefix and postfix strings for value display. + /// protected (string prefix, string postfix) Fixs { get; set; } + /// + /// Creates a new float option item or returns an existing one with the same ID. + /// + /// The unique identifier for this option. + /// The tab this option belongs to. + /// The translation key for the option name. + /// Tuple containing min value, max value, and increment step. + /// The default value for the option. + /// Tuple containing prefix and postfix strings for display. + /// Optional parent option for hierarchical organization. + /// Whether this option can represent infinite values. + /// A new or existing OptionFloatItem instance. internal static OptionFloatItem Create(int id, OptionTab tab, string tranStr, (float minValue, float maxValue, float incrementValue) Min_Max_Increment, float defaultValue, (string prefix, string postfix) Prefix_Postfix = new(), OptionItem? parent = null, bool canBeInfinite = false) { if (GetOptionById(id) is OptionFloatItem floatItem) @@ -40,6 +74,9 @@ internal class OptionFloatItem : OptionItem return Item; } + /// + /// Creates the UI behavior for this float option. + /// protected override void CreateBehavior() { TryLoad(); @@ -57,6 +94,9 @@ internal class OptionFloatItem : OptionItem SetOptionVisuals(); } + /// + /// Sets up the specific behavior for the NumberOption component. + /// protected override void SetupOptionBehavior() { if (Option is NumberOption numberOption) @@ -71,6 +111,9 @@ internal class OptionFloatItem : OptionItem } } + /// + /// Increases the value based on modifier keys for larger increments. + /// protected void Increase() { int plus = 1; @@ -85,6 +128,9 @@ internal class OptionFloatItem : OptionItem SetValue(value); } + /// + /// Decreases the value based on modifier keys for larger decrements. + /// protected void Decrease() { int plus = 1; @@ -99,12 +145,20 @@ internal class OptionFloatItem : OptionItem SetValue(value); } + /// + /// Sets the value, clamping it to the valid range. + /// + /// The new value to set. internal override void SetValue(float newValue) { newValue = Math.Clamp(newValue, Range.min, Range.max); base.SetValue(newValue); } + /// + /// Updates the visual appearance of the float option based on its current value. + /// + /// Whether to update the parent tab visuals as well. internal override void UpdateVisuals(bool updateTabVisuals = true) { if (Option is NumberOption numberOption) @@ -130,7 +184,10 @@ internal class OptionFloatItem : OptionItem } } - + /// + /// Gets the string representation of the float value with formatting. + /// + /// A formatted string showing the value with prefix/postfix or infinity symbol. internal override string ValueAsString() { if (CanBeInfinite) @@ -144,7 +201,23 @@ internal class OptionFloatItem : OptionItem return $"{Fixs.prefix}{Value}{Fixs.postfix}"; } + /// + /// Gets the float value of this option. + /// + /// The current float value. internal override float GetFloat() => GetValue(); + + /// + /// Checks if the option value matches a specific float. + /// + /// The float value to compare against. + /// True if the option value matches, false otherwise. internal override bool Is(float @float) => @float == GetFloat(); + + /// + /// Checks if the option value matches a specific integer. + /// + /// The integer value to compare against. + /// True if the option value matches (as float), false otherwise. internal override bool Is(int @int) => @int == GetFloat(); -} +} \ No newline at end of file diff --git a/src/Modules/OptionItems/OptionIntItem.cs b/src/Modules/OptionItems/OptionIntItem.cs index e439ac7..1fd5d6e 100644 --- a/src/Modules/OptionItems/OptionIntItem.cs +++ b/src/Modules/OptionItems/OptionIntItem.cs @@ -3,14 +3,48 @@ using UnityEngine; namespace BetterAmongUs.Modules.OptionItems; +/// +/// Represents an integer option item with adjustable value range. +/// internal sealed class OptionIntItem : OptionItem { + /// + /// Gets whether child options should be shown, based on whether the value is greater than zero. + /// internal sealed override bool ShowChildren => base.ShowChildren && Value > 0; + + /// + /// Gets or sets the valid range for this integer option. + /// private IntRange? Range { get; set; } + + /// + /// Gets or sets the increment/decrement step size for this option. + /// private int Increment { get; set; } + + /// + /// Gets or sets whether this option can represent infinite values (value ≤ 0). + /// private bool CanBeInfinite { get; set; } + + /// + /// Gets or sets the prefix and postfix strings for value display. + /// private (string prefix, string postfix) Fixs { get; set; } + /// + /// Creates a new integer option item or returns an existing one with the same ID. + /// + /// The unique identifier for this option. + /// The tab this option belongs to. + /// The translation key for the option name. + /// Tuple containing min value, max value, and increment step. + /// The default value for the option. + /// Tuple containing prefix and postfix strings for display. + /// Optional parent option for hierarchical organization. + /// Whether this option can represent infinite values. + /// A new or existing OptionIntItem instance. internal static OptionIntItem Create(int id, OptionTab tab, string tranStr, (int minValue, int maxValue, int incrementValue) Min_Max_Increment, int defaultValue, (string prefix, string postfix) Prefix_Postfix = new(), OptionItem? parent = null, bool canBeInfinite = false) { if (GetOptionById(id) is OptionIntItem intItem) @@ -40,6 +74,9 @@ internal sealed class OptionIntItem : OptionItem return Item; } + /// + /// Creates the UI behavior for this integer option. + /// protected sealed override void CreateBehavior() { TryLoad(); @@ -57,6 +94,9 @@ internal sealed class OptionIntItem : OptionItem SetOptionVisuals(); } + /// + /// Sets up the specific behavior for the NumberOption component. + /// protected sealed override void SetupOptionBehavior() { if (Option is NumberOption numberOption) @@ -71,6 +111,9 @@ internal sealed class OptionIntItem : OptionItem } } + /// + /// Increases the value based on modifier keys for larger increments. + /// private void Increase() { int plus = 1; @@ -85,6 +128,9 @@ internal sealed class OptionIntItem : OptionItem SetValue(value); } + /// + /// Decreases the value based on modifier keys for larger decrements. + /// private void Decrease() { int plus = 1; @@ -99,12 +145,20 @@ internal sealed class OptionIntItem : OptionItem SetValue(value); } + /// + /// Sets the value, clamping it to the valid range. + /// + /// The new value to set. internal sealed override void SetValue(int newValue) { newValue = Math.Clamp(newValue, Range.min, Range.max); base.SetValue(newValue); } + /// + /// Updates the visual appearance of the integer option based on its current value. + /// + /// Whether to update the parent tab visuals as well. internal sealed override void UpdateVisuals(bool updateTabVisuals = true) { if (Option is NumberOption numberOption) @@ -130,6 +184,10 @@ internal sealed class OptionIntItem : OptionItem } } + /// + /// Gets the string representation of the integer value with formatting. + /// + /// A formatted string showing the value with prefix/postfix or infinity symbol. internal sealed override string ValueAsString() { if (CanBeInfinite) @@ -143,8 +201,29 @@ internal sealed class OptionIntItem : OptionItem return $"{Fixs.prefix}{Value}{Fixs.postfix}"; } + /// + /// Gets the integer value of this option. + /// + /// The current integer value. internal sealed override int GetInt() => GetValue(); + + /// + /// Gets the float representation of the integer value. + /// + /// The current integer value as a float. internal sealed override float GetFloat() => GetValue(); + + /// + /// Checks if the option value matches a specific integer. + /// + /// The integer value to compare against. + /// True if the option value matches, false otherwise. internal sealed override bool Is(int @int) => @int == GetInt(); + + /// + /// Checks if the option value matches a specific float. + /// + /// The float value to compare against. + /// True if the option value matches (as integer), false otherwise. internal sealed override bool Is(float @float) => @float == GetFloat(); -} +} \ No newline at end of file diff --git a/src/Modules/OptionItems/OptionItem.cs b/src/Modules/OptionItems/OptionItem.cs index b96fdf4..3123071 100644 --- a/src/Modules/OptionItems/OptionItem.cs +++ b/src/Modules/OptionItems/OptionItem.cs @@ -7,7 +7,7 @@ using UnityEngine; namespace BetterAmongUs.Modules.OptionItems; /// -/// Base Option class. +/// Base abstract class for configuration option items in BetterAmongUs. /// internal abstract class OptionItem { @@ -37,6 +37,10 @@ internal abstract class OptionItem internal virtual void TryLoad(bool forceLoad = false) { } internal virtual void SetToDefault() { } + /// + /// Sets up the Among Us option behavior with proper masking for UI rendering. + /// + /// The option behavior to set up. protected void SetupAUOption(OptionBehaviour optionBehaviour) { Option?.SetClickMask(Tab.AUTab.ButtonClickMask); @@ -53,6 +57,10 @@ internal abstract class OptionItem } } + /// + /// Displays a notification with the option's current value or custom text. + /// + /// Custom text to display instead of the value. internal void PopNotification(string custom = "") { if (_id == null) return; @@ -62,6 +70,10 @@ internal abstract class OptionItem Utils.SettingsChangeNotifier(Id, msg, false); } + /// + /// Gets the hierarchical path of parent options leading to this option. + /// + /// A formatted string showing the option hierarchy. internal string GetParentPath() { List names = [Name ?? "???"]; @@ -75,8 +87,16 @@ internal abstract class OptionItem return Utils.RemoveSizeHtmlText(string.Join("/", names.AsEnumerable().Reverse())); } + /// + /// Gets the highest-level parent option in the hierarchy. + /// + /// The root parent option, or null if no parent exists. internal OptionItem? GetLastParent() => GetParents().LastOrDefault(); + /// + /// Enumerates all parent options in the hierarchy from immediate parent to root. + /// + /// An enumerable of parent options. internal IEnumerable GetParents() { if (Parent == null) yield break; @@ -90,9 +110,9 @@ internal abstract class OptionItem } /// - /// Get child index of option. + /// Gets the child index depth of this option in the hierarchy. /// - /// + /// The depth level (0 for root options). internal int GetChildIndex() { int index = 0; @@ -106,10 +126,11 @@ internal abstract class OptionItem } /// - /// Generates a automatic tree text display for option. + /// Generates a text tree representation of the option hierarchy. /// - /// Size of text - /// string + /// Text size percentage. + /// Whether to show child options for percent items. + /// A formatted string showing the option tree structure. internal string FormatOptionsToTextTree(float size = 50f, bool showForPercentOption = true) { StringBuilder sb = new(); @@ -176,6 +197,13 @@ internal abstract class OptionItem return sb.ToString(); } + /// + /// Generates text tree representations for multiple option hierarchies. + /// + /// Array of root option items. + /// Text size percentage. + /// Whether to show child options for percent items. + /// A formatted string showing all option tree structures. internal static string FormatOptionsToTextTrees(OptionItem?[] optionItems, float size = 50f, bool showForPercentOption = true) { StringBuilder sb = new(); @@ -251,6 +279,10 @@ internal abstract class OptionItem return sb.ToString(); } + /// + /// Creates a description button that shows additional information when clicked. + /// + /// The description text to display. internal void CreateDescriptionButton(string text) { if (Option == null) return; @@ -277,98 +309,120 @@ internal abstract class OptionItem } /// - /// Get bool for CheckboxOption, could throw exception if option is not a CheckboxOption. + /// Gets the boolean value of the option (for CheckboxOption). /// - /// bool - /// + /// The boolean value. + /// Thrown when the option type doesn't support boolean values. internal virtual bool GetBool() { throw new NotImplementedException(); } /// - /// Get float for FloatOption, could throw exception if option is not a FloatOption. + /// Gets the float value of the option (for FloatOption). /// - /// float - /// + /// The float value. + /// Thrown when the option type doesn't support float values. internal virtual float GetFloat() { throw new NotImplementedException(); } /// - /// Get float for IntOption, could throw exception if option is not a IntOption. + /// Gets the integer value of the option (for IntOption). /// - /// int - /// + /// The integer value. + /// Thrown when the option type doesn't support integer values. internal virtual int GetInt() { throw new NotImplementedException(); } /// - /// Get int for StringOption and PlayerOption, could throw exception if option is not a StringOption. + /// Gets the string value index of the option (for StringOption and PlayerOption). /// - /// int - /// + /// The string value index. + /// Thrown when the option type doesn't support string values. internal virtual int GetStringValue() { throw new NotImplementedException(); } /// - /// Checks if the value is a boolean. + /// Checks if the option's value matches a specific boolean. /// - /// The boolean value to check. - /// True if the value matches, otherwise false. - /// Thrown if the method is not implemented. + /// The boolean value to check against. + /// True if the option value matches, false otherwise. + /// Thrown when the option type doesn't support boolean comparison. internal virtual bool Is(bool @bool) { throw new NotImplementedException(); } /// - /// Checks if the value is a float. + /// Checks if the option's value matches a specific float. /// - /// The float value to check. - /// True if the value matches, otherwise false. - /// Thrown if the method is not implemented. + /// The float value to check against. + /// True if the option value matches, false otherwise. + /// Thrown when the option type doesn't support float comparison. internal virtual bool Is(float @float) { throw new NotImplementedException(); } /// - /// Checks if the value is an integer. + /// Checks if the option's value matches a specific integer. /// - /// The integer value to check. - /// True if the value matches, otherwise false. - /// Thrown if the method is not implemented. + /// The integer value to check against. + /// True if the option value matches, false otherwise. + /// Thrown when the option type doesn't support integer comparison. internal virtual bool Is(int @int) { throw new NotImplementedException(); } /// - /// Checks if the value is a string. + /// Checks if the option's value matches a specific string. /// - /// The string value to check. - /// True if the value matches, otherwise false. - /// Thrown if the method is not implemented. + /// The string value to check against. + /// True if the option value matches, false otherwise. + /// Thrown when the option type doesn't support string comparison. internal virtual bool Is(string @string) { throw new NotImplementedException(); } + /// + /// Represents a node in the option hierarchy tree for text formatting. + /// class TreeNode { + /// + /// Gets or sets the parent tree node. + /// internal TreeNode? ParentNode { get; set; } + + /// + /// Gets or sets the display text for this node. + /// internal string? Text { get; set; } + + /// + /// Gets or sets the depth level in the tree. + /// internal int Depth { get; set; } + + /// + /// Gets or sets whether this is the last child of its parent. + /// internal bool IsLastChild { get; set; } } } +/// +/// Generic base class for typed option items with value storage and serialization. +/// +/// The type of value stored by this option. internal abstract class OptionItem : OptionItem { protected TextMeshPro? TitleTMP { get; set; } @@ -387,6 +441,10 @@ internal abstract class OptionItem : OptionItem internal virtual void OnValueChange(T oldValue, T newValue) { } internal Action? OnValueChangeAction = (opt) => { }; + /// + /// Sets up common text properties for option display. + /// + /// The TextMeshPro component to configure. protected void SetupText(TextMeshPro textPro) { textPro.transform.SetLocalX(-2.5f); @@ -402,8 +460,14 @@ internal abstract class OptionItem : OptionItem textPro.outlineWidth = 0.25f; } + /// + /// Sets up the option behavior after creation. + /// protected virtual void SetupOptionBehavior() { } + /// + /// Configures the visual appearance of the option based on its hierarchy depth. + /// protected void SetOptionVisuals() { if (Option != null) @@ -431,6 +495,10 @@ internal abstract class OptionItem : OptionItem UpdateVisuals(); } + /// + /// Sets the option's value and triggers updates and notifications. + /// + /// The new value to set. internal virtual void SetValue(T newValue) { T? oldValue = Value; @@ -442,6 +510,10 @@ internal abstract class OptionItem : OptionItem OnValueChangeAction.Invoke(this); } + /// + /// Attempts to load the option's value from persistent storage. + /// + /// Whether to force reload even if already loaded. internal override void TryLoad(bool forceLoad = false) { if (!CanLoad) return; @@ -453,6 +525,9 @@ internal abstract class OptionItem : OptionItem } } + /// + /// Loads the option's value from persistent storage. + /// protected virtual void Load() { if (!CanLoad) return; @@ -461,6 +536,9 @@ internal abstract class OptionItem : OptionItem Value = BetterDataManager.LoadSetting(Id, DefaultValue); } + /// + /// Saves the option's value to persistent storage. + /// internal virtual void Save() { if (!CanLoad) return; diff --git a/src/Modules/OptionItems/OptionPercentItem.cs b/src/Modules/OptionItems/OptionPercentItem.cs index 3c766d3..f99b8ad 100644 --- a/src/Modules/OptionItems/OptionPercentItem.cs +++ b/src/Modules/OptionItems/OptionPercentItem.cs @@ -1,7 +1,19 @@ namespace BetterAmongUs.Modules.OptionItems; +/// +/// Represents a percentage option item that displays values from 0% to 100% with color coding. +/// internal sealed class OptionPercentItem : OptionFloatItem { + /// + /// Creates a new percentage option item or returns an existing one with the same ID. + /// + /// The unique identifier for this option. + /// The tab this option belongs to. + /// The translation key for the option name. + /// The default percentage value (0-100). + /// Optional parent option for hierarchical organization. + /// A new or existing OptionPercentItem instance. internal static OptionPercentItem Create(int id, OptionTab tab, string tranStr, float defaultValue, OptionItem? parent = null) { if (GetOptionById(id) is OptionPercentItem floatItem) @@ -30,22 +42,31 @@ internal sealed class OptionPercentItem : OptionFloatItem return Item; } + /// + /// Gets the string representation of the percentage value with color coding. + /// + /// A colored string showing the percentage (e.g., "75%"). internal sealed override string ValueAsString() => $"{Value}%"; + /// + /// Gets the color code for a percentage value based on its magnitude. + /// + /// The percentage value (0-100). + /// A hex color code string representing the value range. internal string GetColor(float num) { switch (num) { case float n when n <= 0f: - return "#ff0600"; + return "#ff0600"; // Red case float n when n <= 25f: - return "#ff9d00"; + return "#ff9d00"; // Orange case float n when n <= 50f: - return "#fff900"; + return "#fff900"; // Yellow case float n when n <= 75f: - return "#80ff00"; + return "#80ff00"; // Light Green default: - return "#80ff00"; + return "#80ff00"; // Green (76-100%) } } -} +} \ No newline at end of file diff --git a/src/Modules/OptionItems/OptionPlayerItem.cs b/src/Modules/OptionItems/OptionPlayerItem.cs index 0d3b724..f0ffe12 100644 --- a/src/Modules/OptionItems/OptionPlayerItem.cs +++ b/src/Modules/OptionItems/OptionPlayerItem.cs @@ -3,16 +3,47 @@ using UnityEngine; namespace BetterAmongUs.Modules.OptionItems; +/// +/// Represents an option item that selects a player from the current game. +/// internal sealed class OptionPlayerItem : OptionItem { + /// + /// Gets whether child options should be shown, based on whether a valid player is selected. + /// internal sealed override bool ShowChildren => base.ShowChildren && Value > Min; + + /// + /// Gets the maximum player index based on the number of players in the game. + /// private int Max => BAUPlugin.AllPlayerControls.Count - 1; + + /// + /// Gets the minimum player index (-1 for random selection, 0 for first player). + /// private int Min => CanBeRandom ? -1 : 0; + + /// + /// Gets whether this option allows random player selection. + /// private bool CanBeRandom { get; set; } + + /// + /// Gets whether this option can load values from persistent storage. + /// internal override bool CanLoad => false; private static List optionPlayerItems = []; + /// + /// Creates a new player option item or returns an existing one with the same ID. + /// + /// The unique identifier for this option. + /// The tab this option belongs to. + /// The translation key for the option name. + /// Optional parent option for hierarchical organization. + /// Whether this option allows random player selection. + /// A new or existing OptionPlayerItem instance. internal static OptionPlayerItem Create(int id, OptionTab tab, string tranStr, OptionItem? parent = null, bool canBeRandom = true) { if (optionPlayerItems.FirstOrDefault(opt => opt.Id == id) is OptionPlayerItem playerItem) @@ -39,10 +70,16 @@ internal sealed class OptionPlayerItem : OptionItem return Item; } + /// + /// Player options are not saved to persistent storage. + /// internal sealed override void Save() { } + /// + /// Creates the UI behavior for this player option. + /// protected sealed override void CreateBehavior() { if (!GameSettingMenu.Instance) return; @@ -59,6 +96,9 @@ internal sealed class OptionPlayerItem : OptionItem SetOptionVisuals(); } + /// + /// Sets up the specific behavior for the NumberOption component. + /// protected sealed override void SetupOptionBehavior() { if (Option is NumberOption numberOption) @@ -73,6 +113,9 @@ internal sealed class OptionPlayerItem : OptionItem } } + /// + /// Increases the player selection index based on modifier keys. + /// private void Increase() { int plus = 1; @@ -87,6 +130,9 @@ internal sealed class OptionPlayerItem : OptionItem SetValue(value); } + /// + /// Decreases the player selection index based on modifier keys. + /// private void Decrease() { int plus = 1; @@ -101,12 +147,19 @@ internal sealed class OptionPlayerItem : OptionItem SetValue(value); } + /// + /// Sets the player selection value, clamping it to the valid range. + /// + /// The new player index to set. internal sealed override void SetValue(int newValue) { newValue = Math.Clamp(newValue, Min, Max); base.SetValue(newValue); } + /// + /// Resets all player option items to their minimum values. + /// internal static void ResetAllValues() { foreach (var opt in optionPlayerItems) @@ -115,6 +168,9 @@ internal sealed class OptionPlayerItem : OptionItem } } + /// + /// Updates all player option items to ensure their values are within valid ranges. + /// internal static void UpdateAllValues() { foreach (var opt in optionPlayerItems) @@ -123,17 +179,27 @@ internal sealed class OptionPlayerItem : OptionItem } } + /// + /// Updates this player option's value to ensure it's within the valid range. + /// internal void UpdateValue() { Value = Math.Clamp(Value, Min, Max); UpdateVisuals(); } + /// + /// Resets this player option to its minimum value. + /// internal void ResetValue() { Value = Min; } + /// + /// Updates the visual appearance of the player option based on its current value. + /// + /// Whether to update the parent tab visuals as well. internal sealed override void UpdateVisuals(bool updateTabVisuals = true) { if (!GameSettingMenu.Instance) return; @@ -161,6 +227,10 @@ internal sealed class OptionPlayerItem : OptionItem } } + /// + /// Gets the string representation of the selected player. + /// + /// The player's colored name or "Random" for random selection. internal sealed override string ValueAsString() { if (Value != -1) @@ -177,8 +247,29 @@ internal sealed class OptionPlayerItem : OptionItem } } + /// + /// Gets the integer value (player index) of this option. + /// + /// The current player index. internal sealed override int GetInt() => GetValue(); + + /// + /// Gets the float representation of the player index. + /// + /// The current player index as a float. internal sealed override float GetFloat() => GetValue(); + + /// + /// Checks if the option value matches a specific integer. + /// + /// The integer value to compare against. + /// True if the option value matches, false otherwise. internal sealed override bool Is(int @int) => @int == GetInt(); + + /// + /// Checks if the option value matches a specific float. + /// + /// The float value to compare against. + /// True if the option value matches (as integer), false otherwise. internal sealed override bool Is(float @float) => @float == GetFloat(); -} +} \ No newline at end of file diff --git a/src/Modules/OptionItems/OptionStringItem.cs b/src/Modules/OptionItems/OptionStringItem.cs index 3e094d3..e1c7e0a 100644 --- a/src/Modules/OptionItems/OptionStringItem.cs +++ b/src/Modules/OptionItems/OptionStringItem.cs @@ -2,12 +2,38 @@ namespace BetterAmongUs.Modules.OptionItems; +/// +/// Represents an option item that selects from a list of string values. +/// internal sealed class OptionStringItem : OptionItem { + /// + /// Gets or sets the valid range of indices for this string option. + /// private IntRange Range { get; set; } = new(); + + /// + /// Gets or sets the array of translation keys for the string options. + /// private string[] TranslatorStrings { get; set; } = []; + + /// + /// Gets or sets whether this option includes a random selection. + /// private bool CanBeRandom { get; set; } + /// + /// Creates a new string option item or returns an existing one with the same ID. + /// + /// The unique identifier for this option. + /// The tab this option belongs to. + /// The translation key for the option name. + /// Array of translation keys for the selectable string values. + /// The default index value. + /// Optional parent option for hierarchical organization. + /// Whether this option includes a random selection. + /// A new or existing OptionStringItem instance. + /// Thrown when tranStrings has less than 2 elements. internal static OptionStringItem Create(int id, OptionTab tab, string tranStr, string[] tranStrings, int defaultValue, OptionItem? parent = null, bool canBeRandom = false) { if (tranStrings.Length < 2) @@ -53,6 +79,9 @@ internal sealed class OptionStringItem : OptionItem return Item; } + /// + /// Creates the UI behavior for this string option. + /// protected sealed override void CreateBehavior() { TryLoad(); @@ -70,6 +99,9 @@ internal sealed class OptionStringItem : OptionItem SetOptionVisuals(); } + /// + /// Sets up the specific behavior for the NumberOption component. + /// protected sealed override void SetupOptionBehavior() { if (Option is NumberOption numberOption) @@ -84,22 +116,36 @@ internal sealed class OptionStringItem : OptionItem } } + /// + /// Increases the string selection index. + /// private void Increase() { SetValue(Value + 1); } + /// + /// Decreases the string selection index. + /// private void Decrease() { SetValue(Value - 1); } + /// + /// Sets the string selection value, clamping it to the valid range. + /// + /// The new string index to set. internal sealed override void SetValue(int newValue) { newValue = Math.Clamp(newValue, Range.min, Range.max); base.SetValue(newValue); } + /// + /// Updates the visual appearance of the string option based on its current value. + /// + /// Whether to update the parent tab visuals as well. internal sealed override void UpdateVisuals(bool updateTabVisuals = true) { if (Option is NumberOption numberOption) @@ -125,7 +171,16 @@ internal sealed class OptionStringItem : OptionItem } } + /// + /// Gets the translated string representation of the current selection. + /// + /// The translated string for the current index. internal sealed override string ValueAsString() => Translator.GetString(TranslatorStrings[Value], showInvalid: false); + + /// + /// Gets the effective string value index, accounting for random selection. + /// + /// The actual string index (random if selected). internal sealed override int GetStringValue() { var value = GetValue(); @@ -146,6 +201,17 @@ internal sealed class OptionStringItem : OptionItem } } + /// + /// Checks if the option's string value matches a specific string. + /// + /// The string value to compare against. + /// True if the option value matches, false otherwise. internal sealed override bool Is(string @string) => TranslatorStrings[Value] == @string || ValueAsString() == @string; + + /// + /// Checks if the option's index value matches a specific integer. + /// + /// The integer value to compare against. + /// True if the option value matches, false otherwise. internal sealed override bool Is(int @int) => !CanBeRandom ? Value == @int : Value == @int - 1; -} +} \ No newline at end of file diff --git a/src/Modules/OptionItems/OptionTab.cs b/src/Modules/OptionItems/OptionTab.cs index 7d15dfb..6b5da40 100644 --- a/src/Modules/OptionItems/OptionTab.cs +++ b/src/Modules/OptionItems/OptionTab.cs @@ -5,20 +5,64 @@ using UnityEngine; namespace BetterAmongUs.Modules.OptionItems; +/// +/// Represents a tab in the options menu that groups related option items. +/// internal sealed class OptionTab { internal static List AllTabs = []; internal readonly List Children = []; + + /// + /// Gets the unique identifier for this tab. + /// internal int Id { get; private set; } + + /// + /// Gets the translated name of this tab. + /// internal string? Name => Translator.GetString(TranName); + + /// + /// Gets or sets the translation key for the tab name. + /// internal string? TranName { get; private set; } + + /// + /// Gets the translated description of this tab. + /// internal string? Description => Translator.GetString(TranDescription); + + /// + /// Gets or sets the translation key for the tab description. + /// internal string? TranDescription { get; private set; } + + /// + /// Gets or sets the Among Us options menu tab instance. + /// internal GameOptionsMenu? AUTab { get; private set; } + + /// + /// Gets or sets the button that activates this tab. + /// internal PassiveButton? TabButton { get; private set; } + + /// + /// Gets or sets the color theme for this tab. + /// internal Color Color { get; private set; } + /// + /// Creates a new option tab or returns an existing one with the same ID. + /// + /// The unique identifier for the tab. + /// Translation key for the tab name. + /// Translation key for the tab description. + /// The color theme for the tab. + /// Whether to preserve the map picker UI. + /// A new or existing OptionTab instance. internal static OptionTab Create(int Id, string tranName, string tranDescription, Color Color, bool doNotDestroyMapPicker = false) { if (GetTabById(Id) is OptionTab optionTab) @@ -41,8 +85,17 @@ internal sealed class OptionTab return Item; } + /// + /// Gets an option tab by its ID. + /// + /// The ID of the tab to find. + /// The OptionTab with the matching ID, or null if not found. internal static OptionTab? GetTabById(int id) => AllTabs.FirstOrDefault(tab => tab.Id == id); + /// + /// Creates the UI behavior for this option tab. + /// + /// Whether to preserve the map picker UI. private void CreateBehavior(bool doNotDestroyMapPicker) { if (!GameSettingMenu.Instance) return; @@ -81,11 +134,17 @@ internal sealed class OptionTab AUTab.gameObject.SetActive(false); } + /// + /// Updates the visual layout of all options in this tab. + /// internal void UpdateVisuals() { ShowOptions(); } + /// + /// Shows and positions all option items in this tab. + /// private void ShowOptions() { if (AUTab == null) return; @@ -135,8 +194,13 @@ internal sealed class OptionTab AUTab?.scrollBar?.ScrollRelative(new(0f, 0f)); } + /// + /// Finds options by name (not implemented). + /// + /// The name to search for. + /// Always thrown as this method is not implemented. internal static void FindOptions(string name) { throw new NotImplementedException(); } -} +} \ No newline at end of file diff --git a/src/Modules/TextFileHandler.cs b/src/Modules/TextFileHandler.cs index 3c4008b..232116e 100644 --- a/src/Modules/TextFileHandler.cs +++ b/src/Modules/TextFileHandler.cs @@ -3,8 +3,17 @@ using System.Text.RegularExpressions; namespace BetterAmongUs.Modules; +/// +/// Provides utilities for handling text files, including filtering, parsing, and formatting. +/// internal static class TextFileHandler { + /// + /// Compares strings against filter patterns in a file using wildcard matching. + /// + /// Path to the filter file. + /// Array of strings to check against filters. + /// True if any string matches a filter pattern, false otherwise. internal static bool CompareStringFilters(string filePath, string[] strings) { foreach (var content in ReadContents(filePath)) @@ -21,6 +30,12 @@ internal static class TextFileHandler return false; } + /// + /// Compares strings for exact matches with content in a file. + /// + /// Path to the file containing match strings. + /// Array of strings to check for exact matches. + /// True if any string exactly matches content in the file, false otherwise. internal static bool CompareStringMatch(string filePath, string[] strings) { var stringSet = new HashSet( @@ -41,6 +56,11 @@ internal static class TextFileHandler return false; } + /// + /// Reads and parses content from a file, ignoring comments and empty lines. + /// + /// Path to the file to read. + /// Enumerable collection of trimmed, non-empty content strings. private static IEnumerable ReadContents(string filePath) { if (File.Exists(filePath)) @@ -54,6 +74,12 @@ internal static class TextFileHandler return []; } + /// + /// Checks if a text matches a filter pattern with wildcard support. + /// + /// The filter pattern (supports ** wildcards). + /// The text to check. + /// True if the text matches the filter pattern, false otherwise. private static bool CheckFilterString(string filter, string text) { string pattern = filter switch @@ -67,6 +93,11 @@ internal static class TextFileHandler return Regex.IsMatch(text, pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); } + /// + /// Parses a YAML-like string into a dictionary of key-value pairs. + /// + /// The YAML string to parse. + /// Dictionary containing parsed key-value pairs. internal static Dictionary ParseYaml(string input) { var result = new Dictionary(); @@ -125,6 +156,12 @@ internal static class TextFileHandler return result; } + /// + /// Determines if a line represents a YAML key-value pair. + /// + /// The line to check. + /// Index of the colon separator. + /// True if the line is a valid key-value pair, false otherwise. private static bool IsKeyValueLine(string line, int separatorIndex) { string keyPart = line[..separatorIndex].Trim(); @@ -136,6 +173,11 @@ internal static class TextFileHandler return true; } + /// + /// Converts markdown-like syntax to Unity rich text format. + /// + /// The text to format. + /// The text formatted with Unity rich text tags. internal static string FormatToRichText(string text) { if (string.IsNullOrEmpty(text)) @@ -153,6 +195,9 @@ internal static class TextFileHandler return text; } + /// + /// Processes markdown headers (# through ######) to Unity size tags. + /// private static string ProcessHeaders(string text) { var headerSizes = new Dictionary @@ -170,16 +215,25 @@ internal static class TextFileHandler return text; } + /// + /// Processes block quotes (>) to styled text. + /// private static string ProcessBlockQuotes(string text) { return Regex.Replace(text, @"^>\s+(.+?)(?=\n|$)", "│ $1", RegexOptions.Multiline); } + /// + /// Processes horizontal rules (---, ***, ___) to Unicode line characters. + /// private static string ProcessHorizontalRules(string text) { return Regex.Replace(text, @"^\s*([-*_]){3,}\s*$", "───────────────────────────", RegexOptions.Multiline); } + /// + /// Processes bold (**), italic (*), and bold-italic (***) markdown. + /// private static string ProcessBoldAndItalic(string text) { text = Regex.Replace(text, @"(\*\*|__)(?![*\s])(.*?)(?$2"); @@ -189,24 +243,36 @@ internal static class TextFileHandler return text; } + /// + /// Processes strikethrough (~~) markdown. + /// private static string ProcessStrikethrough(string text) { return Regex.Replace(text, @"~~(.+?)~~", "$1"); } + /// + /// Processes markdown links ([text](url)) to Unity link tags. + /// private static string ProcessLinks(string text) { return Regex.Replace(text, @"\[([^\]]+)\]\(([^)]+)\)", " $1 "); } + /// + /// Processes inline code (`code`) to styled text. + /// private static string ProcessInlineCode(string text) { return Regex.Replace(text, @"`([^`]+)`", "$1"); } + /// + /// Normalizes line breaks for better text flow. + /// private static string ProcessLineBreaks(string text) { return Regex.Replace(text, @"\n\s*\n", "\n\n"); } -} +} \ No newline at end of file diff --git a/src/Modules/Translator.cs b/src/Modules/Translator.cs index eba9d42..8d43a6b 100644 --- a/src/Modules/Translator.cs +++ b/src/Modules/Translator.cs @@ -6,12 +6,18 @@ using System.Text.RegularExpressions; namespace BetterAmongUs.Modules; +/// +/// Provides translation services for BetterAmongUs, supporting multiple languages and fallback mechanisms. +/// internal static class Translator { internal static Dictionary TranslateIdLookup = []; internal static Dictionary> TranslateMaps = []; private const string ResourcePath = "BetterAmongUs.Resources.Lang"; + /// + /// Initializes the translator by loading all language files from embedded resources. + /// internal static void Init() { Logger_.Log("Loading language files...", "Translator"); @@ -19,6 +25,9 @@ internal static class Translator Logger_.Log("Language files loaded successfully", "Translator"); } + /// + /// Loads all language JSON files from the assembly's embedded resources. + /// private static void LoadLanguages() { try @@ -47,6 +56,11 @@ internal static class Translator } } + /// + /// Gets the names of all JSON translation files in the assembly resources. + /// + /// The assembly to search for resources. + /// Array of JSON resource file names. private static string[] GetJsonResourceNames(System.Reflection.Assembly assembly) { return assembly.GetManifestResourceNames() @@ -54,6 +68,11 @@ internal static class Translator .ToArray(); } + /// + /// Loads a single language file from embedded resources. + /// + /// The assembly containing the resource. + /// The name of the resource file. private static void LoadLanguageFile(System.Reflection.Assembly assembly, string resourceName) { try @@ -90,6 +109,12 @@ internal static class Translator } } + /// + /// Merges translations from a language file into the main translation maps. + /// + /// The main translation dictionary to merge into. + /// The language ID for the translations. + /// The translations to merge. private static void MergeTranslations( Dictionary> translationMaps, int languageId, @@ -108,6 +133,11 @@ internal static class Translator } } + /// + /// Gets the language ID by its name. + /// + /// The name of the language. + /// The language ID, or -1 if not found. internal static int GetLanguageIdByName(string name) { if (TranslateIdLookup.TryGetValue(name, out var id)) @@ -118,6 +148,15 @@ internal static class Translator return -1; } + /// + /// Gets a translated string with optional replacements. + /// + /// The translation key. + /// Dictionary of string replacements to apply. + /// Whether to force English for console output. + /// Whether to show invalid key indicators. + /// Whether to use vanilla Among Us translations. + /// The translated string. internal static string GetString(string key, Dictionary? replacements = null, bool useConsoleLanguage = false, bool showInvalid = true, bool useVanilla = false) { if (useVanilla) @@ -131,6 +170,13 @@ internal static class Translator return ApplyReplacements(result, replacements); } + /// + /// Gets a translated string for a specific language. + /// + /// The translation key. + /// The language to use. + /// Whether to show invalid key indicators. + /// The translated string. internal static string GetString(string key, SupportedLangs languageId, bool showInvalid = true) { var fallbackText = showInvalid ? $"" : key; @@ -154,6 +200,9 @@ internal static class Translator } } + /// + /// Gets a translation from the language map with Chinese character detection. + /// private static string GetTranslationFromMap(string key, SupportedLangs languageId, Dictionary languageMap, bool showInvalid) { if (languageMap.TryGetValue((int)languageId, out var translation) && @@ -175,6 +224,9 @@ internal static class Translator return languageId == SupportedLangs.English ? $"*{key}" : GetString(key, SupportedLangs.English, showInvalid); } + /// + /// Gets a vanilla Among Us translated string. + /// private static string GetVanillaString(string key, bool showInvalid) { if (Enum.TryParse(key, out var stringName)) @@ -185,6 +237,9 @@ internal static class Translator return showInvalid ? $" (vanillaStr)" : key; } + /// + /// Fallback method to get vanilla string names. + /// private static string GetVanillaStringFallback(string key, string fallbackText) { var matchingStringNames = EnumHelper.GetAllValues() @@ -194,9 +249,19 @@ internal static class Translator return matchingStringNames.Length > 0 ? GetString(matchingStringNames[0]) : fallbackText; } + /// + /// Gets a vanilla Among Us string by StringNames enum. + /// + /// The StringNames enum value. + /// The translated string. internal static string GetString(StringNames stringName) => TranslationController.Instance.GetString(stringName, new Il2CppReferenceArray(0)); + /// + /// Gets the target language ID based on settings and system configuration. + /// + /// Whether to force English for console. + /// The target language ID. internal static SupportedLangs GetTargetLanguageId(bool useConsoleLanguage = false) { if (useConsoleLanguage) return SupportedLangs.English; @@ -207,6 +272,10 @@ internal static class Translator SupportedLangs.English; } + /// + /// Gets the user's system language as a SupportedLangs enum. + /// + /// The system language ID. internal static SupportedLangs GetUserSystemLanguage() { try @@ -228,6 +297,9 @@ internal static class Translator } } + /// + /// Applies string replacements to a translated text. + /// private static string ApplyReplacements(string text, Dictionary replacements) { if (replacements == null) return text; @@ -239,12 +311,21 @@ internal static class Translator return text; } + /// + /// Gets an English fallback translation. + /// private static string GetEnglishFallback(string key) => GetString(key, SupportedLangs.English); + /// + /// Checks if a language ID represents a Chinese language. + /// private static bool IsChineseLanguage(SupportedLangs languageId) => languageId is SupportedLangs.SChinese or SupportedLangs.TChinese; + /// + /// Checks if a string contains Chinese characters. + /// private static bool ContainsChineseCharacters(string text) => Regex.IsMatch(text, @"[\u4e00-\u9fa5]"); } \ No newline at end of file diff --git a/src/Mono/BetterPingTracker.cs b/src/Mono/BetterPingTracker.cs index d054d4d..c8e5039 100644 --- a/src/Mono/BetterPingTracker.cs +++ b/src/Mono/BetterPingTracker.cs @@ -7,11 +7,26 @@ using static BetterAmongUs.Patches.Gameplay.LobbyPatch; namespace BetterAmongUs.Mono; +/// +/// Provides enhanced ping tracking and display functionality with additional information. +/// Extends the default Among Us ping tracker with custom features. +/// internal sealed class BetterPingTracker : MonoBehaviour { + /// + /// Gets the singleton instance of the BetterPingTracker. + /// + /// The current instance, or null if not initialized. internal static BetterPingTracker? Instance { get; private set; } + private AspectPosition? aspectPosition; private TextMeshPro? text; + + /// + /// Initializes the BetterPingTracker with the required UI components. + /// + /// The TextMeshPro component for displaying ping information. + /// The AspectPosition component for positioning the display. internal void SetUp(TextMeshPro pingText, AspectPosition pingAspectPosition) { if (Instance != null) return; @@ -26,6 +41,9 @@ internal sealed class BetterPingTracker : MonoBehaviour aspectPosition = pingAspectPosition; } + /// + /// Updates the ping tracker display every frame. + /// private void Update() { if (aspectPosition == null || text == null) return; diff --git a/src/Mono/ExtendedPlayerControl.cs b/src/Mono/ExtendedPlayerControl.cs index 59d8a2c..2eade86 100644 --- a/src/Mono/ExtendedPlayerControl.cs +++ b/src/Mono/ExtendedPlayerControl.cs @@ -7,9 +7,19 @@ using UnityEngine; namespace BetterAmongUs.Mono; +/// +/// Extends PlayerControl with additional functionality. +/// internal sealed class ExtendedPlayerControl : MonoBehaviour, IMonoExtension { + /// + /// Gets or sets the base PlayerControl instance. + /// public PlayerControl? BaseMono { get; set; } + + /// + /// Gets the PlayerControl instance. + /// internal PlayerControl? _Player => BaseMono; private void Awake() @@ -19,6 +29,10 @@ internal sealed class ExtendedPlayerControl : MonoBehaviour, IMonoExtension().Init(_Player); } + /// + /// Coroutine to add extended player data. + /// + /// IEnumerator for coroutine. [HideFromIl2Cpp] private IEnumerator CoAddBetterData() { @@ -30,6 +44,10 @@ internal sealed class ExtendedPlayerControl : MonoBehaviour, IMonoExtension + /// Attempts to create extended data for a player. + /// + /// The player data to extend. internal static void TryCreateExtendedData(NetworkedPlayerInfo data) { if (data.BetterData() == null) @@ -44,9 +62,15 @@ internal sealed class ExtendedPlayerControl : MonoBehaviour, IMonoExtension + /// Dictionary storing last name set for each player. + /// internal readonly Dictionary NameSetLastFor = []; } +/// +/// Extension methods for PlayerControl. +/// internal static class PlayerControlExtension { [HarmonyPatch(typeof(PlayerControl))] @@ -59,6 +83,10 @@ internal static class PlayerControlExtension TryCreateExtendedPlayerControl(__instance); } + /// + /// Creates extended player control if it doesn't exist. + /// + /// The PlayerControl instance. internal static void TryCreateExtendedPlayerControl(PlayerControl pc) { if (pc.BetterPlayerControl() == null) @@ -68,11 +96,21 @@ internal static class PlayerControlExtension } } + /// + /// Gets the extended player control for a PlayerControl. + /// + /// The PlayerControl instance. + /// The ExtendedPlayerControl, or null if not found. internal static ExtendedPlayerControl? BetterPlayerControl(this PlayerControl player) { return MonoExtensionManager.Get(player); } + /// + /// Gets the extended player control for a PlayerPhysics. + /// + /// The PlayerPhysics instance. + /// The ExtendedPlayerControl, or null if not found. internal static ExtendedPlayerControl? BetterPlayerControl(this PlayerPhysics playerPhysics) { return MonoExtensionManager.Get(playerPhysics.myPlayer); diff --git a/src/Mono/ExtendedPlayerInfo.cs b/src/Mono/ExtendedPlayerInfo.cs index 5375d06..7e7cf10 100644 --- a/src/Mono/ExtendedPlayerInfo.cs +++ b/src/Mono/ExtendedPlayerInfo.cs @@ -8,6 +8,9 @@ using UnityEngine; namespace BetterAmongUs.Mono; +/// +/// Extended player information with additional data and anti-cheat features. +/// internal sealed class ExtendedPlayerInfo : MonoBehaviour, IMonoExtension { internal ExtendedPlayerInfo() @@ -15,11 +18,22 @@ internal sealed class ExtendedPlayerInfo : MonoBehaviour, IMonoExtension + /// Gets or sets the base NetworkedPlayerInfo instance. + /// public NetworkedPlayerInfo? BaseMono { get; set; } + + /// + /// Gets the NetworkedPlayerInfo instance. + /// internal NetworkedPlayerInfo? _Data => BaseMono; private bool hasSet = false; + /// + /// Initializes the extended player info. + /// + /// The NetworkedPlayerInfo to extend. [HideFromIl2Cpp] internal void SetInfo(NetworkedPlayerInfo data) { @@ -41,6 +55,9 @@ internal sealed class ExtendedPlayerInfo : MonoBehaviour, IMonoExtension + /// Updates anti-cheat monitoring and state tracking. + /// internal void Update() { var time = Time.deltaTime; @@ -69,67 +86,186 @@ internal sealed class ExtendedPlayerInfo : MonoBehaviour, IMonoExtension + /// Gets the handshake handler for this player. + /// [HideFromIl2Cpp] internal HandshakeHandler HandshakeHandler { get; } + + /// + /// Gets the player ID. + /// [HideFromIl2Cpp] internal byte _PlayerId { get; private set; } + + /// + /// Gets the player's real name. + /// internal string RealName => _Data?.PlayerName ?? "???"; + + /// + /// Gets or sets the last name set for this player. + /// internal string NameSetAsLast { get; set; } = string.Empty; + + /// + /// Gets or sets whether this player is a BetterAmongUs user. + /// internal bool IsBetterUser { get; set; } = false; + + /// + /// Gets or sets whether this player is a verified BetterAmongUs user. + /// internal bool IsVerifiedBetterUser { get; set; } = false; + + /// + /// Gets or sets whether disconnect message has been shown. + /// internal bool HasShowDcMsg { get; set; } = false; + + /// + /// Gets or sets the disconnect reason. + /// internal DisconnectReasons DisconnectReason { get; set; } = DisconnectReasons.Unknown; + + /// + /// Gets the extended role information. + /// [HideFromIl2Cpp] internal ExtendedRoleInfo? RoleInfo { get; } = new(); + + /// + /// Gets the extended anti-cheat information. + /// [HideFromIl2Cpp] internal ExtendedAntiCheatInfo? AntiCheatInfo { get; } = new(); } +/// +/// Contains anti-cheat monitoring information for a player. +/// internal class ExtendedAntiCheatInfo { + /// + /// Maximum allowed RPCs per second. + /// internal const int MAX_RPC_SENT = 50; + + /// + /// Gets or sets whether the player is banned by anti-cheat. + /// internal bool BannedByAntiCheat { get; set; } = false; + + /// + /// Gets or sets the list of AUM chat messages. + /// internal List AUMChats { get; set; } = []; + + /// + /// Gets or sets the RPCs sent per second. + /// internal int RPCSentPS { get; set; } = 0; + + /// + /// Gets or sets the number of times attempted to kill. + /// internal int TimesAttemptedKilled { get; set; } = 0; + + /// + /// Gets or sets the number of open sabotages. + /// internal int OpenSabotageNum { get; set; } = 0; + + /// + /// Gets whether the player is fixing panel sabotage. + /// internal bool IsFixingPanelSabotage => OpenSabotageNum != 0; + + /// + /// Gets or sets the time since last task. + /// internal float TimeSinceLastTask { get; set; } = 5f; + + /// + /// Gets or sets the last task ID. + /// internal uint LastTaskId { get; set; } = 999; + + /// + /// Gets or sets whether the player has set their name. + /// internal bool HasSetName { get; set; } + + /// + /// Gets or sets whether the player has set their level. + /// internal bool HasSetLevel { get; set; } } +/// +/// Contains extended role information for a player. +/// internal class ExtendedRoleInfo { + /// + /// Gets or sets the number of kills. + /// internal int Kills { get; set; } = 0; + + /// + /// Gets or sets whether noisemaker notification is enabled. + /// internal bool HasNoisemakerNotify { get; set; } = false; + + /// + /// Gets or sets the role to display when dead. + /// internal RoleTypes DeadDisplayRole { get; set; } } +/// +/// Extension methods for accessing extended player data. +/// internal static class PlayerControlDataExtension { - // Get BetterData from PlayerControl + /// + /// Gets extended player data from a PlayerControl. + /// + /// The PlayerControl instance. + /// The ExtendedPlayerInfo, or null if not found. internal static ExtendedPlayerInfo? BetterData(this PlayerControl player) { return MonoExtensionManager.Get(player.Data); } + /// + /// Waits for extended player data to be available, then calls a callback. + /// + /// The PlayerControl instance. + /// The callback to execute with the extended data. internal static void BetterDataWait(this PlayerControl player, Action callback) { MonoExtensionManager.RunWhenNotNull(player, () => player?.BetterData(), callback); } - // Get BetterData from NetworkedPlayerInfo + /// + /// Gets extended player data from a NetworkedPlayerInfo. + /// + /// The NetworkedPlayerInfo instance. + /// The ExtendedPlayerInfo, or null if not found. internal static ExtendedPlayerInfo? BetterData(this NetworkedPlayerInfo data) { return MonoExtensionManager.Get(data); } - // Get BetterData from ClientData + /// + /// Gets extended player data from a ClientData. + /// + /// The ClientData instance. + /// The ExtendedPlayerInfo, or null if not found. internal static ExtendedPlayerInfo? BetterData(this ClientData data) { var player = Utils.PlayerFromClientId(data.Id); return MonoExtensionManager.Get(player.Data); } -} +} \ No newline at end of file diff --git a/src/Mono/MeetingInfoDisplay.cs b/src/Mono/MeetingInfoDisplay.cs index 6eb1328..ad2d1c9 100644 --- a/src/Mono/MeetingInfoDisplay.cs +++ b/src/Mono/MeetingInfoDisplay.cs @@ -10,6 +10,9 @@ using UnityEngine; namespace BetterAmongUs.Mono; +/// +/// Displays extended player information during meetings. +/// internal sealed class MeetingInfoDisplay : PlayerInfoDisplay { private PlayerVoteArea? _pva; @@ -25,7 +28,9 @@ internal sealed class MeetingInfoDisplay : PlayerInfoDisplay private CachedTranslations _cachedTranslations = new(); - // Cached translations + /// + /// Cached translations for performance optimization. + /// private class CachedTranslations { internal readonly string SickoUser = Translator.GetString("Player.SickoUser"); @@ -40,6 +45,11 @@ internal sealed class MeetingInfoDisplay : PlayerInfoDisplay internal readonly string DisconnectDefault = Translator.GetString("DisconnectReasonMeeting.Disconnect"); } + /// + /// Initializes the meeting info display. + /// + /// The player to display info for. + /// The PlayerVoteArea associated with the player. internal void Init(PlayerControl? player, PlayerVoteArea pva) { _player = player; @@ -70,6 +80,9 @@ internal sealed class MeetingInfoDisplay : PlayerInfoDisplay PlayerLevel.transform.position += new Vector3(0.23f, 0f); } + /// + /// LateUpdate override with cooldown for performance optimization. + /// protected override void LateUpdate() { if (Time.frameCount - _lastUpdateFrame < UPDATE_COOLDOWN) @@ -95,6 +108,9 @@ internal sealed class MeetingInfoDisplay : PlayerInfoDisplay _lastUpdateFrame = Time.frameCount; } + /// + /// Updates the text positions based on content presence. + /// private void UpdateTextPositions() { bool hasInfoText = !string.IsNullOrEmpty(_infoText?.text); @@ -120,6 +136,9 @@ internal sealed class MeetingInfoDisplay : PlayerInfoDisplay } } + /// + /// Updates player information display. + /// private void UpdateInfo() { if (_player?.Data == null || _player.BetterData() == null) return; @@ -134,6 +153,10 @@ internal sealed class MeetingInfoDisplay : PlayerInfoDisplay UpdateTextIfChanged(_topText, roleText, ref _lastTopText); } + /// + /// Sets player tags based on data from BetterDataManager. + /// + /// StringBuilder for tag text. [HideFromIl2Cpp] private void SetPlayerTags(StringBuilder sbTag) { @@ -149,6 +172,11 @@ internal sealed class MeetingInfoDisplay : PlayerInfoDisplay sbTag.Append($"{_cachedTranslations.KnownCheater}+++"); } + /// + /// Formats player information tags into a readable string. + /// + /// StringBuilder containing tags. + /// StringBuilder for formatted info. [HideFromIl2Cpp] private static void FormatPlayerInfo(StringBuilder sbTag, StringBuilder sbInfo) { @@ -170,6 +198,10 @@ internal sealed class MeetingInfoDisplay : PlayerInfoDisplay } } + /// + /// Gets the role text for display. + /// + /// Formatted role text. private string GetRoleText() { if (_player == null) return string.Empty; @@ -198,6 +230,11 @@ internal sealed class MeetingInfoDisplay : PlayerInfoDisplay return role; } + /// + /// Updates name text position based on role and info text presence. + /// + /// The role text. + /// The info text. private void UpdateNameTextPosition(string roleText, string infoText) { bool hasRole = !string.IsNullOrEmpty(roleText); @@ -212,6 +249,12 @@ internal sealed class MeetingInfoDisplay : PlayerInfoDisplay _pva.NameText.transform.localPosition = textPos; } + /// + /// Updates text if changed, optimizing performance. + /// + /// TextMeshPro component to update. + /// New text to set. + /// Reference to last value for comparison. private static void UpdateTextIfChanged(TextMeshPro textMesh, string newText, ref string lastValue) { if (textMesh == null) return; @@ -223,6 +266,12 @@ internal sealed class MeetingInfoDisplay : PlayerInfoDisplay } } + /// + /// Checks if player data exists in a HashSet of UserInfo. + /// + /// HashSet of UserInfo to check. + /// Player data to look for. + /// True if player data exists in the HashSet. private static bool ContainsPlayerData(HashSet dataList, NetworkedPlayerInfo playerData) { foreach (var info in dataList) @@ -233,6 +282,9 @@ internal sealed class MeetingInfoDisplay : PlayerInfoDisplay return false; } + /// + /// Updates display for disconnected players. + /// private void UpdateDisconnect() { string disconnectText = GetDisconnectText(); @@ -255,6 +307,10 @@ internal sealed class MeetingInfoDisplay : PlayerInfoDisplay _pva.SetDisabled(); } + /// + /// Gets disconnect reason text for display. + /// + /// Disconnect reason text. private string GetDisconnectText() { var playerData = GameData.Instance.GetPlayerById(_pva.TargetPlayerId); diff --git a/src/Mono/PlayerInfoDisplay.cs b/src/Mono/PlayerInfoDisplay.cs index 02a4554..8f28427 100644 --- a/src/Mono/PlayerInfoDisplay.cs +++ b/src/Mono/PlayerInfoDisplay.cs @@ -14,6 +14,9 @@ using UnityEngine; namespace BetterAmongUs.Mono; +/// +/// Displays extended player information during gameplay. +/// internal class PlayerInfoDisplay : MonoBehaviour { protected PlayerControl? _player; @@ -29,12 +32,16 @@ internal class PlayerInfoDisplay : MonoBehaviour private int _lastUpdateFrame; private const int UPDATE_COOLDOWN = 10; - // Cached regex patterns + /// + /// Cached regex pattern for friend code validation. + /// private static readonly Regex _friendCodePattern = new(@"^[a-zA-Z0-9#]+$", RegexOptions.Compiled); private CachedTranslations _cachedTranslations = new(); - // Cached colors + /// + /// Cached color values for performance optimization. + /// private static readonly Dictionary _cachedColors = new() { ["#00f583"] = Utils.HexToColor32("#00f583"), @@ -43,7 +50,9 @@ internal class PlayerInfoDisplay : MonoBehaviour ["#8731e7"] = Utils.HexToColor32("#8731e7") }; - // Cached translations + /// + /// Cached translations for performance optimization. + /// private class CachedTranslations { internal readonly string Loading = Translator.GetString("Player.Loading"); @@ -56,6 +65,10 @@ internal class PlayerInfoDisplay : MonoBehaviour internal readonly string BetterUser = Translator.GetString("Player.BetterUser"); } + /// + /// Initializes the player info display. + /// + /// The player to display info for. internal void Init(PlayerControl player) { _player = player; @@ -71,6 +84,13 @@ internal class PlayerInfoDisplay : MonoBehaviour _bottomText.fontSize = 1.3f; } + /// + /// Instantiates a player info text object. + /// + /// The name of the text object. + /// The position offset from the parent. + /// The parent transform. + /// The created TextMeshPro component. protected TextMeshPro InstantiatePlayerInfoText(string name, Vector3 positionOffset, Transform parent) { var newTextObject = Instantiate(_nameText, parent); @@ -85,6 +105,9 @@ internal class PlayerInfoDisplay : MonoBehaviour return textMesh; } + /// + /// Resets all text displays to empty. + /// private void ResetText() { _infoText?.SetText(string.Empty); @@ -92,6 +115,9 @@ internal class PlayerInfoDisplay : MonoBehaviour _bottomText?.SetText(string.Empty); } + /// + /// LateUpdate override with cooldown for performance optimization. + /// protected virtual void LateUpdate() { if (Time.frameCount - _lastUpdateFrame < UPDATE_COOLDOWN) @@ -115,6 +141,9 @@ internal class PlayerInfoDisplay : MonoBehaviour _lastUpdateFrame = Time.frameCount; } + /// + /// Updates player information display. + /// private void UpdatePlayerInfo() { if (_player?.Data == null) return; @@ -183,6 +212,12 @@ internal class PlayerInfoDisplay : MonoBehaviour UpdateTextIfChanged(_infoText, _sbTag, ref _lastInfoText); } + /// + /// Updates text if changed, optimizing performance. + /// + /// TextMeshPro component to update. + /// StringBuilder containing new text. + /// Reference to last value for comparison. private static void UpdateTextIfChanged(TextMeshPro textMesh, StringBuilder sb, ref string lastValue) { if (textMesh == null) return; @@ -195,6 +230,11 @@ internal class PlayerInfoDisplay : MonoBehaviour } } + /// + /// Validates and formats the player's friend code. + /// + /// Output parameter for the friend code color. + /// The formatted friend code string. private string ValidateFriendCode(out string color) { color = "#FFFFFF"; @@ -263,6 +303,10 @@ internal class PlayerInfoDisplay : MonoBehaviour return friendCode.Trim(); } + /// + /// Sets player outline based on data from BetterDataManager. + /// + /// StringBuilder for tag text. [HideFromIl2Cpp] private void SetPlayerOutline(StringBuilder sbTag) { @@ -299,6 +343,12 @@ internal class PlayerInfoDisplay : MonoBehaviour } } + /// + /// Checks if player data exists in a HashSet of UserInfo. + /// + /// HashSet of UserInfo to check. + /// Player data to look for. + /// True if player data exists in the HashSet. [HideFromIl2Cpp] private static bool ContainsPlayerData(HashSet dataList, NetworkedPlayerInfo playerData) { @@ -310,6 +360,12 @@ internal class PlayerInfoDisplay : MonoBehaviour return false; } + /// + /// Sets lobby-specific information. + /// + /// Reference to the player's name. + /// Extended player data. + /// StringBuilder for tag text. [HideFromIl2Cpp] private void SetLobbyInfo(ref string newName, ExtendedPlayerInfo betterData, StringBuilder sbTag) { @@ -327,6 +383,10 @@ internal class PlayerInfoDisplay : MonoBehaviour sbTag.Append($"ID: {_player.PlayerId}+++"); } + /// + /// Sets in-game specific information. + /// + /// StringBuilder for top tag text. [HideFromIl2Cpp] private void SetInGameInfo(StringBuilder sbTagTop) { @@ -349,11 +409,17 @@ internal class PlayerInfoDisplay : MonoBehaviour } } + /// + /// Updates player highlight/outline. + /// private void UpdatePlayerHighlight() { SetPlayerOutline(new StringBuilder(32)); } + /// + /// Updates color blind text position. + /// private void UpdateColorBlindTextPosition() { var text = _player.cosmetics.colorBlindText; diff --git a/src/Network/Configs/NewsData.cs b/src/Network/Configs/NewsData.cs index 71bb129..2427bda 100644 --- a/src/Network/Configs/NewsData.cs +++ b/src/Network/Configs/NewsData.cs @@ -5,45 +5,49 @@ namespace BetterAmongUs.Network.Configs; /// /// Represents the data structure for mod news items within the game. -/// Contains properties to store news-related details such as title, subtitle, content, and metadata. /// internal sealed class NewsData() { /// - /// Indicates whether the news item should be shown. + /// Gets or sets a value indicating whether the news item should be shown. /// public bool Show { get; set; } /// - /// Defines the type/category of the news item. + /// Gets or sets the type/category of the news item. /// public int Type { get; set; } /// - /// The main title of the news item. + /// Gets or sets the main title of the news item. /// public string Title { get; set; } = string.Empty; /// - /// The subtitle providing additional context to the news title. + /// Gets or sets the subtitle providing additional context to the news title. /// public string SubTitle { get; set; } = string.Empty; /// - /// The title used for listing purposes. + /// Gets or sets the title used for listing purposes. /// public string ListTitle { get; set; } = string.Empty; /// - /// The publication date of the news item, formatted as a string. + /// Gets or sets the publication date of the news item, formatted as a string. /// public string Date { get; set; } = string.Empty; /// - /// The content or body of the news item. + /// Gets or sets the content or body of the news item, keyed by language ID. /// public Dictionary Contents { get; set; } = []; + /// + /// Deserializes YAML content into a NewsData object. + /// + /// The YAML string to parse. + /// A NewsData object if deserialization succeeds; otherwise, null. internal static NewsData? Serialize(string input) { var newsData = new NewsData(); diff --git a/src/Network/Configs/UpdateData.cs b/src/Network/Configs/UpdateData.cs index 814bb9d..1023f6e 100644 --- a/src/Network/Configs/UpdateData.cs +++ b/src/Network/Configs/UpdateData.cs @@ -1,4 +1,5 @@ -using BetterAmongUs.Helpers; +using BetterAmongUs.Enums; +using BetterAmongUs.Helpers; using System.Collections; using System.Reflection; using System.Text.Json.Serialization; @@ -6,27 +7,56 @@ using UnityEngine; namespace BetterAmongUs.Network.Configs; +/// +/// Represents update data retrieved from the remote repository. +/// [Serializable] internal sealed class UpdateData { + /// + /// Gets or sets the download link for the updated DLL file. + /// [JsonPropertyName("dllLink")] public string DllLink { get; set; } = string.Empty; + /// + /// Gets or sets the version string of the update. + /// [JsonPropertyName("version")] public string Version { get; set; } = string.Empty; + /// + /// Gets or sets the release type as an integer value. + /// [JsonPropertyName("releaseType")] public int ReleaseType { get; set; } + /// + /// Gets or sets whether this update is a hotfix. + /// [JsonPropertyName("isHotfix")] public bool IsHotfix { get; set; } + /// + /// Gets or sets the hotfix number if this is a hotfix update. + /// [JsonPropertyName("hotfixNumber")] public int HotfixNumber { get; set; } + /// + /// Gets or sets the beta number if this is a beta release. + /// [JsonPropertyName("betaNumber")] public int BetaNumber { get; set; } + /// + /// Determines if this update is newer than the currently installed version. + /// + /// True if the update is newer, false otherwise. + /// + /// Compares version numbers, release types, and specific build numbers + /// to determine if an update should be applied. + /// internal bool IsNewUpdate() { try @@ -79,6 +109,14 @@ internal sealed class UpdateData } } + /// + /// Downloads and applies the update. + /// + /// IEnumerator for coroutine execution. + /// + /// Downloads the new DLL file, renames the current DLL to .old, + /// and replaces it with the downloaded file. + /// internal IEnumerator CoDownload() { int count = 0; @@ -110,6 +148,10 @@ internal sealed class UpdateData yield break; } + /// + /// Returns a string representation of the update data. + /// + /// A formatted string containing version information. public override string ToString() { return $"{Version}:{ReleaseType}:{BetaNumber}:{HotfixNumber}"; diff --git a/src/Network/GitHubFile.cs b/src/Network/GitHubFile.cs index 82a42cf..5ce96bf 100644 --- a/src/Network/GitHubFile.cs +++ b/src/Network/GitHubFile.cs @@ -7,12 +7,22 @@ using UnityEngine.Networking; namespace BetterAmongUs.Network; +/// +/// Provides methods for downloading files from GitHub repositories. +/// internal static class GitHubFile { /// - /// Downloads an individual visor-related file from the remote repository and saves it locally. - /// Handles errors and logs any failed downloads to avoid missing assets. + /// Downloads an individual file from the remote repository and saves it locally. /// + /// The URL of the file to download. + /// The local file path to save the downloaded file to. + /// Optional callback to execute after successful download. + /// Whether to show a progress bar during download. + /// IEnumerator for coroutine execution. + /// + /// Handles errors and logs any failed downloads to avoid missing assets. + /// [HideFromIl2Cpp] internal static IEnumerator CoDownloadFile(string url, string localFilePath, Action? callback = null, bool showProgress = false) { @@ -72,9 +82,11 @@ internal static class GitHubFile } /// - /// Downloads an individual visor-related file from the remote repository and saves it locally. - /// Handles errors and logs any failed downloads to avoid missing assets. + /// Downloads a manifest file from the remote repository. /// + /// The URL of the manifest file to download. + /// Callback to execute with the downloaded manifest content. + /// IEnumerator for coroutine execution. [HideFromIl2Cpp] internal static IEnumerator CoDownloadManifest(string url, Action Callback) { @@ -94,4 +106,4 @@ internal static class GitHubFile www.Dispose(); Callback.Invoke(response); } -} +} \ No newline at end of file diff --git a/src/Network/GitUrlPath.cs b/src/Network/GitUrlPath.cs index f578583..76f7baf 100644 --- a/src/Network/GitUrlPath.cs +++ b/src/Network/GitUrlPath.cs @@ -1,22 +1,54 @@ namespace BetterAmongUs.Network; +/// +/// Represents a Git URL path for accessing BetterAmongUs resources. +/// +/// The folder path within the repository. internal struct GitUrlPath(string folder) { + /// + /// The base URL for the GitHub raw content. + /// private const string BASE_URL = "https://raw.githubusercontent.com/D1GQ/BetterAmongUs"; + + /// + /// The branch name for the repository. + /// internal const string BRANCH = "main"; + /// + /// Gets the URL path for the main repository folder. + /// internal static readonly GitUrlPath Repository = new(BRANCH); + + /// + /// Gets the URL path for the API folder. + /// internal static readonly GitUrlPath RepositoryApi = new($"{BRANCH}/api"); + + /// + /// Gets the URL path for the news folder. + /// internal static readonly GitUrlPath News = new($"{BRANCH}/api/news"); + private readonly string _folder = folder; + /// + /// Combines the base URL with additional path segments. + /// + /// Additional path segments to append. + /// The complete URL string. internal readonly string Combine(params string[] paths) { return $"{BASE_URL}/{_folder}/{string.Join("/", paths)}"; } + /// + /// Returns the base URL for this path. + /// + /// The complete base URL string. public override readonly string ToString() { return $"{BASE_URL}/{_folder}"; } -} +} \ No newline at end of file diff --git a/src/Network/GithubAPI.cs b/src/Network/GithubAPI.cs index 7df5114..34c1d4f 100644 --- a/src/Network/GithubAPI.cs +++ b/src/Network/GithubAPI.cs @@ -6,13 +6,36 @@ using UnityEngine.Networking; namespace BetterAmongUs.Network; +/// +/// Manages API connections to GitHub for news, updates, and user data. +/// internal sealed class GithubAPI : MonoBehaviour { + /// + /// Gets the singleton instance of the GithubAPI. + /// + /// The current instance, or null if not initialized. internal static GithubAPI? Instance { get; private set; } + + /// + /// Gets a value indicating whether the API connection was successful. + /// internal static bool HasConnectedAPI { get; private set; } = false; + + /// + /// Gets a value indicating whether the API connection process has finished. + /// internal static bool Finished { get; private set; } = false; private static bool hasTryConnect = false; + + /// + /// Initializes and connects to the GitHub API. + /// + /// + /// Creates a persistent GameObject to manage API connections. + /// This method ensures only one connection attempt is made. + /// internal static void Connect() { if (hasTryConnect) return; @@ -28,11 +51,12 @@ internal sealed class GithubAPI : MonoBehaviour ConnectToAPI(); } + /// + /// Connects to various GitHub API endpoints. + /// [HideFromIl2Cpp] private void ConnectToAPI() { - ConnectToAPIUsers(); - var newsLoader = gameObject.AddComponent(); this.StartCoroutine(newsLoader.CoFetchNewsData()); @@ -40,11 +64,11 @@ internal sealed class GithubAPI : MonoBehaviour this.StartCoroutine(updateLoader.CoFetchUpdateData()); } - [HideFromIl2Cpp] - private void ConnectToAPIUsers() - { - } - + /// + /// Sets the API connection status based on the web request result. + /// + /// The UnityWebRequest that was made. + /// Whether an error occurred during the request. internal static void SetConnectedAPI(UnityWebRequest www, bool hasErrored) { if (www.result == UnityWebRequest.Result.ConnectionError || @@ -58,6 +82,13 @@ internal sealed class GithubAPI : MonoBehaviour } } + /// + /// Checks if internet connectivity is available. + /// + /// True if internet connection is available; otherwise, false. + /// + /// Performs a quick test by attempting to connect to Google's 204 endpoint. + /// internal static bool IsInternetAvailable() { if (Application.internetReachability == NetworkReachability.NotReachable) @@ -80,4 +111,4 @@ internal sealed class GithubAPI : MonoBehaviour www?.Dispose(); } } -} +} \ No newline at end of file diff --git a/src/Network/Loaders/NewsLoader.cs b/src/Network/Loaders/NewsLoader.cs index af3c43c..5d621e7 100644 --- a/src/Network/Loaders/NewsLoader.cs +++ b/src/Network/Loaders/NewsLoader.cs @@ -16,8 +16,11 @@ internal sealed class NewsLoader : MonoBehaviour { /// /// Coroutine to fetch the news data from the remote repository. - /// If no internet connection is detected, it retries several times before giving up. /// + /// IEnumerator for coroutine execution. + /// + /// If no internet connection is detected, it retries several times before giving up. + /// [HideFromIl2Cpp] internal IEnumerator CoFetchNewsData() { @@ -70,9 +73,12 @@ internal sealed class NewsLoader : MonoBehaviour /// /// Coroutine to download an individual news file from the remote repository. - /// If the download fails or the file cannot be deserialized, the process is skipped. /// /// The name of the news file to download. + /// IEnumerator for coroutine execution. + /// + /// If the download fails or the file cannot be deserialized, the process is skipped. + /// [HideFromIl2Cpp] private IEnumerator CoDownloadNewsFile(string fileName) { @@ -106,6 +112,7 @@ internal sealed class NewsLoader : MonoBehaviour /// /// Loads a test news configuration from an embedded resource for local testing purposes. /// + /// IEnumerator for coroutine execution. [HideFromIl2Cpp] private IEnumerator CoLoadNewsTest() { diff --git a/src/Network/Loaders/UpdateLoader.cs b/src/Network/Loaders/UpdateLoader.cs index ae1ac23..67eee18 100644 --- a/src/Network/Loaders/UpdateLoader.cs +++ b/src/Network/Loaders/UpdateLoader.cs @@ -8,10 +8,25 @@ using UnityEngine; namespace BetterAmongUs.Network.Loaders; +/// +/// Handles downloading and processing of update data from a remote repository. +/// internal sealed class UpdateLoader : MonoBehaviour { + /// + /// Gets the update information retrieved from the remote repository. + /// + /// The update data, or null if not loaded. internal static UpdateData? UpdateInfo { get; private set; } + /// + /// Coroutine to fetch update data from the remote repository. + /// + /// IEnumerator for coroutine execution. + /// + /// If no internet connection is detected, it retries several times before giving up. + /// After successfully loading update data, it initializes the UpdateManager. + /// [HideFromIl2Cpp] internal IEnumerator CoFetchUpdateData() { diff --git a/src/Network/RPC.cs b/src/Network/RPC.cs index bd71e47..08d891b 100644 --- a/src/Network/RPC.cs +++ b/src/Network/RPC.cs @@ -6,29 +6,20 @@ using Hazel; namespace BetterAmongUs.Network; +/// +/// Handles custom RPC (Remote Procedure Call) messages for BetterAmongUs. +/// internal static class RPC { - internal static void RpcSetNameForTarget(PlayerControl player, string name, PlayerControl target) - { - if (!GameState.IsHost) - { - return; - } - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetName, SendOption.Reliable, target.GetClientId()); - writer.Write(player.Data.NetId); - writer.Write(name); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - internal static void RpcExile(PlayerControl player) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.Exiled, SendOption.Reliable, -1); - AmongUsClient.Instance.FinishRpcImmediately(writer); - - player.Exiled(); - } - + /// + /// Processes custom RPC messages received from other players. + /// + /// The player who sent the RPC. + /// The ID of the RPC call. + /// The message reader containing the RPC data. + /// + /// Handles both defined custom RPCs and protects against unknown RPCs in modded lobbies. + /// internal static void HandleCustomRPC(PlayerControl player, byte callId, MessageReader oldReader) { if (player == null || player.IsLocalPlayer() || player.Data == null || Enum.IsDefined(typeof(RpcCalls), callId)) return; @@ -99,5 +90,4 @@ internal static class RPC catch { } } } -} - +} \ No newline at end of file diff --git a/src/Structs/RPCData.cs b/src/Structs/RPCData.cs index 790d161..595384d 100644 --- a/src/Structs/RPCData.cs +++ b/src/Structs/RPCData.cs @@ -3,11 +3,38 @@ using InnerNet; namespace BetterAmongUs.Structs; +/// +/// Represents data for an RPC (Remote Procedure Call) message. +/// +/// The object that sent the RPC. +/// The send option used for the RPC. +/// The target player ID for the RPC. +/// The ID of the RPC call. +/// The message reader containing the RPC data. internal readonly struct RPCData(InnerNetObject sender, SendOption sendOption, int targetId, RpcCalls callId, MessageReader reader) { + /// + /// Gets the object that sent the RPC. + /// public readonly InnerNetObject Sender = sender; + + /// + /// Gets the send option used for the RPC. + /// public readonly SendOption SendOption = sendOption; + + /// + /// Gets the target player ID for the RPC. + /// public readonly int TargetId = targetId; + + /// + /// Gets the ID of the RPC call. + /// public readonly RpcCalls CalledRpc = callId; + + /// + /// Gets the message reader containing the RPC data. + /// public readonly MessageReader Reader = reader; -} +} \ No newline at end of file diff --git a/src/Structs/UserInfo.cs b/src/Structs/UserInfo.cs index a116c9c..263e568 100644 --- a/src/Structs/UserInfo.cs +++ b/src/Structs/UserInfo.cs @@ -3,11 +3,31 @@ using System.Text.Json.Serialization; namespace BetterAmongUs.Structs; +/// +/// Represents user information for player identification and tracking. +/// +/// The player's name. +/// The hashed PUID (Player User ID) of the player. +/// The player's friend code. +/// The reason for tracking this user. [method: JsonConstructor] internal sealed class UserInfo(string playerName, string hashPuid, string friendCode, string reason) { + /// + /// Checks if the player data matches this user information. + /// + /// The player data to check. + /// True if the player data matches; otherwise, false. internal bool CheckPlayerData(NetworkedPlayerInfo data) => CheckPlayerDataWithReason(data).check; + /// + /// Checks if the player data matches this user information and returns the reason. + /// + /// The player data to check. + /// A tuple containing the check result and the reason if matched. + /// + /// Matches by hashed PUID, friend code, or player name. + /// internal (bool check, string reason) CheckPlayerDataWithReason(NetworkedPlayerInfo data) { if (!string.IsNullOrEmpty(data.GetHashPuid()) && HashPuid == data.GetHashPuid() @@ -23,15 +43,27 @@ internal sealed class UserInfo(string playerName, string hashPuid, string friend return (false, ""); } + /// + /// Gets or sets the player's name. + /// [JsonPropertyName("playerName")] public string PlayerName { get; set; } = playerName; + /// + /// Gets or sets the hashed PUID (Player User ID) of the player. + /// [JsonPropertyName("hashPuid")] public string HashPuid { get; set; } = hashPuid; + /// + /// Gets or sets the player's friend code. + /// [JsonPropertyName("friendCode")] public string FriendCode { get; set; } = friendCode; + /// + /// Gets or sets the reason for tracking this user. + /// [JsonPropertyName("reason")] public string Reason { get; set; } = reason; } \ No newline at end of file