using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using AK;
using Agents;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using CellMenu;
using Dissonance;
using GTFO.API;
using GTFO.API.Utilities;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Localization;
using Microsoft.CodeAnalysis;
using Player;
using SNetwork;
using TMPro;
using UnityEngine;
[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("BetterTextChat")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("BetterTextChat")]
[assembly: AssemblyTitle("BetterTextChat")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
internal sealed class NullableAttribute : Attribute
{
public readonly byte[] NullableFlags;
public NullableAttribute(byte P_0)
{
NullableFlags = new byte[1] { P_0 };
}
public NullableAttribute(byte[] P_0)
{
NullableFlags = P_0;
}
}
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
internal sealed class NullableContextAttribute : Attribute
{
public readonly byte Flag;
public NullableContextAttribute(byte P_0)
{
Flag = P_0;
}
}
}
namespace BetterTextChat
{
internal static class BetterComposition
{
private static int caretPosition;
private static bool caretVisible;
private static float caretVisibilityTimer;
private static PlayerAgent? customFromPlayer;
private static PlayerAgent? customToPlayer;
[HarmonyPatch(typeof(PlayerChatManager), "UpdateTextChatInput")]
[HarmonyPrefix]
private static void PlayerChatManager__UpdateTextChatInput__Prefix(PlayerChatManager __instance, ref bool __runOriginal)
{
__runOriginal = false;
if (!PlayerChatManager.TextChatInputEnabled)
{
return;
}
if (PlayerChatManager.Current != null && !PlayerChatManager.InChatMode && InputMapper.GetButtonDownKeyMouseGamepad((InputAction)56, (eFocusState)0))
{
PlayerChatManager.Log("Open chat window");
__instance.EnterChatMode();
}
else if (InputMapper.GetButtonDownKeyMouseGamepad((InputAction)57, (eFocusState)0) || Input.GetKeyDown((KeyCode)27))
{
__instance.ExitChatMode();
}
else if (PlayerChatManager.Current != null && PlayerChatManager.InChatMode)
{
int num = caretPosition;
string currentValue = __instance.m_currentValue;
ChatInputRepetitionManager.Update();
UpdateCaretPosition(__instance);
UpdateMCurrentValue(__instance);
if (caretPosition != num || currentValue != __instance.m_currentValue)
{
UpdateCaretVisibility(forceVisible: true);
}
if (currentValue != __instance.m_currentValue)
{
TypingIndicator.OnLocalPlayerTyping();
}
UpdateRecipientPlayer();
__instance.m_currentImeCompositionString = Input.compositionString;
__instance.UpdateMessage();
}
}
private static void UpdateCaretPosition(PlayerChatManager __instance)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0008: Invalid comparison between Unknown and I4
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0023: Invalid comparison between Unknown and I4
if ((int)ChatInputRepetitionManager.inputActionThisFrame == 17)
{
caretPosition--;
}
if ((int)ChatInputRepetitionManager.inputActionThisFrame == 18)
{
caretPosition++;
}
if (InputMapper.GetButtonDownKeyMouseGamepad((InputAction)21, (eFocusState)0))
{
caretPosition = 0;
}
if (InputMapper.GetButtonDownKeyMouseGamepad((InputAction)22, (eFocusState)0))
{
caretPosition = __instance.m_currentValue.Length;
}
caretPosition = Mathf.Clamp(caretPosition, 0, __instance.m_currentValue.Length);
}
private static void UpdateMCurrentValue(PlayerChatManager __instance)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0008: Invalid comparison between Unknown and I4
if ((int)ChatInputRepetitionManager.inputActionThisFrame == 19 && __instance.m_currentValue.Length > caretPosition)
{
__instance.m_currentValue = __instance.m_currentValue.Remove(caretPosition, 1);
}
string inputString = Input.inputString;
for (int i = 0; i < inputString.Length; i++)
{
char c = inputString[i];
if (!Config.AllowForbiddenChars.Value && ((Il2CppArrayBase<int>)(object)__instance.m_forbiddenChars).Contains((int)c))
{
continue;
}
switch (c)
{
case '\b':
if (caretPosition > 0)
{
__instance.m_currentValue = __instance.m_currentValue.Remove(caretPosition - 1, 1);
caretPosition--;
}
break;
default:
if (c != '\r')
{
__instance.m_currentValue = __instance.m_currentValue.Insert(caretPosition, c.ToString());
caretPosition++;
break;
}
goto case '\n';
case '\n':
if (Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303))
{
__instance.m_currentValue = __instance.m_currentValue.Insert(caretPosition, "\n");
caretPosition++;
}
else
{
__instance.m_currentImeCompositionString = "";
__instance.PostMessage();
}
break;
}
}
if ((Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305)) && Input.GetKeyDown((KeyCode)99))
{
GUIUtility.systemCopyBuffer = __instance.m_currentValue;
}
if ((Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305)) && Input.GetKeyDown((KeyCode)118))
{
string text = GUIUtility.systemCopyBuffer.ReplaceLineEndings("\n");
__instance.m_currentValue = __instance.m_currentValue.Insert(caretPosition, text);
caretPosition += text.Length;
}
}
private static void UpdateRecipientPlayer()
{
//IL_009d: Unknown result type (might be due to invalid IL or missing references)
//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
bool flag = Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305);
Dictionary<KeyCode, int> dictionary = new Dictionary<KeyCode, int>
{
{
(KeyCode)282,
0
},
{
(KeyCode)283,
1
},
{
(KeyCode)284,
2
},
{
(KeyCode)285,
3
},
{
(KeyCode)286,
4
},
{
(KeyCode)287,
5
},
{
(KeyCode)288,
6
},
{
(KeyCode)289,
7
}
};
foreach (KeyCode key in dictionary.Keys)
{
if (!Input.GetKeyDown(key))
{
continue;
}
int num = dictionary[key];
Enumerator<PlayerAgent> enumerator2 = PlayerManager.PlayerAgentsInLevel.GetEnumerator();
while (enumerator2.MoveNext())
{
PlayerAgent current2 = enumerator2.Current;
if (current2.PlayerSlotIndex == num)
{
if (flag)
{
customFromPlayer = (((Object)(object)customFromPlayer == (Object)(object)current2) ? null : current2);
}
else
{
customToPlayer = (((Object)(object)customToPlayer == (Object)(object)current2) ? null : current2);
}
break;
}
}
break;
}
}
[HarmonyPatch(typeof(PlayerChatManager), "EnterChatMode")]
[HarmonyPostfix]
private static void PlayerChatManager__EnterChatMode__Postfix()
{
UpdateCaretVisibility(forceVisible: true);
}
[HarmonyPatch(typeof(PlayerChatManager), "ExitChatMode")]
[HarmonyPostfix]
private static void PlayerChatManager__ExitChatMode__Postfix(PlayerChatManager __instance)
{
if (Config.DiscardDraftMessage.Value)
{
caretPosition = 0;
__instance.m_currentValue = "";
__instance.m_currentImeCompositionString = "";
customFromPlayer = null;
customToPlayer = null;
__instance.UpdateMessage();
}
}
[HarmonyPatch(typeof(PlayerChatManager), "PostMessage")]
[HarmonyPrefix]
private static void PlayerChatManager__PostMessage__Prefix(ref bool __runOriginal)
{
__runOriginal = false;
}
[HarmonyPatch(typeof(PlayerChatManager), "PostMessage")]
[HarmonyPostfix]
private static void PlayerChatManager__PostMessage__Postfix(PlayerChatManager __instance)
{
if (__instance.m_currentValue.Trim('\n').Length > 0)
{
string text = WordWrap.Wrap(__instance.m_currentValue, __instance.m_maxLen);
if (customFromPlayer == null)
{
customFromPlayer = PlayerManager.GetLocalPlayerAgent();
}
string[] array = text.Split('\n');
foreach (string text2 in array)
{
PlayerChatManager.WantToSentTextMessage(customFromPlayer, text2, customToPlayer);
}
}
caretPosition = 0;
__instance.m_currentValue = "";
__instance.m_currentImeCompositionString = "";
customFromPlayer = null;
customToPlayer = null;
__instance.ExitChatMode();
}
[HarmonyPatch(typeof(PUI_GameEventLog), "Update")]
[HarmonyPostfix]
private static void PUI_GameEventLog__Update__Postfix(PUI_GameEventLog __instance)
{
//IL_024f: Unknown result type (might be due to invalid IL or missing references)
//IL_025a: Unknown result type (might be due to invalid IL or missing references)
//IL_0286: Unknown result type (might be due to invalid IL or missing references)
if (PlayerChatManager.InChatMode)
{
((TMP_Text)__instance.m_txtInput).enableWordWrapping = false;
((TMP_Text)__instance.m_txtInput).overflowMode = (TextOverflowModes)0;
((TMP_Text)__instance.m_txtInput).alignment = (TextAlignmentOptions)1025;
UpdateCaretVisibility();
string text = __instance.m_textInputString ?? string.Empty;
if (caretPosition == text.Length)
{
text = WordWrap.Wrap(text, PlayerChatManager.Current.m_maxLen);
text = ((!caretVisible) ? (text + " ") : (text + "_"));
}
else if (caretVisible)
{
char c = text[caretPosition];
char c2 = (char)WordWrap.GetCharType(c);
text = text.Remove(caretPosition, 1);
text = text.Insert(caretPosition, c2.ToString());
text = WordWrap.Wrap(text, PlayerChatManager.Current.m_maxLen);
string text2 = "<mark=#FFF>_</mark>";
if (c2 == '\ue000')
{
text2 += "\n";
}
text = text.Replace(c2.ToString(), text2);
}
else
{
text = WordWrap.Wrap(text, PlayerChatManager.Current.m_maxLen);
}
((TMP_Text)__instance.m_txtInput).text = text;
}
else
{
((TMP_Text)__instance.m_txtInput).text = "";
}
string text3 = "";
if (customFromPlayer != null)
{
text3 = text3 + "From " + PlayerNameExtentions.GetColoredName(customFromPlayer, (string)null) + "\n";
}
if (customToPlayer != null)
{
text3 = text3 + "To " + PlayerNameExtentions.GetColoredName(customToPlayer, (string)null) + "\n";
}
if (text3.Length > 0)
{
((TMP_Text)__instance.m_txtInput).text = "<size=80%>" + text3 + "\n</size>" + ((TMP_Text)__instance.m_txtInput).text;
}
float num = ((TMP_Text)__instance.m_txtInput).preferredHeight + 5.1f;
Transform val = __instance.m_chatActiveRoot.transform.FindChild("Input Background Active");
SpriteRenderer component = ((Component)val).GetComponent<SpriteRenderer>();
component.size = new Vector2(component.size.x, num);
RectTransform component2 = ((Component)val).GetComponent<RectTransform>();
((Transform)component2).localPosition = new Vector3(0f, num / 2f - 1f, 0f);
__instance.ShowAndUpdateItemPositions();
}
[HarmonyPatch(typeof(PUI_GameEventLog), "ShowAndUpdateItemPositions")]
[HarmonyPrefix]
private static void PUI_GameEventLog__ShowAndUpdateItemPositions__Prefix(PUI_GameEventLog __instance, ref bool __runOriginal)
{
//IL_006e: Unknown result type (might be due to invalid IL or missing references)
__runOriginal = false;
float num = ((TMP_Text)__instance.m_txtInput).preferredHeight + 5.1f;
float num2 = (((TMP_Text)__instance.m_txtInput).text.Contains('\n') ? (num - __instance.m_logItemHeight) : 0f);
for (int i = 0; i < __instance.m_logItems.Count; i++)
{
((Component)__instance.m_logItems[i]).transform.localPosition = new Vector3(0f, num2 + (float)(__instance.m_logItems.Count - i - 1) * __instance.m_logItemHeight);
}
}
private static void UpdateCaretVisibility(bool forceVisible = false)
{
if (forceVisible)
{
caretVisible = true;
caretVisibilityTimer = Time.time + 0.6f;
}
else if (Time.time > caretVisibilityTimer)
{
caretVisible = !caretVisible;
caretVisibilityTimer = Time.time + 0.6f;
}
}
}
internal static class ChatInputRepetitionManager
{
private static readonly InputAction[] inputActionsToWatch;
private static InputAction repeatInputAction;
private static float repeatDelayTimer;
private static float repeatRateTimer;
public static InputAction inputActionThisFrame;
public static void Update()
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
//IL_0019: Unknown result type (might be due to invalid IL or missing references)
//IL_0069: Unknown result type (might be due to invalid IL or missing references)
//IL_0092: Unknown result type (might be due to invalid IL or missing references)
//IL_0095: Unknown result type (might be due to invalid IL or missing references)
//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Unknown result type (might be due to invalid IL or missing references)
//IL_004e: Unknown result type (might be due to invalid IL or missing references)
inputActionThisFrame = (InputAction)0;
if (!PlayerChatManager.InChatMode)
{
return;
}
if (InputMapper.GetButtonKeyMouseGamepad(repeatInputAction, (eFocusState)0))
{
if (Time.time > repeatDelayTimer && Time.time > repeatRateTimer)
{
inputActionThisFrame = repeatInputAction;
repeatRateTimer = Time.time + 0.05f;
}
return;
}
repeatInputAction = (InputAction)0;
repeatDelayTimer = 0f;
repeatRateTimer = 0f;
InputAction[] array = inputActionsToWatch;
foreach (InputAction val in array)
{
if (InputMapper.GetButtonKeyMouseGamepad(val, (eFocusState)0))
{
inputActionThisFrame = val;
repeatInputAction = val;
repeatDelayTimer = Time.time + 0.35f;
break;
}
}
}
static ChatInputRepetitionManager()
{
//IL_0017: Unknown result type (might be due to invalid IL or missing references)
//IL_0031: Unknown result type (might be due to invalid IL or missing references)
InputAction[] array = new InputAction[3];
RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/);
inputActionsToWatch = (InputAction[])(object)array;
repeatInputAction = (InputAction)0;
repeatDelayTimer = 0f;
repeatRateTimer = 0f;
inputActionThisFrame = (InputAction)0;
}
}
internal static class WordWrap
{
public enum CharType
{
LineSeparator = 57344,
WordSeparator,
Other
}
public static CharType GetCharType(char c)
{
if (c == '\ue000' || c == '\n')
{
return CharType.LineSeparator;
}
if (c == '\ue001' || char.IsSeparator(c))
{
return CharType.WordSeparator;
}
return CharType.Other;
}
public static string Wrap(string original_text, int width)
{
StringBuilder stringBuilder = new StringBuilder();
StringBuilder stringBuilder2 = new StringBuilder();
StringBuilder stringBuilder3 = new StringBuilder();
foreach (char c in original_text)
{
if (GetCharType(c) == CharType.LineSeparator)
{
if (stringBuilder2.Length + stringBuilder3.Length > width)
{
stringBuilder.Append(stringBuilder2);
stringBuilder.Append('\n');
stringBuilder2.Clear();
}
stringBuilder2.Append(stringBuilder3);
stringBuilder3.Clear();
stringBuilder.Append(stringBuilder2);
stringBuilder.Append(c);
stringBuilder2.Clear();
}
else if (GetCharType(c) == CharType.WordSeparator)
{
if (stringBuilder2.Length + stringBuilder3.Length > width)
{
stringBuilder.Append(stringBuilder2);
stringBuilder.Append('\n');
stringBuilder2.Clear();
}
stringBuilder2.Append(stringBuilder3);
stringBuilder2.Append(c);
stringBuilder3.Clear();
}
else
{
stringBuilder3.Append(c);
if (stringBuilder3.Length > width)
{
int num = width - stringBuilder2.Length;
stringBuilder2.Append(stringBuilder3.ToString(0, num));
stringBuilder3.Remove(0, num);
stringBuilder.Append(stringBuilder2);
stringBuilder.Append('\n');
stringBuilder2.Clear();
}
}
}
if (stringBuilder2.Length + stringBuilder3.Length > width)
{
stringBuilder.Append(stringBuilder2);
stringBuilder.Append('\n');
stringBuilder2.Clear();
}
stringBuilder2.Append(stringBuilder3);
stringBuilder3.Clear();
stringBuilder.Append(stringBuilder2);
stringBuilder2.Clear();
if (stringBuilder.Length > 0)
{
if (stringBuilder[stringBuilder.Length - 1] == '\n')
{
if (original_text[original_text.Length - 1] != '\n')
{
stringBuilder.Remove(stringBuilder.Length - 1, 1);
}
}
}
return stringBuilder.ToString();
}
}
internal static class Config
{
public static readonly ConfigEntry<bool> AllowForbiddenChars;
public static readonly ConfigEntry<bool> DiscardDraftMessage;
public static readonly ConfigEntry<int> ChatHistoryLength;
public static readonly ConfigEntry<bool> DisableAutoSize;
public static readonly ConfigEntry<bool> ShowTypingIndicator;
public static readonly ConfigEntry<bool> SendTypingIndicator;
public static readonly ConfigEntry<bool> ShowTimestampInPageLoadout;
public static readonly ConfigEntry<bool> ShowTimestampInPageMap;
public static readonly ConfigEntry<bool> ShowTimestampInPlayerLayer;
private static readonly ConfigEntry<bool> _timestampUse24Hours;
private static readonly ConfigEntry<bool> _timestampShowSeconds;
public static string TimestampFormatString;
static Config()
{
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
//IL_002c: Expected O, but got Unknown
//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
//IL_01de: Expected O, but got Unknown
TimestampFormatString = string.Empty;
ConfigFile configFile = new ConfigFile(Path.Combine(Paths.ConfigPath, "BetterTextChat.cfg"), true);
AllowForbiddenChars = configFile.Bind<bool>("General", "Allow special characters", true, "Makes it possible to type <, >, and = in chat. (Beware: Things can get weird if you use formatting tags, especially across multiple lines.)");
DiscardDraftMessage = configFile.Bind<bool>("General", "Discard draft message", false, "If you start typing a message, then close chat without sending it, discard it instead of remembering it next time you open chat.");
ChatHistoryLength = configFile.Bind<int>("General", "Chat history length", 15, "Number of messages to show at once in the chat history.");
DisableAutoSize = configFile.Bind<bool>("General", "Disable shrink-to-fit", true, "Prevent font size decreasing when timestamps are enabled, or when displaying messages from players with long names, or when displaying messages with certain formatting tags.");
ShowTypingIndicator = configFile.Bind<bool>("Typing indicator", "Display typing indicator", true, "Show a message like [PlayerName is typing...] when other BetterTextChat users are typing in chat.");
SendTypingIndicator = configFile.Bind<bool>("Typing indicator", "Send typing indicator", true, "Let other BetterTextChat users know when you are typing in chat.");
ShowTimestampInPageLoadout = configFile.Bind<bool>("Timestamps", "Show timestamps in lobby", false, "Show message timestamps in the lobby menu.");
ShowTimestampInPageMap = configFile.Bind<bool>("Timestamps", "Show timestamps in map", false, "Show message timestamps in the map.");
ShowTimestampInPlayerLayer = configFile.Bind<bool>("Timestamps", "Show timestamps in game", false, "Show message timestamps in the in-game chat.");
bool flag = DateTimeFormatInfo.CurrentInfo.ShortTimePattern.Contains('H');
_timestampUse24Hours = configFile.Bind<bool>("Timestamps", "Use 24-hour clock", flag, "Use a 24-hour clock in message timestamps, e.g. 16:30 vs 4:30.");
_timestampShowSeconds = configFile.Bind<bool>("Timestamps", "Show seconds", false, "Show seconds in message timestamps.");
OnUpdate();
LiveEditListener val = LiveEdit.CreateListener(Path.GetDirectoryName(configFile.ConfigFilePath), Path.GetFileName(configFile.ConfigFilePath), false);
val.FileChanged += (LiveEditEventHandler)delegate
{
configFile.Reload();
OnUpdate();
};
}
private static void OnUpdate()
{
PUI_GameEventLog.s_logItemsMax = ChatHistoryLength.Value;
UpdateTimestampFormatString();
if (GuiManager.Current != null)
{
ScrollableGameEventLog.UpdateMLogItems(GuiManager.MainMenuLayer.PageLoadout.m_gameEventLog, force: true);
ScrollableGameEventLog.UpdateMLogItems(GuiManager.MainMenuLayer.PageMap.m_gameEventLog, force: true);
ScrollableGameEventLog.UpdateMLogItems(GuiManager.PlayerLayer.m_gameEventLog, force: true);
}
}
private static void UpdateTimestampFormatString()
{
StringBuilder stringBuilder = new StringBuilder();
if (_timestampUse24Hours.Value)
{
stringBuilder.Append("HH");
}
else
{
stringBuilder.Append('h');
}
stringBuilder.Append(":mm");
if (_timestampShowSeconds.Value)
{
stringBuilder.Append(":ss");
}
TimestampFormatString = stringBuilder.ToString();
}
}
internal static class PluginInfo
{
public const string GUID = "Andocas.BetterTextChat";
public const string NAME = "BetterTextChat";
public const string VERSION = "0.6.2";
}
[BepInPlugin("Andocas.BetterTextChat", "BetterTextChat", "0.6.2")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
internal class Plugin : BasePlugin
{
public override void Load()
{
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_0017: Expected O, but got Unknown
Utils.LogInfo("Andocas.BetterTextChat is loading...");
Harmony val = new Harmony("Andocas.BetterTextChat");
val.PatchAll(typeof(BetterComposition));
val.PatchAll(typeof(ScrollableGameEventLog));
TypingIndicator.Setup();
val.PatchAll(typeof(TypingIndicator));
((BasePlugin)this).AddComponent<PttSoundPlayer>();
_ = Config.AllowForbiddenChars;
Utils.LogInfo("Andocas.BetterTextChat is loaded");
}
}
internal static class ScrollableGameEventLog
{
private static readonly Dictionary<int, PUI_GameEventLog_ExtensionData> extensionDataForEachInstance = new Dictionary<int, PUI_GameEventLog_ExtensionData>();
private static PUI_GameEventLog_ExtensionData GetInstanceData(PUI_GameEventLog __instance)
{
int instanceID = ((Object)__instance).GetInstanceID();
if (!extensionDataForEachInstance.ContainsKey(instanceID))
{
extensionDataForEachInstance[instanceID] = new PUI_GameEventLog_ExtensionData(instanceID);
}
return extensionDataForEachInstance[instanceID];
}
[HarmonyPatch(typeof(PUI_GameEventLog), "AddLogItem")]
[HarmonyPrefix]
private static void PUI_GameEventLog__AddLogItem__Prefix(PUI_GameEventLog __instance, ref bool __runOriginal, string log, eGameEventChatLogType type)
{
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_001b: Unknown result type (might be due to invalid IL or missing references)
__runOriginal = false;
PUI_GameEventLog_ExtensionData instanceData = GetInstanceData(__instance);
log = FixMessagesFromBots(log, type);
instanceData.rawEventLog.Add((log, type, DateTime.Now));
UpdateMLogItems(__instance);
}
[HarmonyPatch(typeof(PUI_GameEventLog), "OnLevelCleanup")]
[HarmonyPrefix]
private static void PUI_GameEventLog__OnLevelCleanup__Prefix(PUI_GameEventLog __instance, ref bool __runOriginal)
{
__runOriginal = false;
if ((Object)(object)__instance == (Object)(object)GuiManager.PlayerLayer.m_gameEventLog)
{
PUI_GameEventLog_ExtensionData instanceData = GetInstanceData(__instance);
instanceData.rawEventLog.Clear();
}
}
[HarmonyPatch(typeof(PlayerChatManager), "EnterChatMode")]
[HarmonyPatch(typeof(PlayerChatManager), "ExitChatMode")]
[HarmonyPostfix]
private static void PlayerChatManager__EnterExitChatMode__Postfix()
{
foreach (PUI_GameEventLog_ExtensionData value in extensionDataForEachInstance.Values)
{
value.scrollPosition = null;
}
}
[HarmonyPatch(typeof(PUI_GameEventLog), "Update")]
[HarmonyPostfix]
private static void PUI_GameEventLog__Update__Postfix(PUI_GameEventLog __instance)
{
//IL_0030: Unknown result type (might be due to invalid IL or missing references)
//IL_0087: Unknown result type (might be due to invalid IL or missing references)
PUI_GameEventLog_ExtensionData instanceData = GetInstanceData(__instance);
int num = Math.Min(__instance.m_limitNRLines, PUI_GameEventLog.s_logItemsMax);
if (PlayerChatManager.TextChatInputEnabled && PlayerChatManager.InChatMode && Input.mouseScrollDelta.y != 0f)
{
int? scrollPosition = instanceData.scrollPosition;
if (!scrollPosition.HasValue)
{
instanceData.scrollPosition = instanceData.rawEventLog.Count - num;
}
instanceData.scrollPosition -= (int)Input.mouseScrollDelta.y;
if (instanceData.scrollPosition < 0)
{
instanceData.scrollPosition = 0;
}
if (instanceData.scrollPosition >= instanceData.rawEventLog.Count - num)
{
instanceData.scrollPosition = null;
}
}
UpdateMLogItems(__instance);
}
internal static void UpdateMLogItems(PUI_GameEventLog __instance, bool force = false)
{
//IL_0132: Unknown result type (might be due to invalid IL or missing references)
//IL_0137: Unknown result type (might be due to invalid IL or missing references)
//IL_0203: Unknown result type (might be due to invalid IL or missing references)
PUI_GameEventLog_ExtensionData instanceData = GetInstanceData(__instance);
int num = Math.Min(__instance.m_limitNRLines, PUI_GameEventLog.s_logItemsMax);
int sequenceHashCode = instanceData.rawEventLog.GetSequenceHashCode();
if (!(instanceData.scrollPosition != instanceData.previousScrollPosition || sequenceHashCode != instanceData.previousRawEventLogHash || force))
{
return;
}
instanceData.previousScrollPosition = instanceData.scrollPosition;
instanceData.previousRawEventLogHash = sequenceHashCode;
Enumerator<PUI_GameEventLog_Item> enumerator = __instance.m_logItems.GetEnumerator();
while (enumerator.MoveNext())
{
PUI_GameEventLog_Item current = enumerator.Current;
Object.Destroy((Object)(object)((Component)current).gameObject);
}
__instance.m_logItems.Clear();
int num2 = instanceData.scrollPosition ?? Mathf.Max(instanceData.rawEventLog.Count - num, 0);
int count = Mathf.Min(num, instanceData.rawEventLog.Count - num2);
foreach (var (text, type, dateTime) in instanceData.rawEventLog.GetRange(num2, count))
{
string text3;
if (((Object)(object)__instance == (Object)(object)GuiManager.MainMenuLayer.PageLoadout.m_gameEventLog && Config.ShowTimestampInPageLoadout.Value) || ((Object)(object)__instance == (Object)(object)GuiManager.MainMenuLayer.PageMap.m_gameEventLog && Config.ShowTimestampInPageMap.Value) || ((Object)(object)__instance == (Object)(object)GuiManager.PlayerLayer.m_gameEventLog && Config.ShowTimestampInPlayerLayer.Value))
{
string text2 = dateTime.ToString(Config.TimestampFormatString);
text3 = "<color=#A1A1A133>[" + text2 + "]</color> " + text;
}
else
{
text3 = text;
}
PUI_GameEventLog_Item val = GOUtil.SpawnChildAndGetComp<PUI_GameEventLog_Item>(__instance.m_logItemPrefab, __instance.m_itemAlign);
((RectTransformComp)val).Setup();
val.SetText(text3);
val.SetType(type);
__instance.m_logItems.Add(val);
}
__instance.ShowAndUpdateItemPositions();
__instance.ResetHideTimer();
}
[HarmonyPatch(typeof(PUI_GameEventLog_Item), "Setup")]
[HarmonyPostfix]
private static void PUI_GameEventLog_Item__Setup__Postfix(PUI_GameEventLog_Item __instance)
{
if (Config.DisableAutoSize.Value)
{
((TMP_Text)__instance.m_text).enableAutoSizing = false;
((TMP_Text)__instance.m_text).enableWordWrapping = false;
}
}
[HarmonyPatch(typeof(CM_PageMap), "UpdateZoomAndPan")]
[HarmonyPrefix]
private static void CM_PageMap__UpdateZoomAndPan__Prefix(CM_PageMap __instance, ref bool __runOriginal)
{
if (PlayerChatManager.InChatMode && ((CM_PageBase)__instance).ScrollInput != 0f)
{
__runOriginal = false;
}
}
private static string FixMessagesFromBots(string message, eGameEventChatLogType type)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
if ((int)type == 0 && message.StartsWith("<color=orange>[Bot]</color><color=#"))
{
Dictionary<string, string> dictionary = new Dictionary<string, string>
{
{ "<color=orange>[Bot]</color><color=#C21F4E>> > Woods</color>: ", "<color=#C21F4E>> > <color=orange>[Bot]</color>Woods</color>: " },
{ "<color=orange>[Bot]</color><color=#18935E>> > Dauda</color>: ", "<color=#18935E>> > <color=orange>[Bot]</color>Dauda</color>: " },
{ "<color=orange>[Bot]</color><color=#20558C>> > Hackett</color>: ", "<color=#20558C>> > <color=orange>[Bot]</color>Hackett</color>: " },
{ "<color=orange>[Bot]</color><color=#7A1A8E>> > Bishop</color>: ", "<color=#7A1A8E>> > <color=orange>[Bot]</color>Bishop</color>: " }
};
foreach (KeyValuePair<string, string> item in dictionary)
{
if (message.StartsWith(item.Key))
{
return string.Concat(item.Value, message.AsSpan(item.Value.Length));
}
}
}
return message;
}
}
internal class PUI_GameEventLog_ExtensionData
{
public int instanceID;
public List<(string, eGameEventChatLogType, DateTime)> rawEventLog = new List<(string, eGameEventChatLogType, DateTime)>();
public int previousRawEventLogHash = 0;
public int? scrollPosition = null;
public int? previousScrollPosition = null;
public PUI_GameEventLog_ExtensionData(int instanceID)
{
this.instanceID = instanceID;
}
}
internal static class Extensions
{
public static int GetSequenceHashCode<T>(this List<T> sequence)
{
return sequence.Aggregate(487, (int current, T item) => current * 31 + (item?.GetHashCode() ?? 0));
}
}
internal static class TypingIndicator
{
private static readonly Dictionary<ulong, SNet_Player> playersWithBetterTextChat = new Dictionary<ulong, SNet_Player>();
private static readonly Dictionary<ulong, TypingPlayerData> typingPlayers = new Dictionary<ulong, TypingPlayerData>();
private static readonly Dictionary<ulong, float> lastMessageFromPlayer = new Dictionary<ulong, float>();
internal static void Setup()
{
SetupHello();
SetupPlayerIsTyping();
}
[HarmonyPatch(typeof(SNet_GlobalManager), "OnPlayerEvent")]
[HarmonyPostfix]
private static void SNet_GlobalManager__OnPlayerEvent__Postfix(SNet_Player player, SNet_PlayerEvent playerEvent)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0003: Invalid comparison between Unknown and I4
//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
//IL_00aa: Invalid comparison between Unknown and I4
if ((int)playerEvent == 2)
{
if (player.IsLocal)
{
Enumerator<SNet_Player> enumerator = SNet.LobbyPlayers.GetEnumerator();
while (enumerator.MoveNext())
{
SNet_Player current = enumerator.Current;
if (!current.IsLocal)
{
Utils.LogInfo("Sending Hello to " + current.NickName);
NetworkAPI.InvokeEvent<EmptyStruct>("BetterTextChat-Hello", default(EmptyStruct), current, (SNet_ChannelType)0);
}
}
}
else
{
Utils.LogInfo("Sending Hello to " + player.NickName);
NetworkAPI.InvokeEvent<EmptyStruct>("BetterTextChat-Hello", default(EmptyStruct), player, (SNet_ChannelType)0);
}
}
if ((int)playerEvent == 0 && playersWithBetterTextChat.ContainsKey(player.Lookup))
{
playersWithBetterTextChat.Remove(player.Lookup);
}
}
private static void SetupHello()
{
NetworkAPI.RegisterEvent<EmptyStruct>("BetterTextChat-Hello", (Action<ulong, EmptyStruct>)OnSNetPlayerHello);
}
private static void OnSNetPlayerHello(ulong steamID, EmptyStruct emptyStruct)
{
SNet_Player val = default(SNet_Player);
if (SNet.Core.TryGetPlayer(steamID, ref val, false))
{
Utils.LogInfo("Received Hello from " + val.NickName);
playersWithBetterTextChat[steamID] = val;
}
else
{
Utils.LogError($"OnSNetPlayerHello > Could not find SNet_Player for {steamID}");
}
}
internal static void OnLocalPlayerTyping()
{
if (Config.SendTypingIndicator.Value)
{
NetworkAPI.InvokeEvent<EmptyStruct>("BetterTextChat-PlayerIsTyping", default(EmptyStruct), playersWithBetterTextChat.Values.ToList(), (SNet_ChannelType)4);
}
}
private static void SetupPlayerIsTyping()
{
NetworkAPI.RegisterEvent<EmptyStruct>("BetterTextChat-PlayerIsTyping", (Action<ulong, EmptyStruct>)OnSNetPlayerTyping);
}
private static void OnSNetPlayerTyping(ulong steamID, EmptyStruct emptyStruct)
{
if (!Config.ShowTypingIndicator.Value || (lastMessageFromPlayer.ContainsKey(steamID) && Time.time < lastMessageFromPlayer[steamID] + 0.5f))
{
return;
}
SNet_Player sNetPlayer = default(SNet_Player);
if (typingPlayers.ContainsKey(steamID))
{
typingPlayers[steamID].lastSeenTypingAt = Time.time;
}
else if (SNet.Core.TryGetPlayer(steamID, ref sNetPlayer, false))
{
PlayerAgent playerAgentFromSNetPlayer = GetPlayerAgentFromSNetPlayer(sNetPlayer);
if (playerAgentFromSNetPlayer != null)
{
typingPlayers[steamID] = new TypingPlayerData(playerAgentFromSNetPlayer, Time.time);
}
}
}
[HarmonyPatch(typeof(PlayerChatManager), "DoSendChatMessage")]
[HarmonyPostfix]
private static void PlayerChatManager__DoSendChatMessage__Postfix(pChatMessage data)
{
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
//IL_003c: Unknown result type (might be due to invalid IL or missing references)
lastMessageFromPlayer[data.fromPlayer.lookup] = Time.time;
if (typingPlayers.ContainsKey(data.fromPlayer.lookup))
{
typingPlayers.Remove(data.fromPlayer.lookup);
}
}
[HarmonyPatch(typeof(PUI_GameEventLog), "UpdateHelpText")]
[HarmonyPrefix]
private static void PUI_GameEventLog__UpdateHelpText__Prefix(ref bool __runOriginal)
{
__runOriginal = false;
}
[HarmonyPatch(typeof(PUI_GameEventLog), "Update")]
[HarmonyPostfix]
private static void PUI_GameEventLog__Update__Postfix(PUI_GameEventLog __instance)
{
//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
//IL_00a9: Invalid comparison between Unknown and I4
__instance.m_checkPushToTalk = CellSettingsManager.SettingsData.VoiceChat.Mode.Value == 1;
foreach (ulong item in typingPlayers.Keys.ToList())
{
TypingPlayerData typingPlayerData = typingPlayers[item];
if (Time.time > typingPlayerData.lastSeenTypingAt + 2f || typingPlayerData.playerAgent == null)
{
typingPlayers.Remove(item);
}
}
List<string> list = new List<string>();
bool flag = (int)LocalizationAPI.CurrentLanguage == 1;
if (PlayerChatManager.Current.m_dissonanceVoiceBroadcast.IsTransmitting)
{
list.Add("<color=red>" + Text.Get(966u) + "</color>");
}
if (PlayerChatManager.Current.m_speakingPlayers.Count > 0)
{
List<string> list2 = new List<string>();
Enumerator<VoicePlayerState> enumerator2 = PlayerChatManager.Current.m_speakingPlayers.GetEnumerator();
while (enumerator2.MoveNext())
{
VoicePlayerState current2 = enumerator2.Current;
SNet_Player sNetPlayerByVoiceState = PlayerChatManager.Current.GetSNetPlayerByVoiceState(current2);
PlayerAgent playerAgentFromSNetPlayer = GetPlayerAgentFromSNetPlayer(sNetPlayerByVoiceState);
if (playerAgentFromSNetPlayer != null)
{
list2.Add(PlayerNameExtentions.GetColoredName(playerAgentFromSNetPlayer, (string)null));
}
}
if (flag)
{
list.Add($"[{FormatStringList(list2)} {FormatIsArePlural(list2)} speaking]");
}
else
{
foreach (string item2 in list2)
{
list.Add(Text.Format(965u, (Object[])(object)new Object[1] { Object.op_Implicit(item2) }));
}
}
}
if (typingPlayers.Count > 0)
{
List<string> list3 = new List<string>();
foreach (TypingPlayerData item3 in typingPlayers.Values.OrderBy((TypingPlayerData playerData) => playerData.playerAgent.PlayerSlotIndex))
{
list3.Add(PlayerNameExtentions.GetColoredName(item3.playerAgent, (string)null));
}
list.Add($"[{FormatStringList(list3)} {FormatIsArePlural(list3)} typing...]");
}
if (list.Count > 0)
{
((TMP_Text)__instance.m_txtHelp).text = string.Join(" ", list);
}
else if (__instance.m_checkPushToTalk)
{
((TMP_Text)__instance.m_txtHelp).text = Text.Format(1392u, (Object[])(object)new Object[4]
{
Object.op_Implicit("<color=orange>[" + InputMapper.GetBindingName((InputAction)56) + "]</color>"),
Object.op_Implicit("<color=orange>[" + InputMapper.GetBindingName((InputAction)55) + "]</color>"),
Object.op_Implicit("<color=orange>[" + InputMapper.GetBindingName((InputAction)33) + "]</color>"),
Object.op_Implicit("<color=orange>[" + InputMapper.GetBindingName((InputAction)63) + "]</color>")
});
}
else
{
((TMP_Text)__instance.m_txtHelp).text = Text.Format(1393u, (Object[])(object)new Object[3]
{
Object.op_Implicit("<color=orange>[" + InputMapper.GetBindingName((InputAction)56) + "]</color>"),
Object.op_Implicit("<color=orange>[" + InputMapper.GetBindingName((InputAction)33) + "]</color>"),
Object.op_Implicit("<color=orange>[" + InputMapper.GetBindingName((InputAction)63) + "]</color>")
});
}
}
private static PlayerAgent? GetPlayerAgentFromSNetPlayer(SNet_Player sNetPlayer)
{
Enumerator<PlayerAgent> enumerator = PlayerManager.PlayerAgentsInLevel.GetEnumerator();
while (enumerator.MoveNext())
{
PlayerAgent current = enumerator.Current;
if ((Object)(object)((Agent)current).m_replicator.OwningPlayer == (Object)(object)sNetPlayer)
{
return current;
}
}
Utils.LogError("GetPlayerAgentFromSNetPlayer > Could not find PlayerAgent for " + ((Object)sNetPlayer).name);
return null;
}
private static string FormatStringList(List<string> strings)
{
switch (strings.Count)
{
case 0:
return string.Empty;
case 1:
return strings[0];
case 2:
return strings[0] + " and " + strings[1];
default:
return string.Join(", ", strings.GetRange(0, strings.Count - 1)) + ", and " + strings[strings.Count - 1];
}
}
private static string FormatIsArePlural(List<string> strings)
{
return (strings.Count == 1) ? "is" : "are";
}
}
[StructLayout(LayoutKind.Sequential, Size = 1)]
public struct EmptyStruct
{
}
internal class TypingPlayerData
{
public PlayerAgent playerAgent;
public float lastSeenTypingAt;
public TypingPlayerData(PlayerAgent playerAgent, float lastSeenTypingAt)
{
this.playerAgent = playerAgent;
this.lastSeenTypingAt = lastSeenTypingAt;
}
}
internal class PttSoundPlayer : MonoBehaviour
{
private bool previousTransmittingStatus = false;
private void Update()
{
bool flag = CellSettingsManager.SettingsData.VoiceChat.Mode.Value == 1;
bool value = CellSettingsManager.SettingsData.VoiceChat.PushToTalk_EnableSound.Value;
bool isTransmitting = PlayerChatManager.Current.m_dissonanceVoiceBroadcast.IsTransmitting;
if (flag && value)
{
if (isTransmitting && !previousTransmittingStatus)
{
CM_PageBase.PostSound(EVENTS.PUSH_TO_TALK_ON, "");
}
if (previousTransmittingStatus && !isTransmitting)
{
CM_PageBase.PostSound(EVENTS.PUSH_TO_TALK_OFF, "");
}
}
previousTransmittingStatus = isTransmitting;
}
}
internal static class Utils
{
private static readonly ManualLogSource logger = Logger.CreateLogSource("Andocas.BetterTextChat");
public static void LogInfo(object? o)
{
logger.LogInfo((object)GetMessage(o));
}
public static void LogError(object? o)
{
logger.LogError((object)GetMessage(o));
}
private static string GetMessage(object? o)
{
if (o == null)
{
return "No string representation for <null>";
}
return o.ToString() ?? $"No string representation for <{o.GetType()}>";
}
}
}