Move to string based ids for Settings, add option to disable setting compressing

This commit is contained in:
D1GQ committed 2026-06-27 20:46:42 -05:00
1 parent 0b8f284676
commit 7460720178
16 files changed
+255 -236

No files matched your search

+38 -16
View File
@@ -89,7 +89,7 @@ internal static class BetterDataManager
/// <summary>
/// Current compressed settings file path.
/// </summary>
internal static string SettingsFilePath => Path.Combine(Folders.settingsFolderPath, $"Preset-{BAUConfigs.SettingsPreset.Value}.dat");
internal static string SettingsFilePath => GetSettingsFilePathFromPreset(BAUConfigs.SettingsPreset.Value);
/// <summary>
/// File containing banned player identifiers.
@@ -133,6 +133,15 @@ internal static class BetterDataManager
}
}
/// <summary>
/// Gets the file path for the settings file associated with a specific preset number.
/// </summary>
/// <param name="preset">The preset number to get the settings file for.</param>
internal static string GetSettingsFilePathFromPreset(int preset)
{
return Path.Combine(Folders.settingsFolderPath, $"Preset-{preset}.json");
}
/// <summary>
/// Gets the persistent data path for Among Us.
/// </summary>
@@ -239,24 +248,29 @@ internal static class BetterDataManager
}
/// <summary>
/// Saves a setting with the specified ID.
/// Saves a setting with the specified identifier to the game settings file.
/// </summary>
/// <param name="id">The setting identifier.</param>
/// <param name="input">The setting value to save.</param>
internal static void SaveSetting(int id, object? input)
/// <param name="id">The unique identifier key for the setting.</param>
/// <param name="input">The value to save. Can be any object type that is JSON-serializable.</param>
internal static void SaveSetting(string id, object? input)
{
id = id.Split(".").Last();
Files.BetterGameSettingsFile.Settings[id] = input;
Files.BetterGameSettingsFile.Save();
}
/// <summary>
/// Checks if a setting can be loaded as the specified type.
/// Determines whether a setting exists and can be loaded as the specified type.
/// </summary>
/// <typeparam name="T">The type to check against.</typeparam>
/// <param name="id">The setting identifier.</param>
/// <returns>True if the setting exists and can be cast to type T, false otherwise.</returns>
internal static bool CanLoadSetting<T>(int id)
/// <typeparam name="T">The target type to check compatibility with.</typeparam>
/// <param name="id">The unique identifier key for the setting.</param>
/// <returns>
/// <see langword="true"/> if the setting exists and the value is of type <typeparamref name="T"/>;
/// otherwise, <see langword="false"/>.
/// </returns>
internal static bool CanLoadSetting<T>(string id)
{
id = id.Split(".").Last();
if (Files.BetterGameSettingsFile.Settings.TryGetValue(id, out var value))
{
if (value is T)
@@ -269,14 +283,22 @@ internal static class BetterDataManager
}
/// <summary>
/// Loads a setting with the specified ID, returning a default value if not found.
/// Loads a setting with the specified identifier, returning a default value if the setting
/// does not exist or cannot be cast to the requested type.
/// </summary>
/// <typeparam name="T">The type of the setting value.</typeparam>
/// <param name="id">The setting identifier.</param>
/// <param name="Default">The default value to return and save if the setting doesn't exist.</param>
/// <returns>The setting value or the default value if not found.</returns>
internal static T? LoadSetting<T>(int id, T? Default = default)
/// <typeparam name="T">The expected type of the setting value.</typeparam>
/// <param name="id">The unique identifier key for the setting.</param>
/// <param name="Default">
/// The default value to return if the setting is not found. This value will also be
/// automatically saved to the settings file.
/// </param>
/// <returns>
/// The stored setting value cast to type <typeparamref name="T"/>, or the specified
/// <paramref name="Default"/> value if the setting doesn't exist.
/// </returns>
internal static T? LoadSetting<T>(string id, T? Default = default)
{
id = id.Split(".").Last();
if (Files.BetterGameSettingsFile.Settings.TryGetValue(id, out var value))
{
if (value is T castValue)
+5
View File
@@ -68,6 +68,11 @@ internal static class BAUConfigs
/// </summary>
internal static BAUConfigEntry<bool> VentColorGroups { get; } = new("Better Options", "VentColorGroups", true);
/// <summary>
/// Gets the configuration entry for compress settings file setting.
/// </summary>
internal static BAUConfigEntry<bool> CompressSettingFiles { get; } = new("Better Options", "CompressSettingFiles", false);
/// <summary>
/// Gets the configuration entry for command prefix setting.
/// </summary>
+66 -95
View File
@@ -6,18 +6,21 @@ using System.Text.Json.Serialization;
namespace BetterAmongUs.Data.Json;
/// <summary>
/// Abstract base class for JSON file handling with serialization and deserialization capabilities.
/// Provides a base class for JSON-backed data files with automatic
/// loading, validation, and serialization.
/// </summary>
internal abstract class AbstractJsonFile
{
/// <summary>
/// Gets the file path where the JSON data will be stored.
/// Gets the path to the JSON file.
/// </summary>
internal abstract string FilePath { get; }
private bool _hasInit;
private bool _initialized;
/// <summary>
/// Gets the JSON serializer options used for serialization and deserialization.
/// Gets the <see cref="JsonSerializerOptions"/> used for serialization
/// and deserialization.
/// </summary>
protected virtual JsonSerializerOptions SerializerOptions { get; } = new()
{
@@ -28,161 +31,129 @@ internal abstract class AbstractJsonFile
};
/// <summary>
/// Initializes the JSON file, creating it if it doesn't exist or loading existing data.
/// Initializes the file by loading existing data if possible and
/// writing the current state back to disk.
/// </summary>
internal virtual void Init()
{
if (_hasInit)
if (_initialized)
return;
_hasInit = true;
_initialized = true;
if (!IsFileValid())
{
Save();
return;
}
if (IsFileValid())
Load();
Load();
Save();
}
/// <summary>
/// Loads data from the JSON file into the current instance.
/// Loads the file contents into the current instance.
/// </summary>
/// <returns>True if loading was successful, false otherwise.</returns>
/// <returns>
/// <see langword="true"/> if the file was successfully loaded;
/// otherwise, <see langword="false"/>.
/// </returns>
protected virtual bool Load()
{
try
{
var content = TryReadFromFile();
if (string.IsNullOrEmpty(content.Trim()))
{
return false;
}
var json = ReadFromFile();
var data = JsonSerializer.Deserialize(content, GetType(), SerializerOptions);
if (data == null)
{
Logger_.Error("Deserialization returned null");
if (string.IsNullOrWhiteSpace(json))
return false;
var data = JsonSerializer.Deserialize(json, GetType(), SerializerOptions);
if (data is null)
return false;
}
foreach (var property in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (property.GetCustomAttribute<JsonPropertyNameAttribute>() == null) continue;
if (property.CanWrite)
if (property.CanWrite &&
property.GetCustomAttribute<JsonPropertyNameAttribute>() != null)
{
var value = property.GetValue(data);
property.SetValue(this, value);
property.SetValue(this, property.GetValue(data));
}
}
return true;
}
catch (JsonException ex)
{
Logger_.Error($"JSON parsing error: {ex.Message}");
return false;
}
catch (Exception ex)
{
Logger_.Error($"Unexpected error: {ex.Message}");
return false;
}
}
/// <summary>
/// Attempts to read content from the file, returning an empty string on failure.
/// </summary>
/// <returns>The file content or an empty string if reading fails.</returns>
private string TryReadFromFile()
{
try
{
return ReadFromFile();
}
catch
{
return string.Empty;
}
}
/// <summary>
/// Saves the current instance's data to the JSON file.
/// </summary>
/// <returns>True if saving was successful, false otherwise.</returns>
internal virtual bool Save()
{
try
{
var json = JsonSerializer.Serialize(this, GetType(), SerializerOptions);
WriteToFile(json);
}
catch (Exception ex)
{
Logger_.Error(ex);
return false;
}
return true;
}
/// <summary>
/// Determines whether the file specified by the current FilePath exists and contains valid, non-empty JSON content.
/// Saves the current instance to the JSON file.
/// </summary>
/// <returns>true if the file exists, is not empty, and contains valid, non-empty JSON content; otherwise, false.</returns>
private bool IsFileValid()
/// <returns>
/// <see langword="true"/> if the file was successfully written;
/// otherwise, <see langword="false"/>.
/// </returns>
internal virtual bool Save()
{
var directory = Path.GetDirectoryName(FilePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
try
{
Directory.CreateDirectory(directory);
WriteToFile(JsonSerializer.Serialize(this, GetType(), SerializerOptions));
return true;
}
if (!File.Exists(FilePath))
catch (Exception ex)
{
Logger_.Error(ex);
return false;
}
}
var content = TryReadFromFile();
if (string.IsNullOrEmpty(content.Trim())) return false;
/// <summary>
/// Determines whether the JSON file exists and contains valid,
/// non-empty JSON.
/// </summary>
/// <returns>
/// <see langword="true"/> if the file is valid; otherwise,
/// <see langword="false"/>.
/// </returns>
private bool IsFileValid()
{
Directory.CreateDirectory(Path.GetDirectoryName(FilePath)!);
if (!File.Exists(FilePath))
return false;
try
{
var jsonElement = JsonSerializer.Deserialize<JsonElement>(content);
var json = ReadFromFile();
var fileInfo = new FileInfo(FilePath);
if (fileInfo.Length == 0)
{
if (string.IsNullOrWhiteSpace(json))
return false;
}
if (jsonElement.ValueKind == JsonValueKind.Object && !jsonElement.EnumerateObject().Any() ||
jsonElement.ValueKind == JsonValueKind.Array && !jsonElement.EnumerateArray().Any())
var element = JsonSerializer.Deserialize<JsonElement>(json);
return element.ValueKind switch
{
return false;
}
JsonValueKind.Object => element.EnumerateObject().Any(),
JsonValueKind.Array => element.EnumerateArray().Any(),
_ => true
};
}
catch (Exception)
catch
{
return false;
}
return true;
}
/// <summary>
/// Reads the content from the JSON file.
/// Reads the contents of the JSON file.
/// </summary>
/// <returns>The content of the JSON file.</returns>
/// <returns>The raw JSON string.</returns>
protected virtual string ReadFromFile()
{
return File.ReadAllText(FilePath);
}
/// <summary>
/// Writes the JSON string to the file.
/// Writes JSON content to the file.
/// </summary>
/// <param name="json">The JSON string to write.</param>
protected virtual void WriteToFile(string json)
+71 -10
View File
@@ -1,4 +1,7 @@
using BetterAmongUs.Modules;
using BetterAmongUs.Data.Config;
using BetterAmongUs.Modules;
using BetterAmongUs.Modules.OptionItems;
using BetterAmongUs.Patches.Gameplay.UI.Settings;
using System.IO.Compression;
using System.Text;
using System.Text.Json;
@@ -11,10 +14,25 @@ namespace BetterAmongUs.Data.Json;
/// </summary>
internal sealed class BetterGameSettingsFile : AbstractJsonFile
{
/// <summary>
/// Gets or initializes the custom file path override for the settings file.
/// </summary>
internal string? OverrideFilePath { get; init; }
/// <summary>
/// The current version identifier for the settings file format.
/// </summary>
internal const string SETTINGS_VERSION = "2.0";
/// <summary>
/// The dictionary key used to store the settings file version.
/// </summary>
internal const string SETTINGS_VERSION_KEY = "FileVer";
/// <summary>
/// Gets the file path for the game settings file.
/// </summary>
internal override string FilePath => BetterDataManager.Files.SettingsFilePath;
internal override string FilePath => OverrideFilePath ?? BetterDataManager.Files.SettingsFilePath;
/// <summary>
/// Loads the settings file and converts JSON elements to their appropriate types.
@@ -48,16 +66,53 @@ internal sealed class BetterGameSettingsFile : AbstractJsonFile
}
}
}
// Validate settings file version
object? versionObject = null;
Settings.TryGetValue(SETTINGS_VERSION_KEY, out versionObject);
if (versionObject == null || versionObject is not SETTINGS_VERSION)
{
Settings.Clear();
Save();
foreach (var opt in OptionItem.AllOptions)
{
opt.TryLoad(true);
}
GameSettingsPatch.BetterSettingsTab?.UpdateVisuals();
return false;
}
}
return success;
}
/// <summary>
/// Saves the current settings to the file, ensuring the version key is included.
/// </summary>
/// <returns>
/// <see langword="true"/> if the save operation was successful;
/// otherwise, <see langword="false"/>.
/// </returns>
internal override bool Save()
{
Settings[SETTINGS_VERSION_KEY] = SETTINGS_VERSION;
Settings = Settings.OrderBy(kvp => kvp.Key != SETTINGS_VERSION_KEY)
.ThenBy(kvp => kvp.Key)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
return base.Save();
}
/// <summary>
/// Writes JSON data to the file with GZIP compression and base64 encoding.
/// </summary>
/// <param name="json">The JSON string to write to the file.</param>
protected override void WriteToFile(string json)
{
if (!BAUConfigs.CompressSettingFiles.Value)
{
base.WriteToFile(json);
return;
}
var jsonDoc = JsonDocument.Parse(json);
var settingsDict = jsonDoc.RootElement.GetProperty(nameof(Settings));
var sb = new StringBuilder();
@@ -73,7 +128,7 @@ internal sealed class BetterGameSettingsFile : AbstractJsonFile
{
if (kvp.Value.TryGetSingle(out float floatValue))
{
if (Settings.TryGetValue(int.Parse(kvp.Name), out var originalValue) && originalValue is float)
if (Settings.TryGetValue(kvp.Name, out var originalValue) && originalValue is float)
{
sb.Append(floatValue.ToString("0.0########", System.Globalization.CultureInfo.InvariantCulture));
}
@@ -102,13 +157,22 @@ internal sealed class BetterGameSettingsFile : AbstractJsonFile
ms.Position = 0;
File.WriteAllText(FilePath, Convert.ToBase64String(ms.ToArray()));
}
/// <summary>
/// Reads compressed JSON data from the file and decompresses it.
/// </summary>
/// <returns>The decompressed JSON string.</returns>
protected override string ReadFromFile()
{
byte[] compressedBytes = Convert.FromBase64String(File.ReadAllText(FilePath).Trim());
string text = File.ReadAllText(FilePath).Trim();
byte[] compressedBytes = new byte[text.Length];
if (!Convert.TryFromBase64String(text, compressedBytes, out var bytesWritten))
{
return text; // Not valid base64, return original json
}
Array.Resize(ref compressedBytes, bytesWritten);
using var ms = new MemoryStream(compressedBytes);
using var gzip = new GZipStream(ms, CompressionMode.Decompress);
using var reader = new StreamReader(gzip);
@@ -125,13 +189,10 @@ internal sealed class BetterGameSettingsFile : AbstractJsonFile
}
}
var resultDict = new Dictionary<int, object?>();
var resultDict = new Dictionary<string, object?>();
foreach (var kvp in settingsDict)
{
if (int.TryParse(kvp.Key, out int key))
{
resultDict[key] = kvp.Value.Deserialize<object?>();
}
resultDict[kvp.Key] = kvp.Value.Deserialize<object?>();
}
return JsonSerializer.Serialize(new { Settings = resultDict });
@@ -141,5 +202,5 @@ internal sealed class BetterGameSettingsFile : AbstractJsonFile
/// Gets the dictionary of game settings with integer keys and various value types.
/// </summary>
[JsonPropertyName(nameof(Settings))]
public Dictionary<int, object?> Settings { get; set; } = [];
public Dictionary<string, object?> Settings { get; set; } = [];
}
@@ -16,17 +16,16 @@ public sealed class OptionCheckboxItem : OptionItem<bool>
internal sealed override bool ShowChildren => base.ShowChildren && Value;
/// <summary>
/// Creates a new checkbox option item or returns an existing one with the same ID.
/// Creates a new checkbox option item.
/// </summary>
/// <param name="id">The unique identifier for this option.</param>
/// <param name="tab">The tab this option belongs to.</param>
/// <param name="translationString">The translation key for the option name.</param>
/// <param name="defaultValue">The default value for the checkbox.</param>
/// <param name="parent">Optional parent option for hierarchical organization.</param>
/// <returns>A new or existing OptionCheckboxItem instance.</returns>
internal static OptionCheckboxItem Create(int id, OptionTab tab, TranslationStrings.TranslationString translationString, bool defaultValue, OptionItem? parent = null)
internal static OptionCheckboxItem Create(OptionTab tab, TranslationStrings.TranslationString translationString, bool defaultValue, OptionItem? parent = null)
{
if (GetOptionById(id) is OptionCheckboxItem checkboxItem)
if (GetOptionByTranslationName(translationString) is OptionCheckboxItem checkboxItem)
{
checkboxItem.CreateBehavior();
return checkboxItem;
@@ -34,7 +33,6 @@ public sealed class OptionCheckboxItem : OptionItem<bool>
OptionCheckboxItem Item = new();
AllOptions.Add(Item);
Item._id = id;
Item.Tab = tab;
Item.TranslationName = translationString;
Item.DefaultValue = defaultValue;
+3 -5
View File
@@ -36,9 +36,8 @@ public class OptionFloatItem : OptionItem<float>
protected (string prefix, string postfix) Fixs { get; set; }
/// <summary>
/// Creates a new float option item or returns an existing one with the same ID.
/// Creates a new float option item.
/// </summary>
/// <param name="id">The unique identifier for this option.</param>
/// <param name="tab">The tab this option belongs to.</param>
/// <param name="translationString">The translation key for the option name.</param>
/// <param name="Min_Max_Increment">Tuple containing min value, max value, and increment step.</param>
@@ -47,9 +46,9 @@ public class OptionFloatItem : OptionItem<float>
/// <param name="parent">Optional parent option for hierarchical organization.</param>
/// <param name="canBeInfinite">Whether this option can represent infinite values.</param>
/// <returns>A new or existing OptionFloatItem instance.</returns>
internal static OptionFloatItem Create(int id, OptionTab tab, TranslationStrings.TranslationString translationString, (float minValue, float maxValue, float incrementValue) Min_Max_Increment, float defaultValue, (string prefix, string postfix) Prefix_Postfix = new(), OptionItem? parent = null, bool canBeInfinite = false)
internal static OptionFloatItem Create(OptionTab tab, TranslationStrings.TranslationString translationString, (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)
if (GetOptionByTranslationName(translationString) is OptionFloatItem floatItem)
{
floatItem.CreateBehavior();
return floatItem;
@@ -57,7 +56,6 @@ public class OptionFloatItem : OptionItem<float>
OptionFloatItem Item = new();
AllOptions.Add(Item);
Item._id = id;
Item.Tab = tab;
Item.TranslationName = translationString;
Item.Increment = Min_Max_Increment.incrementValue;
+3 -5
View File
@@ -36,9 +36,8 @@ public sealed class OptionIntItem : OptionItem<int>
private (string prefix, string postfix) Fixs { get; set; }
/// <summary>
/// Creates a new integer option item or returns an existing one with the same ID.
/// Creates a new integer option item.
/// </summary>
/// <param name="id">The unique identifier for this option.</param>
/// <param name="tab">The tab this option belongs to.</param>
/// <param name="translationString">The translation key for the option name.</param>
/// <param name="Min_Max_Increment">Tuple containing min value, max value, and increment step.</param>
@@ -47,9 +46,9 @@ public sealed class OptionIntItem : OptionItem<int>
/// <param name="parent">Optional parent option for hierarchical organization.</param>
/// <param name="canBeInfinite">Whether this option can represent infinite values.</param>
/// <returns>A new or existing OptionIntItem instance.</returns>
internal static OptionIntItem Create(int id, OptionTab tab, TranslationStrings.TranslationString translationString, (int minValue, int maxValue, int incrementValue) Min_Max_Increment, int defaultValue, (string prefix, string postfix) Prefix_Postfix = new(), OptionItem? parent = null, bool canBeInfinite = false)
internal static OptionIntItem Create(OptionTab tab, TranslationStrings.TranslationString translationString, (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)
if (GetOptionByTranslationName(translationString) is OptionIntItem intItem)
{
intItem.CreateBehavior();
return intItem;
@@ -57,7 +56,6 @@ public sealed class OptionIntItem : OptionItem<int>
OptionIntItem Item = new();
AllOptions.Add(Item);
Item._id = id;
Item.Tab = tab;
Item.TranslationName = translationString;
Item.Increment = Min_Max_Increment.incrementValue;
+7 -16
View File
@@ -24,15 +24,15 @@ public abstract class OptionItem
/// <summary>
/// Gets the localized display name of the option.
/// </summary>
public string Name => TranslationName != null ? TranslationName.Value.LocalizedString : string.Empty;
public string Name => TranslationName.LocalizedString;
private static int nextIdIndex;
/// <summary>
/// Gets the unique identifier of the option.
/// </summary>
public int Id => _id ?? -1;
public int Id { get; } = 1000 * (++nextIdIndex);
protected int? _id { get; set; } = null;
protected TranslationStrings.TranslationString? TranslationName { get; set; } = null;
protected TranslationStrings.TranslationString TranslationName { get; set; } = default;
internal OptionTab? Tab { get; set; }
internal OptionBehaviour? Option { get; set; }
internal GameObject? Obj { get; set; }
@@ -54,7 +54,7 @@ public abstract class OptionItem
/// </summary>
/// <param name="id">The unique identifier of the option.</param>
/// <returns>The option with the specified ID, or null if not found.</returns>
internal static OptionItem? GetOptionById(int id) => AllOptions.FirstOrDefault(opt => opt._id == id);
internal static OptionItem? GetOptionByTranslationName(TranslationStrings.TranslationString translationString) => AllOptions.FirstOrDefault(opt => opt.TranslationName.Key == translationString.Key);
/// <summary>
/// Updates the visual appearance of the option.
@@ -105,9 +105,6 @@ public abstract class OptionItem
/// <param name="custom">Optional custom text to display instead of the value.</param>
internal void PopNotification(string custom = "")
{
if (_id == null)
return;
string value = custom == string.Empty ? ValueAsString() : custom;
string msg = $"<font=\"Barlow-Black SDF\" material=\"Barlow-Black Outline\">{GetParentPath()} " +
$"<color=#868686><size=85%>{TranslationStrings.BetterSetting_SetTo}</size></color> {value}";
@@ -557,10 +554,7 @@ public abstract class OptionItem<T> : OptionItem
if (!CanLoad)
return;
if (_id == null)
return;
Value = BetterDataManager.LoadSetting(Id, DefaultValue);
Value = BetterDataManager.LoadSetting(TranslationName.Key, DefaultValue);
}
/// <summary>
@@ -571,9 +565,6 @@ public abstract class OptionItem<T> : OptionItem
if (!CanLoad)
return;
if (_id == null)
return;
BetterDataManager.SaveSetting(Id, Value);
BetterDataManager.SaveSetting(TranslationName.Key, Value);
}
}
+3 -5
View File
@@ -8,17 +8,16 @@ namespace BetterAmongUs.Modules.OptionItems;
public sealed class OptionPercentItem : OptionFloatItem
{
/// <summary>
/// Creates a new percentage option item or returns an existing one with the same ID.
/// Creates a new percentage option item.
/// </summary>
/// <param name="id">The unique identifier for this option.</param>
/// <param name="tab">The tab this option belongs to.</param>
/// <param name="translationString">The translation key for the option name.</param>
/// <param name="defaultValue">The default percentage value (0-100).</param>
/// <param name="parent">Optional parent option for hierarchical organization.</param>
/// <returns>A new or existing OptionPercentItem instance.</returns>
internal static OptionPercentItem Create(int id, OptionTab tab, TranslationStrings.TranslationString translationString, float defaultValue, OptionItem? parent = null)
internal static OptionPercentItem Create(OptionTab tab, TranslationStrings.TranslationString translationString, float defaultValue, OptionItem? parent = null)
{
if (GetOptionById(id) is OptionPercentItem floatItem)
if (GetOptionByTranslationName(translationString) is OptionPercentItem floatItem)
{
floatItem.CreateBehavior();
return floatItem;
@@ -26,7 +25,6 @@ public sealed class OptionPercentItem : OptionFloatItem
OptionPercentItem Item = new();
AllOptions.Add(Item);
Item._id = id;
Item.Tab = tab;
Item.TranslationName = translationString;
Item.Increment = 5;
+4 -6
View File
@@ -35,20 +35,19 @@ public sealed class OptionPlayerItem : OptionItem<int>
/// </summary>
internal override bool CanLoad => false;
private static List<OptionPlayerItem> optionPlayerItems = [];
private static readonly List<OptionPlayerItem> optionPlayerItems = [];
/// <summary>
/// Creates a new player option item or returns an existing one with the same ID.
/// Creates a new player option item.
/// </summary>
/// <param name="id">The unique identifier for this option.</param>
/// <param name="tab">The tab this option belongs to.</param>
/// <param name="translationString">The translation key for the option name.</param>
/// <param name="parent">Optional parent option for hierarchical organization.</param>
/// <param name="canBeRandom">Whether this option allows random player selection.</param>
/// <returns>A new or existing OptionPlayerItem instance.</returns>
internal static OptionPlayerItem Create(int id, OptionTab tab, TranslationStrings.TranslationString translationString, OptionItem? parent = null, bool canBeRandom = true)
internal static OptionPlayerItem Create(OptionTab tab, TranslationStrings.TranslationString translationString, OptionItem? parent = null, bool canBeRandom = true)
{
if (optionPlayerItems.FirstOrDefault(opt => opt.Id == id) is OptionPlayerItem playerItem)
if (optionPlayerItems.FirstOrDefault(opt => opt.TranslationName.Key == translationString.Key) is OptionPlayerItem playerItem)
{
playerItem.CreateBehavior();
return playerItem;
@@ -57,7 +56,6 @@ public sealed class OptionPlayerItem : OptionItem<int>
OptionPlayerItem Item = new();
optionPlayerItems.Add(Item);
Item.Value = canBeRandom ? -1 : 0; ;
Item._id = id;
Item.Tab = tab;
Item.TranslationName = translationString;
Item.CanBeRandom = canBeRandom;
+2 -6
View File
@@ -13,15 +13,12 @@ internal sealed class OptionPresetItem : OptionStringItem
internal override bool CanLoad => false;
/// <summary>
/// Creates a new preset item for the options menu. If an item with the preset ID already exists,
/// it reuses the existing item and sets up its behavior.
/// Creates a new preset item for the options menu.
/// </summary>
/// <returns>The created or reused <see cref="OptionPresetItem"/> instance.</returns>
internal static OptionPresetItem Create()
{
int id = int.MaxValue;
if (GetOptionById(id) is OptionPresetItem stringItem)
if (GetOptionByTranslationName(TranslationStrings.Setting_Presets) is OptionPresetItem stringItem)
{
stringItem.CreateBehavior();
return stringItem;
@@ -29,7 +26,6 @@ internal sealed class OptionPresetItem : OptionStringItem
OptionPresetItem Item = new();
AllOptions.Add(Item);
Item._id = id;
Item.Tab = GameSettingsPatch.BetterSettingsTab;
Item.TranslationName = TranslationStrings.Setting_Presets;
Item.TranslatorStrings = Enumerable.Repeat(new TranslationStrings.TranslationString(string.Empty), 10).ToArray();
+3 -5
View File
@@ -20,9 +20,8 @@ public class OptionStringItem : OptionItem<int>
protected TranslationStrings.TranslationString[] TranslatorStrings { get; set; } = [];
/// <summary>
/// Creates a new string option item or returns an existing one with the same ID.
/// Creates a new string option item.
/// </summary>
/// <param name="id">The unique identifier for this option.</param>
/// <param name="tab">The tab this option belongs to.</param>
/// <param name="translationStringName">The translation key for the option name.</param>
/// <param name="translationStrings">Array of translation keys for the selectable string values.</param>
@@ -30,14 +29,14 @@ public class OptionStringItem : OptionItem<int>
/// <param name="parent">Optional parent option for hierarchical organization.</param>
/// <returns>A new or existing OptionStringItem instance.</returns>
/// <exception cref="ArgumentException">Thrown when tranStrings has less than 2 elements.</exception>
internal static OptionStringItem Create(int id, OptionTab tab, TranslationStrings.TranslationString translationStringName, TranslationStrings.TranslationString[] translationStrings, int defaultValue, OptionItem? parent = null)
internal static OptionStringItem Create(OptionTab tab, TranslationStrings.TranslationString translationStringName, TranslationStrings.TranslationString[] translationStrings, int defaultValue, OptionItem? parent = null)
{
if (translationStrings.Length < 2)
{
throw new ArgumentException("tranStrings must have more then 1 string!");
}
if (GetOptionById(id) is OptionStringItem stringItem)
if (GetOptionByTranslationName(translationStringName) is OptionStringItem stringItem)
{
stringItem.CreateBehavior();
return stringItem;
@@ -45,7 +44,6 @@ public class OptionStringItem : OptionItem<int>
OptionStringItem Item = new();
AllOptions.Add(Item);
Item._id = id;
Item.Tab = tab;
Item.TranslationName = translationStringName;
Item.TranslatorStrings = translationStrings;
@@ -1,5 +1,6 @@
using BetterAmongUs.Data;
using BetterAmongUs.Data.Config;
using BetterAmongUs.Data.Json;
using BetterAmongUs.Generated;
using BetterAmongUs.Managers;
using BetterAmongUs.Modules;
@@ -56,6 +57,7 @@ internal static class OptionsMenuBehaviourPatch
ClientOptionItem.CreateToggle(TranslationStrings.BetterOption_VentColorGroups, BAUConfigs.VentColorGroups, 2, __instance, MiniMapBehaviourPatch.ClearMapIcons);
ClientOptionItem.CreateToggle(TranslationStrings.BetterOption_MinimapIcons, BAUConfigs.MinimapIcons, 2, __instance, MiniMapBehaviourPatch.ClearMapIcons);
ClientOptionItem.CreateToggle(TranslationStrings.BetterOption_CompressSettingFiles, BAUConfigs.CompressSettingFiles, 2, __instance, ConvertAllSettingFiles);
// Button options (no toggle)
if (!ModInfo.Starlight)
@@ -126,6 +128,28 @@ internal static class OptionsMenuBehaviourPatch
});
}
private static void ConvertAllSettingFiles()
{
for (int i = 0; i < 10; i++)
{
if (i == BAUConfigs.SettingsPreset.Value)
{
BetterDataManager.Files.BetterGameSettingsFile.Save();
continue;
}
string path = BetterDataManager.GetSettingsFilePathFromPreset(i);
if (File.Exists(path))
{
var settings = new BetterGameSettingsFile
{
OverrideFilePath = path
};
settings.Init();
}
}
}
private static TabGroup CreateTabPage(OptionsMenuBehaviour __instance, string name)
{
// Clone last tab as template for new Better Options tab
@@ -67,79 +67,34 @@ internal static class GameSettingsPatch
if (IsPreload || GameState.IsHost)
{
OptionTitleItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_TextHeader_HostOnly);
BetterGameSettings.WhenCheating = OptionStringItem.Create(100, BetterSettingsTab, TranslationStrings.BetterSetting_Setting_WhenCheating,
BetterGameSettings.WhenCheating = OptionStringItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_WhenCheating,
[TranslationStrings.BetterSetting_Setting_WhenCheating_Notify, TranslationStrings.BetterSetting_Setting_WhenCheating_Kick, TranslationStrings.BetterSetting_Setting_WhenCheating_Ban], 2);
BetterGameSettings.InvalidFriendCode = OptionCheckboxItem.Create(200, BetterSettingsTab, TranslationStrings.BetterSetting_Setting_InvalidFriendCode, true);
BetterGameSettings.CancelInvalidSabotage = OptionCheckboxItem.Create(900, BetterSettingsTab, TranslationStrings.BetterSetting_Setting_CancelInvalidSabotage, true);
BetterGameSettings.UseBanPlayerList = OptionCheckboxItem.Create(300, BetterSettingsTab, TranslationStrings.BetterSetting_Setting_UseBanPlayerList, true);
BetterGameSettings.UseBanNameList = OptionCheckboxItem.Create(400, BetterSettingsTab, TranslationStrings.BetterSetting_Setting_UseBanNameList, true);
BetterGameSettings.UseBanWordList = OptionCheckboxItem.Create(500, BetterSettingsTab, TranslationStrings.BetterSetting_Setting_UseBanWordList, true);
BetterGameSettings.UseBanWordListOnlyLobby = OptionCheckboxItem.Create(1400, BetterSettingsTab, TranslationStrings.BetterSetting_Setting_UseBanWordListOnlyLobby, true, BetterGameSettings.UseBanWordList);
BetterGameSettings.InvalidFriendCode = OptionCheckboxItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_InvalidFriendCode, true);
BetterGameSettings.CancelInvalidSabotage = OptionCheckboxItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_CancelInvalidSabotage, true);
BetterGameSettings.UseBanPlayerList = OptionCheckboxItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_UseBanPlayerList, true);
BetterGameSettings.UseBanNameList = OptionCheckboxItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_UseBanNameList, true);
BetterGameSettings.UseBanWordList = OptionCheckboxItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_UseBanWordList, true);
BetterGameSettings.UseBanWordListOnlyLobby = OptionCheckboxItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_UseBanWordListOnlyLobby, true, BetterGameSettings.UseBanWordList);
}
// General detection settings
OptionTitleItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_TextHeader_Detections);
BetterGameSettings.CensorDetectionReason = OptionCheckboxItem.Create(1300, BetterSettingsTab, TranslationStrings.BetterSetting_Setting_CensorDetectionReason, false);
BetterGameSettings.DetectedLevel = OptionCheckboxItem.Create(1800, BetterSettingsTab, TranslationStrings.BetterSetting_Setting_DetectedLevel, false);
BetterGameSettings.DetectedLevelAbove = OptionIntItem.Create(600, BetterSettingsTab, TranslationStrings.BetterSetting_Setting_DetectedLevelAbove, (100, 10000, 5), 500, ("Lv ", ""), BetterGameSettings.DetectedLevel);
BetterGameSettings.KickLevel = OptionCheckboxItem.Create(1900, BetterSettingsTab, TranslationStrings.BetterSetting_Setting_KickLevel, false);
BetterGameSettings.KickLevelBelow = OptionIntItem.Create(1700, BetterSettingsTab, TranslationStrings.BetterSetting_Setting_KickLevelBelow, (0, 10000, 1), 0, ("Lv ", ""), BetterGameSettings.KickLevel);
BetterGameSettings.DetectCheatClients = OptionCheckboxItem.Create(700, BetterSettingsTab, TranslationStrings.BetterSetting_Setting_DetectCheatClients, true);
BetterGameSettings.DetectInvalidRpcs = OptionCheckboxItem.Create(800, BetterSettingsTab, TranslationStrings.BetterSetting_Setting_DetectInvalidRpcs, true);
BetterGameSettings.RpcRateLimiting = OptionCheckboxItem.Create(2000, BetterSettingsTab, TranslationStrings.BetterSetting_Setting_RpcRateLimiting, true);
BetterGameSettings.RpcRateLimit = OptionIntItem.Create(2100, BetterSettingsTab, TranslationStrings.BetterSetting_Setting_RateLimit, (25, 1000, 1), 50, ("", " PS"), BetterGameSettings.RpcRateLimiting);
BetterGameSettings.CensorDetectionReason = OptionCheckboxItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_CensorDetectionReason, false);
BetterGameSettings.DetectedLevel = OptionCheckboxItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_DetectedLevel, false);
BetterGameSettings.DetectedLevelAbove = OptionIntItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_DetectedLevelAbove, (100, 10000, 5), 500, ("Lv ", ""), BetterGameSettings.DetectedLevel);
BetterGameSettings.KickLevel = OptionCheckboxItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_KickLevel, false);
BetterGameSettings.KickLevelBelow = OptionIntItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_KickLevelBelow, (0, 10000, 1), 0, ("Lv ", ""), BetterGameSettings.KickLevel);
BetterGameSettings.DetectCheatClients = OptionCheckboxItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_DetectCheatClients, true);
BetterGameSettings.DetectInvalidRpcs = OptionCheckboxItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_DetectInvalidRpcs, true);
BetterGameSettings.RpcRateLimiting = OptionCheckboxItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_RpcRateLimiting, true);
BetterGameSettings.RpcRateLimit = OptionIntItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_RateLimit, (25, 1000, 1), 50, ("", " PS"), BetterGameSettings.RpcRateLimiting);
}
// Role algorithm settings
if (IsPreload || GameState.IsHost)
{
OptionHeaderItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_MainHeader_RoleAlgorithm);
BetterGameSettings.RoleRandomizer = OptionStringItem.Create(1100, BetterSettingsTab, TranslationStrings.BetterSetting_Setting_RoleRandomizer, [new("System.Random"), new("UnityEngine.Random")], 0);
// BetterGameSettings.DesyncRoles = OptionCheckboxItem.Create(1200, BetterSettingsTab, "BetterSetting.Setting.DesyncRoles", true);
}
// Gameplay Settings section
{
// Show only for hosts
if (IsPreload || GameState.IsHost)
{
/*
// Normal game mode settings
if (IsPreload || !GameState.IsHideNSeek)
{
OptionHeaderItem.Create(BetterSettingsTab, "BetterSetting.MainHeader.Gameplay");
BetterGameSettings.DisableSabotages = OptionCheckboxItem.Create(1500, BetterSettingsTab, "BetterSetting.Setting.DisableSabotages", false);
BetterGameSettings.DisableSabotagesForDead = OptionCheckboxItem.Create(1600, BetterSettingsTab, "BetterSetting.Setting.DisableSabotagesForDead", false);
}
// Hide & Seek specific settings with dynamic impostor selection
if (IsPreload || GameState.IsHideNSeek)
{
OptionHeaderItem.Create(BetterSettingsTab, "BetterSetting.MainHeader.HideNSeek");
OptionTitleItem.Create(BetterSettingsTab, $"<color={RoleTypes.Impostor.GetRoleHex()}>{Translator.GetString(StringNames.ImpostorsCategory)}</color>");
BetterGameSettings.HideAndSeekImpNum = OptionIntItem.Create(1000, BetterSettingsTab, "BetterSetting.Setting.HideAndSeekImpNum", (1, 5, 1), 1);
// Create player selection dropdowns that appear based on impostor count
BetterGameSettingsTemp.HideAndSeekImp2 = OptionPlayerItem.Create(0, BetterSettingsTab, "BetterSetting.TempSetting.HideAndSeekImpNum", BetterGameSettings.HideAndSeekImpNum);
BetterGameSettingsTemp.HideAndSeekImp2.ShowCondition = () => BetterGameSettings.HideAndSeekImpNum.GetValue() >= 2;
BetterGameSettingsTemp.HideAndSeekImp3 = OptionPlayerItem.Create(1, BetterSettingsTab, "BetterSetting.TempSetting.HideAndSeekImpNum", BetterGameSettings.HideAndSeekImpNum);
BetterGameSettingsTemp.HideAndSeekImp3.ShowCondition = () => BetterGameSettings.HideAndSeekImpNum.GetValue() >= 3 &&
BetterGameSettingsTemp.HideAndSeekImp2.GetValue() != -1;
BetterGameSettingsTemp.HideAndSeekImp4 = OptionPlayerItem.Create(2, BetterSettingsTab, "BetterSetting.TempSetting.HideAndSeekImpNum", BetterGameSettings.HideAndSeekImpNum);
BetterGameSettingsTemp.HideAndSeekImp4.ShowCondition = () => BetterGameSettings.HideAndSeekImpNum.GetValue() >= 4 &&
BetterGameSettingsTemp.HideAndSeekImp2.GetValue() != -1 &&
BetterGameSettingsTemp.HideAndSeekImp3.GetValue() != -1;
BetterGameSettingsTemp.HideAndSeekImp5 = OptionPlayerItem.Create(3, BetterSettingsTab, "BetterSetting.TempSetting.HideAndSeekImpNum", BetterGameSettings.HideAndSeekImpNum);
BetterGameSettingsTemp.HideAndSeekImp5.ShowCondition = () => BetterGameSettings.HideAndSeekImpNum.GetValue() >= 5 &&
BetterGameSettingsTemp.HideAndSeekImp2.GetValue() != -1 &&
BetterGameSettingsTemp.HideAndSeekImp3.GetValue() != -1 &&
BetterGameSettingsTemp.HideAndSeekImp4.GetValue() != -1;
}
*/
}
BetterGameSettings.RoleRandomizer = OptionStringItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_RoleRandomizer, [new("System.Random"), new("UnityEngine.Random")], 0);
}
BetterSettingsTab.UpdateVisuals();
+1
View File
@@ -63,6 +63,7 @@
"BetterOption.ShowFPS": "ShowFPS",
"BetterOption.VentColorGroups": "Vent Color Groups",
"BetterOption.MinimapIcons": "Minimap Icons",
"BetterOption.CompressSettingFiles": "Compress Setting Files",
"BetterOption.SaveData": "Open Save Data",
"BetterOption.ToVanilla": "Switch To Vanilla",
@@ -323,6 +323,11 @@ public static class TranslationStrings
/// </summary>
public static readonly TranslationString BetterOption_MinimapIcons = new("BetterOption.MinimapIcons");
/// <summary>
/// Base Translation: Compress Setting Files
/// </summary>
public static readonly TranslationString BetterOption_CompressSettingFiles = new("BetterOption.CompressSettingFiles");
/// <summary>
/// Base Translation: Open Save Data
/// </summary>