diff --git a/src/MonoScripts/MeetingInfoDisplay.cs b/src/MonoScripts/MeetingInfoDisplay.cs
index 0a3eb36..152ceeb 100644
--- a/src/MonoScripts/MeetingInfoDisplay.cs
+++ b/src/MonoScripts/MeetingInfoDisplay.cs
@@ -1,12 +1,11 @@
-using AmongUs.GameOptions;
-using BetterAmongUs.Attributes;
+using BetterAmongUs.Attributes;
using BetterAmongUs.Data;
using BetterAmongUs.Generated;
using BetterAmongUs.Modules.Support;
using BetterAmongUs.MonoScripts.Extended;
+using BetterAmongUs.Structs;
using BetterAmongUs.Utilities;
using Il2CppInterop.Runtime.Attributes;
-using System.Text;
using TMPro;
using UnityEngine;
@@ -23,8 +22,8 @@ internal sealed class MeetingInfoDisplay : PlayerInfoDisplay
private Vector3 _infoPos;
private Vector3 _TopPos;
- private readonly StringBuilder _sbTag = new(256);
- private readonly StringBuilder _sbInfo = new(256);
+ private readonly SplitStringBuilder _ssTag = new(100, '-');
+ private readonly SplitStringBuilder _ssInfo = new(100, '-');
private string _lastInfoText = "", _lastTopText = "";
private int _lastUpdateFrame;
private const int UPDATE_COOLDOWN = 5;
@@ -75,8 +74,8 @@ internal sealed class MeetingInfoDisplay : PlayerInfoDisplay
if (_pva == null)
return;
- _sbTag.Clear();
- _sbInfo.Clear();
+ _ssTag.Clear();
+ _ssInfo.Clear();
if (_player != null)
{
@@ -142,22 +141,21 @@ internal sealed class MeetingInfoDisplay : PlayerInfoDisplay
if (_player == null || _player.Data == null || _player.ExtendedData() == null)
return;
- SetPlayerTags(_sbTag);
- FormatPlayerInfo(_sbTag, _sbInfo);
+ SetPlayerTags(_ssTag);
+ _ssInfo.Append(_player.GetRoleInfo(true));
- string roleText = GetRoleText();
- UpdateNameTextPosition(roleText, _sbInfo.ToString());
+ UpdateNameTextPosition(_ssInfo.ToString(), _ssInfo.ToString());
- UpdateTextIfChanged(_infoText, _sbInfo.ToString(), ref _lastInfoText);
- UpdateTextIfChanged(_topText, roleText, ref _lastTopText);
+ UpdateTextIfChanged(_infoText, _ssInfo.ToString(), ref _lastInfoText);
+ UpdateTextIfChanged(_topText, _ssTag.ToString(), ref _lastTopText);
}
///
/// Sets player tags based on data from BetterDataManager.
///
- /// StringBuilder for tag text.
+ /// StringBuilder for tag text.
[HideFromIl2Cpp]
- private void SetPlayerTags(StringBuilder sbTag)
+ private void SetPlayerTags(SplitStringBuilder ssTag)
{
if (_player == null)
return;
@@ -167,70 +165,10 @@ internal sealed class MeetingInfoDisplay : PlayerInfoDisplay
if (BetterDataManager.Files.BetterDataFile.TryGetCheatInfo(_player.Data, out var info))
{
- sbTag.Append(info.title.ToColor(info.hexColor) + "+++");
+ ssTag.Append(info.title.ToColor(info.hexColor));
}
}
- ///
- /// Formats player information tags into a readable string.
- ///
- /// StringBuilder containing tags.
- /// StringBuilder for formatted info.
- [HideFromIl2Cpp]
- private static void FormatPlayerInfo(StringBuilder sbTag, StringBuilder sbInfo)
- {
- if (sbTag.Length == 0)
- return;
-
- string tagString = sbTag.ToString();
- string[] tags = tagString.Split(["+++"], StringSplitOptions.RemoveEmptyEntries);
-
- for (int i = 0; i < tags.Length; i++)
- {
- if (!string.IsNullOrEmpty(tags[i]))
- {
- sbInfo.Append(tags[i]);
- if (i < tags.Length - 1)
- {
- sbInfo.Append(" - ");
- }
- }
- }
- }
-
- ///
- /// Gets the role text for display.
- ///
- /// Formatted role text.
- private string GetRoleText()
- {
- if (_player == null) return string.Empty;
-
- string roleHexColor = _player.IsImpostorTeam() ? "#ff1919" : "#8cffff";
- string role = $"{_player.GetRoleName()}";
-
- if (!_player.IsImpostorTeam() && _player.myTasks.Count > 0)
- {
- int completedTasks = 0;
- foreach (var task in _player.Data.Tasks)
- {
- if (task.Complete)
- completedTasks++;
- }
- role += $" ({completedTasks}/{_player.Data.Tasks.Count})";
- }
-
- if (!_player.IsImpostorTeammate())
- {
- if ((PlayerControl.LocalPlayer.IsAlive() || PlayerControl.LocalPlayer.Is(RoleTypes.GuardianAngel)) && !_player.IsLocalPlayer())
- {
- return string.Empty;
- }
- }
-
- return role;
- }
-
///
/// Updates name text position based on role and info text presence.
///
diff --git a/src/MonoScripts/PlayerInfoDisplay.cs b/src/MonoScripts/PlayerInfoDisplay.cs
index 8c70c2f..731aa24 100644
--- a/src/MonoScripts/PlayerInfoDisplay.cs
+++ b/src/MonoScripts/PlayerInfoDisplay.cs
@@ -1,5 +1,4 @@
using AmongUs.Data;
-using AmongUs.GameOptions;
using BetterAmongUs.Attributes;
using BetterAmongUs.Data;
using BetterAmongUs.Data.Config;
@@ -8,10 +7,9 @@ using BetterAmongUs.Modules;
using BetterAmongUs.Modules.Support;
using BetterAmongUs.MonoScripts.Extended;
using BetterAmongUs.Patches.Gameplay.UI.Settings;
+using BetterAmongUs.Structs;
using BetterAmongUs.Utilities;
-using HarmonyLib;
using Il2CppInterop.Runtime.Attributes;
-using System.Text;
using System.Text.RegularExpressions;
using TMPro;
using UnityEngine;
@@ -30,9 +28,9 @@ internal class PlayerInfoDisplay : MonoBehaviour
protected TextMeshPro? _topText;
protected TextMeshPro? _bottomText;
- private readonly StringBuilder _sbTag = new(256);
- private readonly StringBuilder _sbTagTop = new(256);
- private readonly StringBuilder _sbTagBottom = new(256);
+ private readonly SplitStringBuilder _ssTag = new(100, '-');
+ private readonly SplitStringBuilder _ssTagTop = new(100, '-');
+ private readonly SplitStringBuilder _ssTagBottom = new(100, '-');
private string _lastTopText = "", _lastBottomText = "", _lastInfoText = "";
private int _lastUpdateFrame;
private const int UPDATE_COOLDOWN = 10;
@@ -132,9 +130,9 @@ internal class PlayerInfoDisplay : MonoBehaviour
return;
}
- _sbTag.Clear();
- _sbTagTop.Clear();
- _sbTagBottom.Clear();
+ _ssTag.Clear();
+ _ssTagTop.Clear();
+ _ssTagBottom.Clear();
UpdatePlayerInfo();
UpdatePlayerHighlight();
@@ -158,9 +156,6 @@ internal class PlayerInfoDisplay : MonoBehaviour
if (_nameText == null)
return;
- if (_sbTag == null || _sbTagTop == null || _sbTagBottom == null)
- return;
-
if (_topText == null || _bottomText == null || _infoText == null)
return;
@@ -195,20 +190,20 @@ internal class PlayerInfoDisplay : MonoBehaviour
platform = TranslationStrings.Player_PlatformHidden.LocalizedString;
if (!_player.IsInShapeshift())
- SetPlayerOutline(_sbTag);
+ SetPlayerOutline(_ssTag);
if (GameState.IsInGame && GameState.IsLobby && !GameState.IsFreePlay)
{
- SetLobbyInfo(ref newName, betterData, _sbTag);
+ SetLobbyInfo(ref newName, betterData, _ssTag);
- _sbTagTop.Append($"{platform}+++")
- .Append($"Lv: {_player.Data.PlayerLevel + 1}+++");
+ _ssTagTop.Append($"{platform}")
+ .Append($"Lv: {_player.Data.PlayerLevel + 1}");
- _sbTagBottom.Append($"{friendCode}+++");
+ _ssTagBottom.Append($"{friendCode}");
}
else if ((GameState.IsInGame || GameState.IsFreePlay) && !GameState.IsHideNSeek)
{
- SetInGameInfo(_sbTagTop);
+ SetInGameInfo(_ssTagTop);
}
if (!BAUModdedSupportFlags.HasFlag(BAUModdedSupportFlags.Disable_NameOverride))
@@ -236,18 +231,18 @@ internal class PlayerInfoDisplay : MonoBehaviour
}
}
- UpdateTextIfChanged(_topText, _sbTagTop, ref _lastTopText);
- UpdateTextIfChanged(_bottomText, _sbTagBottom, ref _lastBottomText);
- UpdateTextIfChanged(_infoText, _sbTag, ref _lastInfoText);
+ UpdateTextIfChanged(_topText, _ssTagTop, ref _lastTopText);
+ UpdateTextIfChanged(_bottomText, _ssTagBottom, ref _lastBottomText);
+ UpdateTextIfChanged(_infoText, _ssTag, ref _lastInfoText);
}
///
/// Updates text if changed, optimizing performance.
///
/// TextMeshPro component to update.
- /// StringBuilder containing new text.
+ /// StringBuilder containing new text.
/// Reference to last value for comparison.
- private static void UpdateTextIfChanged(TextMeshPro textMesh, StringBuilder sb, ref string lastValue)
+ private static void UpdateTextIfChanged(TextMeshPro textMesh, SplitStringBuilder ss, ref string lastValue)
{
if (BAUModdedSupportFlags.HasFlag(BAUModdedSupportFlags.Disable_PlayerInfo))
{
@@ -258,7 +253,7 @@ internal class PlayerInfoDisplay : MonoBehaviour
if (textMesh == null)
return;
- string newText = Utils.FormatInfo(sb);
+ string newText = ss.ToString();
if (newText != lastValue)
{
textMesh.SetText(newText);
@@ -341,9 +336,9 @@ internal class PlayerInfoDisplay : MonoBehaviour
///
/// Sets player outline based on data from BetterDataManager.
///
- /// StringBuilder for tag text.
+ /// StringBuilder for tag text.
[HideFromIl2Cpp]
- private void SetPlayerOutline(StringBuilder? sbTag)
+ private void SetPlayerOutline(SplitStringBuilder? ssTag)
{
if (_player == null)
return;
@@ -358,9 +353,9 @@ internal class PlayerInfoDisplay : MonoBehaviour
if (BetterDataManager.Files.BetterDataFile.TryGetCheatInfo(_player.Data, out var info))
{
- if (sbTag != null)
+ if (ssTag.HasValue)
{
- sbTag.Append(info.title.ToColor(info.hexColor) + "+++");
+ ssTag.Value.Append(info.title.ToColor(info.hexColor));
}
_player.SetOutlineByHex(true, info.hexColor);
}
@@ -374,7 +369,7 @@ internal class PlayerInfoDisplay : MonoBehaviour
/// Sets lobby specific information.
///
[HideFromIl2Cpp]
- private void SetLobbyInfo(ref string newName, ExtendedPlayerInfo betterData, StringBuilder sbTag)
+ private void SetLobbyInfo(ref string newName, ExtendedPlayerInfo betterData, SplitStringBuilder ssTag)
{
if (betterData == null)
return;
@@ -386,37 +381,20 @@ internal class PlayerInfoDisplay : MonoBehaviour
{
string verificationSymbol = betterData.IsVerifiedBetterUser || _player.IsLocalPlayer() ? "✓ " : "";
- sbTag.AppendFormat("{1}{0}+++",
+ ssTag.AppendFormat("{1}{0}",
TranslationStrings.Player_BetterUser.LocalizedString, verificationSymbol);
}
- sbTag.Append($"ID: {_player.PlayerId}+++");
+ ssTag.Append($"ID: {_player.PlayerId}");
}
///
/// Sets in-game specific information.
///
- /// StringBuilder for top tag text.
+ /// StringBuilder for top tag text.
[HideFromIl2Cpp]
- private void SetInGameInfo(StringBuilder sbTagTop)
+ private void SetInGameInfo(SplitStringBuilder ssTagTop)
{
- if (_player.IsImpostorTeammate() || _player.IsLocalPlayer() ||
- !PlayerControl.LocalPlayer.IsAlive() && !PlayerControl.LocalPlayer.Is(RoleTypes.GuardianAngel))
- {
- string roleInfo = _player.GetRoleName().ToColor(_player.Data.Role.TeamColor);
-
- if (!_player.IsImpostorTeam() && _player.myTasks.Count > 0)
- {
- int completedTasks = 0;
- foreach (var task in _player.Data.Tasks)
- {
- if (task.Complete)
- completedTasks++;
- }
- roleInfo += $" ({completedTasks}/{_player.Data.Tasks.Count})";
- }
-
- sbTagTop.Append(roleInfo + "+++");
- }
+ ssTagTop.Append(_player.GetRoleInfo(true));
}
///
diff --git a/src/Patches/Gameplay/UI/Chat/ChatPatch.cs b/src/Patches/Gameplay/UI/Chat/ChatPatch.cs
index 3df93f7..80685ff 100644
--- a/src/Patches/Gameplay/UI/Chat/ChatPatch.cs
+++ b/src/Patches/Gameplay/UI/Chat/ChatPatch.cs
@@ -1,14 +1,13 @@
-using AmongUs.GameOptions;
-using BetterAmongUs.Data;
+using BetterAmongUs.Data;
using BetterAmongUs.Data.Config;
using BetterAmongUs.Generated;
using BetterAmongUs.Modules;
using BetterAmongUs.Modules.Support;
using BetterAmongUs.MonoScripts.Extended;
+using BetterAmongUs.Structs;
using BetterAmongUs.Utilities;
using BetterAmongUs.Utilities.Extension;
using HarmonyLib;
-using System.Text;
using TMPro;
using UnityEngine;
@@ -234,21 +233,15 @@ internal static class ChatPatch
if (localPlayer == null)
return;
- StringBuilder sbTag = new();
- StringBuilder sbInfo = new();
+ SplitStringBuilder ssTag = new(100, '-');
string hashPuid = Utils.GetHashPuid(sourcePlayer);
string friendCode = playerInfo.FriendCode;
string playerName = playerInfo.ExtendedData()?.RealName ?? "???";
- // Format role display with team color
- string Role = $"{sourcePlayer.GetRoleName()}+++";
-
// In lobby, show player tags instead of roles
if (GameState.IsLobby && !GameState.IsFreePlay)
{
- Role = "";
-
var betterData = sourcePlayer.ExtendedData();
if (betterData == null)
return;
@@ -256,50 +249,25 @@ internal static class ChatPatch
// Show BAU user tag
if (sourcePlayer.IsLocalPlayer() || betterData.IsBetterUser)
{
- sbTag.AppendFormat("{1}{0}+++", TranslationStrings.Player_BetterUser, betterData.IsVerifiedBetterUser || sourcePlayer.IsLocalPlayer() ? "✓ " : "");
+ ssTag.AppendFormat("{1}{0}", TranslationStrings.Player_BetterUser.LocalizedString, betterData.IsVerifiedBetterUser || sourcePlayer.IsLocalPlayer() ? "✓ " : "");
}
// Show mod-specific tags based on player data
if (BetterDataManager.Files.BetterDataFile.TryGetCheatInfo(sourcePlayer.Data, out var info))
{
- sbTag.Append(info.title.ToColor(info.hexColor) + "+++");
+ ssTag.Append(info.title.ToColor(info.hexColor));
}
}
- // Hide roles from alive players (unless same team)
- if (!sourcePlayer.IsImpostorTeammate())
- {
- if (localPlayer.IsAlive() && !sourcePlayer.IsLocalPlayer())
- Role = "";
- }
+ ssTag.Append(sourcePlayer.GetRoleInfo(false).Size(75f));
- // Show role for dead players or if local player is Guardian Angel
- if ((localPlayer.Is(RoleTypes.GuardianAngel) && !sourcePlayer.IsAlive()) || !localPlayer.Is(RoleTypes.GuardianAngel))
- {
- sbTag.Append(Role);
- }
-
- // Format tags with separators
- sbInfo.Append("");
- var parts = sbTag.ToString().Split("+++");
-
- for (int i = 0; i < parts.Length; i++)
- {
- if (string.IsNullOrEmpty(parts[i])) continue;
-
- sbInfo.Append(parts[i]);
-
- if (i != parts.Length - 2)
- sbInfo.Append(" - ");
- }
-
- sbInfo.Append("");
+ string infoText = ssTag.ToString().Size(75f);
// Position tags before local player name, after other players' names
if (sourcePlayer.IsLocalPlayer())
- playerName = sbInfo + " " + playerName;
+ playerName = infoText + " " + playerName;
else
- playerName += " " + sbInfo;
+ playerName += " " + infoText;
bubble.NameText.SetText(playerName);
}
diff --git a/src/Structs/SplitStringBuilder.cs b/src/Structs/SplitStringBuilder.cs
new file mode 100644
index 0000000..b6c57df
--- /dev/null
+++ b/src/Structs/SplitStringBuilder.cs
@@ -0,0 +1,70 @@
+using BetterAmongUs.Utilities;
+using System.Text;
+
+namespace BetterAmongUs.Structs;
+
+///
+/// Represents a mutable string builder that concatenates text with a specified separator.
+///
+internal readonly struct SplitStringBuilder(int capacity, char? separator = null)
+{
+ private readonly char? _separator = separator;
+ private readonly StringBuilder _value = new(capacity);
+
+ ///
+ /// Appends a text segment to the current string with automatic separator insertion.
+ ///
+ /// The text segment to append.
+ /// The current instance for fluent method chaining.
+ internal SplitStringBuilder Append(string text)
+ {
+ if (Utils.RemoveHtmlText(text) == string.Empty)
+ return this;
+
+ if (_value.Length > 0)
+ {
+ if (_separator != null)
+ {
+ _value.Append($" {_separator} ");
+ }
+ else
+ {
+ _value.Append(' ');
+ }
+ }
+
+ _value.Append(text);
+ return this;
+ }
+
+ ///
+ /// Appends a formatted string to the current builder with automatic separator insertion.
+ ///
+ /// A composite format string.
+ /// An array of objects to format.
+ /// The current instance for fluent method chaining.
+ internal SplitStringBuilder AppendFormat(string format, params string[] strings)
+ {
+ return Append(string.Format(format, strings));
+ }
+
+ ///
+ /// Clears all content from the string builder, resetting it to an empty state.
+ ///
+ internal void Clear()
+ {
+ _value.Clear();
+ }
+
+ ///
+ /// Returns the complete concatenated string with all appended segments.
+ ///
+ ///
+ /// The full string containing all appended text segments separated by the configured separator.
+ /// Returns if no text has been appended.
+ ///
+ public readonly override string ToString()
+ {
+ return _value.ToString();
+ }
+}
\ No newline at end of file
diff --git a/src/Utilities/RoleUtils.cs b/src/Utilities/RoleUtils.cs
index a64cf6f..323ff69 100644
--- a/src/Utilities/RoleUtils.cs
+++ b/src/Utilities/RoleUtils.cs
@@ -1,4 +1,5 @@
using AmongUs.GameOptions;
+using BetterAmongUs.Modules;
namespace BetterAmongUs.Utilities;
@@ -83,23 +84,48 @@ internal static class RoleUtils
}
///
- /// Determines whether a role type is cliant side and can be desynced.
+ /// Gets the role information for a specified player, optionally including task progress.
///
- /// The role type to check.
- /// True if the role can be desynced to other clients, false otherwise.
- internal static bool CanDesyncRole(this RoleTypes role)
+ /// The player to get role information for.
+ /// Whether to display the player's task completion progress.
+ /// Returns role info or if the player's role information cannot be displayed.
+ internal static string GetRoleInfo(this PlayerControl player, bool displayTask)
{
- if (role is RoleTypes.Phantom or RoleTypes.Viper)
+ if (!GameState.IsInGamePlay)
{
- return false;
+ return string.Empty;
}
- if (role is RoleTypes.Noisemaker)
+ if (!player.IsLocalPlayer() && !player.IsImpostorTeammate())
{
- return false;
+ if (PlayerControl.LocalPlayer.IsAlive())
+ {
+ return string.Empty;
+ }
+
+ if (PlayerControl.LocalPlayer.Is(RoleTypes.GuardianAngel))
+ {
+ return string.Empty;
+ }
}
- return true;
+ string roleInfo = player.GetRoleName().ToColor(player.Data.Role.TeamColor);
+
+ if (!player.IsImpostorTeam() && player.myTasks.Count > 0)
+ {
+ if (displayTask)
+ {
+ int completedTasks = 0;
+ foreach (var task in player.Data.Tasks)
+ {
+ if (task.Complete)
+ completedTasks++;
+ }
+ roleInfo += $" ({completedTasks}/{player.Data.Tasks.Count})";
+ }
+ }
+
+ return roleInfo;
}
///
diff --git a/src/Utilities/Utils.cs b/src/Utilities/Utils.cs
index 90977f2..3198b21 100644
--- a/src/Utilities/Utils.cs
+++ b/src/Utilities/Utils.cs
@@ -43,26 +43,6 @@ 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;
-
- var sb = new StringBuilder();
- foreach (var part in source.ToString().Split("+++"))
- {
- if (!string.IsNullOrEmpty(RemoveHtmlText(part)))
- {
- sb.Append(part).Append(" - ");
- }
- }
- return sb.ToString().TrimEnd(" - ".ToCharArray());
- }
-
///
/// Removes all HTML tags and formatting from a string.
///