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