Add comments to patches

This commit is contained in:
D1GQ committed 2026-02-01 18:17:02 -06:00
1 parent 9bb98b7bc7
commit 49ee73740b
20 files changed
+370 -62

No files matched your search

+13 -3
View File
@@ -15,14 +15,18 @@ internal static class AnnouncementPanelPatch
{
[HarmonyPatch(typeof(PlayerAnnouncementData), nameof(PlayerAnnouncementData.SetAnnouncements))]
[HarmonyPrefix]
private static bool PlayerAnnouncementData_SetModAnnouncements_Prefix(PlayerAnnouncementData __instance, ref Il2CppReferenceArray<Announcement> aRange)
private static void PlayerAnnouncementData_SetModAnnouncements_Prefix(PlayerAnnouncementData __instance, ref Il2CppReferenceArray<Announcement> aRange)
{
// Load and process mod news from github
ModNews.ProcessModNewsFiles();
// Sort mod news by date, newest first (higher number = newer date)
ModNews.AllModNews.Sort((a1, a2) => DateTime.Compare(DateTime.Parse(a2.Date), DateTime.Parse(a1.Date)));
// Convert all mod news to Announcement objects
var finalAllNews = ModNews.AllModNews.Select(n => n.ToAnnouncement()).ToList();
// Add original game announcements that aren't mod news
foreach (var news in aRange)
{
if (!ModNews.AllModNews.Any(x => x.Number == news.Number))
@@ -31,37 +35,42 @@ internal static class AnnouncementPanelPatch
}
}
// Sort combined list by date (newest first) using proper date parsing
finalAllNews.Sort((a1, a2) =>
DateTime.Compare(
DateTime.Parse(a2.Date, null, DateTimeStyles.RoundtripKind),
DateTime.Parse(a1.Date, null, DateTimeStyles.RoundtripKind)
));
// Convert List<Announcement> back to Il2CppReferenceArray<Announcement> for game compatibility
aRange = new Il2CppReferenceArray<Announcement>(finalAllNews.Count);
for (int i = 0; i < finalAllNews.Count; i++)
{
aRange[i] = finalAllNews[i];
}
return true;
}
[HarmonyPatch(typeof(AnnouncementPanel), nameof(AnnouncementPanel.SetUp))]
[HarmonyPostfix]
private static void AnnouncementPanel_SetUpPanel_Postfix(AnnouncementPanel __instance, Announcement announcement)
{
// Check if this is a mod announcement (mod news have numbers >= 100000)
if (announcement.Number >= 100000)
{
// Create a GameObject to display the mod icon/label
var obj = new GameObject("ModLabel");
obj.transform.SetParent(__instance.transform);
// Position in top-left corner of announcement panel
obj.transform.localPosition = new Vector3(-0.8f, 0.13f, 0.5f);
obj.transform.localScale = new Vector3(0.9f, 0.9f, 0.9f);
// Add SpriteRenderer to display the icon
var renderer = obj.AddComponent<SpriteRenderer>();
var modNews = ModNews.AllModNews.Find(a => a.Number == announcement.Number);
if (modNews != null)
{
// Load appropriate icon based on mod type
switch (modNews.NewsType)
{
case NewsTypes.BAU:
@@ -69,6 +78,7 @@ internal static class AnnouncementPanelPatch
break;
}
// Ensure icon stays within announcement panel bounds
renderer.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
}
}
+26 -2
View File
@@ -18,6 +18,7 @@ internal static class ClientPatch
[HarmonyPostfix]
private static void AccountTab_Awake_Postfix(AccountTab __instance)
{
// Apply custom UI colors to the friends button
__instance.signInStatusComponent.friendsButton.SetUIColors();
}
@@ -25,24 +26,29 @@ internal static class ClientPatch
[HarmonyPrefix]
private static bool SignInStatusComponent_SetOnline_Prefix(SignInStatusComponent __instance)
{
// Get supported Among Us versions for BAU
var varSupportedVersions = BAUPlugin.SupportedAmongUsVersions;
Version currentVersion = new(BAUPlugin.AppVersion);
Version firstSupportedVersion = new(varSupportedVersions.First());
Version lastSupportedVersion = new(varSupportedVersions.Last());
// Check if current Among Us version is higher than supported range
if (currentVersion > firstSupportedVersion)
{
var verText = $"<b>{varSupportedVersions.First()}</b>";
// Format version range if there are multiple supported versions
if (firstSupportedVersion != lastSupportedVersion)
{
verText = $"<b>{varSupportedVersions.Last()}</b> - <b>{varSupportedVersions.First()}</b>";
}
// Show warning popup for newer Among Us version
Utils.ShowPopUp($"<size=200%>-= <color=#ff2200><b>Warning</b></color> =-</size>\n\n" +
$"<size=125%><color=#0dff00>Better Among Us {BAUPlugin.GetVersionText()}</color>\nsupports <color=#4f92ff>Among Us {verText}</color>,\n" +
$"<color=#4f92ff>Among Us <b>{BAUPlugin.AppVersion}</b></color> is above the supported versions!\n" +
$"<color=#ae1700>You may encounter minor to game breaking bugs.</color></size>");
}
// Check if current Among Us version is lower than supported range
else if (currentVersion < lastSupportedVersion)
{
var verText = $"<b>{varSupportedVersions.First()}</b>";
@@ -51,6 +57,7 @@ internal static class ClientPatch
verText = $"<b>{varSupportedVersions.Last()}</b> - <b>{varSupportedVersions.First()}</b>";
}
// Show warning popup for older Among Us version
Utils.ShowPopUp($"<size=200%>-= <color=#ff2200><b>Warning</b></color> =-</size>\n\n" +
$"<size=125%><color=#0dff00>Better Among Us {BAUPlugin.GetVersionText()}</color>\nsupports <color=#4f92ff>Among Us {verText}</color>,\n" +
$"<color=#4f92ff>Among Us <b>{BAUPlugin.AppVersion}</b></color> is below the supported versions!\n" +
@@ -64,6 +71,7 @@ internal static class ClientPatch
[HarmonyPostfix]
private static void AmongUsClient_ExitGame_Postfix([HarmonyArgument(0)] DisconnectReasons reason)
{
// Hide custom loading bar when exiting game
CustomLoadingBarManager.ToggleLoadingBar(false);
Logger_.Log($"Client has left game for: {Enum.GetName(reason)}", "AmongUsClientPatch");
}
@@ -72,11 +80,13 @@ internal static class ClientPatch
[HarmonyPrefix]
private static void AmongUsClient_OnGameEnd_Prefix()
{
// Preserve all player GameObjects during scene transitions
foreach (var data in GameData.Instance.AllPlayers)
{
UnityEngine.Object.DontDestroyOnLoad(data.gameObject);
}
// Move player GameObjects to active scene after a short delay
LateTask.Schedule(() =>
{
foreach (var data in GameData.Instance.AllPlayers)
@@ -90,17 +100,22 @@ internal static class ClientPatch
[HarmonyPostfix]
private static void AmongUsClient_CoStartGame_Postfix(AmongUsClient __instance)
{
// Clear in-game chat if chat feature is enabled
if (BAUPlugin.ChatInGameplay.Value)
{
ChatPatch.ClearChat();
}
// Start custom loading sequence
__instance.StartCoroutine(CoLoading());
}
private static IEnumerator CoLoading()
{
// Show custom loading bar
CustomLoadingBarManager.ToggleLoadingBar(true);
// Run different loading logic for host vs client
if (GameState.IsHost)
{
yield return CoLoadingHost();
@@ -110,6 +125,7 @@ internal static class ClientPatch
yield return CoLoadingClient();
}
// Mark loading as complete and hide bar after delay
CustomLoadingBarManager.SetLoadingPercent(100f, "Complete");
yield return new WaitForSeconds(0.25f);
CustomLoadingBarManager.ToggleLoadingBar(false);
@@ -120,8 +136,10 @@ internal static class ClientPatch
var client = AmongUsClient.Instance.GetClient(AmongUsClient.Instance.ClientId);
var clients = AmongUsClient.Instance.allClients;
// Continue loading while there are unassigned roles
while (BAUPlugin.AllPlayerControls.Count > 0 && BAUPlugin.AllPlayerControls.Any(pc => !pc.roleAssigned))
{
// Early exit if game ended during loading
if (!GameState.IsInGame)
{
CustomLoadingBarManager.ToggleLoadingBar(false);
@@ -131,6 +149,7 @@ internal static class ClientPatch
string loadingText = "Initializing Game";
float progress = 0f;
// Progress through different loading stages
if (AmongUsClient.Instance.GameState != InnerNetClient.GameStates.Started)
{
loadingText = "Starting Game Session";
@@ -150,6 +169,7 @@ internal static class ClientPatch
}
else if (BAUPlugin.AllPlayerControls.Any(player => !player.roleAssigned))
{
// Calculate role assignment progress
int totalPlayers = BAUPlugin.AllPlayerControls.Count;
int assignedPlayers = BAUPlugin.AllPlayerControls.Count(pc => pc.roleAssigned);
float assignmentProgress = (float)assignedPlayers / Mathf.Max(1, totalPlayers);
@@ -159,6 +179,7 @@ internal static class ClientPatch
}
else if (!client.IsReady)
{
// Wait for other clients to be ready
int readyClients = clients.CountIl2Cpp(c => c?.Character != null && c.IsReady);
int totalClients = clients.CountIl2Cpp(c => c?.Character != null);
@@ -166,6 +187,7 @@ internal static class ClientPatch
progress = 0.8f + 0.2f * readyClients / Mathf.Max(1, totalClients);
}
// Update loading bar with current progress
int percent = Mathf.RoundToInt(progress * 100f);
CustomLoadingBarManager.SetLoadingPercent(percent, loadingText);
@@ -178,9 +200,10 @@ internal static class ClientPatch
var client = AmongUsClient.Instance.GetClient(AmongUsClient.Instance.ClientId);
var clients = AmongUsClient.Instance.allClients;
// Client loading logic (similar to host but with some differences)
while (BAUPlugin.AllPlayerControls.Count > 0 && BAUPlugin.AllPlayerControls.Any(pc => !pc.roleAssigned))
{
// Switch to host logic if client becomes host mid-loading
if (GameState.IsHost)
{
yield return CoLoadingHost();
@@ -220,6 +243,7 @@ internal static class ClientPatch
}
else
{
// Wait for other players (including host) to be ready
int readyClients = clients.CountIl2Cpp(c => c?.Character != null && c.IsReady);
int totalClients = clients.CountIl2Cpp(c => c?.Character != null);
@@ -233,4 +257,4 @@ internal static class ClientPatch
yield return null;
}
}
}
}
+4 -1
View File
@@ -10,9 +10,12 @@ internal static class DisconnectPenaltyPatch
[HarmonyPrefix]
private static bool PlayerBanData_IsBanned_Prefix(PlayerBanData __instance, ref bool __result)
{
// Reset ban points to zero to prevent disconnect penalties
__instance.BanPoints = 0f;
__instance.banPoints = 0f;
// Always return false (not banned) regardless of actual ban status
__result = false;
return false;
}
}
}
+10 -1
View File
@@ -16,21 +16,26 @@ internal static class DiscordPatch
[HarmonyPrefix]
private static void ActivityManager_UpdateActivity_Prefix(Activity activity)
{
// Skip Discord Rich Presence if other mods have disabled it via BAUModdedSupportFlags
if (BAUModdedSupportFlags.HasFlag(BAUModdedSupportFlags.Disable_DiscordRP)) return;
if (activity == null) return;
string details = $"BAU {BAUPlugin.GetVersionText()}";
activity.Details = details;
// Skip lobby info processing if Discord already shows "In Menus"
if (activity.State == "In Menus") return;
try
{
// Only show lobby code/region if streamer mode is off
if (!DataManager.Settings.Gameplay.StreamerMode)
{
UpdateRegionAndLobbyCode();
// Build lobby info string only when both code and region exist
if (!string.IsNullOrEmpty(lobbycode) && !string.IsNullOrEmpty(region))
{
// Show lobby code with region in parentheses
if (GameState.IsNormalGame)
details = $"BAU - {lobbycode} ({region})";
else if (GameState.IsHideNSeek)
@@ -39,12 +44,14 @@ internal static class DiscordPatch
}
else
{
// Streamer mode hides lobby code, only show mode info
if (GameState.IsHideNSeek)
details = $"BAU v{ModInfo.PLUGIN_VERSION} - Hide & Seek";
}
}
catch
{
// Silent fail to prevent Discord crashes from affecting gameplay
}
activity.Details = details;
@@ -52,12 +59,14 @@ internal static class DiscordPatch
private static void UpdateRegionAndLobbyCode()
{
// Only fetch lobby data when player is in a lobby
if (GameState.IsLobby)
{
if (GameStartManager.Instance?.GameRoomNameCode != null)
{
lobbycode = GameStartManager.Instance.GameRoomNameCode.text;
region = ServerManager.Instance.CurrentRegion.Name;
// Convert full region names to short codes
region = region switch
{
"North America" => "NA",
@@ -71,4 +80,4 @@ internal static class DiscordPatch
}
}
}
}
}
+15 -3
View File
@@ -16,8 +16,9 @@ internal static class InnerNetClientPatch
[HarmonyPrefix]
private static bool InnerNetClient_SendOrDisconnect_Prefix(InnerNetClient __instance, MessageWriter msg)
{
// Route all outgoing messages through custom NetworkManager
// This allows BAU to intercept/modify network traffic
NetworkManager.SendToServer(msg);
return false;
}
@@ -25,6 +26,8 @@ internal static class InnerNetClientPatch
[HarmonyPrefix]
private static bool InnerNetClient_HandleGameDataInner_Prefix([HarmonyArgument(0)] MessageReader oldReader)
{
// Route all incoming game data through custom NetworkManager
// This allows BAU to process/modify incoming network messages
NetworkManager.HandleGameData(oldReader);
return false;
}
@@ -33,6 +36,7 @@ internal static class InnerNetClientPatch
[HarmonyPrefix]
private static bool InnerNetClient_CanBan_Prefix(ref bool __result)
{
// Only allow hosts to ban players in BAU
__result = GameState.IsHost;
return false;
}
@@ -41,7 +45,11 @@ internal static class InnerNetClientPatch
[HarmonyPrefix]
private static bool InnerNetClient_CanKick_Prefix(ref bool __result)
{
__result = GameState.IsHost || GameState.IsInGamePlay && (GameState.IsMeeting || GameState.IsExilling);
// Allow kicking under specific conditions:
// 1. Host can always kick
// 2. Non-hosts can only kick during meetings or when player is being voted out
__result = GameState.IsHost || (GameState.IsInGamePlay && (GameState.IsMeeting || GameState.IsExilling));
return false;
}
@@ -49,10 +57,14 @@ internal static class InnerNetClientPatch
[HarmonyPrefix]
private static void InnerNetClient_KickPlayer_Prefix(ref int clientId, ref bool ban)
{
// When banning a player, add them to BAU's custom ban list if enabled
if (ban && BetterGameSettings.UseBanPlayerList.GetBool())
{
// Get player info from client ID
NetworkedPlayerInfo info = Utils.PlayerFromClientId(clientId).Data;
// Add player to ban list using both friend code and PUID
BetterDataManager.AddToBanList(info.FriendCode, info.Puid);
}
}
}
}
@@ -15,42 +15,57 @@ internal static class MainMenuManagerPatch
[HarmonyPostfix]
private static void MainMenuManager_LateUpdate_Postfix(MainMenuManager __instance)
{
// Create list of all main menu buttons to recolor
List<PassiveButton> buttons = [__instance.playButton, __instance.inventoryButton, __instance.shopButton, __instance.playLocalButton, __instance.PlayOnlineButton, __instance.backButtonOnline,
__instance.newsButton, __instance.myAccountButton, __instance.settingsButton, __instance.howToPlayButton, __instance.freePlayButton, __instance.accountCTAButton, __instance.accountStatsButton];
// Apply custom UI colors to each button's icon and background
foreach (var button in buttons)
{
button.gameObject?.SetUIColors(sprite =>
{
// Only recolor white sprites to preserve original color variations
return sprite.color == Color.white;
},
"Icon", "Background");
}
}
// Replace AU logo with BAU logo
[HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))]
[HarmonyPostfix]
private static void MainMenuManager_Start_Postfix(MainMenuManager __instance)
{
// Check if other mods haven't disabled the BAU logo replacement
if (!BAUModdedSupportFlags.HasFlag(BAUModdedSupportFlags.Disable_BAULogo))
{
// Find the original Among Us logo
GameObject logo = GameObject.Find("LeftPanel/Sizer/LOGO-AU");
GameObject sizer = logo.transform.parent.gameObject;
// Adjust logo position downward and move it forward in Z-axis
sizer.transform.localPosition = new Vector3(sizer.transform.localPosition.x, sizer.transform.localPosition.y - 0.035f, sizer.transform.localPosition.z);
sizer.transform.position = new Vector3(sizer.transform.position.x, sizer.transform.position.y, -0.5f);
// Scale down the logo slightly
logo.transform.localScale = new Vector3(0.003f, 0.0025f, 0f);
// Replace Among Us sprite with Better Among Us logo
logo.GetComponent<SpriteRenderer>().sprite = Utils.LoadSprite("BetterAmongUs.Resources.Images.BetterAmongUs-Logo.png", 1f);
}
// Apply custom colors to main menu background
__instance.transform.Find("MainUI/AspectScaler/BackgroundTexture")?.gameObject?.SetSpriteColors(sprite => ObjectHelper.AddColor(sprite));
// Create a reusable button prefab if it doesn't exist yet
if (ButtonPrefab == null)
{
// Clone inventory button as template for custom UI elements
ButtonPrefab = UnityEngine.Object.Instantiate(__instance.inventoryButton);
ButtonPrefab.gameObject.SetActive(false);
UnityEngine.Object.DontDestroyOnLoad(ButtonPrefab);
}
// Notify UpdateManager that we're in the main menu
UpdateManager.Instance?.OnMainMenu();
}
}
}
@@ -16,19 +16,24 @@ internal static class ModManagerPatch
[HarmonyPostfix]
private static void LateUpdate_Postfix(ModManager __instance)
{
// Show the mod stamp only after the splash screen has fully loaded
if (SplashIntroPatch.IsReallyDoneLoading)
{
__instance.ShowModStamp();
}
// Check if the mod stamp is currently visible in the UI
if (__instance.ModStamp.gameObject.active == true)
{
// Cache the SpriteRenderer component on first access
if (modStamp == null)
{
modStamp = __instance.ModStamp.GetComponent<SpriteRenderer>();
}
else
{
// Replace the mod stamp sprite with BAU's custom version
// unless other mods have disabled custom mod stamps
if (!BAUModdedSupportFlags.HasFlag(BAUModdedSupportFlags.Disable_CustomModStamp))
{
modStamp.sprite = Utils.LoadSprite("BetterAmongUs.Resources.Images.BetterAmongUs-Mod.png", 250f);
@@ -36,8 +41,9 @@ internal static class ModManagerPatch
}
}
// Update various BAU systems each frame
BetterAntiCheat.Update();
LateTask.UpdateAll(Time.deltaTime);
BetterNotificationManager.Update();
}
}
}
@@ -21,8 +21,13 @@ internal static class OptionsMenuBehaviourPatch
[HarmonyPostfix]
private static void Start_Postfix(OptionsMenuBehaviour __instance)
{
// Create custom "Better Options" tab in settings menu
BetterOptionsTab = CreateTabPage(__instance, Translator.GetString("BetterOption"));
// Populate the tab with all BAU client options
SetupAllClientOptions(__instance);
// Reposition tabs to fit new Better Options tab
UpdateTabPositions(__instance);
}
@@ -30,6 +35,7 @@ internal static class OptionsMenuBehaviourPatch
{
if (__instance.DisableMouseMovement == null) return;
// Clear previous client options to prevent duplicates
ClientOptionItem.ClientOptions.Clear();
// Toggle options with config binding
@@ -47,6 +53,7 @@ internal static class OptionsMenuBehaviourPatch
// Button options (no toggle)
ClientOptionItem.CreateButton(Translator.GetString("BetterOption.SaveData"), __instance, OpenSaveData, () =>
{
// Only allow opening save data in lobby/main menu, not during gameplay
bool cannotOpen = GameState.IsInGame && !GameState.IsLobby;
if (cannotOpen)
{
@@ -57,6 +64,7 @@ internal static class OptionsMenuBehaviourPatch
ClientOptionItem.CreateButton(Translator.GetString("BetterOption.ToVanilla"), __instance, SwitchToVanilla, () =>
{
// Prevent switching to vanilla while in a game
bool cannotSwitch = GameState.IsInGame;
if (cannotSwitch)
{
@@ -68,6 +76,7 @@ internal static class OptionsMenuBehaviourPatch
private static void SwitchToVanilla()
{
// Clean up BAU mod components and return to vanilla Among Us
ConsoleManager.DetachConsole();
BetterNotificationManager.BAUNotificationManagerObj?.DestroyObj();
Harmony.UnpatchAll();
@@ -77,6 +86,7 @@ internal static class OptionsMenuBehaviourPatch
private static void SendBetterRpcAction()
{
// Resend handshake secret to all other players when option is toggled
if (!GameState.IsInGame) return;
foreach (var player in BAUPlugin.AllPlayerControls)
@@ -88,6 +98,7 @@ internal static class OptionsMenuBehaviourPatch
private static void ClearNotifications()
{
// Clear all active notifications when option is toggled
BetterNotificationManager.NotifyQueue.Clear();
BetterNotificationManager.showTime = 0f;
BetterNotificationManager.Notifying = false;
@@ -95,6 +106,7 @@ internal static class OptionsMenuBehaviourPatch
private static void ToggleLobbyTheme()
{
// Play lobby theme music if re-enabled while in lobby
if (GameState.IsLobby && !BAUPlugin.DisableLobbyTheme.Value)
{
SoundManager.instance.CrossFadeSound("MapTheme", LobbyBehaviour.Instance.MapTheme, 0.5f, 1.5f);
@@ -103,11 +115,13 @@ internal static class OptionsMenuBehaviourPatch
private static void UpdateFrameRate()
{
// Toggle between 60 FPS (default) and 165 FPS
Application.targetFrameRate = BAUPlugin.UnlockFPS.Value ? 165 : 60;
}
private static void OpenSaveData()
{
// Open BAU save data folder in file explorer
if (!File.Exists(BetterDataManager.dataPath)) return;
Process.Start(new ProcessStartInfo
@@ -120,6 +134,7 @@ internal static class OptionsMenuBehaviourPatch
private static TabGroup CreateTabPage(OptionsMenuBehaviour __instance, string name)
{
// Clone last tab as template for new Better Options tab
var tabPrefab = __instance.Tabs[^1];
var tab = UnityEngine.Object.Instantiate(tabPrefab, tabPrefab.transform.parent);
@@ -128,15 +143,18 @@ internal static class OptionsMenuBehaviourPatch
tab.GetComponentInChildren<TextMeshPro>(true)?.SetText(name);
tab.gameObject.SetActive(true);
// Create content container for the new tab
var content = new GameObject($"{name}Tab");
content.SetActive(false);
content.transform.SetParent(tab.Content.transform.parent);
content.transform.localScale = Vector3.one;
tab.Content = content;
// Add new tab to the tabs array
var tabs = new List<TabGroup>(__instance.Tabs) { tab };
__instance.Tabs = tabs.ToArray();
// Set up click handler for the tab button
var index = __instance.Tabs.Length - 1;
var button = tab.GetComponent<PassiveButton>();
button.OnClick = new();
@@ -151,10 +169,12 @@ internal static class OptionsMenuBehaviourPatch
private static void UpdateTabPositions(OptionsMenuBehaviour __instance)
{
// Position tabs based on game state (in-game vs main menu)
Vector3 basePos = new(0f, !GameState.InGame ? 0 : 2.5f, -1f);
const float buttonSpacing = 0.6f;
const float buttonWidth = 1.0f;
// Count only active tabs
int activeCount = 0;
foreach (var tabButton in __instance.Tabs)
{
@@ -163,9 +183,11 @@ internal static class OptionsMenuBehaviourPatch
if (activeCount == 0) return;
// Calculate total width needed for all active tabs
float totalWidth = (activeCount - 1) * buttonSpacing + activeCount * buttonWidth;
float startX = basePos.x - (totalWidth / 2f) + (buttonWidth / 2f);
// Position each active tab evenly spaced
int activeIndex = 0;
foreach (var tabButton in __instance.Tabs)
{
+26 -6
View File
@@ -11,31 +11,38 @@ namespace BetterAmongUs.Patches.Client;
internal static class PrivateLobbyPatch
{
private static GameObject? toggle;
private static List<PassiveButton>? buttons = [];
private static readonly List<PassiveButton>? buttons = [];
private static TextMeshPro? toggleText;
[HarmonyPatch(typeof(CreateGameOptions), nameof(CreateGameOptions.Show))]
[HarmonyPostfix]
private static void CreateGameOptions_Show_Postfix(CreateGameOptions __instance)
{
// Check if other mods have disabled private lobby feature
if (BAUModdedSupportFlags.HasFlag(BAUModdedSupportFlags.Disable_PrivateLobby))
{
BAUPlugin.PrivateOnlyLobby.Value = false;
return;
}
// Only create toggle UI once
if (toggle != null) return;
buttons.Clear();
// Clone April Fools toggle as template for private lobby toggle
toggle = UnityEngine.Object.Instantiate(__instance.AprilFoolsToggle, __instance.contentObjects.First().transform.parent);
if (toggle != null)
{
toggle.name = "PrivateOnlyLobby";
// Get the ON and OFF buttons from the toggle
buttons.Add(toggle.transform.Find("AprilOn").GetComponent<PassiveButton>());
buttons.Add(toggle.transform.Find("AprilOff").GetComponent<PassiveButton>());
if (buttons.Count < 2) return;
toggle.gameObject.SetActive(true);
// Set up ON button click handler
var onButton = buttons[0];
if (onButton != null)
{
@@ -43,6 +50,8 @@ internal static class PrivateLobbyPatch
onButton.OnClick = new();
onButton.OnClick.AddListener((Action)(() => TogglePrivateOnlyLobby(true)));
}
// Set up OFF button click handler
var offButton = buttons[1];
if (offButton != null)
{
@@ -50,11 +59,14 @@ internal static class PrivateLobbyPatch
offButton.OnClick = new();
offButton.OnClick.AddListener((Action)(() => TogglePrivateOnlyLobby(false)));
}
// Position toggle at top-right of create game screen
var aspect = toggle.gameObject.AddComponent<AspectPosition>();
aspect.Alignment = AspectPosition.EdgeAlignments.Top;
aspect.DistanceFromEdge = new Vector3(0.4f, 1.62f, 0);
aspect.AdjustPosition();
// Update toggle text label
var text = toggle.transform.Find("BlackSquare/ModeText")?.GetComponent<TextMeshPro>();
if (text != null)
{
@@ -64,6 +76,7 @@ internal static class PrivateLobbyPatch
}
}
// Initialize toggle state from saved configuration
TogglePrivateOnlyLobby(BAUPlugin.PrivateOnlyLobby.Value);
}
@@ -71,11 +84,15 @@ internal static class PrivateLobbyPatch
{
if (buttons.Count < 2) return;
// Show both buttons first
buttons[0].gameObject.SetActive(true);
buttons[1].gameObject.SetActive(true);
// Deselect both buttons
buttons[0].SelectButton(false);
buttons[1].SelectButton(false);
// Select appropriate button based on mode
if (modeOn)
{
buttons[0].SelectButton(true);
@@ -84,6 +101,8 @@ internal static class PrivateLobbyPatch
{
buttons[1].SelectButton(true);
}
// Save setting to configuration
BAUPlugin.PrivateOnlyLobby.Value = modeOn;
}
@@ -91,24 +110,25 @@ internal static class PrivateLobbyPatch
[HarmonyPostfix]
private static void LobbyInfoPane_Update_Postfix(LobbyInfoPane __instance)
{
// Enforce private lobby setting if enabled by host
if (BAUPlugin.PrivateOnlyLobby.Value && !GameState.IsLocalGame && GameState.IsHost)
{
// Ensure game stays private
if (AmongUsClient.Instance.IsGamePublic)
{
AmongUsClient.Instance.ChangeGamePublic(false);
}
// Disable and visually indicate that public lobby button is locked
var button = __instance.HostPrivateButton.GetComponent<PassiveButton>();
if (button != null)
{
button.enabled = false;
// Change inactive sprite to cyan color to indicate locked state
var sprite = __instance.HostPrivateButton.transform.Find("Inactive").GetComponent<SpriteRenderer>();
if (sprite != null)
{
sprite.color = new(0.35f, 1, 1, 1);
}
sprite?.color = new(0.35f, 1, 1, 1);
}
}
}
}
}
+20 -1
View File
@@ -16,9 +16,12 @@ internal static class SplashIntroPatch
[HarmonyPrefix]
private static void SplashManager_Start_Prefix(SplashManager __instance)
{
// Reset all flags when splash screen starts
Skip = false;
BetterIntro = false;
IsReallyDoneLoading = false;
// Hide black overlay by moving it out of view
__instance.logoAnimFinish.transform.Find("BlackOverlay").transform.SetLocalY(100f);
}
@@ -26,17 +29,20 @@ internal static class SplashIntroPatch
[HarmonyPrefix]
private static bool SplashManager_Update_Prefix(SplashManager __instance)
{
// If skip is triggered, just check if loading is done
if (Skip)
{
CheckIfDone(__instance);
return false;
}
// After 1.8 seconds in BAU intro, remove audio to prevent overlap
if (Time.time - __instance.startTime > 1.8f && BetterIntro)
{
UnityEngine.Object.Destroy(__instance.logoAnimFinish.GetComponent<AudioSource>());
}
// Allow mouse click to skip splash screen
if (Input.GetKeyDown(KeyCode.Mouse0) || Input.GetKeyDown(KeyCode.Mouse1))
{
if (CheckIfDone(__instance, true))
@@ -46,35 +52,48 @@ internal static class SplashIntroPatch
}
}
// When game data is loaded and minimum time has passed
if (__instance.doneLoadingRefdata && !__instance.startedSceneLoad && Time.time - __instance.startTime > __instance.minimumSecondsBeforeSceneChange)
{
if (!BetterIntro)
{
// Start BAU custom intro sequence
__instance.startTime = Time.time;
__instance.logoAnimFinish.gameObject.SetActive(false);
__instance.logoAnimFinish.gameObject.SetActive(true);
// Replace InnerSloth logo with BAU logo
GameObject InnerLogo = __instance.logoAnimFinish.transform.Find("LogoRoot/ISLogo").gameObject;
BetterLogo = UnityEngine.Object.Instantiate(InnerLogo, InnerLogo.transform.parent);
InnerLogo.DestroyObj();
BetterLogo.name = "BetterLogo";
BetterLogo.GetComponent<SpriteRenderer>().sprite = Utils.LoadSprite("BetterAmongUs.Resources.Images.BetterAmongUs-Logo.png", 150f);
// Show black overlay
__instance.logoAnimFinish.transform.Find("BlackOverlay").transform.SetLocalY(0f);
BetterIntro = true;
return false;
}
// Check if BAU intro has completed
CheckIfDone(__instance);
}
// Return false to prevent original Update from running (we handle everything)
return false;
}
private static bool CheckIfDone(SplashManager __instance, bool isSkip = false)
{
if (Time.time - __instance.startTime > 2f && BetterIntro || isSkip && BetterIntro)
// Allow transition if:
// 1. BAU intro played for 2 seconds OR
// 2. User skipped and BAU intro was shown
if ((Time.time - __instance.startTime > 2f && BetterIntro) || (isSkip && BetterIntro))
{
IsReallyDoneLoading = true;
// Allow scene transition to proceed
__instance.sceneChanger.AllowFinishLoadingScene();
__instance.startedSceneLoad = true;
__instance.loadingObject.SetActive(true);
@@ -15,22 +15,26 @@ internal static class FindAGameManagerPatch
[HarmonyPrefix]
private static void FindAGameManager_Start_Prefix(FindAGameManager __instance)
{
// Apply custom UI colors to all Find Game menu buttons
__instance.refreshButton.gameObject.SetUIColors();
__instance.BackButton.gameObject.SetUIColors();
__instance.clearFilterButton.gameObject.SetUIColors("Disabled");
__instance.serverButton.gameObject.SetUIColors("Inactive", "Disabled", "Background");
__instance.serverButton.activeTextColor = Color.cyan * 0.3f;
// Modify game container hover colors
foreach (var con in __instance.gameContainers)
{
var roll = con.GetComponent<ButtonRolloverHandler>();
roll.OverColor = (roll.OverColor * 0.6f) + (Color.green * 0.5f);
}
// Create scrollable container for game listings
var prefab = __instance.gameContainers[4];
var list = new GameObject("GameListScroller");
list.transform.SetParent(prefab.transform.parent);
// Set up scroller component for vertical scrolling
Scroller = list.AddComponent<Scroller>();
Scroller.Inner = list.transform;
Scroller.MouseMustBeOverToScroll = true;
@@ -42,6 +46,7 @@ internal static class FindAGameManagerPatch
Scroller.SetYBoundsMax(3.5f);
Scroller.allowY = true;
// Move existing game containers into scrollable list
foreach (var con in __instance.gameContainers)
{
con.transform.SetParent(list.transform);
@@ -49,6 +54,7 @@ internal static class FindAGameManagerPatch
con.transform.position = new Vector3(oldPos.x, oldPos.y, 25);
}
// Add more game containers to support longer server lists
var oldGameContainers = __instance.gameContainers.ToList();
for (int i = 0; i < 5; i++)
@@ -61,6 +67,7 @@ internal static class FindAGameManagerPatch
__instance.gameContainers = oldGameContainers.ToArray();
// Create visual cutoff for scrolling content
var cutOffTop = CreateBlackSquareSprite();
cutOffTop.transform.SetParent(list.transform.parent);
cutOffTop.transform.localPosition = new Vector3(0, 3, 1);
@@ -71,6 +78,7 @@ internal static class FindAGameManagerPatch
[HarmonyPostfix]
private static void FindAGameManager_RefreshList_Postfix(FindAGameManager __instance)
{
// Reset scroll position when refreshing game list
Scroller?.ScrollRelative(new(0f, -100f));
}
@@ -78,12 +86,16 @@ internal static class FindAGameManagerPatch
[HarmonyPostfix]
private static void FindAGameManager_HandleList_Postfix(FindAGameManager __instance, HttpMatchmakerManager.FindGamesListFilteredResponse response)
{
// Clear existing containers before populating with new data
__instance.ResetContainers();
GameListing[] games = response.Games.ToArray();
// Sort games: first by player count (highest first), then by host name
games = [.. games.OrderByDescending(game => game.PlayerCount).ThenBy(game => game.TrueHostName)];
int gameNum = 0;
int count = 0;
// Populate visible containers with available games
while (count < __instance.gameContainers.Length && count < games.Count())
{
if (games[count].Options != null)
@@ -96,11 +108,13 @@ internal static class FindAGameManagerPatch
count++;
}
// Add true host name and platform info to each game container
foreach (var container in __instance.gameContainers)
{
Transform child = container.transform.Find("Container");
Transform tmproObject = child.Find("TrueHostName_TMP");
// Use existing TMP component or create new one
TMP_Text tmpro = tmproObject != null
? tmproObject.GetComponent<TextMeshPro>()
: CreateNewTextMeshPro(child);
@@ -113,24 +127,30 @@ internal static class FindAGameManagerPatch
private static TMP_Text CreateNewTextMeshPro(Transform parent)
{
// Create new TextMeshPro component for displaying host info
var tmproObject = new GameObject("TrueHostName_TMP").transform;
tmproObject.SetParent(parent, true);
// Position text in container
var aspectPos = tmproObject.gameObject.AddComponent<AspectPosition>();
aspectPos.Alignment = AspectPosition.EdgeAlignments.Center;
aspectPos.anchorPoint = new Vector2(0.2f, 0.5f);
aspectPos.DistanceFromEdge = new Vector3(10.9f, -2.17f, -2f);
aspectPos.AdjustPosition();
return tmproObject.gameObject.AddComponent<TextMeshPro>();
}
private static string FormatGameInfoText(GameListing listing)
{
// Format game info text with host name, platform, and game code
var hostStr = !string.IsNullOrEmpty(listing.TrueHostName) ? listing.TrueHostName : listing.HostName;
return @$"{hostStr}{Environment.NewLine}<size=65%>{Utils.GetPlatformName(listing.Platform)} ({GameCode.IntToGameName(listing.GameId)})";
}
private static SpriteRenderer CreateBlackSquareSprite()
{
// Create a black sprite to visually cut off scrolling content
var square = new GameObject("CutOffTop");
var renderer = square.AddComponent<SpriteRenderer>();
Texture2D texture = new(100, 100);
@@ -146,4 +166,4 @@ internal static class FindAGameManagerPatch
square.transform.localScale = new Vector3(100, 100, 1);
return renderer;
}
}
}
@@ -20,6 +20,7 @@ internal static class FindGameMoreInfoPopupPatch
{
_findGameMoreInfoPopup = __instance;
// Create custom settings info text box if it doesn't exist
if (_textBox == null)
{
_textBox = UnityEngine.Object.Instantiate(AccountManager.Instance.genericInfoDisplayBox, __instance.transform);
@@ -41,6 +42,7 @@ internal static class FindGameMoreInfoPopupPatch
pos.AdjustPosition();
}
// Display all game settings when popup opens
ShowAll();
}
@@ -51,8 +53,10 @@ internal static class FindGameMoreInfoPopupPatch
_sb.Clear();
var options = _findGameMoreInfoPopup.gameListing.Options;
// Display settings based on game mode
if (options.GameMode is GameModes.Normal or GameModes.NormalFools)
{
// Impostor settings
FormatOption(StringNames.GameNumImpostors, $"{options.NumImpostors}");
if (options.TryGetFloat(FloatOptionNames.KillCooldown, out var value1))
{
@@ -69,6 +73,7 @@ internal static class FindGameMoreInfoPopupPatch
AddBreak();
// General gameplay settings
if (options.TryGetFloat(FloatOptionNames.PlayerSpeedMod, out var value4))
{
FormatOption(StringNames.GamePlayerSpeed, $"{value4}x");
@@ -80,6 +85,7 @@ internal static class FindGameMoreInfoPopupPatch
AddBreak();
// Meeting settings
if (options.TryGetInt(Int32OptionNames.NumEmergencyMeetings, out var value6))
{
FormatOption(StringNames.GameNumMeetings, $"{value6}");
@@ -107,6 +113,7 @@ internal static class FindGameMoreInfoPopupPatch
AddBreak();
// Task settings
if (options.TryGetInt(Int32OptionNames.TaskBarMode, out var value12))
{
FormatOption(StringNames.GameTaskBarMode, GetTaskBarMode(value12));
@@ -130,6 +137,7 @@ internal static class FindGameMoreInfoPopupPatch
}
else if (options.GameMode is GameModes.HideNSeek or GameModes.SeekFools)
{
// Hide & Seek specific settings (Hider/Crewmate)
if (options.TryGetFloat(FloatOptionNames.PlayerSpeedMod, out var value1))
{
FormatOption(StringNames.GamePlayerSpeed, $"{value1}x");
@@ -165,6 +173,7 @@ internal static class FindGameMoreInfoPopupPatch
AddBreak();
// Hide & Seek Seeker (Impostor) settings
if (options.TryGetFloat(FloatOptionNames.ImpostorFlashlightSize, out var value9))
{
FormatOption(StringNames.ImpostorFlashlightSize, $"{value9}x");
@@ -176,6 +185,7 @@ internal static class FindGameMoreInfoPopupPatch
AddBreak();
// Hide & Seek final phase settings
if (options.TryGetFloat(FloatOptionNames.FinalEscapeTime, out var value11))
{
FormatOption(StringNames.FinalEscapeTime, $"{value11}s");
@@ -199,6 +209,7 @@ internal static class FindGameMoreInfoPopupPatch
AddBreak();
// Hide & Seek task settings
if (options.TryGetInt(Int32OptionNames.NumCommonTasks, out var value16))
{
FormatOption(StringNames.GameCommonTasks, $"{value16}");
@@ -213,6 +224,7 @@ internal static class FindGameMoreInfoPopupPatch
}
}
// Update text box with formatted settings
if (_textBox != null)
{
_textBox.SetText(_sb.ToString());
@@ -221,11 +233,13 @@ internal static class FindGameMoreInfoPopupPatch
private static void FormatOption(StringNames optName, string value)
{
// Format each setting line with gray label and yellow value
_sb.AppendLine($"{Translator.GetString(optName)}<#989898>:</color> <#CFCF00>{value}</color>");
}
private static void AddBreak()
{
// Add separator line between settings groups
_sb.AppendLine("<#999999>----------------------</color>");
}
@@ -233,6 +247,7 @@ internal static class FindGameMoreInfoPopupPatch
private static string GetKillDistance(int value)
{
// Convert kill distance integer to readable string
switch (value)
{
case 0:
@@ -250,6 +265,7 @@ internal static class FindGameMoreInfoPopupPatch
private static string GetTaskBarMode(int value)
{
// Convert task bar mode integer to readable string
switch (value)
{
case 0:
@@ -5,7 +5,6 @@ using HarmonyLib;
using TMPro;
using UnityEngine;
namespace BetterAmongUs.Patches.Gameplay.Managers;
[HarmonyPatch]
@@ -21,6 +20,7 @@ internal static class HudManagerPatch
[HarmonyPostfix]
private static void HudManager_Start_Postfix(HudManager __instance)
{
// Create custom BAU notification system if it doesn't exist
if (BetterNotificationManager.BAUNotificationManagerObj == null)
{
var ChatNotifications = __instance.Chat.chatNotification;
@@ -28,31 +28,46 @@ internal static class HudManagerPatch
{
ChatNotifications.timeOnScreen = 1f;
ChatNotifications.gameObject.SetActive(true);
// Clone chat notification system for BAU notifications
GameObject BAUNotification = UnityEngine.Object.Instantiate(ChatNotifications.gameObject);
BAUNotification.name = "BAUNotification";
BAUNotification.GetComponent<ChatNotification>().DestroyMono();
// Remove unnecessary elements from the clone
GameObject.Find($"{BAUNotification.name}/Sizer/PoolablePlayer").DestroyObj();
GameObject.Find($"{BAUNotification.name}/Sizer/ColorText").DestroyObj();
// Position notification at bottom-left corner
BAUNotification.GetComponent<AspectPosition>().DistanceFromEdge = new Vector3(-1.57f, 5.3f, -15f);
GameObject.Find($"{BAUNotification.name}/Sizer/NameText").transform.localPosition = new Vector3(-3.3192f, -0.0105f);
// Cache TextMeshPro component for text updates
BetterNotificationManager.NameText = GameObject.Find($"{BAUNotification.name}/Sizer/NameText").GetComponent<TextMeshPro>();
UnityEngine.Object.DontDestroyOnLoad(BAUNotification);
BetterNotificationManager.BAUNotificationManagerObj = BAUNotification;
BAUNotification.SetActive(false);
// Reset original chat notification settings
ChatNotifications.timeOnScreen = 0f;
ChatNotifications.gameObject.SetActive(false);
// Configure text wrapping for multi-line notifications
BetterNotificationManager.TextArea.enableWordWrapping = true;
BetterNotificationManager.TextArea.m_firstOverflowCharacterIndex = 0;
BetterNotificationManager.TextArea.overflowMode = TextOverflowModes.Overflow;
}
}
// Show welcome message after 1 second delay (only once per session)
LateTask.Schedule(() =>
{
if (!HasBeenWelcomed && GameState.IsInGame && GameState.IsLobby && !GameState.IsFreePlay)
{
// Show notification with welcome text
BetterNotificationManager.Notify($"<b><color=#00751f>{string.Format(Translator.GetString("WelcomeMsg.WelcomeToBAU"), Translator.GetString("BetterAmongUs"))}!</color></b>", 8f);
// Send detailed welcome message to private chat
Utils.AddChatPrivate(WelcomeMessage, overrideName: " ");
HasBeenWelcomed = true;
}
@@ -65,16 +80,17 @@ internal static class HudManagerPatch
{
try
{
// Adjust GameStartManager position for better UI layout
GameObject gameStart = GameObject.Find("GameStartManager");
if (gameStart != null)
gameStart.transform.SetLocalY(-2.8f);
// Set chat
// Manage in-game chat visibility based on settings and game state
if (GameState.InGame)
{
if (!BAUPlugin.ChatInGameplay.Value)
{
// Vanilla chat behavior: only show chat when dead or during meetings
if (!PlayerControl.LocalPlayer.IsAlive())
{
__instance.Chat.gameObject.SetActive(true);
@@ -86,6 +102,7 @@ internal static class HudManagerPatch
}
else
{
// BAU chat behavior: always show chat when enabled
if (__instance?.Chat?.gameObject.active == false)
{
__instance.Chat.gameObject.SetActive(true);
@@ -93,6 +110,8 @@ internal static class HudManagerPatch
}
}
}
catch { }
catch
{
}
}
}
}
@@ -15,6 +15,7 @@ internal static class RoleManagerPatch
internal static Dictionary<string, int> ImpostorMultiplier = []; // HashPuid, Multiplier
private static readonly Random random = new();
// Check if client is not verified Better Among Us user
static readonly Func<InnerNet.ClientData, bool> clientCheck = (clientData) =>
{
return clientData?.BetterData()?.IsVerifiedBetterUser != true;
@@ -24,6 +25,7 @@ internal static class RoleManagerPatch
[HarmonyPrefix]
private static void RoleManager_SetRole_Prefix(RoleManager __instance, PlayerControl targetPlayer, RoleTypes roleType)
{
// Store the original role when player dies (for ghost role purposes)
if (RoleManager.IsGhostRole(roleType))
{
if (!RoleManager.IsGhostRole(targetPlayer.Data.RoleType))
@@ -33,16 +35,18 @@ internal static class RoleManagerPatch
}
}
// Better role algorithm
// Better role assignment algorithm (replaces vanilla role assignment)
[HarmonyPatch(typeof(RoleManager), nameof(RoleManager.SelectRoles))]
[HarmonyPrefix]
private static bool RoleManager_SelectRoles_Prefix()
{
// Skip BAU role assignment if other mods disabled it
if (BAUModdedSupportFlags.HasFlag(BAUModdedSupportFlags.Disable_BetterRoleAlgorithm))
{
return true;
}
// Use different algorithms for different game modes
if (!GameState.IsHideNSeek)
{
RegularBetterRoleAssignment();
@@ -52,6 +56,7 @@ internal static class RoleManagerPatch
HideAndSeekBetterRoleAssignment();
}
// Return false to prevent vanilla role assignment from running
return false;
}
@@ -59,14 +64,14 @@ internal static class RoleManagerPatch
{
Logger_.LogHeader($"Better Role Assignment Has Started", "RoleManager");
// Set roles up
// Initialize impostor multiplier tracking for all players
foreach (var addplayer in BAUPlugin.AllPlayerControls.Where(pc => !ImpostorMultiplier.ContainsKey(Utils.GetHashPuid(pc))))
ImpostorMultiplier[Utils.GetHashPuid(addplayer)] = 0;
int NumImpostors = GameOptionsManager.Instance.CurrentGameOptions.NumImpostors;
int NumPlayers = BAUPlugin.AllPlayerControls.Count;
// Apply player count limits to impostor numbers
var impostorLimits = new Dictionary<int, int>
{
{ 3, 1 },
@@ -86,6 +91,7 @@ internal static class RoleManagerPatch
List<PlayerControl> Impostors = [];
List<PlayerControl> Crewmates = [];
// Track available roles and their counts
Dictionary<RoleTypes, int> ImpostorRoles = new() // Role, Amount
{
{ RoleTypes.Shapeshifter, 0 },
@@ -104,6 +110,7 @@ internal static class RoleManagerPatch
List<RoleTypes> Roles = [.. ImpostorRoles.Keys, .. CrewmateRoles.Keys];
// Get role counts from game options
foreach (RoleTypes role in Roles)
{
if (role.GetBehaviourPrefab().IsImpostor)
@@ -112,7 +119,7 @@ internal static class RoleManagerPatch
CrewmateRoles[role] = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(role);
}
// Get players in random order
// Shuffle players for random role assignment
List<PlayerControl> players = [.. BAUPlugin.AllPlayerControls.Where(player => !Impostors.Contains(player) && !Crewmates.Contains(player) && player.roleAssigned == false)];
int n = players.Count;
@@ -125,24 +132,26 @@ internal static class RoleManagerPatch
players[n] = value;
}
// Assign roles
// Assign roles to each player
foreach (PlayerControl pc in players)
{
if (pc == null || pc.roleAssigned == true) continue;
// Check if player should be impostor based on multiplier and available slots
if (Impostors.Count < NumImpostors && RNG() > ImpostorMultiplier[Utils.GetHashPuid(pc)])
{
var impRoles = ImpostorRoles.Shuffle();
foreach (var kvp in impRoles)
{
// Assign special impostor role based on chance and availability
if (RNG() <= GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(kvp.Key) && kvp.Value > 0)
{
ImpostorMultiplier[Utils.GetHashPuid(pc)] += 15;
ImpostorMultiplier[Utils.GetHashPuid(pc)] += 15; // Increase chance of being crewmate next game
ImpostorRoles[kvp.Key]--;
Impostors.Add(pc);
pc.RpcSetRole(kvp.Key);
// Desync role to other clients to prevent revealing the true role
// Desync role to hide special role from non-BAU players
if (BetterGameSettings.DesyncRoles.GetBool())
{
if (kvp.Key is not RoleTypes.Phantom or RoleTypes.Viper)
@@ -159,6 +168,7 @@ internal static class RoleManagerPatch
}
}
// If no special role assigned, give regular impostor
if (!Impostors.Contains(pc))
{
ImpostorMultiplier[Utils.GetHashPuid(pc)] += 15;
@@ -167,19 +177,20 @@ internal static class RoleManagerPatch
Logger_.LogPrivate($"Assigned {RoleTypes.Impostor.GetRoleName()} role to {pc.Data.PlayerName}", "RoleManager");
}
}
else
else // Assign crewmate role
{
var crewRoles = CrewmateRoles.Shuffle();
foreach (var kvp in crewRoles)
{
// Assign special crewmate role based on chance and availability
if (RNG() <= GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(kvp.Key) && kvp.Value > 0)
{
ImpostorMultiplier[Utils.GetHashPuid(pc)] = 0;
ImpostorMultiplier[Utils.GetHashPuid(pc)] = 0; // Reset impostor chance
CrewmateRoles[kvp.Key]--;
Crewmates.Add(pc);
pc.RpcSetRole(kvp.Key);
// Desync role to other clients to prevent revealing the true role
// Desync role to hide special role from non-BAU players
if (BetterGameSettings.DesyncRoles.GetBool())
{
if (kvp.Key is not RoleTypes.Noisemaker)
@@ -196,6 +207,7 @@ internal static class RoleManagerPatch
}
}
// If no special role assigned, give regular crewmate
if (!Crewmates.Contains(pc))
{
ImpostorMultiplier[Utils.GetHashPuid(pc)] = 0;
@@ -213,6 +225,7 @@ internal static class RoleManagerPatch
{
Logger_.LogHeader($"Better Role Assignment Has Started", "RoleManager");
// Get impostor count from BAU settings (defaults to 1)
int NumImpostors = BetterGameSettings.HideAndSeekImpNum?.GetInt() ?? 1;
if (NumImpostors > BAUPlugin.AllPlayerControls.Count)
@@ -222,7 +235,7 @@ internal static class RoleManagerPatch
List<NetworkedPlayerInfo> Crewmates = [];
List<NetworkedPlayerInfo> CrewAndImps() => [.. Impostors, .. Crewmates];
// Set imp from settings
// Get predefined impostors from settings (host can set specific players as impostors)
int[] betterImpostorSettings =
[
GameOptionsManager.Instance.currentHideNSeekGameOptions.ImpostorPlayerID,
@@ -250,7 +263,7 @@ internal static class RoleManagerPatch
}
}
// Get players in random order
// Shuffle remaining players for random role assignment
List<PlayerControl> players = [.. BAUPlugin.AllPlayerControls.Where(player => !CrewAndImps().Contains(player.Data))];
int n = players.Count;
@@ -263,7 +276,7 @@ internal static class RoleManagerPatch
players[n] = value;
}
// Assign roles
// Assign roles to remaining players
foreach (PlayerControl pc in players)
{
if (pc == null || pc.roleAssigned == true) continue;
@@ -280,6 +293,7 @@ internal static class RoleManagerPatch
}
}
// Apply roles to all players
foreach (var data in Impostors)
{
var player = Utils.PlayerFromPlayerId(data.PlayerId);
@@ -299,6 +313,7 @@ internal static class RoleManagerPatch
[HarmonyPrefix]
internal static bool RoleManager_AssignRoleOnDeath_Prefix(PlayerControl player)
{
// Track available ghost roles
Dictionary<RoleTypes, int> GhostRoles = new() // Role, Amount
{
{ RoleTypes.GuardianAngel, 0 },
@@ -311,6 +326,7 @@ internal static class RoleManagerPatch
GhostRoles[role] = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(role);
}
// Deduct roles already assigned to dead players
foreach (var allDeadPlayers in BAUPlugin.AllPlayerControls.Where(pc => !pc.IsAlive()))
{
for (int i = 0; i < Roles.Count; i++)
@@ -324,6 +340,7 @@ internal static class RoleManagerPatch
var ghostRoles = GhostRoles.Shuffle();
// Assign ghost role based on chance and availability
foreach (var kvp in ghostRoles)
{
if (player.IsImpostorTeam() && kvp.Key is RoleTypes.GuardianAngel) continue;
@@ -332,7 +349,7 @@ internal static class RoleManagerPatch
{
player.RpcSetRole(kvp.Key);
// Desync role to other clients to prevent revealing the true role
// Desync ghost role to hide it from non-BAU players
if (BetterGameSettings.DesyncRoles.GetBool())
{
List<MessageWriter> messageWriter = AmongUsClient.Instance.StartRpcDesync(player.NetId, (byte)RpcCalls.SetRole, SendOption.None, player.GetClientId(), clientCheck);
@@ -345,6 +362,7 @@ internal static class RoleManagerPatch
}
}
// Assign default ghost role if no special role available
player.RpcSetRole(player.Data.Role.DefaultGhostRole);
return false;
@@ -352,14 +370,15 @@ internal static class RoleManagerPatch
internal static int RNG()
{
// Choose random number generator based on settings
switch (BetterGameSettings.RoleRandomizer.GetStringValue())
{
case 1:
return UnityEngine.Random.Range(0, 100);
return UnityEngine.Random.Range(0, 100); // Unity RNG
default:
Random Random = new Random();
return Random.Next(0, 100);
return Random.Next(0, 100); // .NET RNG
}
}
}
@@ -3,7 +3,6 @@ using HarmonyLib;
namespace BetterAmongUs.Patches.Gameplay.Player;
// Set text color
[HarmonyPatch]
internal static class CosmeticsLayerPatch
{
@@ -11,13 +10,15 @@ internal static class CosmeticsLayerPatch
[HarmonyPrefix]
private static bool CosmeticsLayer_GetColorBlindText_Prefix(CosmeticsLayer __instance, ref string __result)
{
// Skip processing if color ID is out of bounds (custom colors)
if (__instance.bodyMatProperties.ColorId > Palette.PlayerColors.Length) return true;
string colorName = Palette.GetColorName(__instance.bodyMatProperties.ColorId);
if (!string.IsNullOrEmpty(colorName))
{
__result = (char.ToUpperInvariant(colorName[0]) + colorName[1..].ToLowerInvariant()).ToColor(Palette.PlayerColors[__instance.bodyMatProperties.ColorId]);
__result = (char.ToUpperInvariant(colorName[0]) + colorName[1..].ToLowerInvariant())
.ToColor(Palette.PlayerColors[__instance.bodyMatProperties.ColorId]);
}
else
{
@@ -26,4 +27,4 @@ internal static class CosmeticsLayerPatch
return false;
}
}
}
@@ -14,18 +14,24 @@ internal static class PlayerControlPatch
[HarmonyPostfix]
private static void PlayerControl_Start_Postfix(PlayerControl __instance, ref Il2CppSystem.Collections.IEnumerator __result)
{
// Add player to global player list
BAUPlugin.AllPlayerControls.Add(__instance);
// Update option UI values for all players
OptionPlayerItem.UpdateAllValues();
// Append favorite color setting to player initialization coroutine
__result = Effects.Sequence(__result, CoSetFavoriteColor(__instance).WrapToIl2Cpp());
}
private static IEnumerator CoSetFavoriteColor(PlayerControl player)
{
// Apply player's favorite color setting if they own this character
if (player.AmOwner)
{
if (BAUPlugin.FavoriteColor.Value >= 0 && player.cosmetics.ColorId != (byte)BAUPlugin.FavoriteColor.Value)
{
// Send command to server to change color
player.CmdCheckColor((byte)BAUPlugin.FavoriteColor.Value);
}
}
@@ -37,7 +43,10 @@ internal static class PlayerControlPatch
[HarmonyPostfix]
private static void PlayerControl_OnDestroy_Postfix(PlayerControl __instance)
{
// Remove player from global list when destroyed
BAUPlugin.AllPlayerControls.Remove(__instance);
// Update option UI values
OptionPlayerItem.UpdateAllValues();
}
@@ -47,8 +56,10 @@ internal static class PlayerControlPatch
{
if (target == null) return;
// Log kill event with player names and roles
Logger_.LogPrivate($"{__instance.Data.PlayerName} Has killed {target.Data.PlayerName} as {__instance.Data.RoleType.GetRoleName()}", "EventLog");
// Track kill count in player's BetterData
__instance.BetterData().RoleInfo.Kills += 1;
}
@@ -58,6 +69,7 @@ internal static class PlayerControlPatch
{
if (targetPlayer == null) return;
// Log shapeshift events (both shifting and unshifting)
if (__instance != targetPlayer)
Logger_.LogPrivate($"{__instance.Data.PlayerName} Has Shapeshifted into {targetPlayer.Data.PlayerName}, did animate: {animate}", "EventLog");
else
@@ -68,7 +80,7 @@ internal static class PlayerControlPatch
[HarmonyPostfix]
private static void PlayerControl_SetRoleInvisibility_Postfix(PlayerControl __instance, bool isActive, bool shouldAnimate)
{
// Log Phantom role visibility changes
if (isActive)
Logger_.LogPrivate($"{__instance.Data.PlayerName} Has Vanished as Phantom, did animate: {shouldAnimate}", "EventLog");
else
@@ -79,6 +91,7 @@ internal static class PlayerControlPatch
[HarmonyPostfix]
private static void PlayerControl_SetName_Postfix(PlayerControl __instance, string playerName)
{
// Store the last set name in player's BetterData
__instance.BetterDataWait(data =>
{
data.NameSetAsLast = playerName;
@@ -89,6 +102,7 @@ internal static class PlayerControlPatch
[HarmonyPostfix]
private static void PlayerPhysics_BootFromVent_Postfix(PlayerPhysics __instance, int ventId)
{
// Log vent boot events (when engineer boots someone)
Logger_.LogPrivate($"{__instance.myPlayer.Data.PlayerName} Has been booted from vent: {ventId}, as {__instance.myPlayer.Data.RoleType.GetRoleName()}", "EventLog");
}
@@ -96,6 +110,7 @@ internal static class PlayerControlPatch
[HarmonyPostfix]
private static void PlayerPhysics_CoEnterVent_Postfix(PlayerPhysics __instance, int id)
{
// Log vent entry events
Logger_.LogPrivate($"{__instance.myPlayer.Data.PlayerName} Has entered vent: {id}, as {__instance.myPlayer.Data.RoleType.GetRoleName()}", "EventLog");
}
@@ -103,6 +118,7 @@ internal static class PlayerControlPatch
[HarmonyPostfix]
private static void PlayerPhysics_CoExitVent_Postfix(PlayerPhysics __instance, int id)
{
// Log vent exit events
Logger_.LogPrivate($"{__instance.myPlayer.Data.PlayerName} Has exit vent: {id}, as {__instance.myPlayer.Data.RoleType.GetRoleName()}", "EventLog");
}
}
}
@@ -16,7 +16,7 @@ internal static class PlayerJoinAndLeftPatch
[HarmonyPostfix]
private static void AmongUsClient_OnGameJoined_Postfix()
{
// Fix host icon in lobby on modded servers
// Fix host icon color display on modded servers
if (!GameState.IsVanillaServer)
{
var host = AmongUsClient.Instance.GetHost().Character;
@@ -31,6 +31,7 @@ internal static class PlayerJoinAndLeftPatch
[HarmonyPostfix]
private static void AmongUsClient_OnPlayerJoined_Postfix(ClientData data)
{
// Schedule ban list checks 2.5 seconds after player joins
LateTask.Schedule(() =>
{
if (GameState.IsHost)
@@ -39,7 +40,7 @@ internal static class PlayerJoinAndLeftPatch
{
var player = Utils.PlayerFromClientId(data.Id);
// Auto ban player on ban list
// Check if player is in ban list by friend code or PUID
if (BetterGameSettings.UseBanPlayerList.GetBool())
{
if (player != null)
@@ -52,6 +53,8 @@ internal static class PlayerJoinAndLeftPatch
}
}
}
// Check if player name matches banned name patterns
if (BetterGameSettings.UseBanNameList.GetBool())
{
if (player != null)
@@ -71,6 +74,7 @@ internal static class PlayerJoinAndLeftPatch
[HarmonyPostfix]
private static void AmongUsClient_OnPlayerLeft_Postfix(ClientData data, DisconnectReasons reason)
{
// Reclaim favorite color when player leaves in lobby
if (GameState.IsLobby)
{
var favColorId = (byte)BAUPlugin.FavoriteColor.Value;
@@ -83,6 +87,7 @@ internal static class PlayerJoinAndLeftPatch
}
}
// Update host icon in meeting
MeetingHudPatch.UpdateHostIcon();
}
@@ -93,11 +98,13 @@ internal static class PlayerJoinAndLeftPatch
[HarmonyPrefix]
private static void GameData_HandleDisconnect_Prefix(PlayerControl player, DisconnectReasons reason)
{
// Store disconnect reason in player's BetterData
if (player.BetterData() != null)
{
player.BetterData().DisconnectReason = reason;
}
// Show custom disconnect notification
BetterShowNotification(player.Data, reason);
}
@@ -105,16 +112,19 @@ internal static class PlayerJoinAndLeftPatch
[HarmonyPrefix]
internal static bool GameData_ShowNotification_Prefix()
{
// Disable vanilla disconnect notifications (use BAU's instead)
return false;
}
internal static void BetterShowNotification(NetworkedPlayerInfo playerData, DisconnectReasons reason = DisconnectReasons.Unknown, string forceReasonText = "")
{
// Prevent showing duplicate notifications
if (playerData.BetterData().AntiCheatInfo.BannedByAntiCheat || playerData.BetterData().HasShowDcMsg) return;
playerData.BetterData().HasShowDcMsg = true;
string? playerName = playerData.BetterData().RealName;
// Use custom reason text if provided
if (forceReasonText != "")
{
var ReasonText = $"<color=#ff0>{playerData.BetterData().RealName}</color> {forceReasonText}";
@@ -127,6 +137,7 @@ internal static class PlayerJoinAndLeftPatch
{
string ReasonText;
// Format disconnect message based on reason type
switch (reason)
{
case DisconnectReasons.ExitGame:
@@ -157,7 +168,8 @@ internal static class PlayerJoinAndLeftPatch
Logger_.Log(ReasonText);
// Add formatted disconnect message to game UI
HudManager.Instance.Notifier.AddDisconnectMessage(ReasonText);
}
}
}
}
@@ -17,8 +17,10 @@ internal static class VoteBanSystemPatch
[HarmonyPrefix]
private static bool VoteBanSystem_AddVote_Prefix(VoteBanSystem __instance, int srcClient, int clientId)
{
// Skip BAU anti-cheat if disabled by other mods
if (BAUModdedSupportFlags.HasFlag(BAUModdedSupportFlags.Disable_Anticheat)) return true;
// If not host, allow vote and log it
if (!GameState.IsHost)
{
DoLog = true;
@@ -28,7 +30,7 @@ internal static class VoteBanSystemPatch
var client = Utils.ClientFromClientId(srcClient);
if (client == null) return false;
// Skip if host client
// Allow host to vote without restrictions
if (client.Id == AmongUsClient.Instance.GetHost().Id)
{
return true;
@@ -43,53 +45,62 @@ internal static class VoteBanSystemPatch
}
}
// Prevent voting in lobby (anti-cheat measure)
if (GameState.IsLobby)
{
TryFlagPlayer();
return false;
}
// Initialize vote tracking for this VoteBanSystem instance
if (!_voteData.TryGetValue(__instance, out var voters))
{
_voteData.Clear();
_voteData[__instance] = voters = [];
}
// If client has no ID, allow vote but log it
if (string.IsNullOrEmpty(client.ProductUserId) && string.IsNullOrEmpty(client.FriendCode))
{
DoLog = true;
return true;
}
// Generate hash for client's ProductUserId
var clientHash = Utils.GetHashUInt16(client.ProductUserId);
// Check if this client has already voted for the same target
foreach (var (targetClientId, (existingHash, existingFriendCode)) in voters)
{
if (targetClientId != clientId)
continue;
// Detect duplicate votes by comparing hash or friend code
bool isDuplicateVote = existingHash == clientHash ||
!string.IsNullOrEmpty(client.FriendCode) &&
existingFriendCode == client.FriendCode;
if (isDuplicateVote)
{
// Block duplicate vote attempt
return false;
}
}
// Record the vote
voters.Add((clientId, (clientHash, client.FriendCode)));
DoLog = true;
return true;
}
[HarmonyPatch(typeof(VoteBanSystem), nameof(VoteBanSystem.AddVote))]
[HarmonyPostfix]
private static void VoteBanSystem_AddVote_Postfix(VoteBanSystem __instance, int srcClient, int clientId)
{
// Skip logging if anti-cheat disabled
if (BAUModdedSupportFlags.HasFlag(BAUModdedSupportFlags.Disable_Anticheat)) return;
// Log the vote if it was allowed
if (DoLog)
{
LogVote(__instance, srcClient, clientId);
@@ -99,18 +110,21 @@ internal static class VoteBanSystemPatch
private static void LogVote(VoteBanSystem voteBanSystem, int srcClient, int clientId)
{
// Get source and target client info
var src = Utils.ClientFromClientId(srcClient);
var client = Utils.ClientFromClientId(clientId);
// Calculate current votes and required votes
int currentVotes = 0;
int maxVotes = 0;
if (voteBanSystem.Votes.TryGetValue(clientId, out var votes))
{
currentVotes = votes.Count(v => v != 0);
maxVotes = votes.Length;
currentVotes = votes.Count(v => v != 0); // Count non-zero votes
maxVotes = votes.Length; // Total possible votes
}
// Log vote with colored player names and vote count
Logger_.InGame(
$"{src.Character?.GetPlayerNameAndColor() ?? src.PlayerName} " +
$"voted to kick {client.Character?.GetPlayerNameAndColor() ?? client.PlayerName} " +
@@ -15,7 +15,7 @@ internal static class ChatCommandsPatch
private static bool _enabled = true;
internal static string CommandPrefix => BAUModdedSupportFlags.HasFlag(BAUModdedSupportFlags.Force_BAU_Command_Prefix) ? "bau:" : BAUPlugin.CommandPrefix.Value;
// Run code for specific commands
// Execute command when valid command is typed
private static void HandleCommand()
{
if (closestCommand != null && isTypedOut)
@@ -28,45 +28,55 @@ internal static class ChatCommandsPatch
}
}
// Check if command is typed when sending chat message
// Intercept chat messages to handle commands
[HarmonyPatch(typeof(ChatController), nameof(ChatController.SendChat))]
[HarmonyPrefix]
private static bool ChatController_SendChat_Prefix(ChatController __instance)
{
// Skip if commands are disabled
if (!_enabled || BAUModdedSupportFlags.HasFlag(BAUModdedSupportFlags.Disable_AllCommands))
{
return true;
}
// Check chat cooldown
bool IsOnCooldown = 3f - __instance.timeSinceLastMessage > 0f;
string text = __instance.freeChatField.textArea.text;
// If not a command, handle as normal chat (with restrictions)
if (!text.StartsWith(CommandPrefix) || IsOnCooldown)
{
// Prevent chat during gameplay if not in meeting
if (GameState.IsInGame && !GameState.IsLobby && !GameState.IsFreePlay && !GameState.IsMeeting && !GameState.IsExilling && PlayerControl.LocalPlayer.IsAlive())
return false;
// Add to chat history
if (ChatPatch.ChatHistory.Count == 0 || ChatPatch.ChatHistory[^1] != text) ChatPatch.ChatHistory.Add(text);
ChatPatch.CurrentHistorySelection = ChatPatch.ChatHistory.Count;
return true;
}
// Check if command can be executed
if (!closestCommand.CanRunCommand(out string _))
{
return false;
}
// Execute command
HandleCommand();
// Add command to chat history
if (ChatPatch.ChatHistory.Count == 0 || ChatPatch.ChatHistory[^1] != text) ChatPatch.ChatHistory.Add(text);
ChatPatch.CurrentHistorySelection = ChatPatch.ChatHistory.Count;
// Reset chat timer if command sets it
if (closestCommand?.SetChatTimer == true)
{
__instance.timeSinceLastMessage = 0f;
}
// Clear chat input
__instance.freeChatField.Clear();
__instance.quickChatMenu.Clear();
__instance.quickChatField.Clear();
@@ -74,7 +84,7 @@ internal static class ChatCommandsPatch
return false;
}
// Set up command helper
// Set up command helper UI elements
private static TextMeshPro? commandText;
private static TextMeshPro? commandInfo;
@@ -82,6 +92,7 @@ internal static class ChatCommandsPatch
[HarmonyPostfix]
private static void ChatController_Awake_Postfix(ChatController __instance)
{
// Create semi-transparent text for command suggestions
if (commandText == null)
{
var TextArea = __instance.freeChatField.textArea.outputText;
@@ -92,6 +103,7 @@ internal static class ChatCommandsPatch
commandText.GetComponent<TextMeshPro>().color = new Color(1f, 1f, 1f, 0.5f);
}
// Create info text for command descriptions and arguments
if (commandInfo == null)
{
var TextArea = __instance.freeChatField.textArea.outputText;
@@ -112,7 +124,7 @@ internal static class ChatCommandsPatch
private static string typedCommand = "";
private static BaseCommand? closestCommand;
// Command helper
// Update command suggestion UI as user types
[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))]
[HarmonyPostfix]
private static void ChatController_Update_Postfix(ChatController __instance)
@@ -128,11 +140,13 @@ internal static class ChatCommandsPatch
if (commandText == null || commandInfo == null)
return;
// Check if text starts with command prefix
if (text.Length > 0 && text.StartsWith(CommandPrefix))
{
typedCommand = text.Length > CommandPrefix.Length ? text[CommandPrefix.Length..] : string.Empty;
string[] typedParts = typedCommand.Split(' ');
// Find closest matching command
closestCommand = GetClosestCommand(typedParts[0]);
bool isSuggestionValid = closestCommand != null
&& (typedParts[0].Equals(closestCommand.Name, StringComparison.OrdinalIgnoreCase) || typedParts.Length == 1);
@@ -163,36 +177,44 @@ internal static class ChatCommandsPatch
{
isTypedOut = true;
// Generate suggestion text
string suggestion = GenerateSuggestion(typedParts);
string fullSuggestion = CommandPrefix + suggestion;
// Tab completion
if (Input.GetKeyDown(KeyCode.Tab) && typedParts.Length >= 1)
{
__instance.freeChatField.textArea.SetText(fullSuggestion);
}
// Red text if command cannot be run
if (!closestCommand.CanRunCommand(out string _))
{
fullSuggestion = fullSuggestion.ToColor("#FF0300".HexToColor());
}
// Update UI elements
commandText.text = fullSuggestion;
commandInfo.text = $"{closestCommand.Description}{GenerateArgumentInfo()}{GenerateCanRunInfo()}";
}
private static string GenerateSuggestion(string[] typedParts)
{
// If only command name typed, return full command name
if (typedParts.Length == 1)
return closestCommand.Name;
// Update command arguments with typed values
UpdateCommandArguments(typedParts);
// Get next argument index for autocomplete
int nextArgumentIndex = typedParts.Length - 2;
if (nextArgumentIndex >= 0 && nextArgumentIndex < closestCommand.Arguments.Length)
{
var argument = closestCommand.Arguments[nextArgumentIndex];
var closestSuggestion = argument.GetClosestSuggestion();
// Complete partial argument
return typedCommand + closestSuggestion[Math.Min(argument.Arg.Length, closestSuggestion.Length)..];
}
@@ -212,12 +234,14 @@ internal static class ChatCommandsPatch
if (closestCommand.Arguments.Length == 0)
return string.Empty;
// Format argument help text
var argumentInfo = string.Join(" ", closestCommand.Arguments.Select(arg => arg.ArgInfo));
return $" - <#a6a6a6>{argumentInfo}</color>";
}
private static string GenerateCanRunInfo()
{
// Show reason if command cannot be run
if (!closestCommand.CanRunCommand(out string reason))
{
return $" - <#FF0300>{reason}</color>";
@@ -228,12 +252,14 @@ internal static class ChatCommandsPatch
internal static BaseCommand? GetClosestCommand(string typedCommand)
{
// First try exact match
var directNormalMatch = BaseCommand.allCommands
.FirstOrDefault(c => FilterCommand(c, CommandType.Normal) &&
c.Names.Any(name => string.Equals(name, typedCommand, StringComparison.OrdinalIgnoreCase)));
if (directNormalMatch != null)
return directNormalMatch;
// Then try partial match
var closestNormalCommand = BaseCommand.allCommands
.OrderBy(c => c.Name)
.FirstOrDefault(c => FilterCommand(c, CommandType.Normal) &&
@@ -246,6 +272,7 @@ internal static class ChatCommandsPatch
private static bool FilterCommand(BaseCommand command, CommandType commandType)
{
// Check if command is enabled and not disabled by other mods
return command.Type == commandType && command.ShowCommand() && !BAUModdedSupportFlags.HasFlag(BAUModdedSupportFlags.Disable_Command + command.Name);
}
}
}
+27 -3
View File
@@ -22,6 +22,7 @@ internal static class ChatPatch
{
if (!HudManager.InstanceExists) return;
// Clear all chat bubbles
HudManager.Instance.Chat.chatBubblePool.ReclaimAll();
}
@@ -29,6 +30,7 @@ internal static class ChatPatch
{
if (!HudManager.InstanceExists) return;
// Clear only player chat bubbles (keep command bubbles)
foreach (var obj in HudManager.Instance.Chat.chatBubblePool.activeChildren.ToArray())
{
var chatBubble = obj.GetComponent<ChatBubble>();
@@ -45,6 +47,7 @@ internal static class ChatPatch
{
if (!HudManager.InstanceExists) return;
// Clear only command chat bubbles (keep player chat)
foreach (var obj in HudManager.Instance.Chat.chatBubblePool.activeChildren.ToArray())
{
var chatBubble = obj.GetComponent<ChatBubble>();
@@ -61,6 +64,7 @@ internal static class ChatPatch
[HarmonyPostfix]
private static void ChatController_Toggle_Postfix(/*ChatController __instance*/)
{
// Apply chat theme when chat is opened/closed
SetChatTheme();
}
@@ -69,6 +73,7 @@ internal static class ChatPatch
[HarmonyPriority(Priority.First)]
private static void ChatController_Update_Prefix(ChatController __instance)
{
// Apply dark/light theme to chat input field
if (BAUPlugin.ChatDarkMode.Value)
{
// Free chat color
@@ -84,18 +89,21 @@ internal static class ChatPatch
__instance.freeChatField.textArea.outputText.color = Color.black;
}
// Ctrl+X to cut text to clipboard
if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.X))
{
ClipboardHelper.PutClipboardString(__instance.freeChatField.textArea.text);
__instance.freeChatField.textArea.SetText("");
}
// Up arrow for chat history navigation
if (Input.GetKeyDown(KeyCode.UpArrow) && ChatHistory.Any())
{
CurrentHistorySelection = Mathf.Clamp(--CurrentHistorySelection, 0, ChatHistory.Count - 1);
__instance.freeChatField.textArea.SetText(ChatHistory[CurrentHistorySelection]);
}
// Down arrow for chat history navigation
if (Input.GetKeyDown(KeyCode.DownArrow) && ChatHistory.Any())
{
CurrentHistorySelection++;
@@ -105,11 +113,12 @@ internal static class ChatPatch
}
}
// Add extra information to chat bubble
// Log chat messages to console
[HarmonyPatch(typeof(ChatController), nameof(ChatController.AddChat))]
[HarmonyPostfix]
private static void ChatController_AddChat_Postfix(ChatController __instance, PlayerControl sourcePlayer, string chatText)
{
// Log chat publicly if player is alive, privately if dead
if (sourcePlayer.IsAlive() || !PlayerControl.LocalPlayer.IsAlive())
{
Logger_.Log($"{sourcePlayer.Data.PlayerName} -> {chatText}", "ChatLog");
@@ -133,15 +142,20 @@ internal static class ChatPatch
string hashPuid = Utils.GetHashPuid(sourcePlayer);
string friendCode = playerInfo.FriendCode;
string playerName = playerInfo.BetterData()?.RealName ?? "???";
// Format role display with team color
string Role = $"<size=75%><color={sourcePlayer.GetTeamHexColor()}>{sourcePlayer.GetRoleName()}</color></size>+++";
// In lobby, show player tags instead of roles
if (GameState.IsLobby && !GameState.IsFreePlay)
{
Role = "";
// Show BAU user tag
if (sourcePlayer.IsLocalPlayer() || sourcePlayer.BetterData().IsBetterUser)
sbTag.AppendFormat("<color=#0dff00>{1}{0}</color>+++", Translator.GetString("Player.BetterUser"), sourcePlayer.BetterData().IsVerifiedBetterUser || sourcePlayer.IsLocalPlayer() ? "✓ " : "");
// Show mod-specific tags based on player data
if (BetterDataManager.BetterDataFile.SickoData.Any(info => info.CheckPlayerData(sourcePlayer.Data)))
sbTag.Append($"<color=#00f583>{Translator.GetString("Player.SickoUser")}</color>+++");
else if (BetterDataManager.BetterDataFile.AUMData.Any(info => info.CheckPlayerData(sourcePlayer.Data)))
@@ -152,6 +166,7 @@ internal static class ChatPatch
sbTag.Append($"<color=#fc0000>{Translator.GetString("Player.KnownCheater")}</color>+++");
}
// Hide roles from alive players (unless same team)
if (!sourcePlayer.IsImpostorTeammate())
{
if (PlayerControl.LocalPlayer.IsAlive() && !sourcePlayer.IsLocalPlayer())
@@ -160,11 +175,13 @@ internal static class ChatPatch
}
}
// Show role for dead players or if local player is Guardian Angel
if (PlayerControl.LocalPlayer.Is(RoleTypes.GuardianAngel) && !sourcePlayer.IsAlive() || !PlayerControl.LocalPlayer.Is(RoleTypes.GuardianAngel))
{
sbTag.Append(Role);
}
// Format tags with separators
sbInfo.Append("<size=75%>");
for (int i = 0; i < sbTag.ToString().Split("+++").Length; i++)
{
@@ -182,6 +199,7 @@ internal static class ChatPatch
}
sbInfo.Append("</size>");
// Position tags before local player name, after other players' names
bool flag = sourcePlayer.IsLocalPlayer();
if (flag)
{
@@ -227,13 +245,14 @@ internal static class ChatPatch
chat.openKeyboardButton.transform.Find("OpenKeyboardIcon").GetComponent<SpriteRenderer>().color = new Color(1f, 1f, 1f, 1f);
}
// Apply theme to all existing chat bubbles
foreach (var item in HudManager.Instance.Chat.chatBubblePool.activeChildren.SelectIl2Cpp(c => c.GetComponent<ChatBubble>()))
{
SetChatPoolTheme(item);
}
}
// Set chat theme
// Apply theme to individual chat bubble
internal static ChatBubble SetChatPoolTheme(ChatBubble asChatBubble)
{
ChatBubble chatBubble = asChatBubble;
@@ -243,6 +262,7 @@ internal static class ChatPatch
chatBubble.transform.Find("ChatText (TMP)").GetComponentInChildren<TextMeshPro>(true).color = new Color(1f, 1f, 1f, 1f);
chatBubble.transform.Find("Background").GetComponentInChildren<SpriteRenderer>(true).color = new Color(0.15f, 0.15f, 0.15f, 1f);
// Make dead player chat bubbles more transparent
if (chatBubble.transform.Find("PoolablePlayer/xMark") != null)
{
if (chatBubble.transform.Find("PoolablePlayer/xMark").GetComponentInChildren<SpriteRenderer>(true).enabled == true)
@@ -256,6 +276,7 @@ internal static class ChatPatch
chatBubble.transform.Find("ChatText (TMP)").GetComponentInChildren<TextMeshPro>(true).color = new Color(0f, 0f, 0f, 1f);
chatBubble.transform.Find("Background").GetComponentInChildren<SpriteRenderer>(true).color = new Color(1f, 1f, 1f, 1f);
// Make dead player chat bubbles more transparent
if (chatBubble.transform.Find("PoolablePlayer/xMark") != null)
{
if (chatBubble.transform.Find("PoolablePlayer/xMark").GetComponentInChildren<SpriteRenderer>(true).enabled == true)
@@ -272,6 +293,7 @@ internal static class ChatPatch
[HarmonyPostfix]
private static void FreeChatInputField_Awake_Postfix(FreeChatInputField __instance)
{
// Enable extended character support for chat
__instance.textArea.allowAllCharacters = true;
__instance.textArea.AllowSymbols = true;
__instance.textArea.AllowPaste = true;
@@ -284,6 +306,7 @@ internal static class ChatPatch
[HarmonyPostfix]
private static void FreeChatInputField_UpdateCharCount_Postfix(FreeChatInputField __instance)
{
// Update character counter with color coding
int length = __instance.textArea.text.Length;
__instance.charCountText.text = string.Format("{0}/118", length);
__instance.charCountText.color = GetCharColor(length);
@@ -291,6 +314,7 @@ internal static class ChatPatch
private static Color GetCharColor(int length)
{
// Color gradient: green -> yellow -> red as text length increases
Color[] colorGradient =
[
Color.green,
@@ -301,4 +325,4 @@ internal static class ChatPatch
(float min, float max) lerpRange = (0f, 117f);
return colorGradient.LerpColor(lerpRange, length);
}
}
}