using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using Fusion; using HarmonyLib; using Il2CppInterop.Runtime.Injection; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem.Collections.Generic; using Microsoft.CodeAnalysis; using Steamworks; using TMPro; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.Rendering; using UnityEngine.SceneManagement; 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("Chatter")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("Chatter")] [assembly: AssemblyTitle("Chatter")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ShiftAtMidnightChatter { internal sealed class BubbleField { private readonly Dictionary _bubbles = new Dictionary(); private readonly List _finished = new List(); public void Say(ulong owner, string message) { For(owner).Say(message); } public void Typing(ulong owner, bool typing) { if (typing || _bubbles.ContainsKey(owner)) { For(owner).Typing(typing); } } public void LateTick() { if (_bubbles.Count == 0) { return; } Camera localCamera = NetworkContext.LocalCamera; _finished.Clear(); foreach (KeyValuePair bubble in _bubbles) { PlayerManager body = PlayerRoster.Body(bubble.Key); bubble.Value.LateTick(PlayerRoster.Head(body), localCamera); if (bubble.Value.Idle) { _finished.Add(bubble.Key); } } foreach (ulong item in _finished) { _bubbles[item].Destroy(); _bubbles.Remove(item); } } public void Clear() { foreach (KeyValuePair bubble in _bubbles) { bubble.Value.Destroy(); } _bubbles.Clear(); } private ChatBubble For(ulong owner) { if (_bubbles.TryGetValue(owner, out var value) && value != null) { return value; } value = new ChatBubble(owner); _bubbles[owner] = value; return value; } } internal sealed class ChatBox { private const float Width = 760f; private const float MinHeight = 108f; private const float BottomMargin = 190f; private const float BorderThickness = 3f; private const float TextInset = 24f; private const float VerticalPadding = 34f; private const float FontSize = 40f; private const float CaretWidth = 3f; private const float CaretGap = 5f; private const float CaretBlinkPeriod = 1.06f; private static readonly Color Border = new Color(1f, 1f, 1f, 0.92f); private static readonly Color Backing = new Color(0f, 0f, 0f, 0.35f); private readonly GameObject _root; private readonly RectTransform _panel; private readonly TextMeshProUGUI _text; private readonly RectTransform _caret; private readonly Image _caretFill; public ChatBox() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: 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_011b: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0188: 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_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) //IL_02b2: Unknown result type (might be due to invalid IL or missing references) //IL_02cd: Unknown result type (might be due to invalid IL or missing references) _panel = ChatCanvas.Panel("ChatterBox", ChatCanvas.Root); _root = ((Component)_panel).gameObject; _panel.anchorMin = new Vector2(0.5f, 0f); _panel.anchorMax = new Vector2(0.5f, 0f); _panel.pivot = new Vector2(0.5f, 0f); _panel.anchoredPosition = new Vector2(0f, 190f); _panel.sizeDelta = new Vector2(760f, 108f); Image val = ChatCanvas.Fill("Backing", (Transform)(object)_panel, Backing); ChatCanvas.Stretch(((Graphic)val).rectTransform); Edge((Transform)(object)_panel, new Vector2(0f, 1f), Vector2.one, new Vector2(0f, 3f)); Edge((Transform)(object)_panel, Vector2.zero, new Vector2(1f, 0f), new Vector2(0f, 3f)); Edge((Transform)(object)_panel, Vector2.zero, new Vector2(0f, 1f), new Vector2(3f, 0f)); Edge((Transform)(object)_panel, new Vector2(1f, 0f), Vector2.one, new Vector2(3f, 0f)); RectTransform val2 = ChatCanvas.Panel("Text", (Transform)(object)_panel); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = new Vector2(24f, 0f); val2.offsetMax = new Vector2(-24f, 0f); _text = ((Component)val2).gameObject.AddComponent(); ((TMP_Text)_text).font = GameFont.Asset; ((TMP_Text)_text).fontSize = 40f; ((TMP_Text)_text).alignment = (TextAlignmentOptions)514; ((TMP_Text)_text).textWrappingMode = (TextWrappingModes)1; ((TMP_Text)_text).overflowMode = (TextOverflowModes)0; ((Graphic)_text).raycastTarget = false; ((TMP_Text)_text).richText = false; _caretFill = ChatCanvas.Fill("Caret", (Transform)(object)val2, Border); _caret = ((Graphic)_caretFill).rectTransform; _caret.anchorMin = new Vector2(0.5f, 0.5f); _caret.anchorMax = new Vector2(0.5f, 0.5f); _caret.pivot = new Vector2(0f, 0.5f); _root.SetActive(false); } public void Show(string typed) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) _root.SetActive(true); ((TMP_Text)_text).text = typed; ((TMP_Text)_text).ForceMeshUpdate(false, false); Grow(); PlaceCaret(); bool flag = Mathf.Repeat(Time.unscaledTime, 1.06f) < 0.53f; ((Graphic)_caretFill).color = (flag ? Border : Color.clear); } public void Hide() { _root.SetActive(false); } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } } private void Grow() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Max(108f, ((TMP_Text)_text).preferredHeight + 34f); _panel.sizeDelta = new Vector2(760f, num); } private void PlaceCaret() { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) TMP_TextInfo textInfo = ((TMP_Text)_text).textInfo; int num = ((textInfo != null) ? textInfo.characterCount : 0); if (num == 0) { _caret.sizeDelta = new Vector2(3f, ((TMP_Text)_text).fontSize); _caret.anchoredPosition = new Vector2(5f, 0f); } else { TMP_CharacterInfo val = ((Il2CppArrayBase)(object)textInfo.characterInfo)[num - 1]; _caret.sizeDelta = new Vector2(3f, val.ascender - val.descender); _caret.anchoredPosition = new Vector2(val.xAdvance + 5f, (val.ascender + val.descender) * 0.5f); } } private static void Edge(Transform parent, Vector2 anchorMin, Vector2 anchorMax, Vector2 size) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) Image val = ChatCanvas.Fill("Edge", parent, Border); RectTransform rectTransform = ((Graphic)val).rectTransform; rectTransform.anchorMin = anchorMin; rectTransform.anchorMax = anchorMax; rectTransform.pivot = new Vector2(0.5f, 0.5f); rectTransform.sizeDelta = size; rectTransform.anchoredPosition = Vector2.zero; } } internal sealed class ChatBubble { private const float TagClearance = 0.06f; private const float LeastTagHeight = 0.05f; private const float MostTagHeight = 0.3f; private static readonly Color Ink = Color.white; private const float WorldScale = 1f; private const float PointSize = 2f; private const float LineWidth = 4f; private const float SolidDistance = 8f; private const float FaintDistance = 40f; private const float FaintestAlpha = 0.3f; private const float OccludedAlpha = 0.5f; private const float OccludedDim = 0.75f; private const float ArrivalDuration = 0.12f; private const float FadeDuration = 0.6f; private const int OccludedQueue = 2999; private const string TypingText = "..."; private const float TypingPulseSpeed = 3.5f; private const float TypingFaintest = 0.35f; private readonly GameObject _root; private readonly Transform _transform; private readonly TextMeshPro _front; private readonly TextMeshPro _behind; private string _message = string.Empty; private float _shownAt; private float _expiresAt; private bool _typing; public bool Idle => !_typing && Time.unscaledTime >= _expiresAt; public ChatBubble(ulong owner) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown _root = new GameObject($"ChatterBubble_{owner}"); Object.DontDestroyOnLoad((Object)(object)_root); _transform = _root.transform; _front = Build("Front", _transform, overlay: false); _behind = Build("Behind", _transform, overlay: true); _root.SetActive(false); } public void Say(string message) { _message = message; _shownAt = Time.unscaledTime; _expiresAt = Time.unscaledTime + 8f; } public void Typing(bool typing) { _typing = typing; } public void LateTick(Transform head, Camera eye) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) bool flag = Time.unscaledTime < _expiresAt; if ((Object)(object)head == (Object)null || (Object)(object)eye == (Object)null || (!flag && !_typing)) { _root.SetActive(false); return; } _root.SetActive(true); _transform.position = head.position + Vector3.up * (TagHeight(head) + 0.06f); _transform.rotation = ((Component)eye).transform.rotation; float distance = Vector3.Distance(((Component)eye).transform.position, _transform.position); float num = Falloff(distance); if (flag) { Set(_message, num * Arriving() * Leaving()); return; } float num2 = Mathf.Sin(Time.unscaledTime * 3.5f) * 0.5f + 0.5f; Set("...", num * Mathf.Lerp(0.35f, 1f, num2)); } private static float TagHeight(Transform tag) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) RectTransform val = ((Il2CppObjectBase)tag).TryCast(); if ((Object)(object)val == (Object)null) { return 0.3f; } Rect rect = val.rect; float num = ((Rect)(ref rect)).height * Mathf.Abs(((Transform)val).lossyScale.y); return Mathf.Clamp(num, 0.05f, 0.3f); } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } } private void Set(string text, float alpha) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) ((TMP_Text)_front).text = text; ((TMP_Text)_behind).text = text; ((Graphic)_front).color = new Color(Ink.r, Ink.g, Ink.b, alpha); ((Graphic)_behind).color = new Color(Ink.r * 0.75f, Ink.g * 0.75f, Ink.b * 0.75f, alpha * 0.5f); } private static float Falloff(float distance) { return Mathf.Lerp(1f, 0.3f, Mathf.SmoothStep(0f, 1f, Mathf.InverseLerp(8f, 40f, distance))); } private float Arriving() { return Mathf.Clamp01((Time.unscaledTime - _shownAt) / 0.12f); } private float Leaving() { return Mathf.Clamp01((_expiresAt - Time.unscaledTime) / 0.6f); } private static TextMeshPro Build(string name, Transform parent, bool overlay) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); val.transform.SetParent(parent, false); val.transform.localScale = Vector3.one * 1f; TextMeshPro val2 = val.AddComponent(); ((TMP_Text)val2).font = GameFont.Asset; ((TMP_Text)val2).fontSize = 2f; ((TMP_Text)val2).alignment = (TextAlignmentOptions)1026; ((TMP_Text)val2).textWrappingMode = (TextWrappingModes)1; ((TMP_Text)val2).overflowMode = (TextOverflowModes)0; ((TMP_Text)val2).richText = false; ((TMP_Text)val2).rectTransform.sizeDelta = new Vector2(4f, 1f); ((TMP_Text)val2).rectTransform.pivot = new Vector2(0.5f, 0f); Material fontMaterial = ((TMP_Text)val2).fontMaterial; if (overlay) { ((TMP_Text)val2).isOverlay = true; if ((Object)(object)fontMaterial != (Object)null) { fontMaterial.renderQueue = 2999; } } Renderer component = val.GetComponent(); if ((Object)(object)component != (Object)null) { component.shadowCastingMode = (ShadowCastingMode)0; component.receiveShadows = false; } return val2; } } internal static class ChatCanvas { private static readonly Vector2 Reference = new Vector2(1920f, 1080f); private const int SortingOrder = 1000; private static GameObject _root; public static Transform Root { get { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root != (Object)null) { return _root.transform; } _root = new GameObject("ChatterCanvas"); Object.DontDestroyOnLoad((Object)(object)_root); Canvas val = _root.AddComponent(); val.renderMode = (RenderMode)0; val.sortingOrder = 1000; CanvasScaler val2 = _root.AddComponent(); val2.uiScaleMode = (ScaleMode)1; val2.referenceResolution = Reference; val2.screenMatchMode = (ScreenMatchMode)0; val2.matchWidthOrHeight = 0.5f; return _root.transform; } } public static void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } _root = null; } public static RectTransform Panel(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); return val.AddComponent(); } public static Image Fill(string name, Transform parent, Color colour) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) Image val = ((Component)Panel(name, parent)).gameObject.AddComponent(); ((Graphic)val).color = colour; ((Graphic)val).raycastTarget = false; return val; } public static void Stretch(RectTransform rect) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero; } } internal sealed class ChatComposer { private const int MostCharacters = 67; private const float SilentTimeout = 20f; private const char Backspace = '\b'; private const char Return = '\r'; private const char Newline = '\n'; private const char FirstPrintable = ' '; private readonly List _held = new List(); private readonly StringBuilder _typed = new StringBuilder(67); private readonly ChatSync _sync; private readonly ChatBox _box; private bool _open; private bool _ourMove; private bool _ourCam; private bool _ourInventory; private bool _ourHotbar; private bool _ourKeyboard; private int _openedFrame; private float _lastActivity; public bool Open => _open; public event Action Sent; public ChatComposer(ChatSync sync, ChatBox box) { _sync = sync; _box = box; } public void Tick() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (!_open) { if (Input.GetKeyDown(ChatterPlugin.ChatKey.Value) && CanOpen()) { Begin(); } return; } if (!CanStayOpen()) { End(send: false); return; } if (Input.GetKeyDown((KeyCode)27)) { End(send: false); return; } if (Time.unscaledTime - _lastActivity > 20f) { ModLog.Warning("Nothing typed for a while, so the chat box closed itself."); End(send: false); return; } Reassert(); if (Time.frameCount > _openedFrame + 1) { Consume(); } if (_open) { _box.Show(_typed.ToString()); } } public void Cancel() { if (_open) { End(send: false); } } private void Begin() { _typed.Clear(); _open = true; _openedFrame = Time.frameCount; _lastActivity = Time.unscaledTime; Suppress(on: true); _box.Show(string.Empty); _sync.SetTyping(typing: true); } private void Consume() { string inputString = Input.inputString; if (inputString.Length > 0) { _lastActivity = Time.unscaledTime; } foreach (char c in inputString) { switch (c) { case '\b': if (_typed.Length > 0) { _typed.Length--; } break; default: if (c != '\n') { if (c >= ' ' && _typed.Length < 67) { _typed.Append(c); } break; } goto case '\r'; case '\r': End(send: true); return; } } if (Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271)) { End(send: true); } } private void End(bool send) { if (!_open) { return; } _open = false; Suppress(on: false); _box.Hide(); _sync.SetTyping(typing: false); if (send) { string text = _typed.ToString().Trim(); _typed.Clear(); if (text.Length != 0) { this.Sent?.Invoke(text); } } } private void Suppress(bool on) { PlayerManager localPlayerManager = NetworkContext.LocalPlayerManager; if ((Object)(object)localPlayerManager == (Object)null) { return; } FPSController fpsScript = localPlayerManager.fpsScript; InventoryManager inventoryMan = localPlayerManager.inventoryMan; if (on) { _ourMove = (Object)(object)fpsScript != (Object)null && !fpsScript.lockMove; _ourCam = (Object)(object)fpsScript != (Object)null && !fpsScript.lockCam; _ourInventory = (Object)(object)inventoryMan != (Object)null; _ourHotbar = _ourInventory && (Object)(object)inventoryMan.inventoryUIHolder != (Object)null && inventoryMan.inventoryUIHolder.activeSelf; if (_ourInventory) { inventoryMan.PauseInventory(); } InputBlock.FreezeHotbar = true; InputBlock.FreezeHistory = true; Gather(); Reassert(); return; } InputBlock.FreezeHotbar = false; InputBlock.FreezeHistory = false; Restore(); foreach (PlayerInput item in _held) { Activate(item); } _held.Clear(); if ((Object)(object)fpsScript != (Object)null) { if (_ourMove) { fpsScript.lockMove = false; } if (_ourCam) { fpsScript.lockCam = false; } } if (_ourInventory && (Object)(object)inventoryMan != (Object)null) { inventoryMan.canControlItem = true; inventoryMan.UnpauseInventory(); if (_ourHotbar && (Object)(object)inventoryMan.inventoryUIHolder != (Object)null) { inventoryMan.inventoryUIHolder.SetActive(true); } } _ourMove = false; _ourCam = false; _ourInventory = false; _ourHotbar = false; } private void Gather() { _held.Clear(); Il2CppArrayBase val = Object.FindObjectsOfType(); if (val == null) { return; } for (int i = 0; i < val.Length; i++) { PlayerInput val2 = val[i]; if (!((Object)(object)val2 == (Object)null) && val2.inputIsActive) { _held.Add(val2); } } } private void Reassert() { PlayerManager localPlayerManager = NetworkContext.LocalPlayerManager; if ((Object)(object)localPlayerManager == (Object)null) { return; } FPSController fpsScript = localPlayerManager.fpsScript; InventoryManager inventoryMan = localPlayerManager.inventoryMan; foreach (PlayerInput item in _held) { Deactivate(item); } Silence(); if ((Object)(object)fpsScript != (Object)null) { if (_ourMove && !fpsScript.lockMove) { fpsScript.lockMove = true; } if (_ourCam && !fpsScript.lockCam) { fpsScript.lockCam = true; } } if (_ourInventory && !((Object)(object)inventoryMan == (Object)null)) { if (inventoryMan.canControlItem) { inventoryMan.canControlItem = false; } if (_ourHotbar && (Object)(object)inventoryMan.inventoryUIHolder != (Object)null && inventoryMan.inventoryUIHolder.activeSelf) { inventoryMan.inventoryUIHolder.SetActive(false); } } } private void Silence() { Keyboard current = Keyboard.current; if (current != null && ((InputDevice)current).enabled) { InputSystem.DisableDevice((InputDevice)(object)current, false); _ourKeyboard = true; } } private void Restore() { if (_ourKeyboard) { _ourKeyboard = false; Keyboard current = Keyboard.current; if (current != null && !((InputDevice)current).enabled) { InputSystem.EnableDevice((InputDevice)(object)current); } } } private static void Deactivate(PlayerInput input) { if (!((Object)(object)input == (Object)null) && input.inputIsActive) { input.DeactivateInput(); } } private static void Activate(PlayerInput input) { if (!((Object)(object)input == (Object)null) && !input.inputIsActive) { input.ActivateInput(); } } private static bool CanOpen() { PlayerManager localPlayerManager = NetworkContext.LocalPlayerManager; if ((Object)(object)localPlayerManager == (Object)null) { return false; } return !localPlayerManager.paused && !localPlayerManager.dead && !localPlayerManager.customizablesMenuOpen && !localPlayerManager.dontAllowLockCursor; } private static bool CanStayOpen() { PlayerManager localPlayerManager = NetworkContext.LocalPlayerManager; if ((Object)(object)localPlayerManager == (Object)null) { return false; } return !localPlayerManager.paused && !localPlayerManager.dead; } } internal sealed class ChatSync { private struct Incoming { public int Seq; public bool Typing; } private const string Key = "chatter"; private const string Version = "2"; private const char Separator = '|'; private const float PollInterval = 0.2f; private const float PublishInterval = 0.25f; private const int Fields = 4; private static readonly Encoding Wire = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); private readonly Dictionary _seen = new Dictionary(); private string _published; private string _text = string.Empty; private int _seq; private bool _typing; private bool _changed; private float _nextPoll; private float _nextPublish; private float _textExpiresAt; public event Action MessageArrived; public event Action TypingChanged; public void Send(string text) { _text = text; _seq++; _textExpiresAt = Time.unscaledTime + 8f; _changed = true; } public void SetTyping(bool typing) { if (_typing != typing) { _typing = typing; _changed = true; } } public void Tick() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) try { CSteamID lobby = NetworkContext.Lobby; if (lobby.m_SteamID != 0) { Expire(); Publish(lobby); if (!(Time.unscaledTime < _nextPoll)) { _nextPoll = Time.unscaledTime + 0.2f; Poll(lobby); } } } catch (Exception value) { ModLog.Error($"Sync: {value}"); } } public void Clear() { _seen.Clear(); _published = null; _text = string.Empty; _typing = false; _changed = false; } private void Expire() { if (_text.Length != 0 && !(Time.unscaledTime < _textExpiresAt)) { _text = string.Empty; _changed = true; } } private void Publish(CSteamID lobby) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (_changed && !(Time.unscaledTime < _nextPublish)) { string text = Serialise(); _changed = false; if (!(text == _published)) { SteamMatchmaking.SetLobbyMemberData(lobby, "chatter", text); _published = text; _nextPublish = Time.unscaledTime + 0.25f; } } } private string Serialise() { if (_text.Length == 0 && !_typing) { return string.Empty; } return string.Join('|'.ToString(), "2", _typing ? "1" : "0", _seq.ToString(CultureInfo.InvariantCulture), Convert.ToBase64String(Wire.GetBytes(_text))); } private void Poll(CSteamID lobby) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0058: 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) ulong localSteamId = NetworkContext.LocalSteamId; int numLobbyMembers = SteamMatchmaking.GetNumLobbyMembers(lobby); for (int i = 0; i < numLobbyMembers; i++) { CSteamID lobbyMemberByIndex = SteamMatchmaking.GetLobbyMemberByIndex(lobby, i); if (lobbyMemberByIndex.m_SteamID != localSteamId) { string lobbyMemberData = SteamMatchmaking.GetLobbyMemberData(lobby, lobbyMemberByIndex, "chatter"); if (string.IsNullOrEmpty(lobbyMemberData)) { Quiet(lobbyMemberByIndex.m_SteamID); } else { Read(lobbyMemberByIndex.m_SteamID, lobbyMemberData); } } } } private void Read(ulong owner, string raw) { string[] array = raw.Split('|'); if (array.Length != 4 || array[0] != "2" || !int.TryParse(array[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return; } bool flag = array[1] == "1"; if (Decode(array[3], out var text)) { Incoming value; bool flag2 = _seen.TryGetValue(owner, out value); if (!flag2 || value.Typing != flag) { this.TypingChanged?.Invoke(owner, flag); } bool flag3 = text.Length > 0 && (!flag2 || result > value.Seq); _seen[owner] = new Incoming { Seq = result, Typing = flag }; if (flag3) { this.MessageArrived?.Invoke(owner, text); } } } private void Quiet(ulong owner) { if (_seen.TryGetValue(owner, out var value) && value.Typing) { value.Typing = false; _seen[owner] = value; this.TypingChanged?.Invoke(owner, arg2: false); } } private static bool Decode(string encoded, out string text) { text = string.Empty; if (encoded.Length == 0) { return true; } try { text = Wire.GetString(Convert.FromBase64String(encoded)); return true; } catch { return false; } } } [BepInPlugin("clementinise.shiftatmidnight.chatter", "Chatter", "1.0.0")] public class ChatterPlugin : BasePlugin { public const string Guid = "clementinise.shiftatmidnight.chatter"; internal static ConfigEntry ChatKey; public const float BubbleLifetime = 8f; public override void Load() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) ModLog.Source = ((BasePlugin)this).Log; ChatKey = ((BasePlugin)this).Config.Bind("Chat", "ChatKey", (KeyCode)13, "Opens the chat box. Enter sends, Escape cancels."); InputBlock.Install(); ClassInjector.RegisterTypeInIl2Cpp(); ((BasePlugin)this).AddComponent(); ModLog.Msg($"Chatter ready. {ChatKey.Value} opens the chat box, {ChatKey.Value} again sends."); } } public class ChatterBehaviour : MonoBehaviour { private ChatSync _sync; private BubbleField _bubbles; private ChatBox _box; private DialogueHistory _log; private ChatComposer _composer; private int _lastSceneHandle; private bool _built; public ChatterBehaviour(IntPtr handle) : base(handle) { } private void Awake() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); _lastSceneHandle = ((Scene)(ref activeScene)).handle; } private void Update() { try { DetectSceneChange(); if (Build()) { _composer.Tick(); _sync.Tick(); _log.Tick(); } } catch (Exception value) { ModLog.Error($"Update: {value}"); } } private void LateUpdate() { try { if (_built) { _bubbles.LateTick(); _log.LateTick(); } } catch (Exception value) { ModLog.Error($"LateUpdate: {value}"); } } private bool Build() { if (_built) { return true; } if ((Object)(object)NetworkContext.LocalPlayerManager == (Object)null) { return false; } _sync = new ChatSync(); _bubbles = new BubbleField(); _box = new ChatBox(); _log = new DialogueHistory(); _composer = new ChatComposer(_sync, _box); _composer.Sent += Send; _sync.MessageArrived += Receive; _sync.TypingChanged += delegate(ulong owner, bool typing) { _bubbles.Typing(owner, typing); }; _built = true; return true; } private void Send(string message) { _sync.Send(message); _log.Add(PlayerRoster.Name(NetworkContext.LocalSteamId), message); } private void Receive(ulong owner, string message) { _bubbles.Say(owner, message); _log.Add(PlayerRoster.Name(owner), message); } private void DetectSceneChange() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); int handle = ((Scene)(ref activeScene)).handle; if (handle != _lastSceneHandle) { _lastSceneHandle = handle; Teardown(); } } private void OnDestroy() { try { Teardown(); } catch { } } private void Teardown() { if (_built) { _composer.Cancel(); _bubbles.Clear(); _sync.Clear(); _log.Close(); _box.Destroy(); } ChatCanvas.Destroy(); PlayerRoster.Clear(); GameFont.Rescan(); _sync = null; _bubbles = null; _box = null; _log = null; _composer = null; _built = false; } } internal sealed class DialogueHistory { private sealed class Line { public GameObject Object; public ChatLogNode Node; public string Who; public string What; public float ForwardUntil; public bool Forward; } private const int MostLines = 20; private const string ForwardParameter = "Show"; private readonly List _mine = new List(); private bool _warned; public void Add(string who, string what) { SpeakingManager instance = SpeakingManager.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.chatLogNode == (Object)null || (Object)(object)instance.chatLogNodeHolder == (Object)null) { WarnOnce(); return; } GameObject val = Object.Instantiate(instance.chatLogNode, instance.chatLogNodeHolder, false); ((Object)val).name = "ChatterLine"; ChatLogNode component = val.GetComponent(); if ((Object)(object)component != (Object)null) { if ((Object)(object)component.correct != (Object)null) { component.correct.SetActive(false); } if ((Object)(object)component.incorrect != (Object)null) { component.incorrect.SetActive(false); } if (instance.chatQueue != null) { instance.chatQueue.Enqueue(component); } } Line line = new Line { Object = val, Node = component, Who = who, What = what }; val.SetActive(true); line.Forward = Forward(component, forward: true); line.ForwardUntil = Time.unscaledTime + 8f; Write(line); _mine.Add(line); while (_mine.Count > 20) { Object.Destroy((Object)(object)_mine[0].Object); _mine.RemoveAt(0); } RectTransform chatContent = instance.chatContent; if ((Object)(object)chatContent != (Object)null) { LayoutRebuilder.ForceRebuildLayoutImmediate(chatContent); } } public void Tick() { SpeakingManager instance = SpeakingManager.Instance; if ((Object)(object)instance == (Object)null) { return; } for (int i = 0; i < _mine.Count; i++) { Line line = _mine[i]; if (line.Forward && !(Time.unscaledTime < line.ForwardUntil)) { line.Forward = Forward(line.Node, forward: false); } } } private static bool Forward(ChatLogNode node, bool forward) { if ((Object)(object)node == (Object)null || (Object)(object)node.anim == (Object)null) { return false; } node.anim.SetBool("Show", forward); return forward; } public void LateTick() { for (int i = 0; i < _mine.Count; i++) { Write(_mine[i]); } } private static void Write(Line line) { ChatLogNode node = line.Node; if ((Object)(object)node == (Object)null) { return; } if ((Object)(object)node.nameText != (Object)null && ((TMP_Text)node.nameText).text != line.Who) { ((TMP_Text)node.nameText).text = line.Who; } TextMeshProUGUI subtitleText = node.subtitleText; if (!((Object)(object)subtitleText == (Object)null)) { if (((TMP_Text)subtitleText).text != line.What) { ((TMP_Text)subtitleText).text = line.What; ((TMP_Text)subtitleText).richText = false; } if (((TMP_Text)subtitleText).maxVisibleCharacters != int.MaxValue) { ((TMP_Text)subtitleText).maxVisibleCharacters = int.MaxValue; } } } private void Forget() { foreach (Line item in _mine) { Object.Destroy((Object)(object)item.Object); } _mine.Clear(); } public void Close() { Forget(); } private void WarnOnce() { if (!_warned) { _warned = true; ModLog.Warning("SpeakingManager has no chat log to write into, so messages will only appear over people's heads."); } } } internal static class GameFont { private static TMP_FontAsset _asset; public static TMP_FontAsset Asset { get { if ((Object)(object)_asset != (Object)null) { return _asset; } SpeakingManager instance = SpeakingManager.Instance; if ((Object)(object)instance != (Object)null && (Object)(object)instance.subtitleText != (Object)null) { _asset = ((TMP_Text)instance.subtitleText).font; } if ((Object)(object)_asset == (Object)null) { PlayerManager localPlayerManager = NetworkContext.LocalPlayerManager; if ((Object)(object)localPlayerManager != (Object)null && (Object)(object)localPlayerManager.nameText != (Object)null) { _asset = ((TMP_Text)localPlayerManager.nameText).font; } } if ((Object)(object)_asset == (Object)null) { _asset = AnyLoaded(); } if ((Object)(object)_asset == (Object)null) { ModLog.Warning("No TMP font found, so text will fall back to TMP's default."); } return _asset; } } public static void Rescan() { _asset = null; } private static TMP_FontAsset AnyLoaded() { Il2CppArrayBase val = Object.FindObjectsOfType(); if (val == null) { return null; } for (int i = 0; i < val.Length; i++) { TextMeshProUGUI val2 = val[i]; if ((Object)(object)val2 != (Object)null && (Object)(object)((TMP_Text)val2).font != (Object)null) { return ((TMP_Text)val2).font; } } return null; } } internal static class InputBlock { private const string Id = "clementinise.shiftatmidnight.chatter.input"; public static bool FreezeHotbar; public static bool FreezeHistory; private static Harmony _harmony; public static void Install() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown if (_harmony != null) { return; } try { _harmony = new Harmony("clementinise.shiftatmidnight.chatter.input"); Refuse(AccessTools.Method(typeof(InventoryManager), "ChangeInventorySlot", (Type[])null, (Type[])null), "BeforeChangeSlot", "InventoryManager.ChangeInventorySlot", "the hotbar will stay live while typing"); Refuse(AccessTools.Method(typeof(SpeakingManager), "Update", (Type[])null, (Type[])null), "BeforeSpeakingUpdate", "SpeakingManager.Update", "T will keep opening the dialogue history while typing"); } catch (Exception ex) { ModLog.Warning("Could not install the input patches: " + ex.Message); _harmony = null; } } private static void Refuse(MethodBase target, string prefix, string what, string consequence) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown if (target == null) { ModLog.Warning(what + " not found - " + consequence + "."); } else { _harmony.Patch(target, new HarmonyMethod(AccessTools.Method(typeof(InputBlock), prefix, (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } public static void Remove() { FreezeHotbar = false; FreezeHistory = false; try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception ex) { ModLog.Warning("Could not unpatch the hotbar: " + ex.Message); } _harmony = null; } private static bool BeforeChangeSlot() { return !FreezeHotbar; } private static bool BeforeSpeakingUpdate() { return !FreezeHistory; } } internal static class ModLog { public static ManualLogSource Source; [Conditional("DEVTOOLS")] public static void Debug(string message) { ManualLogSource source = Source; if (source != null) { source.LogInfo((object)message); } } public static void Msg(string message) { ManualLogSource source = Source; if (source != null) { source.LogInfo((object)message); } } public static void Warning(string message) { ManualLogSource source = Source; if (source != null) { source.LogWarning((object)message); } } public static void Error(string message) { ManualLogSource source = Source; if (source != null) { source.LogError((object)message); } } } internal static class NetworkContext { public static NetworkRunner Runner { get { FusionNetworkManager instance = FusionNetworkManager.Instance; if ((Object)(object)instance == (Object)null) { return null; } NetworkRunner runner = instance._runner; if ((Object)(object)runner == (Object)null || !runner.IsRunning) { return null; } return runner; } } public static PlayerManager LocalPlayerManager { get { ClientPlayer instance = ClientPlayer.Instance; return ((Object)(object)instance == (Object)null) ? null : instance.playerMan; } } public static ThirdPersonManager LocalBody { get { PlayerManager localPlayerManager = LocalPlayerManager; return ((Object)(object)localPlayerManager == (Object)null) ? null : localPlayerManager.thirdPersonMan; } } public static Camera LocalCamera { get { PlayerManager localPlayerManager = LocalPlayerManager; if ((Object)(object)localPlayerManager != (Object)null) { FPSController fpsScript = localPlayerManager.fpsScript; if ((Object)(object)fpsScript != (Object)null && (Object)(object)fpsScript.playerCamera != (Object)null) { return fpsScript.playerCamera; } InteractManager interactMan = localPlayerManager.interactMan; if ((Object)(object)interactMan != (Object)null && (Object)(object)interactMan.mainCamera != (Object)null) { return interactMan.mainCamera; } } return Camera.main; } } public static ulong LocalSteamId { get { //IL_0002: Unknown result type (might be due to invalid IL or missing references) try { return SteamUser.GetSteamID().m_SteamID; } catch { return 0uL; } } } public static CSteamID Lobby { get { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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) //IL_0037: Unknown result type (might be due to invalid IL or missing references) PlatformManager instance = PlatformManager.Instance; if ((Object)(object)instance == (Object)null) { return default(CSteamID); } return new CSteamID { m_SteamID = instance._lobbyID }; } } } internal static class PlayerRoster { private const float ScanInterval = 1f; private static readonly Dictionary _bodies = new Dictionary(); private static readonly Dictionary _names = new Dictionary(); private static float _nextScan; private static string _lastShape; public static PlayerManager Body(ulong steamId) { Scan(); if (!_bodies.TryGetValue(steamId, out var value)) { return null; } return ((Object)(object)value == (Object)null) ? null : value; } public static string Name(ulong steamId) { //IL_002e: 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) Scan(); if (_names.TryGetValue(steamId, out var value) && !string.IsNullOrEmpty(value)) { return value; } try { string friendPersonaName = SteamFriends.GetFriendPersonaName(new CSteamID { m_SteamID = steamId }); if (!string.IsNullOrEmpty(friendPersonaName)) { return friendPersonaName; } } catch { } return "Player"; } public static Transform Head(PlayerManager body) { if ((Object)(object)body == (Object)null) { return null; } ThirdPersonManager thirdPersonMan = body.thirdPersonMan; if ((Object)(object)thirdPersonMan != (Object)null && (Object)(object)thirdPersonMan.nameCanvas != (Object)null) { return thirdPersonMan.nameCanvas.transform; } return ((Component)body).transform; } public static void Clear() { _bodies.Clear(); _names.Clear(); _nextScan = 0f; _lastShape = null; } private static void Scan() { if (Time.unscaledTime < _nextScan) { return; } _nextScan = Time.unscaledTime + 1f; _bodies.Clear(); PlatformManager instance = PlatformManager.Instance; if ((Object)(object)instance == (Object)null) { return; } ReadNames(instance); Il2CppArrayBase val = Object.FindObjectsOfType(); if (val == null) { return; } PlayerManager localPlayerManager = NetworkContext.LocalPlayerManager; for (int i = 0; i < val.Length; i++) { PlayerManager val2 = val[i]; if (!((Object)(object)val2 == (Object)null) && Identify(instance, val2, localPlayerManager, out var steamId)) { _bodies[steamId] = val2; } } string text = $"{val.Length}/{_bodies.Count}/{_names.Count}"; if (!(text == _lastShape)) { _lastShape = text; } } private static bool Identify(PlatformManager platform, PlayerManager body, PlayerManager local, out ulong steamId) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) NetworkObject val = ((SimulationBehaviour)body).Object; if ((Object)(object)val != (Object)null) { PlayerRef inputAuthority = val.InputAuthority; if (Parse(platform.GetPlayerUIDForFusionPlayerId(((PlayerRef)(ref inputAuthority)).PlayerId), out steamId)) { return true; } } if ((Object)(object)body == (Object)(object)local && Parse(platform.GetPlayerUID(), out steamId)) { return true; } return ByName(body.playerName, out steamId); } private static bool ByName(string playerName, out ulong steamId) { steamId = 0uL; if (string.IsNullOrEmpty(playerName)) { return false; } foreach (KeyValuePair name in _names) { if (name.Value != playerName) { continue; } steamId = name.Key; return true; } return false; } private static bool Parse(string uid, out ulong steamId) { steamId = 0uL; if (string.IsNullOrEmpty(uid)) { return false; } return ulong.TryParse(uid, NumberStyles.Integer, CultureInfo.InvariantCulture, out steamId); } private static void ReadNames(PlatformManager platform) { List lobbyMembers = platform.GetLobbyMembers(); if (lobbyMembers == null) { return; } for (int i = 0; i < lobbyMembers.Count; i++) { NetPlayerData val = lobbyMembers[i]; if (val != null && !string.IsNullOrEmpty(val._UID) && ulong.TryParse(val._UID, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { _names[result] = val._Gamertag; } } } } }