using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using HarmonyLib; using SOD.Common; using SOD.Common.BepInEx; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("GlobalChat")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+d0e60919a46380d27189dbc92c5189a9765ec9bd")] [assembly: AssemblyProduct("GlobalChat")] [assembly: AssemblyTitle("GlobalChat")] [assembly: AssemblyVersion("1.0.0.0")] namespace GlobalChat; public sealed class ChatCommandService { private static readonly Random Random = new Random(); public ChatCommandResult Resolve(string input) { if (string.IsNullOrWhiteSpace(input)) { return new ChatCommandResult { Handled = true, ShouldSend = false, LocalFeedback = string.Empty }; } string text = input.Trim(); if (!text.StartsWith("//", StringComparison.Ordinal)) { return new ChatCommandResult { Handled = false, ShouldSend = true, MessageType = "chat", RawText = input, FormattedText = "[" + GlobalChatRuntime.DisplayName + "] " + input }; } string text2 = text.Substring(2); string[] array = text2.Split(new char[1] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries); string text3 = ((array.Length != 0) ? array[0].ToLowerInvariant() : string.Empty); string text4 = ((array.Length > 1) ? array[1] : string.Empty); switch (text3) { case "wealth": case "money": return SendFormatted("command", input, $"[{GlobalChatRuntime.DisplayName}] has {PlayerContextService.GetWealthText()}."); case "location": return SendFormatted("command", input, $"[{GlobalChatRuntime.DisplayName}] is at {PlayerContextService.GetLocationText()}."); case "case": return SendFormatted("command", input, $"[{GlobalChatRuntime.DisplayName}] is working on case: {PlayerContextService.GetCaseText()}."); case "time": return SendFormatted("command", input, $"[{GlobalChatRuntime.DisplayName}] says the time is {PlayerContextService.GetTimeText()}."); case "social": return SendFormatted("command", input, $"[{GlobalChatRuntime.DisplayName}] has social rating: {PlayerContextService.GetSocialText()}."); case "roll": return SendFormatted("command", input, $"[{GlobalChatRuntime.DisplayName}] rolled {ResolveRollValue(text4)}."); case "mute": return ResolveMute(text4); case "unmute": return ResolveUnmute(text4); case "color": case "colour": return ResolveColor(text4); case "muted": return new ChatCommandResult { Handled = true, ShouldSend = false, LocalFeedback = ((GlobalChatRuntime.Instance != null) ? GlobalChatRuntime.Instance.GetMutedUsersSummary() : "Muted users: none") }; case "help": return new ChatCommandResult { Handled = true, ShouldSend = false, LocalFeedback = "Commands: //wealth, //money, //location, //case, //time, //social, //roll [max], //mute , //unmute , //muted, //color [#RRGGBB|reset], //help, //me " }; case "me": if (string.IsNullOrWhiteSpace(text4)) { return new ChatCommandResult { Handled = true, ShouldSend = false, LocalFeedback = "Usage: //me " }; } return SendFormatted("emote", input, "* " + GlobalChatRuntime.DisplayName + " " + text4); default: return new ChatCommandResult { Handled = true, ShouldSend = false, LocalFeedback = "Unknown command: //" + text3 }; } } private static ChatCommandResult SendFormatted(string messageType, string rawText, string formattedText) { return new ChatCommandResult { Handled = true, ShouldSend = true, MessageType = messageType, RawText = rawText, FormattedText = formattedText }; } private static ChatCommandResult ResolveColor(string remainder) { if (GlobalChatRuntime.Instance == null) { return new ChatCommandResult { Handled = true, ShouldSend = false, LocalFeedback = "Color system is unavailable." }; } string text = NormalizeNameArgument(remainder); if (string.IsNullOrWhiteSpace(text)) { string userNameColorSummary = GlobalChatRuntime.Instance.GetUserNameColorSummary(); return new ChatCommandResult { Handled = true, ShouldSend = false, LocalFeedback = userNameColorSummary }; } if (text.Equals("reset", StringComparison.OrdinalIgnoreCase) || text.Equals("clear", StringComparison.OrdinalIgnoreCase)) { GlobalChatRuntime.Instance.ResetUserNameColor(); return new ChatCommandResult { Handled = true, ShouldSend = false, LocalFeedback = "Username color reset." }; } if (!GlobalChatRuntime.Instance.TrySetUserNameColor(text, out var normalizedColor, out var error)) { return new ChatCommandResult { Handled = true, ShouldSend = false, LocalFeedback = (string.IsNullOrWhiteSpace(error) ? "Usage: //color <#RRGGBB|reset>" : error) }; } return new ChatCommandResult { Handled = true, ShouldSend = false, LocalFeedback = "Username color set to " + normalizedColor + "." }; } private static ChatCommandResult ResolveMute(string remainder) { string text = NormalizeNameArgument(remainder); if (string.IsNullOrWhiteSpace(text)) { return new ChatCommandResult { Handled = true, ShouldSend = false, LocalFeedback = "Usage: //mute " }; } bool flag = GlobalChatRuntime.Instance != null && GlobalChatRuntime.Instance.AddMutedUser(text); return new ChatCommandResult { Handled = true, ShouldSend = false, LocalFeedback = (flag ? ("Muted " + text + ".") : (text + " is already muted.")) }; } private static ChatCommandResult ResolveUnmute(string remainder) { string text = NormalizeNameArgument(remainder); if (string.IsNullOrWhiteSpace(text)) { return new ChatCommandResult { Handled = true, ShouldSend = false, LocalFeedback = "Usage: //unmute " }; } bool flag = GlobalChatRuntime.Instance != null && GlobalChatRuntime.Instance.RemoveMutedUser(text); return new ChatCommandResult { Handled = true, ShouldSend = false, LocalFeedback = (flag ? ("Unmuted " + text + ".") : (text + " is not muted.")) }; } private static string NormalizeNameArgument(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } string text = value.Trim(); if (text.Length >= 2 && text.StartsWith("\"", StringComparison.Ordinal) && text.EndsWith("\"", StringComparison.Ordinal)) { text = text.Substring(1, text.Length - 2).Trim(); } return text; } private static string ResolveRollValue(string remainder) { int num = 100; if (!string.IsNullOrWhiteSpace(remainder)) { string text = remainder.Trim(); int result3; if (text.Contains("d", StringComparison.OrdinalIgnoreCase)) { string[] array = text.Split(new char[2] { 'd', 'D' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length == 2 && int.TryParse(array[0], out var result) && int.TryParse(array[1], out var result2) && result > 0 && result <= 20 && result2 > 1 && result2 <= 1000) { int num2 = 0; for (int i = 0; i < result; i++) { num2 += Random.Next(1, result2 + 1); } return $"{num2} ({result}d{result2})"; } } else if (int.TryParse(text, out result3) && result3 > 1) { num = Mathf.Min(result3, 1000); } } return $"{Random.Next(1, num + 1)} (1-{num})"; } } public static class PlayerContextService { public static string GetWealthText() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown try { if ((Object)(object)GameplayController.Instance != (Object)null) { return $"${GameplayController.Instance.money}"; } } catch (Exception ex) { ManualLogSource log = PluginController.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(23, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to read wealth: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogWarning(val); } return "unknown wealth"; } public static string GetLocationText() { //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Expected O, but got Unknown try { if ((Object)(object)Player.Instance == (Object)null || ((Actor)Player.Instance).currentNode == null) { return "an unknown location"; } NewNode currentNode = ((Actor)Player.Instance).currentNode; if ((Object)(object)currentNode.room != (Object)null) { string text = ((!string.IsNullOrWhiteSpace(currentNode.room.name)) ? currentNode.room.name : currentNode.room.GetName()); string text2 = (((Object)(object)currentNode.building != (Object)null) ? ((Object)currentNode.building).name : "Unknown Building"); return text + ", " + text2; } if ((Object)(object)currentNode.building != (Object)null && !string.IsNullOrWhiteSpace(((Object)currentNode.building).name)) { return ((Object)currentNode.building).name; } StreetController val = (((Object)(object)currentNode.building != (Object)null) ? currentNode.building.street : null); if ((Object)(object)val == (Object)null && (Object)(object)((Actor)Player.Instance).currentGameLocation != (Object)null) { val = ((Actor)Player.Instance).currentGameLocation.thisAsStreet; } if ((Object)(object)val != (Object)null && !string.IsNullOrWhiteSpace(((Object)val).name)) { return ((Object)val).name; } } catch (Exception ex) { ManualLogSource log = PluginController.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(25, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Failed to read location: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex.Message); } log.LogWarning(val2); } return "an unknown location"; } public static string GetCaseText() { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown try { if ((Object)(object)CasePanelController.Instance == (Object)null || CasePanelController.Instance.activeCase == null) { return "no active case"; } Case activeCase = CasePanelController.Instance.activeCase; if (!string.IsNullOrWhiteSpace(activeCase.name)) { return activeCase.name; } } catch (Exception ex) { ManualLogSource log = PluginController.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(28, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to read active case: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogWarning(val); } return "an unknown case"; } public static string GetTimeText() { //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Expected O, but got Unknown try { object obj = ReadStaticProperty(typeof(SessionData), "Instance") ?? ReadStaticField(typeof(SessionData), "instance"); if (obj != null) { string text = ReadFirstString(obj, "day", "dayName", "weekday", "weekdayName", "currentDayName"); string text2 = ReadFirstString(obj, "gameTimeString", "timeString", "formattedTime", "currentTimeString"); if (string.IsNullOrWhiteSpace(text2)) { object obj2 = ReadFirstValue(obj, "decimalClock", "gameTime", "timeOfDay", "decimalTime"); if (obj2 is float num) { text2 = num.ToString("0.00"); } else if (obj2 is double num2) { text2 = num2.ToString("0.00"); } } if (!string.IsNullOrWhiteSpace(text) && !string.IsNullOrWhiteSpace(text2)) { return text2 + " on " + text; } if (!string.IsNullOrWhiteSpace(text2)) { return text2; } } } catch (Exception ex) { ManualLogSource log = PluginController.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(21, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to read time: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogWarning(val); } return "an unknown time"; } public static string GetSocialText() { //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected O, but got Unknown try { if ((Object)(object)GameplayController.Instance != (Object)null) { object obj = ReadFirstValue(GameplayController.Instance, "socialCredit", "socialScore", "creditRating", "citizenRank", "socialRating"); if (obj != null) { return obj.ToString(); } } if ((Object)(object)Player.Instance != (Object)null) { object obj2 = ReadFirstValue(Player.Instance, "socialCredit", "socialScore", "creditRating", "citizenRank", "socialRating"); if (obj2 != null) { return obj2.ToString(); } } } catch (Exception ex) { ManualLogSource log = PluginController.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(29, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to read social score: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogWarning(val); } return "unknown"; } private static object ReadStaticProperty(Type type, string propertyName) { PropertyInfo property = type.GetProperty(propertyName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); return (property != null) ? property.GetValue(null, null) : null; } private static object ReadStaticField(Type type, string fieldName) { FieldInfo field = type.GetField(fieldName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); return (field != null) ? field.GetValue(null) : null; } private static object ReadFirstValue(object target, params string[] memberNames) { if (target == null) { return null; } Type type = target.GetType(); for (int i = 0; i < memberNames.Length; i++) { FieldInfo field = type.GetField(memberNames[i], BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(target); } PropertyInfo property = type.GetProperty(memberNames[i], BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property.GetValue(target, null); } } return null; } private static string ReadFirstString(object target, params string[] memberNames) { return ReadFirstValue(target, memberNames)?.ToString(); } } [Serializable] public class ChatMessageDto { public string messageId; public string playerId; public string displayName; public string resolvedDisplayName; public string usernameColorHex; public string channelId; public string messageType; public string rawText; public string formattedText; public string filteredRawText; public string filteredFormattedText; public bool containsProfanity; public string createdAt; public string clientVersion; } [Serializable] public class SendChatMessageRequest { public string playerId; public string displayName; public string usernameColorHex; public string channelId; public string messageType; public string rawText; public string formattedText; public string clientVersion; public bool applyProfanityFilter = true; } [Serializable] public class UpdatePresenceRequest { public string playerId; public string displayName; public string usernameColorHex; public string clientVersion; public string currentStatus; } [Serializable] public class ChatMessagesResponse { public List messages = new List(); public string nextCursor; public int onlineCount; public List onlinePlayers = new List(); public bool success = true; public string error; } [Serializable] public class ChatSendResponse { public bool success = true; public string messageId; public string createdAt; public string error; } [Serializable] public class PresenceUpdateResponse { public bool success = true; public string updatedAt; public int onlineCount; public List onlinePlayers = new List(); public string error; } [Serializable] public class PresenceDisconnectResponse { public bool success = true; public int onlineCount; public List onlinePlayers = new List(); public string error; } [Serializable] public class OnlinePlayerDto { public string playerId; public string displayName; public string usernameColorHex; public string updatedAt; } public sealed class OnlinePlayerViewModel { public string PlayerId { get; set; } public string DisplayName { get; set; } public string UsernameColorHex { get; set; } } public sealed class ChatMessageViewModel { public string Id { get; set; } public string DisplayName { get; set; } public string ResolvedDisplayName { get; set; } public string UsernameColorHex { get; set; } public string DisplayText { get; set; } public string FilteredDisplayText { get; set; } public string RawText { get; set; } public string MessageType { get; set; } public bool ContainsProfanity { get; set; } public DateTime CreatedAtUtc { get; set; } } public sealed class ChatCommandResult { public bool Handled { get; set; } public bool ShouldSend { get; set; } public string MessageType { get; set; } public string RawText { get; set; } public string FormattedText { get; set; } public string LocalFeedback { get; set; } } public enum ChatConnectionState { Offline, Connecting, Online, Error } public interface IChatTransport { ChatConnectionState ConnectionState { get; } string StatusText { get; } int OnlineUserCount { get; } IReadOnlyList OnlinePlayers { get; } void Initialize(); void Tick(float unscaledDeltaTime); void QueueMessage(SendChatMessageRequest request); void UpdatePresence(string playerId, string displayName); void DisconnectPresence(string playerId); List DrainIncomingMessages(); } public sealed class HttpPollingChatTransport : IChatTransport { private static readonly HttpClient HttpClient = new HttpClient(); private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions { IncludeFields = true, PropertyNameCaseInsensitive = true }; private readonly Queue _outbox = new Queue(); private readonly List _inbox = new List(); private readonly HashSet _seenMessageIds = new HashSet(); private readonly List _onlinePlayers = new List(); private float _pollTimer; private float _presenceTimer; private bool _initialized; private bool _pollInFlight; private bool _sendInFlight; private bool _presenceInFlight; private bool _disconnectSent; private float _retryDelayTimer; private string _cursor; private string _statusText = "Offline"; private ChatConnectionState _connectionState = ChatConnectionState.Offline; private int _onlineUserCount; public ChatConnectionState ConnectionState => _connectionState; public string StatusText => _statusText; public int OnlineUserCount => _onlineUserCount; public IReadOnlyList OnlinePlayers => _onlinePlayers; public void Initialize() { _initialized = true; _connectionState = ChatConnectionState.Connecting; _statusText = "Connecting"; _pollTimer = 0f; _presenceTimer = 0f; _retryDelayTimer = 0f; _disconnectSent = false; } public void Tick(float unscaledDeltaTime) { if (_initialized && GlobalChatRuntime.IsConfigured) { _pollTimer -= unscaledDeltaTime; _presenceTimer -= unscaledDeltaTime; if (_retryDelayTimer > 0f) { _retryDelayTimer -= unscaledDeltaTime; } if (_pollTimer <= 0f && !_pollInFlight) { _pollTimer = Mathf.Max(1f, GlobalChatRuntime.PollIntervalSeconds); PollMessagesAsync(); } if (_presenceTimer <= 0f && !_presenceInFlight) { _presenceTimer = Mathf.Max(5f, GlobalChatRuntime.PresenceIntervalSeconds); UpdatePresence(GlobalChatRuntime.PlayerId, GlobalChatRuntime.DisplayName); } if (_outbox.Count > 0 && !_sendInFlight && _retryDelayTimer <= 0f) { SendChatMessageRequest request = _outbox.Dequeue(); SendMessageAsync(request); } } } public void QueueMessage(SendChatMessageRequest request) { if (request != null) { _outbox.Enqueue(request); } } public void UpdatePresence(string playerId, string displayName) { if (!_presenceInFlight && GlobalChatRuntime.IsConfigured) { UpdatePresenceAsync(new UpdatePresenceRequest { playerId = playerId, displayName = displayName, usernameColorHex = GlobalChatRuntime.UserNameColorHex, clientVersion = "1.0.0", currentStatus = "online" }); } } public void DisconnectPresence(string playerId) { if (!string.IsNullOrWhiteSpace(playerId) && GlobalChatRuntime.IsConfigured && !_disconnectSent) { DisconnectPresenceBlocking(playerId); } } public List DrainIncomingMessages() { List result = new List(_inbox); _inbox.Clear(); return result; } private async Task PollMessagesAsync() { _pollInFlight = true; try { string endpoint = (string.IsNullOrWhiteSpace(_cursor) ? (GlobalChatRuntime.ApiBaseUrl.TrimEnd('/') + "/api/chat/messages") : (GlobalChatRuntime.ApiBaseUrl.TrimEnd('/') + "/api/chat/messages?after=" + Uri.EscapeDataString(_cursor))); HttpResponseMessage response = await HttpClient.GetAsync(endpoint).ConfigureAwait(continueOnCapturedContext: false); string json = await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false); if (!response.IsSuccessStatusCode) { SetErrorState($"HTTP {response.StatusCode}"); return; } ChatMessagesResponse payload = JsonSerializer.Deserialize(json, JsonOptions); if (payload == null) { SetErrorState("Bad response"); return; } if (!payload.success) { SetErrorState(string.IsNullOrWhiteSpace(payload.error) ? "Poll failed" : payload.error); return; } if (!string.IsNullOrWhiteSpace(payload.nextCursor)) { _cursor = payload.nextCursor; } _onlineUserCount = Mathf.Max(0, payload.onlineCount); UpdateOnlinePlayers(payload.onlinePlayers); if (payload.messages != null) { for (int i = 0; i < payload.messages.Count; i++) { ChatMessageDto dto = payload.messages[i]; if (dto != null) { string id = (string.IsNullOrWhiteSpace(dto.messageId) ? Guid.NewGuid().ToString("N") : dto.messageId); if (_seenMessageIds.Add(id)) { _inbox.Add(Map(dto, id)); } } } } _connectionState = ChatConnectionState.Online; _statusText = "Online"; } catch (Exception ex2) { Exception ex = ex2; ManualLogSource log = PluginController.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(24, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("GlobalChat poll failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); SetErrorState("Offline"); } finally { _pollInFlight = false; } } private async Task SendMessageAsync(SendChatMessageRequest request) { _sendInFlight = true; try { string endpoint = GlobalChatRuntime.ApiBaseUrl.TrimEnd('/') + "/api/chat/send"; string json = JsonSerializer.Serialize(request, JsonOptions); StringContent content = new StringContent(json, Encoding.UTF8, "application/json"); HttpResponseMessage response = await HttpClient.PostAsync(endpoint, content).ConfigureAwait(continueOnCapturedContext: false); string responseJson = await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false); if (!response.IsSuccessStatusCode) { RequeueFailedMessage(request); SetErrorState($"Send {response.StatusCode}"); return; } ChatSendResponse payload = JsonSerializer.Deserialize(responseJson, JsonOptions); if (payload != null && !payload.success) { RequeueFailedMessage(request); SetErrorState(string.IsNullOrWhiteSpace(payload.error) ? "Send failed" : payload.error); } else { _connectionState = ChatConnectionState.Online; _statusText = "Online"; _retryDelayTimer = 0f; } } catch (Exception ex2) { Exception ex = ex2; ManualLogSource log = PluginController.Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(24, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("GlobalChat send failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } log.LogError(val); RequeueFailedMessage(request); SetErrorState("Offline"); } finally { _sendInFlight = false; } } private async Task UpdatePresenceAsync(UpdatePresenceRequest request) { _presenceInFlight = true; try { string endpoint = GlobalChatRuntime.ApiBaseUrl.TrimEnd('/') + "/api/presence/update"; string json = JsonSerializer.Serialize(request, JsonOptions); StringContent content = new StringContent(json, Encoding.UTF8, "application/json"); HttpResponseMessage response = await HttpClient.PostAsync(endpoint, content).ConfigureAwait(continueOnCapturedContext: false); if (response.IsSuccessStatusCode) { PresenceUpdateResponse payload = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false), JsonOptions); if (payload != null) { _onlineUserCount = Mathf.Max(0, payload.onlineCount); UpdateOnlinePlayers(payload.onlinePlayers); } else { _onlineUserCount = Mathf.Max(0, _onlineUserCount); } if (_connectionState != ChatConnectionState.Error) { _connectionState = ChatConnectionState.Online; _statusText = "Online"; } } } catch (Exception ex2) { Exception ex = ex2; ManualLogSource log = PluginController.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(35, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("GlobalChat presence update failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogWarning(val); } finally { _presenceInFlight = false; } } private void DisconnectPresenceBlocking(string playerId) { //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Expected O, but got Unknown try { _disconnectSent = true; string requestUri = GlobalChatRuntime.ApiBaseUrl.TrimEnd('/') + "/api/presence/disconnect"; string content = JsonSerializer.Serialize(new { playerId }, JsonOptions); StringContent content2 = new StringContent(content, Encoding.UTF8, "application/json"); HttpResponseMessage result = HttpClient.PostAsync(requestUri, content2).GetAwaiter().GetResult(); if (result.IsSuccessStatusCode) { string result2 = result.Content.ReadAsStringAsync().GetAwaiter().GetResult(); PresenceDisconnectResponse presenceDisconnectResponse = JsonSerializer.Deserialize(result2, JsonOptions); if (presenceDisconnectResponse != null && presenceDisconnectResponse.success) { _onlineUserCount = Mathf.Max(0, presenceDisconnectResponse.onlineCount); UpdateOnlinePlayers(presenceDisconnectResponse.onlinePlayers); } } } catch (Exception ex) { _disconnectSent = false; ManualLogSource log = PluginController.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(39, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("GlobalChat presence disconnect failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogWarning(val); } } private static ChatMessageViewModel Map(ChatMessageDto dto, string id) { DateTime result = DateTime.UtcNow; if (!string.IsNullOrWhiteSpace(dto.createdAt)) { DateTime.TryParse(dto.createdAt, out result); } string text = (string.IsNullOrWhiteSpace(dto.formattedText) ? dto.rawText : dto.formattedText); string text2 = (string.IsNullOrWhiteSpace(dto.filteredFormattedText) ? dto.filteredRawText : dto.filteredFormattedText); if (string.IsNullOrWhiteSpace(text)) { text = "[empty]"; } if (string.IsNullOrWhiteSpace(text2)) { text2 = text; } return new ChatMessageViewModel { Id = id, DisplayName = dto.displayName, ResolvedDisplayName = (string.IsNullOrWhiteSpace(dto.resolvedDisplayName) ? dto.displayName : dto.resolvedDisplayName), UsernameColorHex = dto.usernameColorHex, RawText = dto.rawText, DisplayText = text, FilteredDisplayText = text2, MessageType = (string.IsNullOrWhiteSpace(dto.messageType) ? "chat" : dto.messageType), ContainsProfanity = dto.containsProfanity, CreatedAtUtc = ((result == default(DateTime)) ? DateTime.UtcNow : result) }; } private void UpdateOnlinePlayers(List players) { _onlinePlayers.Clear(); if (players == null) { return; } for (int i = 0; i < players.Count; i++) { OnlinePlayerDto onlinePlayerDto = players[i]; if (onlinePlayerDto != null && !string.IsNullOrWhiteSpace(onlinePlayerDto.displayName)) { _onlinePlayers.Add(new OnlinePlayerViewModel { PlayerId = onlinePlayerDto.playerId, DisplayName = onlinePlayerDto.displayName.Trim(), UsernameColorHex = onlinePlayerDto.usernameColorHex }); } } } private void RequeueFailedMessage(SendChatMessageRequest request) { if (request != null) { _retryDelayTimer = Mathf.Max(_retryDelayTimer, 2f); _outbox.Enqueue(request); _statusText = $"Offline ({_outbox.Count} queued)"; } } private void SetErrorState(string error) { _connectionState = ChatConnectionState.Error; string text = (string.IsNullOrWhiteSpace(error) ? "Error" : error); _statusText = ((_outbox.Count > 0) ? $"{text} ({_outbox.Count} queued)" : text); } } [HarmonyPatch(typeof(Player), "Update")] public class GlobalChatPatch { public static void Postfix() { if (SaveGameHandlers.IsGameActive) { GlobalChatRuntime.EnsureCreated(); if (GlobalChatRuntime.Instance != null) { GlobalChatRuntime.Instance.Tick(); } } } } public sealed class GlobalChatRuntime { private static readonly Regex UserNameColorRegex = new Regex("^#?[0-9A-Fa-f]{6}$", RegexOptions.Compiled); private static GlobalChatRuntime _instance; private readonly List _messages = new List(); private readonly HashSet _localEchoPayloads = new HashSet(); private readonly HashSet _mutedUsers = new HashSet(StringComparer.OrdinalIgnoreCase); private IChatTransport _transport; private ChatCommandService _commandService; private ChatUiController _ui; private float _localMessageCooldown; private bool _isPaused; private int _unreadCount; public static GlobalChatRuntime Instance => _instance; public static string ApiBaseUrl => Plugin.ApiBaseUrl.Value; public static string DisplayName => Plugin.DisplayName.Value; public static string PlayerId => Plugin.PlayerId.Value; public static string UserNameColorHex => (Plugin.UserNameColorHex != null) ? Plugin.UserNameColorHex.Value : string.Empty; public static float PollIntervalSeconds => Plugin.PollIntervalSeconds.Value; public static float PresenceIntervalSeconds => Plugin.PresenceIntervalSeconds.Value; public static bool IsConfigured => !string.IsNullOrWhiteSpace(ApiBaseUrl) && !string.IsNullOrWhiteSpace(DisplayName) && !string.IsNullOrWhiteSpace(PlayerId); public IReadOnlyList Messages => _messages; public static void EnsureCreated() { if (_instance == null) { _instance = new GlobalChatRuntime(); } } private GlobalChatRuntime() { LoadMutedUsers(); _commandService = new ChatCommandService(); _transport = new HttpPollingChatTransport(); _transport.Initialize(); _ui = new ChatUiController(); _ui.Initialize(this); } public void Tick() { if (!SaveGameHandlers.IsGameActive) { if (_ui != null && _ui.IsVisible) { _ui.Hide(); } return; } if (_transport != null) { _transport.Tick(Time.unscaledDeltaTime); List list = _transport.DrainIncomingMessages(); if (list.Count > 0) { for (int i = 0; i < list.Count; i++) { AddMessage(list[i]); } } } if (_localMessageCooldown > 0f) { _localMessageCooldown -= Time.unscaledDeltaTime; } if (_ui != null) { _ui.SetStatus((_transport != null) ? _transport.StatusText : "Offline"); _ui.SetOnlineCount((_transport != null) ? _transport.OnlineUserCount : 0); ChatUiController ui = _ui; IReadOnlyList onlinePlayers; if (_transport == null) { IReadOnlyList readOnlyList = Array.Empty(); onlinePlayers = readOnlyList; } else { onlinePlayers = _transport.OnlinePlayers; } ui.SetOnlinePlayers(onlinePlayers); _ui.SetUnreadCount(_unreadCount); } if (ShouldToggleChat()) { ToggleChat(); } if (_ui != null) { _ui.Tick(); } } private void SyncProfileColor() { if (_transport != null) { _transport.UpdatePresence(PlayerId, DisplayName); } RefreshVisibleMessages(); } private static string NormalizeUserNameColor(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } string text = value.Trim(); if (!UserNameColorRegex.IsMatch(text)) { return string.Empty; } if (!text.StartsWith("#", StringComparison.Ordinal)) { text = "#" + text; } return text.ToUpperInvariant(); } public bool AddMutedUser(string displayName) { string text = NormalizeMutedUser(displayName); if (string.IsNullOrWhiteSpace(text) || !_mutedUsers.Add(text)) { return false; } SaveMutedUsers(); RefreshVisibleMessages(); return true; } public bool RemoveMutedUser(string displayName) { string text = NormalizeMutedUser(displayName); if (string.IsNullOrWhiteSpace(text) || !_mutedUsers.Remove(text)) { return false; } SaveMutedUsers(); RefreshVisibleMessages(); return true; } public string GetMutedUsersSummary() { if (_mutedUsers.Count == 0) { return "Muted users: none"; } return "Muted users: " + string.Join(", ", _mutedUsers.OrderBy((string name) => name, StringComparer.OrdinalIgnoreCase)); } public string GetUserNameColorSummary() { string text = NormalizeUserNameColor(UserNameColorHex); return string.IsNullOrWhiteSpace(text) ? "Username color: default" : ("Username color: " + text); } public bool TrySetUserNameColor(string value, out string normalizedColor, out string error) { normalizedColor = NormalizeUserNameColor(value); if (string.IsNullOrWhiteSpace(normalizedColor)) { error = "Usage: //color <#RRGGBB|reset>"; return false; } if (Plugin.UserNameColorHex == null) { error = "Username color setting is unavailable."; return false; } Plugin.UserNameColorHex.Value = normalizedColor; ((ConfigEntryBase)Plugin.UserNameColorHex).ConfigFile.Save(); SyncProfileColor(); error = string.Empty; return true; } public void ResetUserNameColor() { if (Plugin.UserNameColorHex != null) { Plugin.UserNameColorHex.Value = string.Empty; ((ConfigEntryBase)Plugin.UserNameColorHex).ConfigFile.Save(); SyncProfileColor(); } } public void HandlePauseStateChanged(bool isPaused) { _isPaused = isPaused; if (_isPaused && _ui != null) { _ui.Hide(); } } public void Shutdown() { if (_transport != null && !string.IsNullOrWhiteSpace(PlayerId)) { _transport.DisconnectPresence(PlayerId); } if (_ui != null) { _ui.Hide(); } } public void SubmitInput(string input) { if (!SaveGameHandlers.IsGameActive) { return; } if (_localMessageCooldown > 0f) { AddSystemMessage("You are sending messages too quickly."); return; } ChatCommandResult chatCommandResult = _commandService.Resolve(input); if (!string.IsNullOrWhiteSpace(chatCommandResult.LocalFeedback)) { AddSystemMessage(chatCommandResult.LocalFeedback); } if (chatCommandResult.ShouldSend) { SendChatMessageRequest sendChatMessageRequest = new SendChatMessageRequest { playerId = PlayerId, displayName = DisplayName, usernameColorHex = UserNameColorHex, channelId = "global", messageType = (string.IsNullOrWhiteSpace(chatCommandResult.MessageType) ? "chat" : chatCommandResult.MessageType), rawText = (string.IsNullOrWhiteSpace(chatCommandResult.RawText) ? input : chatCommandResult.RawText), formattedText = chatCommandResult.FormattedText, clientVersion = "1.0.0" }; _transport.QueueMessage(sendChatMessageRequest); string text = BuildEchoKey(sendChatMessageRequest.formattedText, sendChatMessageRequest.rawText); if (!string.IsNullOrWhiteSpace(text)) { _localEchoPayloads.Add(text); } string text2 = (string.IsNullOrWhiteSpace(sendChatMessageRequest.formattedText) ? sendChatMessageRequest.rawText : sendChatMessageRequest.formattedText); string text3 = ProfanityFilterService.Apply(text2); bool containsProfanity = !string.Equals(text2, text3, StringComparison.Ordinal); AddMessage(new ChatMessageViewModel { Id = Guid.NewGuid().ToString("N"), DisplayName = DisplayName, ResolvedDisplayName = DisplayName, UsernameColorHex = UserNameColorHex, RawText = sendChatMessageRequest.rawText, DisplayText = text2, FilteredDisplayText = text3, MessageType = "local_echo", ContainsProfanity = containsProfanity, CreatedAtUtc = DateTime.UtcNow }); _localMessageCooldown = Plugin.LocalSendCooldownSeconds.Value; } } public void AddSystemMessage(string text) { AddMessage(new ChatMessageViewModel { Id = Guid.NewGuid().ToString("N"), DisplayName = "System", ResolvedDisplayName = "System", UsernameColorHex = string.Empty, RawText = text, DisplayText = "[System] " + text, MessageType = "system", CreatedAtUtc = DateTime.UtcNow }); } private void AddMessage(ChatMessageViewModel message) { string text = BuildEchoKey(message.DisplayText, message.RawText); if (message != null && message.MessageType != "local_echo" && !string.IsNullOrWhiteSpace(text) && _localEchoPayloads.Remove(text)) { for (int num = _messages.Count - 1; num >= 0; num--) { ChatMessageViewModel chatMessageViewModel = _messages[num]; if (chatMessageViewModel != null && !(chatMessageViewModel.MessageType != "local_echo")) { string a = BuildEchoKey(chatMessageViewModel.DisplayText, chatMessageViewModel.RawText); if (string.Equals(a, text, StringComparison.Ordinal)) { chatMessageViewModel.Id = message.Id; chatMessageViewModel.DisplayName = message.DisplayName; chatMessageViewModel.ResolvedDisplayName = message.ResolvedDisplayName; chatMessageViewModel.UsernameColorHex = message.UsernameColorHex; chatMessageViewModel.DisplayText = message.DisplayText; chatMessageViewModel.FilteredDisplayText = message.FilteredDisplayText; chatMessageViewModel.RawText = message.RawText; chatMessageViewModel.MessageType = message.MessageType; chatMessageViewModel.ContainsProfanity = message.ContainsProfanity; chatMessageViewModel.CreatedAtUtc = message.CreatedAtUtc; RefreshVisibleMessages(); break; } } } } else { _messages.Add(message); while (_messages.Count > Plugin.MaxMessages.Value) { _messages.RemoveAt(0); } if (ShouldCountAsUnread(message)) { _unreadCount++; } RefreshVisibleMessages(); } } private void RefreshVisibleMessages() { if (_ui != null) { _ui.RefreshMessages(GetVisibleMessages()); _ui.SetUnreadCount(_unreadCount); if (!_isPaused) { _ui.ShowMessagePreview(); } } } private bool ShouldToggleChat() { //IL_007b: Unknown result type (might be due to invalid IL or missing references) if (!SaveGameHandlers.IsGameActive) { return false; } if (_isPaused) { return false; } if (_ui != null && _ui.IsVisible) { return false; } if (string.IsNullOrWhiteSpace(Plugin.ToggleChatKey.Value)) { return Input.GetKeyDown((KeyCode)13); } if (Enum.TryParse(Plugin.ToggleChatKey.Value, ignoreCase: true, out KeyCode result)) { return Input.GetKeyDown(result); } return Input.GetKeyDown((KeyCode)13); } private void ToggleChat() { if (_ui == null) { return; } if (!SaveGameHandlers.IsGameActive) { _ui.Hide(); return; } if (!_ui.IsVisible) { _unreadCount = 0; _ui.SetUnreadCount(_unreadCount); } _ui.ToggleVisibility(); } private IReadOnlyList GetVisibleMessages() { if (_mutedUsers.Count == 0) { return _messages; } List list = new List(_messages.Count); for (int i = 0; i < _messages.Count; i++) { ChatMessageViewModel chatMessageViewModel = _messages[i]; if (chatMessageViewModel != null && !IsMuted(chatMessageViewModel.DisplayName)) { list.Add(chatMessageViewModel); } } return list; } private bool ShouldCountAsUnread(ChatMessageViewModel message) { if (_ui == null || _ui.IsVisible || !Plugin.EnableUnreadBadge.Value || message == null) { return false; } if (message.MessageType == "system" || message.MessageType == "local_echo") { return false; } return !IsMuted(message.DisplayName); } private bool IsMuted(string displayName) { string text = NormalizeMutedUser(displayName); return !string.IsNullOrWhiteSpace(text) && _mutedUsers.Contains(text); } private void LoadMutedUsers() { _mutedUsers.Clear(); if (Plugin.MutedUsers == null || string.IsNullOrWhiteSpace(Plugin.MutedUsers.Value)) { return; } string[] array = Plugin.MutedUsers.Value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = NormalizeMutedUser(array[i]); if (!string.IsNullOrWhiteSpace(text)) { _mutedUsers.Add(text); } } } private void SaveMutedUsers() { if (Plugin.MutedUsers != null) { Plugin.MutedUsers.Value = string.Join(",", _mutedUsers.OrderBy((string name) => name, StringComparer.OrdinalIgnoreCase)); ((ConfigEntryBase)Plugin.MutedUsers).ConfigFile.Save(); } } private static string NormalizeMutedUser(string value) { return string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim(); } private static string BuildEchoKey(string displayText, string rawText) { if (string.IsNullOrWhiteSpace(displayText) && string.IsNullOrWhiteSpace(rawText)) { return string.Empty; } return displayText + "|" + rawText; } } public sealed class ChatUiController { private static TMP_FontAsset _cachedFont; private static Material _cachedFontMaterial; private GlobalChatRuntime _runtime; private GameObject _root; private Canvas _canvas; private GameObject _panel; private CanvasGroup _panelCanvasGroup; private GameObject _inputRoot; private ScrollRect _messagesScrollRect; private RectTransform _messagesViewport; private RectTransform _messagesContent; private TextMeshProUGUI _messagesText; private TextMeshProUGUI _statusText; private TextMeshProUGUI _onlineCountText; private TextMeshProUGUI _onlinePlayersText; private TextMeshProUGUI _unreadText; private TextMeshProUGUI _placeholderText; private TextMeshProUGUI _inputText; private TMP_InputField _inputField; private bool _visible; private bool _previousPlayerTextInputActive; private bool _previousInputControllerEnabled = true; private bool _previousMouseLookEnabled = true; private bool _previousCharacterControllerEnabled = true; private int _openedFrame = -1; private bool _fontRefreshAttemptedInGame; private bool _previewVisible; private float _previewTimer; private const float PreviewOpacity = 0.2f; private const float PreviewDurationSeconds = 5f; private const float PreviewFadeSpeed = 2.5f; public bool IsVisible => _visible; public void Initialize(GlobalChatRuntime runtime) { _runtime = runtime; BuildUi(); SetVisible(visible: false); } public void Tick() { RefreshFontsIfNeeded(); if (!_visible) { TickPreview(); } if (!_visible || (Object)(object)_inputField == (Object)null || Time.frameCount == _openedFrame) { return; } if (Input.GetKeyDown((KeyCode)27)) { SetVisible(visible: false); } else if (Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271)) { string text = _inputField.text; if (string.IsNullOrWhiteSpace(text)) { _inputField.text = string.Empty; SetVisible(visible: false); } else { _inputField.text = string.Empty; _runtime.SubmitInput(text); _inputField.ActivateInputField(); } } } public void ToggleVisibility() { SetVisible(!_visible); } public void Hide() { SetVisible(visible: false); } public void ShowMessagePreview() { if (!_visible) { _previewVisible = true; _previewTimer = 5f; UpdatePanelState(); } } public void SetStatus(string status) { if ((Object)(object)_statusText != (Object)null) { ((TMP_Text)_statusText).text = "GlobalChat: " + status; } } public void SetOnlineCount(int onlineCount) { if ((Object)(object)_onlineCountText != (Object)null) { ((TMP_Text)_onlineCountText).text = $"Online: {Mathf.Max(0, onlineCount)}"; } } public void SetUnreadCount(int unreadCount) { if (!((Object)(object)_unreadText == (Object)null)) { bool flag = Plugin.EnableUnreadBadge != null && Plugin.EnableUnreadBadge.Value; int num = Mathf.Max(0, unreadCount); ((Component)_unreadText).gameObject.SetActive(flag && num > 0); if (flag && num > 0) { ((TMP_Text)_unreadText).text = $"Unread: {num}"; } } } public void SetOnlinePlayers(IReadOnlyList onlinePlayers) { if ((Object)(object)_onlinePlayersText == (Object)null) { return; } if (onlinePlayers == null || onlinePlayers.Count == 0) { ((TMP_Text)_onlinePlayersText).text = string.Empty; return; } int num = Mathf.Min(6, onlinePlayers.Count); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < num; i++) { OnlinePlayerViewModel onlinePlayerViewModel = onlinePlayers[i]; if (onlinePlayerViewModel != null && !string.IsNullOrWhiteSpace(onlinePlayerViewModel.DisplayName)) { if (stringBuilder.Length > 0) { stringBuilder.AppendLine(); } stringBuilder.Append(FormatUsername(onlinePlayerViewModel.DisplayName.Trim(), onlinePlayerViewModel.UsernameColorHex)); } } if (onlinePlayers.Count > num) { if (stringBuilder.Length > 0) { stringBuilder.AppendLine(); } StringBuilder stringBuilder2 = stringBuilder; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(6, 1, stringBuilder2); handler.AppendLiteral("+"); handler.AppendFormatted(onlinePlayers.Count - num); handler.AppendLiteral(" more"); stringBuilder2.Append(ref handler); } ((TMP_Text)_onlinePlayersText).text = stringBuilder.ToString(); } private static string FormatMessageText(string text, ChatMessageViewModel message) { if (string.IsNullOrWhiteSpace(text) || message == null) { return text; } string text2 = ((!string.IsNullOrWhiteSpace(message.ResolvedDisplayName)) ? message.ResolvedDisplayName : message.DisplayName); if (string.IsNullOrWhiteSpace(text2)) { return text; } if (message.MessageType == "chat" || message.MessageType == "command") { string text3 = "[" + text2 + "]"; if (text.StartsWith(text3, StringComparison.Ordinal)) { return "[" + FormatUsername(text2, message.UsernameColorHex) + "]" + text.Substring(text3.Length); } } if (message.MessageType == "emote") { string text4 = "* " + text2 + " "; if (text.StartsWith(text4, StringComparison.Ordinal)) { return "* " + FormatUsername(text2, message.UsernameColorHex) + " " + text.Substring(text4.Length); } } return text; } private static string FormatUsername(string username, string usernameColorHex) { if (string.IsNullOrWhiteSpace(username)) { return username; } if (Plugin.EnableColoredUsernames == null || !Plugin.EnableColoredUsernames.Value) { return username; } if (string.IsNullOrWhiteSpace(usernameColorHex)) { return username; } string text = usernameColorHex.Trim(); if (!text.StartsWith("#", StringComparison.Ordinal)) { text = "#" + text; } return $"{username}"; } public void RefreshMessages(IReadOnlyList messages) { //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_messagesText == (Object)null) { return; } StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < messages.Count; i++) { ChatMessageViewModel chatMessageViewModel = messages[i]; string text = ((chatMessageViewModel != null && chatMessageViewModel.ContainsProfanity && Plugin.EnableProfanityFilter != null && Plugin.EnableProfanityFilter.Value) ? chatMessageViewModel.FilteredDisplayText : chatMessageViewModel.DisplayText); stringBuilder.AppendLine(FormatMessageText(text, chatMessageViewModel)); } ((TMP_Text)_messagesText).text = stringBuilder.ToString(); ((TMP_Text)_messagesText).ForceMeshUpdate(false, false); if ((Object)(object)_messagesContent != (Object)null) { float preferredHeight = ((TMP_Text)_messagesText).preferredHeight; float num; if (!((Object)(object)_messagesViewport != (Object)null)) { num = 0f; } else { Rect rect = _messagesViewport.rect; num = ((Rect)(ref rect)).height; } float num2 = num; _messagesContent.SetSizeWithCurrentAnchors((Axis)1, Mathf.Max(preferredHeight, num2)); LayoutRebuilder.ForceRebuildLayoutImmediate(_messagesContent); } Canvas.ForceUpdateCanvases(); if ((Object)(object)_messagesScrollRect != (Object)null) { _messagesScrollRect.verticalNormalizedPosition = 0f; } } private void TickPreview() { if (!_previewVisible || (Object)(object)_panelCanvasGroup == (Object)null) { return; } if (_previewTimer > 0f) { _previewTimer -= Time.unscaledDeltaTime; _panelCanvasGroup.alpha = 0.2f; return; } _panelCanvasGroup.alpha = Mathf.MoveTowards(_panelCanvasGroup.alpha, 0f, Time.unscaledDeltaTime * 2.5f); if (_panelCanvasGroup.alpha <= 0.001f) { _previewVisible = false; UpdatePanelState(); } } private void SetVisible(bool visible) { if (visible) { _previewVisible = false; _previewTimer = 0f; } _visible = visible; UpdatePanelState(); if ((Object)(object)InterfaceController.Instance != (Object)null) { if (visible) { _previousPlayerTextInputActive = InterfaceController.Instance.playerTextInputActive; InterfaceController.Instance.playerTextInputActive = true; } else { InterfaceController.Instance.playerTextInputActive = _previousPlayerTextInputActive; } } if ((Object)(object)Player.Instance != (Object)null) { if (visible) { if ((Object)(object)InputController.Instance != (Object)null) { _previousInputControllerEnabled = ((Behaviour)InputController.Instance).enabled; } if ((Object)(object)Player.Instance.fps != (Object)null) { _previousMouseLookEnabled = Player.Instance.fps.enableLook; _previousCharacterControllerEnabled = (Object)(object)Player.Instance.fps.m_CharacterController != (Object)null && ((Collider)Player.Instance.fps.m_CharacterController).enabled; } Player.Instance.EnablePlayerMouseLook(false, false); Player.Instance.EnableCharacterController(false); if ((Object)(object)InputController.Instance != (Object)null) { ((Behaviour)InputController.Instance).enabled = false; } } else { Player.Instance.EnableCharacterController(_previousCharacterControllerEnabled); Player.Instance.EnablePlayerMouseLook(_previousMouseLookEnabled, _previousMouseLookEnabled); if ((Object)(object)InputController.Instance != (Object)null) { ((Behaviour)InputController.Instance).enabled = _previousInputControllerEnabled; } } } if (visible) { _openedFrame = Time.frameCount; EnsureEventSystem(); _inputField.text = string.Empty; ((Selectable)_inputField).interactable = true; _inputField.ActivateInputField(); ((Selectable)_inputField).Select(); if ((Object)(object)_messagesScrollRect != (Object)null) { Canvas.ForceUpdateCanvases(); _messagesScrollRect.verticalNormalizedPosition = 0f; } } else { if ((Object)(object)_inputField != (Object)null) { _inputField.DeactivateInputField(false); _inputField.ReleaseSelection(); ((Selectable)_inputField).interactable = false; } if ((Object)(object)EventSystem.current != (Object)null) { EventSystem.current.SetSelectedGameObject((GameObject)null); } } } private void UpdatePanelState() { bool active = _visible || _previewVisible; if ((Object)(object)_panel != (Object)null) { _panel.SetActive(active); } if ((Object)(object)_inputRoot != (Object)null) { _inputRoot.SetActive(_visible); } if ((Object)(object)_panelCanvasGroup != (Object)null) { _panelCanvasGroup.alpha = (_visible ? 1f : (_previewVisible ? 0.2f : 0f)); _panelCanvasGroup.blocksRaycasts = _visible; _panelCanvasGroup.interactable = _visible; } } private void BuildUi() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_02b0: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Unknown result type (might be due to invalid IL or missing references) //IL_036b: Unknown result type (might be due to invalid IL or missing references) //IL_0382: Unknown result type (might be due to invalid IL or missing references) //IL_038f: Unknown result type (might be due to invalid IL or missing references) //IL_039c: Unknown result type (might be due to invalid IL or missing references) //IL_0403: Unknown result type (might be due to invalid IL or missing references) //IL_0445: Unknown result type (might be due to invalid IL or missing references) //IL_045c: Unknown result type (might be due to invalid IL or missing references) //IL_0469: Unknown result type (might be due to invalid IL or missing references) //IL_0476: Unknown result type (might be due to invalid IL or missing references) //IL_04ad: Unknown result type (might be due to invalid IL or missing references) //IL_04c4: Unknown result type (might be due to invalid IL or missing references) //IL_04d1: Unknown result type (might be due to invalid IL or missing references) //IL_04de: Unknown result type (might be due to invalid IL or missing references) //IL_0508: Unknown result type (might be due to invalid IL or missing references) //IL_0598: Unknown result type (might be due to invalid IL or missing references) //IL_05b3: Unknown result type (might be due to invalid IL or missing references) //IL_05ce: Unknown result type (might be due to invalid IL or missing references) //IL_05e9: Unknown result type (might be due to invalid IL or missing references) //IL_0604: Unknown result type (might be due to invalid IL or missing references) //IL_0652: Unknown result type (might be due to invalid IL or missing references) //IL_06af: Unknown result type (might be due to invalid IL or missing references) //IL_06cf: Unknown result type (might be due to invalid IL or missing references) //IL_06e6: Unknown result type (might be due to invalid IL or missing references) //IL_06fd: Unknown result type (might be due to invalid IL or missing references) //IL_070a: Unknown result type (might be due to invalid IL or missing references) //IL_0721: Unknown result type (might be due to invalid IL or missing references) //IL_078e: Unknown result type (might be due to invalid IL or missing references) //IL_07b2: Unknown result type (might be due to invalid IL or missing references) //IL_07c9: Unknown result type (might be due to invalid IL or missing references) //IL_07d6: Unknown result type (might be due to invalid IL or missing references) //IL_07e3: Unknown result type (might be due to invalid IL or missing references) //IL_085b: Unknown result type (might be due to invalid IL or missing references) //IL_088c: Unknown result type (might be due to invalid IL or missing references) //IL_08a3: Unknown result type (might be due to invalid IL or missing references) //IL_08b0: Unknown result type (might be due to invalid IL or missing references) //IL_08bd: Unknown result type (might be due to invalid IL or missing references) //IL_0910: Unknown result type (might be due to invalid IL or missing references) //IL_0941: Unknown result type (might be due to invalid IL or missing references) //IL_0958: Unknown result type (might be due to invalid IL or missing references) //IL_0965: Unknown result type (might be due to invalid IL or missing references) //IL_0972: Unknown result type (might be due to invalid IL or missing references) //IL_09ed: Unknown result type (might be due to invalid IL or missing references) _root = new GameObject("GlobalChatUI"); Object.DontDestroyOnLoad((Object)(object)_root); _canvas = _root.AddComponent(); _canvas.renderMode = (RenderMode)0; _canvas.sortingOrder = 9000; CanvasScaler val = _root.AddComponent(); val.uiScaleMode = (ScaleMode)1; val.referenceResolution = new Vector2(1920f, 1080f); _root.AddComponent(); _panel = CreateUiObject("Panel", _root.transform); _panelCanvasGroup = _panel.AddComponent(); Image val2 = _panel.AddComponent(); ((Graphic)val2).color = new Color(0f, 0f, 0f, Plugin.ChatOpacity.Value); RectTransform component = _panel.GetComponent(); component.anchorMin = new Vector2(0.02f, 0.02f); component.anchorMax = new Vector2(0.42f, 0.38f); component.offsetMin = Vector2.zero; component.offsetMax = Vector2.zero; GameObject val3 = CreateUiObject("Status", _panel.transform); _statusText = val3.AddComponent(); ApplyGameFont(_statusText, preferTitleFont: true); ((TMP_Text)_statusText).fontSize = 20f; ((Graphic)_statusText).color = Color.white; ((TMP_Text)_statusText).alignment = (TextAlignmentOptions)513; RectTransform component2 = val3.GetComponent(); component2.anchorMin = new Vector2(0.03f, 0.88f); component2.anchorMax = new Vector2(0.52f, 0.97f); component2.offsetMin = Vector2.zero; component2.offsetMax = Vector2.zero; GameObject val4 = CreateUiObject("OnlineCount", _panel.transform); _onlineCountText = val4.AddComponent(); ApplyGameFont(_onlineCountText, preferTitleFont: false); ((TMP_Text)_onlineCountText).fontSize = 18f; ((Graphic)_onlineCountText).color = new Color(0.85f, 0.95f, 1f, 1f); ((TMP_Text)_onlineCountText).alignment = (TextAlignmentOptions)516; ((TMP_Text)_onlineCountText).text = "Online: 0"; RectTransform component3 = val4.GetComponent(); component3.anchorMin = new Vector2(0.56f, 0.88f); component3.anchorMax = new Vector2(0.97f, 0.97f); component3.offsetMin = Vector2.zero; component3.offsetMax = Vector2.zero; GameObject val5 = CreateUiObject("UnreadCount", _panel.transform); _unreadText = val5.AddComponent(); ApplyGameFont(_unreadText, preferTitleFont: false); ((TMP_Text)_unreadText).fontSize = 16f; ((Graphic)_unreadText).color = new Color(1f, 0.45f, 0.45f, 1f); ((TMP_Text)_unreadText).alignment = (TextAlignmentOptions)516; ((TMP_Text)_unreadText).text = "Unread: 0"; ((Component)_unreadText).gameObject.SetActive(false); RectTransform component4 = val5.GetComponent(); component4.anchorMin = new Vector2(0.56f, 0.83f); component4.anchorMax = new Vector2(0.97f, 0.89f); component4.offsetMin = Vector2.zero; component4.offsetMax = Vector2.zero; GameObject val6 = CreateUiObject("OnlinePlayers", _panel.transform); _onlinePlayersText = val6.AddComponent(); ApplyGameFont(_onlinePlayersText, preferTitleFont: false); ((TMP_Text)_onlinePlayersText).fontSize = 14f; ((Graphic)_onlinePlayersText).color = new Color(0.82f, 0.9f, 1f, 0.95f); ((TMP_Text)_onlinePlayersText).alignment = (TextAlignmentOptions)260; ((TMP_Text)_onlinePlayersText).text = string.Empty; RectTransform component5 = val6.GetComponent(); component5.anchorMin = new Vector2(0.62f, 0.58f); component5.anchorMax = new Vector2(0.97f, 0.82f); component5.offsetMin = Vector2.zero; component5.offsetMax = Vector2.zero; GameObject val7 = CreateUiObject("MessagesRoot", _panel.transform); RectTransform component6 = val7.GetComponent(); component6.anchorMin = new Vector2(0.03f, 0.22f); component6.anchorMax = new Vector2(0.6f, 0.84f); component6.offsetMin = Vector2.zero; component6.offsetMax = Vector2.zero; Image val8 = val7.AddComponent(); ((Graphic)val8).color = new Color(0f, 0f, 0f, 0.12f); val7.AddComponent(); _messagesViewport = component6; _messagesScrollRect = val7.AddComponent(); _messagesScrollRect.horizontal = false; _messagesScrollRect.vertical = true; _messagesScrollRect.movementType = (MovementType)2; _messagesScrollRect.scrollSensitivity = 18f; GameObject val9 = CreateUiObject("MessagesContent", val7.transform); _messagesContent = val9.GetComponent(); _messagesContent.anchorMin = new Vector2(0f, 1f); _messagesContent.anchorMax = new Vector2(1f, 1f); _messagesContent.pivot = new Vector2(0.5f, 1f); _messagesContent.offsetMin = new Vector2(8f, 0f); _messagesContent.offsetMax = new Vector2(-8f, 0f); ContentSizeFitter val10 = val9.AddComponent(); val10.verticalFit = (FitMode)2; _messagesText = val9.AddComponent(); ApplyGameFont(_messagesText, preferTitleFont: false); ((TMP_Text)_messagesText).fontSize = 18f; ((Graphic)_messagesText).color = Color.white; ((TMP_Text)_messagesText).enableWordWrapping = true; ((TMP_Text)_messagesText).alignment = (TextAlignmentOptions)257; ((TMP_Text)_messagesText).overflowMode = (TextOverflowModes)2; ((MaskableGraphic)_messagesText).maskable = true; ((TMP_Text)_messagesText).margin = new Vector4(6f, 6f, 6f, 6f); RectTransform component7 = val9.GetComponent(); component7.anchorMin = new Vector2(0f, 1f); component7.anchorMax = new Vector2(1f, 1f); component7.pivot = new Vector2(0.5f, 1f); component7.anchoredPosition = Vector2.zero; component7.sizeDelta = new Vector2(0f, 0f); _messagesScrollRect.viewport = _messagesViewport; _messagesScrollRect.content = _messagesContent; _inputRoot = CreateUiObject("InputRoot", _panel.transform); Image val11 = _inputRoot.AddComponent(); ((Graphic)val11).color = new Color(0.12f, 0.12f, 0.12f, 0.95f); RectTransform component8 = _inputRoot.GetComponent(); component8.anchorMin = new Vector2(0.03f, 0.04f); component8.anchorMax = new Vector2(0.97f, 0.18f); component8.offsetMin = Vector2.zero; component8.offsetMax = Vector2.zero; GameObject val12 = CreateUiObject("Placeholder", _inputRoot.transform); _placeholderText = val12.AddComponent(); ApplyGameFont(_placeholderText, preferTitleFont: false); ((TMP_Text)_placeholderText).text = "Type message or //command..."; ((TMP_Text)_placeholderText).fontSize = 18f; ((Graphic)_placeholderText).color = new Color(0.75f, 0.75f, 0.75f, 0.8f); ((TMP_Text)_placeholderText).alignment = (TextAlignmentOptions)4097; RectTransform component9 = val12.GetComponent(); component9.anchorMin = new Vector2(0.02f, 0.1f); component9.anchorMax = new Vector2(0.98f, 0.9f); component9.offsetMin = Vector2.zero; component9.offsetMax = Vector2.zero; GameObject val13 = CreateUiObject("Text", _inputRoot.transform); _inputText = val13.AddComponent(); ApplyGameFont(_inputText, preferTitleFont: false); ((TMP_Text)_inputText).fontSize = 18f; ((Graphic)_inputText).color = Color.white; ((TMP_Text)_inputText).alignment = (TextAlignmentOptions)4097; RectTransform component10 = val13.GetComponent(); component10.anchorMin = new Vector2(0.02f, 0.1f); component10.anchorMax = new Vector2(0.98f, 0.9f); component10.offsetMin = Vector2.zero; component10.offsetMax = Vector2.zero; _inputField = _inputRoot.AddComponent(); _inputField.textViewport = component8; _inputField.textComponent = (TMP_Text)(object)_inputText; _inputField.placeholder = (Graphic)(object)_placeholderText; _inputField.lineType = (LineType)0; _inputField.caretWidth = 2; _inputField.customCaretColor = true; _inputField.caretColor = Color.white; _inputField.richText = false; ApplyResolvedFontsToUi(); } private static GameObject CreateUiObject(string name, Transform parent) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown GameObject val = new GameObject(name); val.transform.SetParent(parent, false); val.AddComponent(); return val; } private static void ApplyGameFont(TextMeshProUGUI text, bool preferTitleFont) { if (!((Object)(object)text == (Object)null)) { ResolveGameFontAssets(); if ((Object)(object)_cachedFont != (Object)null) { ((TMP_Text)text).font = _cachedFont; } if ((Object)(object)_cachedFontMaterial != (Object)null) { ((TMP_Text)text).fontSharedMaterial = _cachedFontMaterial; } ((TMP_Text)text).richText = true; ((TMP_Text)text).isOverlay = true; ((Graphic)text).raycastTarget = false; ((TMP_Text)text).extraPadding = true; ((TMP_Text)text).enableVertexGradient = false; ((MaskableGraphic)text).maskable = true; } } private void RefreshFontsIfNeeded() { if (!SaveGameHandlers.IsGameActive) { _fontRefreshAttemptedInGame = false; } else if (!_fontRefreshAttemptedInGame) { _fontRefreshAttemptedInGame = true; _cachedFont = null; _cachedFontMaterial = null; ApplyResolvedFontsToUi(); } } private void ApplyResolvedFontsToUi() { ApplyGameFont(_statusText, preferTitleFont: true); ApplyGameFont(_onlineCountText, preferTitleFont: false); ApplyGameFont(_onlinePlayersText, preferTitleFont: false); ApplyGameFont(_unreadText, preferTitleFont: false); ApplyGameFont(_messagesText, preferTitleFont: false); ApplyGameFont(_placeholderText, preferTitleFont: false); ApplyGameFont(_inputText, preferTitleFont: false); } private static void ResolveGameFontAssets() { //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Expected O, but got Unknown //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Expected O, but got Unknown //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Expected O, but got Unknown if (((Object)(object)_cachedFont != (Object)null && (Object)(object)_cachedFontMaterial != (Object)null) || !SaveGameHandlers.IsGameActive) { return; } object instance = InterfaceController.Instance; bool flag = default(bool); if (instance != null) { try { Type type = instance.GetType(); _cachedFont = ReadFieldValue(type, instance, "mainFont") ?? ReadFieldValue(type, instance, "bodyFont") ?? ReadFieldValue(type, instance, "titleFont") ?? _cachedFont; _cachedFontMaterial = ReadFieldValue(type, instance, "fontMaterial") ?? ReadFieldValue(type, instance, "fontMaterialOutline") ?? _cachedFontMaterial; } catch (Exception ex) { ManualLogSource log = PluginController.Log; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(51, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to resolve InterfaceController font assets: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogWarning(val); } } if ((Object)(object)_cachedFont == (Object)null) { try { if ((Object)(object)TMP_Settings.defaultFontAsset != (Object)null) { _cachedFont = TMP_Settings.defaultFontAsset; } } catch (Exception ex2) { ManualLogSource log2 = PluginController.Log; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(42, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to resolve default TMP font asset: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex2.Message); } log2.LogWarning(val); } } if (!((Object)(object)_cachedFontMaterial == (Object)null) || !((Object)(object)_cachedFont != (Object)null)) { return; } try { _cachedFontMaterial = ((TMP_Asset)_cachedFont).material; } catch (Exception ex3) { ManualLogSource log3 = PluginController.Log; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(45, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to resolve default TMP font material: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex3.Message); } log3.LogWarning(val); } } private static T ReadFieldValue(Type type, object instance, string fieldName) where T : class { if (type == null || instance == null || string.IsNullOrWhiteSpace(fieldName)) { return null; } FieldInfo field = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { return null; } return field.GetValue(instance) as T; } private static void EnsureEventSystem() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown if (!((Object)(object)EventSystem.current != (Object)null)) { GameObject val = new GameObject("GlobalChatEventSystem"); Object.DontDestroyOnLoad((Object)(object)val); val.AddComponent(); } } } [BepInPlugin("ShaneeexD.GlobalChat", "GlobalChat", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : PluginController { public const string PLUGIN_GUID = "ShaneeexD.GlobalChat"; public const string PLUGIN_NAME = "GlobalChat"; public const string PLUGIN_VERSION = "1.0.0"; public static ConfigEntry ApiBaseUrl; public static ConfigEntry DisplayName; public static ConfigEntry PlayerId; public static ConfigEntry ToggleChatKey; public static ConfigEntry PollIntervalSeconds; public static ConfigEntry PresenceIntervalSeconds; public static ConfigEntry LocalSendCooldownSeconds; public static ConfigEntry ChatOpacity; public static ConfigEntry MaxMessages; public static ConfigEntry EnableProfanityFilter; public static ConfigEntry MutedUsers; public static ConfigEntry EnableUnreadBadge; public static ConfigEntry EnableColoredUsernames; public static ConfigEntry UserNameColorHex; public override void Load() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Expected O, but got Unknown //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Expected O, but got Unknown //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Expected O, but got Unknown //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Expected O, but got Unknown //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Expected O, but got Unknown //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Expected O, but got Unknown //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Expected O, but got Unknown //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Expected O, but got Unknown //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Expected O, but got Unknown //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Expected O, but got Unknown ApiBaseUrl = base.Config.Bind("Network", "ApiBaseUrl", "https://globalchat-wkrf.onrender.com", new ConfigDescription("Base URL for the GlobalChat Python backend.", (AcceptableValueBase)null, Array.Empty())); DisplayName = base.Config.Bind("Profile", "DisplayName", Environment.UserName, new ConfigDescription("Display name shown in global chat.", (AcceptableValueBase)null, Array.Empty())); PlayerId = base.Config.Bind("Profile", "PlayerId", Guid.NewGuid().ToString("N"), new ConfigDescription("Stable unique ID for this player installation.", (AcceptableValueBase)null, Array.Empty())); MutedUsers = base.Config.Bind("Profile", "MutedUsers", string.Empty, new ConfigDescription("Comma-separated list of muted display names for local chat filtering.", (AcceptableValueBase)null, Array.Empty())); ToggleChatKey = base.Config.Bind("Input", "ToggleChatKey", "Return", new ConfigDescription("Keyboard key used to open and close the chat overlay.", (AcceptableValueBase)null, Array.Empty())); PollIntervalSeconds = base.Config.Bind("Network", "PollIntervalSeconds", 3f, new ConfigDescription("How often to poll the backend for new messages.", (AcceptableValueBase)null, Array.Empty())); PresenceIntervalSeconds = base.Config.Bind("Network", "PresenceIntervalSeconds", 30f, new ConfigDescription("How often to refresh online presence with the backend.", (AcceptableValueBase)null, Array.Empty())); LocalSendCooldownSeconds = base.Config.Bind("Safety", "LocalSendCooldownSeconds", 1.5f, new ConfigDescription("Client-side minimum delay between sent messages.", (AcceptableValueBase)null, Array.Empty())); EnableProfanityFilter = base.Config.Bind("Safety", "EnableProfanityFilter", true, new ConfigDescription("If enabled, outgoing messages request backend profanity filtering.", (AcceptableValueBase)null, Array.Empty())); ChatOpacity = base.Config.Bind("UI", "ChatOpacity", 0.85f, new ConfigDescription("Opacity of the chat panel background.", (AcceptableValueBase)null, Array.Empty())); MaxMessages = base.Config.Bind("UI", "MaxMessages", 40, new ConfigDescription("Maximum number of chat lines kept in the on-screen buffer.", (AcceptableValueBase)null, Array.Empty())); EnableUnreadBadge = base.Config.Bind("UI", "EnableUnreadBadge", true, new ConfigDescription("If enabled, show an unread count in the chat header while chat is closed.", (AcceptableValueBase)null, Array.Empty())); EnableColoredUsernames = base.Config.Bind("UI", "EnableColoredUsernames", true, new ConfigDescription("If enabled, color usernames in chat and the online player list.", (AcceptableValueBase)null, Array.Empty())); UserNameColorHex = base.Config.Bind("Profile", "UserNameColorHex", "#7FDBFF", new ConfigDescription("Hex color for your username that is shared with the backend and visible to other players.", (AcceptableValueBase)null, Array.Empty())); ((PluginController)(object)this).Harmony.PatchAll(Assembly.GetExecutingAssembly()); SaveGameHandlers.Initialize(); PluginController.Log.LogInfo((object)"GlobalChat loaded."); } private void OnDestroy() { if (GlobalChatRuntime.Instance != null) { GlobalChatRuntime.Instance.Shutdown(); } } private void OnApplicationQuit() { if (GlobalChatRuntime.Instance != null) { GlobalChatRuntime.Instance.Shutdown(); } } } [HarmonyPatch(typeof(SessionData))] [HarmonyPatch("PauseGame")] public class PauseManager { public static void Prefix(ref bool showPauseText, ref bool delayOverride, ref bool openDesktopMode) { if (GlobalChatRuntime.Instance != null) { GlobalChatRuntime.Instance.HandlePauseStateChanged(isPaused: true); } } } [HarmonyPatch(typeof(SessionData))] [HarmonyPatch("ResumeGame")] public class ResumeGameManager { public static void Prefix() { if (GlobalChatRuntime.Instance != null) { GlobalChatRuntime.Instance.HandlePauseStateChanged(isPaused: false); } } } public static class ProfanityFilterService { private static readonly object Sync = new object(); private static bool _loaded; private static List _words = new List(); public static string FilterIfEnabled(string text) { if (Plugin.EnableProfanityFilter != null && !Plugin.EnableProfanityFilter.Value) { return text; } return Apply(text); } public static string Apply(string text) { if (string.IsNullOrWhiteSpace(text)) { return text; } EnsureLoaded(); if (_words.Count == 0) { return text; } string text2 = text; for (int i = 0; i < _words.Count; i++) { string word = _words[i]; text2 = ReplaceContains(text2, word); } return text2; } private static void EnsureLoaded() { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Expected O, but got Unknown //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Expected O, but got Unknown if (_loaded) { return; } lock (Sync) { if (!_loaded) { _words = LoadWords(); ManualLogSource log = PluginController.Log; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(37, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Profanity filter ready with "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_words.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" entries."); } log.LogInfo(val); ManualLogSource log2 = PluginController.Log; val = new BepInExInfoLogInterpolatedStringHandler(39, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Profanity filter self-test 'wank' => '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ApplyPreview("wank")); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'"); } log2.LogInfo(val); ManualLogSource log3 = PluginController.Log; val = new BepInExInfoLogInterpolatedStringHandler(42, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Profanity filter self-test 'vjayjay' => '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ApplyPreview("vjayjay")); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'"); } log3.LogInfo(val); ManualLogSource log4 = PluginController.Log; val = new BepInExInfoLogInterpolatedStringHandler(44, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Profanity filter self-test 'wet dream' => '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ApplyPreview("wet dream")); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'"); } log4.LogInfo(val); _loaded = true; } } } private static List LoadWords() { //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Expected O, but got Unknown //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Expected O, but got Unknown List list = new List(); HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); bool flag = default(bool); try { string[] array = BuildCandidatePaths(); foreach (string text in array) { if (!File.Exists(text)) { continue; } string[] array2 = File.ReadAllLines(text); for (int j = 0; j < array2.Length; j++) { string text2 = array2[j]?.Trim(); if (!string.IsNullOrWhiteSpace(text2) && !text2.StartsWith("#", StringComparison.Ordinal) && hashSet.Add(text2)) { list.Add(text2); } } if (list.Count > 0) { ManualLogSource log = PluginController.Log; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(38, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Loaded "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(list.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" profanity filter entries from "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(text); } log.LogInfo(val); break; } } } catch (Exception ex) { ManualLogSource log2 = PluginController.Log; BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(36, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Failed to load profanity_words.txt: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex.Message); } log2.LogWarning(val2); } if (list.Count == 0) { string[] array3 = new string[11] { "fuck", "fucking", "shit", "bitch", "cunt", "asshole", "dick", "piss", "bastard", "wanker", "pussy" }; for (int k = 0; k < array3.Length; k++) { if (hashSet.Add(array3[k])) { list.Add(array3[k]); } } } return list.OrderByDescending((string word) => word.Length).ThenBy((string word) => word, StringComparer.OrdinalIgnoreCase).ToList(); } private static string ApplyPreview(string text) { if (string.IsNullOrWhiteSpace(text) || _words.Count == 0) { return text; } string text2 = text; for (int i = 0; i < _words.Count; i++) { text2 = ReplaceContains(text2, _words[i]); } return text2; } private static string ReplaceContains(string text, string word) { if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(word)) { return text; } int num = text.IndexOf(word, StringComparison.OrdinalIgnoreCase); if (num < 0) { return text; } string value = new string('*', word.Length); StringBuilder stringBuilder = new StringBuilder(text.Length); int num2 = 0; while (num >= 0) { stringBuilder.Append(text, num2, num - num2); stringBuilder.Append(value); num2 = num + word.Length; num = text.IndexOf(word, num2, StringComparison.OrdinalIgnoreCase); } stringBuilder.Append(text, num2, text.Length - num2); return stringBuilder.ToString(); } private static string[] BuildCandidatePaths() { string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty; List list = new List { Path.Combine(path, "profanity_words.txt"), Path.Combine(Paths.PluginPath, "profanity_words.txt"), Path.Combine(Paths.BepInExRootPath, "plugins", "profanity_words.txt"), Path.Combine(Environment.CurrentDirectory, "profanity_words.txt") }; return list.ToArray(); } } public static class SaveGameHandlers { private static bool _initialized; public static bool IsGameActive { get; private set; } private static void DisconnectGlobalChat() { if (GlobalChatRuntime.Instance != null) { GlobalChatRuntime.Instance.Shutdown(); } } public static void Initialize() { if (!_initialized) { _initialized = true; Lib.SaveGame.OnAfterLoad += HandleGameLoaded; Lib.SaveGame.OnAfterNewGame += HandleNewGameStarted; Lib.SaveGame.OnBeforeNewGame += HandleGameBeforeNewGame; Lib.SaveGame.OnBeforeLoad += HandleGameBeforeLoad; Lib.SaveGame.OnBeforeDelete += HandleGameBeforeDelete; Lib.SaveGame.OnAfterDelete += HandleGameAfterDelete; } } private static void HandleNewGameStarted(object sender, EventArgs e) { IsGameActive = true; GlobalChatRuntime.EnsureCreated(); } private static void HandleGameLoaded(object sender, EventArgs e) { IsGameActive = true; GlobalChatRuntime.EnsureCreated(); } private static void HandleGameBeforeNewGame(object sender, EventArgs e) { IsGameActive = false; DisconnectGlobalChat(); } private static void HandleGameBeforeLoad(object sender, EventArgs e) { IsGameActive = false; DisconnectGlobalChat(); } private static void HandleGameBeforeDelete(object sender, EventArgs e) { IsGameActive = false; DisconnectGlobalChat(); } private static void HandleGameAfterDelete(object sender, EventArgs e) { IsGameActive = false; DisconnectGlobalChat(); } }