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 <color=#D4AF37>{PlayerContextService.GetWealthText()}</color>.");
case "location":
return SendFormatted("command", input, $"[{GlobalChatRuntime.DisplayName}] is at <color=#00FFFF>{PlayerContextService.GetLocationText()}</color>.");
case "case":
return SendFormatted("command", input, $"[{GlobalChatRuntime.DisplayName}] is working on case: <color=#BF40BF>{PlayerContextService.GetCaseText()}</color>.");
case "time":
return SendFormatted("command", input, $"[{GlobalChatRuntime.DisplayName}] says the time is <color=#7FDBFF>{PlayerContextService.GetTimeText()}</color>.");
case "social":
return SendFormatted("command", input, $"[{GlobalChatRuntime.DisplayName}] has social rating: <color=#7CFC00>{PlayerContextService.GetSocialText()}</color>.");
case "roll":
return SendFormatted("command", input, $"[{GlobalChatRuntime.DisplayName}] rolled <color=#FFA500>{ResolveRollValue(text4)}</color>.");
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 <name>, //unmute <name>, //muted, //color [#RRGGBB|reset], //help, //me <text>"
};
case "me":
if (string.IsNullOrWhiteSpace(text4))
{
return new ChatCommandResult
{
Handled = true,
ShouldSend = false,
LocalFeedback = "Usage: //me <text>"
};
}
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 <name>"
};
}
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 <name>"
};
}
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<Plugin, IEmptyBindings>.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<string>(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<Plugin, IEmptyBindings>.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<string>(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<Plugin, IEmptyBindings>.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<string>(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<Plugin, IEmptyBindings>.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<string>(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<Plugin, IEmptyBindings>.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<string>(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<ChatMessageDto> messages = new List<ChatMessageDto>();
public string nextCursor;
public int onlineCount;
public List<OnlinePlayerDto> onlinePlayers = new List<OnlinePlayerDto>();
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<OnlinePlayerDto> onlinePlayers = new List<OnlinePlayerDto>();
public string error;
}
[Serializable]
public class PresenceDisconnectResponse
{
public bool success = true;
public int onlineCount;
public List<OnlinePlayerDto> onlinePlayers = new List<OnlinePlayerDto>();
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<OnlinePlayerViewModel> OnlinePlayers { get; }
void Initialize();
void Tick(float unscaledDeltaTime);
void QueueMessage(SendChatMessageRequest request);
void UpdatePresence(string playerId, string displayName);
void DisconnectPresence(string playerId);
List<ChatMessageViewModel> 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<SendChatMessageRequest> _outbox = new Queue<SendChatMessageRequest>();
private readonly List<ChatMessageViewModel> _inbox = new List<ChatMessageViewModel>();
private readonly HashSet<string> _seenMessageIds = new HashSet<string>();
private readonly List<OnlinePlayerViewModel> _onlinePlayers = new List<OnlinePlayerViewModel>();
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<OnlinePlayerViewModel> 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<ChatMessageViewModel> DrainIncomingMessages()
{
List<ChatMessageViewModel> result = new List<ChatMessageViewModel>(_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<ChatMessagesResponse>(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<Plugin, IEmptyBindings>.Log;
bool flag = default(bool);
BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(24, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("GlobalChat poll failed: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(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<ChatSendResponse>(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<Plugin, IEmptyBindings>.Log;
bool flag = default(bool);
BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(24, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("GlobalChat send failed: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(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<PresenceUpdateResponse>(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<Plugin, IEmptyBindings>.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<string>(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<PresenceDisconnectResponse>(result2, JsonOptions);
if (presenceDisconnectResponse != null && presenceDisconnectResponse.success)
{
_onlineUserCount = Mathf.Max(0, presenceDisconnectResponse.onlineCount);
UpdateOnlinePlayers(presenceDisconnectResponse.onlinePlayers);
}
}
}
catch (Exception ex)
{
_disconnectSent = false;
ManualLogSource log = PluginController<Plugin, IEmptyBindings>.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<string>(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<OnlinePlayerDto> 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<ChatMessageViewModel> _messages = new List<ChatMessageViewModel>();
private readonly HashSet<string> _localEchoPayloads = new HashSet<string>();
private readonly HashSet<string> _mutedUsers = new HashSet<string>(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<ChatMessageViewModel> 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<ChatMessageViewModel> 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<OnlinePlayerViewModel> onlinePlayers;
if (_transport == null)
{
IReadOnlyList<OnlinePlayerViewModel> readOnlyList = Array.Empty<OnlinePlayerViewModel>();
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, string>((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<KeyCode>(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<ChatMessageViewModel> GetVisibleMessages()
{
if (_mutedUsers.Count == 0)
{
return _messages;
}
List<ChatMessageViewModel> list = new List<ChatMessageViewModel>(_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, string>((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<OnlinePlayerViewModel> 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 $"<color={text}>{username}</color>";
}
public void RefreshMessages(IReadOnlyList<ChatMessageViewModel> 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>();
_canvas.renderMode = (RenderMode)0;
_canvas.sortingOrder = 9000;
CanvasScaler val = _root.AddComponent<CanvasScaler>();
val.uiScaleMode = (ScaleMode)1;
val.referenceResolution = new Vector2(1920f, 1080f);
_root.AddComponent<GraphicRaycaster>();
_panel = CreateUiObject("Panel", _root.transform);
_panelCanvasGroup = _panel.AddComponent<CanvasGroup>();
Image val2 = _panel.AddComponent<Image>();
((Graphic)val2).color = new Color(0f, 0f, 0f, Plugin.ChatOpacity.Value);
RectTransform component = _panel.GetComponent<RectTransform>();
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<TextMeshProUGUI>();
ApplyGameFont(_statusText, preferTitleFont: true);
((TMP_Text)_statusText).fontSize = 20f;
((Graphic)_statusText).color = Color.white;
((TMP_Text)_statusText).alignment = (TextAlignmentOptions)513;
RectTransform component2 = val3.GetComponent<RectTransform>();
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<TextMeshProUGUI>();
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<RectTransform>();
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<TextMeshProUGUI>();
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<RectTransform>();
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<TextMeshProUGUI>();
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<RectTransform>();
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<RectTransform>();
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<Image>();
((Graphic)val8).color = new Color(0f, 0f, 0f, 0.12f);
val7.AddComponent<RectMask2D>();
_messagesViewport = component6;
_messagesScrollRect = val7.AddComponent<ScrollRect>();
_messagesScrollRect.horizontal = false;
_messagesScrollRect.vertical = true;
_messagesScrollRect.movementType = (MovementType)2;
_messagesScrollRect.scrollSensitivity = 18f;
GameObject val9 = CreateUiObject("MessagesContent", val7.transform);
_messagesContent = val9.GetComponent<RectTransform>();
_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<ContentSizeFitter>();
val10.verticalFit = (FitMode)2;
_messagesText = val9.AddComponent<TextMeshProUGUI>();
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<RectTransform>();
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<Image>();
((Graphic)val11).color = new Color(0.12f, 0.12f, 0.12f, 0.95f);
RectTransform component8 = _inputRoot.GetComponent<RectTransform>();
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<TextMeshProUGUI>();
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<RectTransform>();
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<TextMeshProUGUI>();
ApplyGameFont(_inputText, preferTitleFont: false);
((TMP_Text)_inputText).fontSize = 18f;
((Graphic)_inputText).color = Color.white;
((TMP_Text)_inputText).alignment = (TextAlignmentOptions)4097;
RectTransform component10 = val13.GetComponent<RectTransform>();
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<TMP_InputField>();
_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<RectTransform>();
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<TMP_FontAsset>(type, instance, "mainFont") ?? ReadFieldValue<TMP_FontAsset>(type, instance, "bodyFont") ?? ReadFieldValue<TMP_FontAsset>(type, instance, "titleFont") ?? _cachedFont;
_cachedFontMaterial = ReadFieldValue<Material>(type, instance, "fontMaterial") ?? ReadFieldValue<Material>(type, instance, "fontMaterialOutline") ?? _cachedFontMaterial;
}
catch (Exception ex)
{
ManualLogSource log = PluginController<Plugin, IEmptyBindings>.Log;
BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(51, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to resolve InterfaceController font assets: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(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<Plugin, IEmptyBindings>.Log;
BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(42, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to resolve default TMP font asset: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(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<Plugin, IEmptyBindings>.Log;
BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(45, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to resolve default TMP font material: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex3.Message);
}
log3.LogWarning(val);
}
}
private static T ReadFieldValue<T>(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<EventSystem>();
}
}
}
[BepInPlugin("ShaneeexD.GlobalChat", "GlobalChat", "1.0.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : PluginController<Plugin>
{
public const string PLUGIN_GUID = "ShaneeexD.GlobalChat";
public const string PLUGIN_NAME = "GlobalChat";
public const string PLUGIN_VERSION = "1.0.0";
public static ConfigEntry<string> ApiBaseUrl;
public static ConfigEntry<string> DisplayName;
public static ConfigEntry<string> PlayerId;
public static ConfigEntry<string> ToggleChatKey;
public static ConfigEntry<float> PollIntervalSeconds;
public static ConfigEntry<float> PresenceIntervalSeconds;
public static ConfigEntry<float> LocalSendCooldownSeconds;
public static ConfigEntry<float> ChatOpacity;
public static ConfigEntry<int> MaxMessages;
public static ConfigEntry<bool> EnableProfanityFilter;
public static ConfigEntry<string> MutedUsers;
public static ConfigEntry<bool> EnableUnreadBadge;
public static ConfigEntry<bool> EnableColoredUsernames;
public static ConfigEntry<string> 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<string>("Network", "ApiBaseUrl", "https://globalchat-wkrf.onrender.com", new ConfigDescription("Base URL for the GlobalChat Python backend.", (AcceptableValueBase)null, Array.Empty<object>()));
DisplayName = base.Config.Bind<string>("Profile", "DisplayName", Environment.UserName, new ConfigDescription("Display name shown in global chat.", (AcceptableValueBase)null, Array.Empty<object>()));
PlayerId = base.Config.Bind<string>("Profile", "PlayerId", Guid.NewGuid().ToString("N"), new ConfigDescription("Stable unique ID for this player installation.", (AcceptableValueBase)null, Array.Empty<object>()));
MutedUsers = base.Config.Bind<string>("Profile", "MutedUsers", string.Empty, new ConfigDescription("Comma-separated list of muted display names for local chat filtering.", (AcceptableValueBase)null, Array.Empty<object>()));
ToggleChatKey = base.Config.Bind<string>("Input", "ToggleChatKey", "Return", new ConfigDescription("Keyboard key used to open and close the chat overlay.", (AcceptableValueBase)null, Array.Empty<object>()));
PollIntervalSeconds = base.Config.Bind<float>("Network", "PollIntervalSeconds", 3f, new ConfigDescription("How often to poll the backend for new messages.", (AcceptableValueBase)null, Array.Empty<object>()));
PresenceIntervalSeconds = base.Config.Bind<float>("Network", "PresenceIntervalSeconds", 30f, new ConfigDescription("How often to refresh online presence with the backend.", (AcceptableValueBase)null, Array.Empty<object>()));
LocalSendCooldownSeconds = base.Config.Bind<float>("Safety", "LocalSendCooldownSeconds", 1.5f, new ConfigDescription("Client-side minimum delay between sent messages.", (AcceptableValueBase)null, Array.Empty<object>()));
EnableProfanityFilter = base.Config.Bind<bool>("Safety", "EnableProfanityFilter", true, new ConfigDescription("If enabled, outgoing messages request backend profanity filtering.", (AcceptableValueBase)null, Array.Empty<object>()));
ChatOpacity = base.Config.Bind<float>("UI", "ChatOpacity", 0.85f, new ConfigDescription("Opacity of the chat panel background.", (AcceptableValueBase)null, Array.Empty<object>()));
MaxMessages = base.Config.Bind<int>("UI", "MaxMessages", 40, new ConfigDescription("Maximum number of chat lines kept in the on-screen buffer.", (AcceptableValueBase)null, Array.Empty<object>()));
EnableUnreadBadge = base.Config.Bind<bool>("UI", "EnableUnreadBadge", true, new ConfigDescription("If enabled, show an unread count in the chat header while chat is closed.", (AcceptableValueBase)null, Array.Empty<object>()));
EnableColoredUsernames = base.Config.Bind<bool>("UI", "EnableColoredUsernames", true, new ConfigDescription("If enabled, color usernames in chat and the online player list.", (AcceptableValueBase)null, Array.Empty<object>()));
UserNameColorHex = base.Config.Bind<string>("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<object>()));
((PluginController<Plugin, IEmptyBindings>)(object)this).Harmony.PatchAll(Assembly.GetExecutingAssembly());
SaveGameHandlers.Initialize();
PluginController<Plugin, IEmptyBindings>.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<string> _words = new List<string>();
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<Plugin, IEmptyBindings>.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<int>(_words.Count);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" entries.");
}
log.LogInfo(val);
ManualLogSource log2 = PluginController<Plugin, IEmptyBindings>.Log;
val = new BepInExInfoLogInterpolatedStringHandler(39, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Profanity filter self-test 'wank' => '");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ApplyPreview("wank"));
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'");
}
log2.LogInfo(val);
ManualLogSource log3 = PluginController<Plugin, IEmptyBindings>.Log;
val = new BepInExInfoLogInterpolatedStringHandler(42, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Profanity filter self-test 'vjayjay' => '");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ApplyPreview("vjayjay"));
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'");
}
log3.LogInfo(val);
ManualLogSource log4 = PluginController<Plugin, IEmptyBindings>.Log;
val = new BepInExInfoLogInterpolatedStringHandler(44, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Profanity filter self-test 'wet dream' => '");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ApplyPreview("wet dream"));
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'");
}
log4.LogInfo(val);
_loaded = true;
}
}
}
private static List<string> 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<string> list = new List<string>();
HashSet<string> hashSet = new HashSet<string>(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<Plugin, IEmptyBindings>.Log;
BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(38, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Loaded ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(list.Count);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" profanity filter entries from ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text);
}
log.LogInfo(val);
break;
}
}
}
catch (Exception ex)
{
ManualLogSource log2 = PluginController<Plugin, IEmptyBindings>.Log;
BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(36, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Failed to load profanity_words.txt: ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(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, string>((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<string> list = new List<string>
{
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();
}
}