mirror of
https://github.com/D1GQ/BetterAmongUs.git
synced 2026-10-20 01:46:19 +00:00
Add comments to patches
This commit is contained in:
1 parent
9bb98b7bc7
commit
49ee73740b
20 files changed
+370
-62
No files matched your search
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user