Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Socialize v1.0.16
Landoria.Socialize.dll
Decompiled 4 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using BepInEx; using BepInEx.Logging; using HarmonyLib; using Landoria.SharedLib; using Splatform; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Landoria.Socialize")] [assembly: AssemblyDescription("Adds session-based groups and expanded chat channels to Valheim.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Landoria")] [assembly: AssemblyProduct("Landoria.Socialize")] [assembly: AssemblyCopyright("Copyright © 2026 End3rbyte")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("DA5DE9DF-7727-4D5F-8274-896B8EB80ED3")] [assembly: AssemblyFileVersion("1.0.16")] [assembly: AssemblyInformationalVersion("1.0.16")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = "")] [assembly: AssemblyVersion("1.0.16.13989")] namespace Landoria.Socialize { internal static class ChatChannelState { private static PersistentChatChannel current; private static string whisperTarget = ""; private static bool redirecting; internal static void SetNormal() { current = PersistentChatChannel.Normal; whisperTarget = ""; } internal static void SetShout() { current = PersistentChatChannel.Shout; whisperTarget = ""; } internal static void SetWhisper(string target) { current = PersistentChatChannel.Whisper; whisperTarget = target ?? ""; } internal static void SetGroup() { current = PersistentChatChannel.Group; whisperTarget = ""; } internal static string GetPrompt() { return ChatBehaviorPolicy.GetPrompt(current, whisperTarget); } internal static bool TryRedirect(string text) { if (!ChatBehaviorPolicy.ShouldRedirect(current, redirecting, text)) { return false; } redirecting = true; try { return Send(text); } finally { redirecting = false; } } private static bool Send(string text) { switch (current) { case PersistentChatChannel.Shout: SocialChatSender.SendShout(text); return true; case PersistentChatChannel.Whisper: return PrivateChat.Send(whisperTarget, text, (Terminal)(object)Chat.instance); case PersistentChatChannel.Group: GroupService.SendChat(text); return true; default: return false; } } } internal static class SocialChatSender { internal static void SendShout(string text) { Talker val = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Component)Player.m_localPlayer).GetComponent<Talker>() : null); if (!((Object)(object)val == (Object)null)) { val.Say((Type)2, text); } } internal static void ApplyRanges(Talker talker) { talker.m_normalDistance = SocializePlugin.Settings.SayDistance; talker.m_shoutDistance = SocializePlugin.Settings.ShoutDistance; } internal static void ApplyRangesToLoadedTalkers() { if (SocializePlugin.Settings != null) { Talker[] array = Object.FindObjectsByType<Talker>((FindObjectsSortMode)0); for (int i = 0; i < array.Length; i++) { ApplyRanges(array[i]); } } } } internal enum PersistentChatChannel { Normal, Shout, Whisper, Group } internal static class ChatBehaviorPolicy { internal static string GetPrompt(PersistentChatChannel channel, string whisperTarget) { return channel switch { PersistentChatChannel.Shout => "Shouting...", PersistentChatChannel.Whisper => "Talking to " + whisperTarget + "...", PersistentChatChannel.Group => "Speaking to the group...", _ => "Speaking...", }; } internal static bool ShouldRedirect(PersistentChatChannel channel, bool redirecting, string text) { if (!redirecting && channel != PersistentChatChannel.Normal) { return !string.IsNullOrWhiteSpace(text); } return false; } } internal static class PrivateChatPolicy { internal static GroupDecision CanSend(bool targetFound, bool isLocalPlayer, string targetName) { if (!targetFound) { return GroupDecision.Deny("No connected player named \"" + targetName + "\" was found."); } if (!isLocalPlayer) { return GroupDecision.Allow(); } return GroupDecision.Deny("You cannot whisper yourself."); } } internal static class TargetPingPolicy { internal static GroupDecision CanSend(bool targetFound, bool clientReady, string targetName) { if (!(targetFound && clientReady)) { return GroupDecision.Deny("No connected player named \"" + targetName + "\" was found."); } return GroupDecision.Allow(); } } internal static class ChatCommandParser { internal static bool TryParseTarget(string fullLine, out string target, out string message) { target = ""; message = ""; if (string.IsNullOrEmpty(fullLine)) { return false; } int num = fullLine.IndexOf(' '); if (num < 0) { return false; } int num2 = fullLine.IndexOf(' ', num + 1); string text = ((num2 >= 0) ? fullLine.Substring(num + 1, num2 - num - 1) : fullLine.Substring(num + 1)); target = text.Trim(); if (target.StartsWith("@", StringComparison.Ordinal)) { target = target.Substring(1); } message = ((num2 >= 0) ? fullLine.Substring(num2 + 1) : ""); if (!string.IsNullOrWhiteSpace(target)) { return !string.IsNullOrWhiteSpace(message); } return false; } internal static bool IsValidGroupAction(string action, string argument) { switch (action) { case "leave": case "info": return string.IsNullOrEmpty(argument); case "invite": case "remove": case "promote": return !string.IsNullOrEmpty(argument); default: return false; } } } internal static class ChatCommands { internal static void Register() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown Register("sh", new ConsoleEventFailable(SendShout)); Register("shout", new ConsoleEventFailable(SendShout)); Register("s", new ConsoleEventFailable(SendSay)); Register("say", new ConsoleEventFailable(SendSay)); Register("w", new ConsoleEventFailable(SendWhisper)); Register("wping", new ConsoleEventFailable(SendTargetPing)); } private static void Register(string name, ConsoleEventFailable handler) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) new ConsoleCommand(name, GetDescription(name), handler, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } private static string GetDescription(string name) { if (name == "w") { return "[player] [message] sends a private message"; } if (name == "wping") { return "[player] [message] sends a private message with a ping"; } return "[message] " + ((name == "s" || name == "say") ? "says something to nearby players" : "shouts so everyone around you can hear you"); } private static object SendShout(ConsoleEventArgs args) { if (!TryGetMessage(args, out var message)) { args.Context.AddString("Usage: /sh message"); return true; } ChatChannelState.SetShout(); SocialChatSender.SendShout(message); return true; } private static object SendSay(ConsoleEventArgs args) { if (!TryGetMessage(args, out var message)) { args.Context.AddString("Usage: /s message"); return true; } ChatChannelState.SetNormal(); Chat.instance.SendText((Type)1, message); return true; } private static object SendWhisper(ConsoleEventArgs args) { if (!ChatCommandParser.TryParseTarget(args.FullLine, out var target, out var message)) { args.Context.AddString("Usage: /w PlayerName message"); return true; } if (!PrivateChat.Send(target, message, args.Context)) { return true; } ChatChannelState.SetWhisper(target); return true; } private static object SendTargetPing(ConsoleEventArgs args) { if (!ChatCommandParser.TryParseTarget(args.FullLine, out var target, out var message)) { args.Context.AddString("Usage: /wping PlayerName message"); return true; } TargetPingService.Send(target, message, args.Context); return true; } private static bool TryGetMessage(ConsoleEventArgs args, out string message) { message = (args.ArgsAll ?? "").Trim(); if ((Object)(object)Chat.instance != (Object)null) { return !string.IsNullOrEmpty(message); } return false; } } internal static class PrivateChat { private sealed class PendingMessage { internal long TargetPeer; internal string TargetName; internal string Message; internal Terminal Context; internal float SentAt; } private const string MessageRpc = "Landoria_Social_PrivateMessage"; private const string ReceiptRpc = "Landoria_Social_PrivateReceipt"; private const float ReceiptTimeoutSeconds = 15f; private static readonly Dictionary<string, PendingMessage> Pending = new Dictionary<string, PendingMessage>(); private static ZRoutedRpc registeredRpc; internal static void Update() { EnsureRpcs(); if (Pending.Count == 0) { return; } List<string> list = null; foreach (KeyValuePair<string, PendingMessage> item in Pending) { if (!(Time.realtimeSinceStartup - item.Value.SentAt < 15f)) { (list ?? (list = new List<string>())).Add(item.Key); } } if (list == null) { return; } foreach (string item2 in list) { PendingMessage pendingMessage = Pending[item2]; Pending.Remove(item2); SocializePlugin.Log.LogWarning("Private message [" + item2 + "] to '" + pendingMessage.TargetName + "' timed out waiting for a delivery receipt."); Terminal context = pendingMessage.Context; if (context != null) { context.AddString("Private message to " + pendingMessage.TargetName + " was not confirmed."); } } } internal static void Reset() { Pending.Clear(); registeredRpc = null; } internal static bool Send(string targetName, string message, Terminal context) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) PlayerInfo target; bool num = TryFindPlayer(targetName, out target); GroupDecision groupDecision = PrivateChatPolicy.CanSend(num, num && IsLocalPlayer(target), targetName); if (!groupDecision.Allowed) { Terminal obj = context; if (obj != null) { obj.AddString(groupDecision.Message); } return false; } if (!PrivilegeResultExtentions.IsGranted(PlatformManager.DistributionPlatform.PrivilegeProvider.CheckPrivilege((Privilege)3))) { SocializePlugin.Log.LogWarning("Private message to '" + target.m_name + "' blocked by the sender's text privilege."); Terminal obj2 = context; if (obj2 != null) { obj2.AddString("Text communication is not permitted for this account."); } return false; } EnsureRpcs(); SocializePlugin.Log.LogInfo($"Checking sender permission for private message to '{target.m_name}' (peer={((ZDOID)(ref target.m_characterID)).UserID})."); TextPermissionService.Check(target.m_userInfo.m_id, isSender: true, delegate(RelationsManagerPermissionResult result) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) CompleteSendPermission(target, message, context, result); }); return true; } private static void CompleteSendPermission(PlayerInfo target, string message, Terminal context, RelationsManagerPermissionResult result) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: 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_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Invalid comparison between Unknown and I4 //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: 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) SocializePlugin.Log.LogInfo($"Sender permission result for private message to '{target.m_name}': {result}."); if (!RelationsManagerPermissionResultExtentions.IsGranted(result)) { if (context != null) { context.AddString("Private message to " + target.m_name + " is not permitted."); } return; } UserInfo val = default(UserInfo); string text = default(string); Chat.GetChatMessageData(message, (int)result == 1, ref val, ref text); string text2 = Guid.NewGuid().ToString("N").Substring(0, 12); long userID = ((ZDOID)(ref target.m_characterID)).UserID; Pending[text2] = new PendingMessage { TargetPeer = userID, TargetName = target.m_name, Message = text, Context = context, SentAt = Time.realtimeSinceStartup }; SocializePlugin.Log.LogInfo($"Sending private message [{text2}] to '{target.m_name}' (peer={userID}, length={text.Length})."); ZRoutedRpc.instance.InvokeRoutedRPC(userID, "Landoria_Social_PrivateMessage", new object[3] { text2, val, text }); } private static void EnsureRpcs() { RpcRegistry.RegisterIfChanged(ref registeredRpc, RegisterRpcs); } private static void RegisterRpcs(ZRoutedRpc rpc) { rpc.Register<string, UserInfo, string>("Landoria_Social_PrivateMessage", (Action<long, string, UserInfo, string>)RPC_PrivateMessage); rpc.Register<string, int>("Landoria_Social_PrivateReceipt", (Action<long, string, int>)RPC_PrivateReceipt); SocializePlugin.Log.LogDebug("Private message RPCs registered."); } private static void RPC_PrivateMessage(long sender, string requestId, UserInfo user, string message) { //IL_00e5: Unknown result type (might be due to invalid IL or missing references) if (!IsExpectedUser(sender, user)) { SocializePlugin.Log.LogWarning($"Private message [{requestId}] rejected because peer={sender} supplied an invalid identity."); ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(sender, "Landoria_Social_PrivateReceipt", new object[2] { requestId, 3 }); } } else { SocializePlugin.Log.LogInfo($"Private message [{requestId}] received from peer={sender} (user='{user.GetDisplayName()}', length={message.Length}); checking text permission."); TextPermissionService.Check(user.UserId, isSender: false, delegate(RelationsManagerPermissionResult result) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) CompleteReceive(sender, requestId, user, message, result); }); } } private static void CompleteReceive(long sender, string requestId, UserInfo user, string message, RelationsManagerPermissionResult result) { //IL_0011: 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_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Invalid comparison between Unknown and I4 //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected I4, but got Unknown //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Expected I4, but got Unknown SocializePlugin.Log.LogInfo($"Private message [{requestId}] permission result for peer={sender}: {result}."); if (!RelationsManagerPermissionResultExtentions.IsGranted(result)) { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(sender, "Landoria_Social_PrivateReceipt", new object[2] { requestId, (int)result }); } return; } if ((Object)(object)Chat.instance == (Object)null) { SocializePlugin.Log.LogWarning("Private message [" + requestId + "] cannot be displayed because Chat is unavailable."); ZRoutedRpc instance2 = ZRoutedRpc.instance; if (instance2 != null) { instance2.InvokeRoutedRPC(sender, "Landoria_Social_PrivateReceipt", new object[2] { requestId, 3 }); } return; } string text = message.Replace('<', ' ').Replace('>', ' '); if ((int)result == 1) { CensorShittyWords.Filter(text, ref text); } ChatFormatting.AddPrivate((Terminal)(object)Chat.instance, user.GetDisplayName(), text, timestamp: false); SocializePlugin.Log.LogInfo($"Private message [{requestId}] displayed; sending delivery receipt to peer={sender}."); ZRoutedRpc instance3 = ZRoutedRpc.instance; if (instance3 != null) { instance3.InvokeRoutedRPC(sender, "Landoria_Social_PrivateReceipt", new object[2] { requestId, (int)result }); } } private static void RPC_PrivateReceipt(long sender, string requestId, int resultValue) { //IL_0001: 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_006f: 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) RelationsManagerPermissionResult val = (RelationsManagerPermissionResult)resultValue; if (!Pending.TryGetValue(requestId, out var value)) { SocializePlugin.Log.LogWarning($"Ignoring unknown private message receipt [{requestId}] from peer={sender}, result={val}."); return; } if (value.TargetPeer != sender) { SocializePlugin.Log.LogWarning($"Ignoring private message receipt [{requestId}] from unexpected peer={sender}; expected={value.TargetPeer}."); return; } Pending.Remove(requestId); if (!RelationsManagerPermissionResultExtentions.IsGranted(val)) { SocializePlugin.Log.LogWarning($"Private message [{requestId}] to '{value.TargetName}' was rejected: {val}."); Terminal context = value.Context; if (context != null) { context.AddString("Private message to " + value.TargetName + " was not delivered."); } } else { string name = Game.instance.GetPlayerProfile().GetName(); ChatFormatting.AddPrivate(value.Context, name, "to " + value.TargetName + ": " + value.Message, timestamp: false); SocializePlugin.Log.LogInfo("Private message [" + requestId + "] to '" + value.TargetName + "' confirmed and shown to the sender."); } } private static bool TryFindPlayer(string name, out PlayerInfo player) { //IL_0014: 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_001a: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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) //IL_004e: Unknown result type (might be due to invalid IL or missing references) foreach (PlayerInfo player2 in ZNet.instance.GetPlayerList()) { if (string.Equals(player2.m_name, name, StringComparison.OrdinalIgnoreCase)) { player = player2; return true; } } player = default(PlayerInfo); return false; } private static bool IsLocalPlayer(PlayerInfo player) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance != (Object)null) { long userID = ((ZDOID)(ref player.m_characterID)).UserID; ZDOID localPlayerCharacterID = ZNet.instance.LocalPlayerCharacterID; return userID == ((ZDOID)(ref localPlayerCharacterID)).UserID; } return false; } private unsafe static bool IsExpectedUser(long sender, UserInfo user) { //IL_0026: 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) //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_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) if (user == null || (Object)(object)ZNet.instance == (Object)null) { return false; } foreach (PlayerInfo player in ZNet.instance.GetPlayerList()) { ZDOID characterID = player.m_characterID; if (((ZDOID)(ref characterID)).UserID == sender) { int result; if (player.m_name == user.Name) { PlatformUserID id = player.m_userInfo.m_id; result = ((((object)(*(PlatformUserID*)(&id))/*cast due to .constrained prefix*/).ToString() == ((object)Unsafe.As<PlatformUserID, PlatformUserID>(ref user.UserId)/*cast due to .constrained prefix*/).ToString()) ? 1 : 0); } else { result = 0; } return (byte)result != 0; } } return false; } } internal static class ChatFormatting { internal static string FormatGroup(string sender, string message) { return ChatFormattingPolicy.FormatGroup(sender, message); } internal static void AddPrivate(Terminal terminal, string user, string text, bool timestamp) { terminal.AddString(GetTimestamp(timestamp) + ChatFormattingPolicy.FormatPrivate(user, text)); } internal static void AddShout(Terminal terminal, string user, string text, bool timestamp) { terminal.AddString(GetTimestamp(timestamp) + ChatFormattingPolicy.FormatShout(user, text)); } internal static void AddPing(Terminal terminal, string user, string target, string message) { terminal.AddString(ChatFormattingPolicy.FormatPing(user, target, message)); } internal unsafe static string GetPlayerName(PlatformUserID user) { //IL_0000: 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) PlayerInfo val = default(PlayerInfo); if (!ZNet.TryGetPlayerByPlatformUserID(user, ref val)) { return ((object)(*(PlatformUserID*)(&user))/*cast due to .constrained prefix*/).ToString(); } return val.m_name; } private static string GetTimestamp(bool enabled) { if (!enabled) { return ""; } return "[" + DateTime.Now.ToString("MM-dd-yyyy HH:mm:ss") + "] "; } } internal static class ChatFormattingPolicy { private const string GroupColor = "#4A90E2"; private const string PrivateColor = "#2FAE5F"; private const string ShoutColor = "#FFFF00"; internal static string FormatGroup(string sender, string message) { return "<color=#4A90E2>" + sender + ": " + message + "</color>"; } internal static string FormatPrivate(string user, string text) { string text2 = ((text ?? "").StartsWith("to ", StringComparison.OrdinalIgnoreCase) ? " " : ": "); return "<color=#2FAE5F>" + user + text2 + text + "</color>"; } internal static string FormatShout(string user, string text) { return "<color=orange>" + user + "</color>: <color=#FFFF00>" + text + "</color>"; } internal static string FormatArrival(string playerName, string message) { return "<color=orange>" + SecurityElement.Escape(playerName) + "</color><color=white>: </color><color=#FFFF00>" + SecurityElement.Escape(message) + "</color>"; } internal static string FormatPing(string user, string target, string message) { string text = (string.IsNullOrEmpty(target) ? ": " : (" to " + target + ": ")); return "<color=#2FAE5F>" + user + text + "</color><color=#FFFF00>((Ping))</color><color=#2FAE5F> " + message + "</color>"; } } [HarmonyPatch(typeof(Terminal), "InitTerminal")] internal static class RegisterSocialCommandsPatch { private static bool registered; private static void Postfix() { if (!registered) { registered = true; ChatCommands.Register(); GroupCommands.Register(); } } } [HarmonyPatch(typeof(Player), "OnSpawned")] internal static class RequestSocialStateOnSpawnPatch { private static void Postfix(Player __instance) { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer) { GroupService.RequestInitialState(); } } } [HarmonyPatch(typeof(Chat), "AddInworldText")] internal static class DisablePrivateWorldTextPatch { private static bool Prefix(Type type) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 return (int)type > 0; } } [HarmonyPatch(typeof(Chat), "SendPing")] internal static class LimitMapPingToGroupPatch { private static bool Prefix(Vector3 position) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (!SocializePlugin.Settings.RestrictPublicPings) { return true; } if (!MapSharingPolicy.CanSendPublicPing(restricted: true, GroupService.IsLocalPlayerInGroup())) { SocializePlugin.Log.LogDebug("Map ping ignored because the local player is not in a group."); return false; } GroupPingSender.Send(position); return false; } } internal static class GroupPingSender { internal static void Send(Vector3 position) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Player.m_localPlayer == (Object)null) && !((Object)(object)ZNet.instance == (Object)null) && ZRoutedRpc.instance != null) { position.y = ((Component)Player.m_localPlayer).transform.position.y; ZRoutedRpc.instance.InvokeRoutedRPC("Landoria_Social_GroupPingRequest", new object[2] { position, UserInfo.GetLocalUser() }); } } } [HarmonyPatch(typeof(Chat), "SendInput")] internal static class PersistentChatInputPatch { private static bool Prefix(Chat __instance) { if ((Object)(object)((Terminal)__instance).m_input == (Object)null || string.IsNullOrWhiteSpace(((TMP_InputField)((Terminal)__instance).m_input).text) || ((TMP_InputField)((Terminal)__instance).m_input).text.StartsWith("/") || !ChatChannelState.TryRedirect(((TMP_InputField)((Terminal)__instance).m_input).text)) { return true; } ((TMP_InputField)((Terminal)__instance).m_input).text = ""; __instance.Hide(); return false; } } [HarmonyPatch(typeof(Chat), "SendText")] internal static class PersistentChatChannelPatch { private static bool Prefix(Type type, string text) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Invalid comparison between Unknown and I4 if ((int)type == 2) { if (text == Localization.instance.Localize("$text_player_arrived")) { return false; } SocialChatSender.SendShout(text); return false; } if ((int)type == 1) { return !ChatChannelState.TryRedirect(text); } return true; } } [HarmonyPatch(typeof(Talker), "Awake")] internal static class SocialChatRangePatch { private static void Postfix(Talker __instance) { SocialChatSender.ApplyRanges(__instance); } } [HarmonyPatch(typeof(Chat), "Update")] internal static class ChatPresentationPatch { private static Chat owner; private static TMP_Text placeholder; private static float showUntil; private static void Postfix(Chat __instance) { if ((Object)(object)owner != (Object)(object)__instance) { owner = __instance; placeholder = null; } EnsurePlaceholder(__instance); if ((Object)(object)placeholder != (Object)null) { placeholder.text = ChatChannelState.GetPrompt(); } if ((Object)(object)((Terminal)__instance).m_chatWindow != (Object)null && Time.time < showUntil) { ((Component)((Terminal)__instance).m_chatWindow).gameObject.SetActive(true); } } internal static void Show(Terminal terminal) { Chat val = (Chat)(object)((terminal is Chat) ? terminal : null); if (!((Object)(object)val == (Object)null) && !val.HasFocus() && !((Object)(object)((Terminal)val).m_chatWindow == (Object)null)) { ((Component)((Terminal)val).m_chatWindow).gameObject.SetActive(true); showUntil = Time.time + val.m_hideDelay; } } private static void EnsurePlaceholder(Chat chat) { if (!((Object)(object)placeholder != (Object)null) && !((Object)(object)((Terminal)chat).m_input == (Object)null)) { TMP_InputField val = ((Component)((Terminal)chat).m_input).GetComponent<TMP_InputField>() ?? ((Component)((Terminal)chat).m_input).GetComponentInChildren<TMP_InputField>(true); placeholder = (TMP_Text)(((Object)(object)val != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null); } } } [HarmonyPatch(typeof(Terminal), "AddString", new Type[] { typeof(string) })] internal static class AutoDisplaySimpleChatPatch { private static void Postfix(Terminal __instance) { ChatPresentationPatch.Show(__instance); } } [HarmonyPatch(typeof(Terminal), "AddString", new Type[] { typeof(string), typeof(string), typeof(Type), typeof(bool) })] internal static class FormatTitleChatPatch { private static bool Prefix(Terminal __instance, string title, string text, Type type, bool timestamp) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 if ((int)type == 0) { ChatFormatting.AddPrivate(__instance, title, text, timestamp); return false; } if ((int)type == 2) { ChatFormatting.AddShout(__instance, title, text, timestamp); return false; } return true; } private static void Postfix(Terminal __instance) { ChatPresentationPatch.Show(__instance); } } [HarmonyPatch(typeof(Terminal), "AddString", new Type[] { typeof(PlatformUserID), typeof(string), typeof(Type), typeof(bool) })] internal static class FormatUserChatPatch { private static bool Prefix(Terminal __instance, PlatformUserID user, string text, Type type, bool timestamp) { //IL_0000: 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_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Invalid comparison between Unknown and I4 //IL_002d: Unknown result type (might be due to invalid IL or missing references) string playerName = ChatFormatting.GetPlayerName(user); if ((int)type == 0) { ChatFormatting.AddPrivate(__instance, playerName, text, timestamp); return false; } if ((int)type == 2) { ChatFormatting.AddShout(__instance, playerName, text, timestamp); return false; } if ((int)type == 1) { __instance.AddString(playerName, text, type, timestamp); return false; } return true; } private static void Postfix(Terminal __instance) { ChatPresentationPatch.Show(__instance); } } internal static class GroupCommands { internal static void Register() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_0020: 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) //IL_004b: Expected O, but got Unknown //IL_0046: Unknown result type (might be due to invalid IL or missing references) new ConsoleCommand("group", "[help|invite|leave|remove|promote|info] manages your group", new ConsoleEvent(HandleGroup), false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); new ConsoleCommand("g", "[message] sends a message to your group.", new ConsoleEvent(HandleGroupChat), false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } private static void HandleGroup(ConsoleEventArgs args) { string text = (args.ArgsAll ?? "").Trim(); int num = text.IndexOf(' '); string text2 = ((num < 0) ? text.ToLowerInvariant() : text.Substring(0, num).ToLowerInvariant()); string argument = ((num < 0) ? "" : text.Substring(num + 1).Trim()); if (text2 == "help" || string.IsNullOrEmpty(text2)) { ShowHelp(args.Context); } else if (!ChatCommandParser.IsValidGroupAction(text2, argument)) { ShowHelp(args.Context); } else { GroupService.SendRequest(text2, argument); } } private static void HandleGroupChat(ConsoleEventArgs args) { string text = (args.ArgsAll ?? "").Trim(); if (string.IsNullOrEmpty(text)) { args.Context.AddString("Usage: /g message"); return; } ChatChannelState.SetGroup(); GroupService.SendChat(text); } private static void ShowHelp(Terminal context) { context.AddString("/group invite <PlayerName> - Invites a connected player."); context.AddString("/group leave - Leaves your group."); context.AddString("/group remove <PlayerName> - Removes a member."); context.AddString("/group promote <PlayerName> - Promotes a member."); context.AddString("/group info - Lists group members."); context.AddString("/g <message> - Sends a group message."); } } internal sealed class GroupChatResult { internal bool Broadcast { get; } internal string Message { get; } internal GroupChatResult(bool broadcast, string message) { Broadcast = broadcast; Message = message; } } internal static class GroupChatPolicy { internal static GroupChatResult Prepare(SocialGroup group, long actor, string message, Func<long, bool> isOnline, Func<string, string, string> format) { if (group == null) { return Reject("You are not in a group."); } foreach (long key in group.Members.Keys) { if (key != actor && isOnline(key)) { return new GroupChatResult(broadcast: true, format(group.Members[actor], message)); } } return Reject("No other group member is connected."); } private static GroupChatResult Reject(string message) { return new GroupChatResult(broadcast: false, message); } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] internal static class GroupNewConnectionPatch { private static void Prefix(ZNetPeer peer) { if (peer != null) { GroupService.BeginPeerSession(peer.m_uid); } } } [HarmonyPatch(typeof(ZNet), "Disconnect")] internal static class GroupDisconnectPatch { private static void Prefix(ZNetPeer peer) { if (peer != null) { GroupService.DisconnectPeer(peer.m_uid); } } } internal sealed class GroupAcceptanceResult { internal bool Accepted { get; } internal SocialGroup Group { get; } internal string Message { get; } internal GroupAcceptanceResult(bool accepted, SocialGroup group, string message) { Accepted = accepted; Group = group; Message = message; } } internal static class GroupAcceptancePolicy { internal static GroupAcceptanceResult Accept(long playerId, string playerName, string inviterText, IDictionary<long, long> invitations, Func<long, SocialGroup> getOrCreateGroup, IDictionary<long, int> playerGroups) { if (!long.TryParse(inviterText, out var result) || !invitations.TryGetValue(playerId, out var value) || value != result) { return Reject("That group invitation is no longer valid."); } SocialGroup socialGroup = getOrCreateGroup(result); if (socialGroup == null || socialGroup.Members.Count >= 5) { return Reject("That group is no longer available."); } socialGroup.AddMember(playerId, playerName); playerGroups[playerId] = socialGroup.Id; invitations.Remove(playerId); return new GroupAcceptanceResult(accepted: true, socialGroup, null); } private static GroupAcceptanceResult Reject(string message) { return new GroupAcceptanceResult(accepted: false, null, message); } } internal static class GroupInfoPolicy { internal static string Build(SocialGroup group, Func<long, bool> isOnline) { if (group == null) { return "You are not in a group."; } List<string> list = new List<string> { "Group members:" }; foreach (KeyValuePair<long, string> member in group.Members) { string text = (isOnline(member.Key) ? "Connected" : "Disconnected"); string text2 = ((member.Key == group.Leader) ? " - Group Leader" : ""); list.Add(member.Value + " - " + text + text2); } return string.Join("\n", list); } } internal static class GroupInvitationPolicy { internal static GroupDecision TryInvite(SocialGroup group, long inviter, long target, bool targetAlreadyGrouped, IDictionary<long, long> invitations) { GroupDecision groupDecision = GroupPolicy.CanInvite(group, inviter, target, targetAlreadyGrouped); if (groupDecision.Allowed) { invitations[target] = inviter; } return groupDecision; } } internal sealed class GroupRemovalResult { internal bool Disbanded; internal long NewLeader; internal readonly List<long> RemainingMembers = new List<long>(); } internal static class GroupLifecyclePolicy { internal static GroupRemovalResult Remove(SocialGroup group, long playerId) { group.RemoveMember(playerId); GroupRemovalResult groupRemovalResult = new GroupRemovalResult(); if (group.Members.Count <= 1) { groupRemovalResult.Disbanded = true; groupRemovalResult.RemainingMembers.AddRange(group.Members.Keys); group.Members.Clear(); return groupRemovalResult; } if (!group.Members.ContainsKey(group.Leader)) { group.Leader = group.GetOldestMember(); } groupRemovalResult.NewLeader = group.Leader; groupRemovalResult.RemainingMembers.AddRange(group.Members.Keys); return groupRemovalResult; } } internal static class GroupPromotionPolicy { internal static GroupDecision TryPromote(SocialGroup group, long actor, long target, string targetName) { GroupDecision groupDecision = GroupPolicy.CanTargetMember(group, actor, target, targetName); if (!groupDecision.Allowed) { return groupDecision; } groupDecision = GroupPolicy.CanPromote(actor, target); if (groupDecision.Allowed) { group.Leader = target; } return groupDecision; } } internal static class GroupState { internal static readonly Dictionary<int, SocialGroup> Groups = new Dictionary<int, SocialGroup>(); internal static readonly Dictionary<long, int> PlayerGroups = new Dictionary<long, int>(); internal static readonly Dictionary<long, long> PeerPlayers = new Dictionary<long, long>(); internal static readonly Dictionary<long, long> Invitations = new Dictionary<long, long>(); internal static readonly HashSet<long> LocalMembers = new HashSet<long>(); internal static SocialGroup GetGroup(long playerId) { if (!PlayerGroups.TryGetValue(playerId, out var value) || !Groups.TryGetValue(value, out var value2)) { return null; } return value2; } internal static int GetNextGroupId() { int num = 0; foreach (int key in Groups.Keys) { num = ((key > num) ? key : num); } return num + 1; } internal static void ClearServer() { Groups.Clear(); PlayerGroups.Clear(); PeerPlayers.Clear(); Invitations.Clear(); } internal static void ClearAll() { ClearServer(); LocalMembers.Clear(); GroupMapSharing.Clear(); } } internal sealed class GroupDecision { internal bool Allowed { get; } internal string Message { get; } private GroupDecision(bool allowed, string message) { Allowed = allowed; Message = message; } internal static GroupDecision Allow() { return new GroupDecision(allowed: true, null); } internal static GroupDecision Deny(string message = null) { return new GroupDecision(allowed: false, message); } } internal static class GroupPolicy { internal static GroupDecision CanInviteTarget(bool targetReady) { if (!targetReady) { return GroupDecision.Deny("Player not found or not ready."); } return GroupDecision.Allow(); } internal static GroupDecision CanInvite(SocialGroup group, long inviter, long target, bool targetAlreadyGrouped) { if (targetAlreadyGrouped) { return GroupDecision.Deny("That player is already in a group."); } if (group != null && group.Leader != inviter) { return GroupDecision.Deny("Only the group leader can invite players."); } if (group != null && group.Members.Count >= 5) { return GroupDecision.Deny("Your group is full."); } if (inviter == target) { return GroupDecision.Deny(); } return GroupDecision.Allow(); } internal static GroupDecision CanTargetMember(SocialGroup group, long actor, long target, string targetName) { if (group == null) { return GroupDecision.Deny("You are not in a group."); } if (group.Leader != actor) { return GroupDecision.Deny("Only the group leader can do that."); } if (target == 0L) { return GroupDecision.Deny("Player not found in your group: " + targetName); } return GroupDecision.Allow(); } internal static GroupDecision CanRemove(long actor, long target) { if (target == actor) { return GroupDecision.Deny("You cannot remove yourself."); } return GroupDecision.Allow(); } internal static GroupDecision CanPromote(long actor, long target) { if (target == actor) { return GroupDecision.Deny("You are already group leader."); } return GroupDecision.Allow(); } } internal static class GroupMapSharing { private static readonly Dictionary<long, PlayerInfo> Players = new Dictionary<long, PlayerInfo>(); internal static void Clear() { Players.Clear(); } internal static void WritePosition(ZPackage package, long playerId) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) if (!TryGetPosition(playerId, out var characterId, out var position)) { package.Write(false); return; } package.Write(true); package.Write(characterId); package.Write(position); } internal static void ReadPosition(ZPackage package, long playerId, string name) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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_0043: 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) //IL_004d: Unknown result type (might be due to invalid IL or missing references) if (!package.ReadBool()) { Players.Remove(playerId); return; } Players[playerId] = new PlayerInfo { m_name = name, m_characterID = package.ReadZDOID(), m_publicPosition = true, m_position = package.ReadVector3() }; } internal static void AddGroupMembers(List<PlayerInfo> players) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: 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) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: 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) HashSet<long> hashSet = new HashSet<long>(); foreach (PlayerInfo player in players) { ZDOID characterID = player.m_characterID; hashSet.Add(((ZDOID)(ref characterID)).UserID); } long localPlayerId = GetLocalPlayerId(); foreach (KeyValuePair<long, PlayerInfo> player2 in Players) { if (MapSharingPolicy.ShouldAddGroupMember(player2.Key, localPlayerId, hashSet)) { players.Add(player2.Value); hashSet.Add(player2.Key); } } } private static bool TryGetPosition(long playerId, out ZDOID characterId, out Vector3 position) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) Player player = Player.GetPlayer(playerId); if ((Object)(object)player != (Object)null) { characterId = ((Character)player).GetZDOID(); position = ((Component)player).transform.position; return true; } return TryGetPeerPosition(playerId, out characterId, out position); } private static bool TryGetPeerPosition(long playerId, out ZDOID characterId, out Vector3 position) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: 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) //IL_008d: 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) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (GroupState.PeerPlayers.TryGetValue(peer.m_uid, out var value) && value == playerId && peer.IsReady() && !((ZDOID)(ref peer.m_characterID)).IsNone()) { characterId = peer.m_characterID; position = peer.m_refPos; return true; } } characterId = ZDOID.None; position = Vector3.zero; return false; } private static long GetLocalPlayerId() { if (!((Object)(object)Game.instance != (Object)null)) { return 0L; } return Game.instance.GetPlayerProfile().GetPlayerID(); } } internal static class GroupRpc { internal static void RPC_Request(long sender, ZPackage package) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { SocializePlugin.Settings.InitializeServer(SocializePlugin.Log); string action = package.ReadString(); long playerId = package.ReadLong(); string playerName = package.ReadString(); string argument = package.ReadString().Trim(); UserInfo val = new UserInfo(); val.Deserialize(ref package); GroupService.Dispatch(sender, playerId, playerName, action, argument, val); } } internal static void RPC_Response(long sender, ZPackage package) { if (!GroupService.IsExpectedServer(sender)) { SocializePlugin.Log.LogWarning($"Ignored group response from unexpected peer={sender}."); } else { GroupService.ReadResponse(package); } } internal static void RPC_PingRequest(long sender, Vector3 position, UserInfo user) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { GroupService.RelayPing(sender, position, user); } } internal static void RPC_ChatReceipt(long sender, string requestId, int result) { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { GroupService.ReceiveChatReceipt(sender, requestId, (RelationsManagerPermissionResult)result); } } } internal static class GroupService { private sealed class PendingGroupChat { internal long Sender; internal UserInfo User; internal string Message; internal readonly HashSet<long> Waiting = new HashSet<long>(); internal int Rejected; internal float SentAt; } private sealed class PendingInvite { internal long InviterPeer; internal string TargetName; internal float SentAt; } internal const string RequestRpc = "Landoria_Social_GroupRequest"; internal const string ResponseRpc = "Landoria_Social_GroupResponse"; internal const string PingRequestRpc = "Landoria_Social_GroupPingRequest"; internal const string ChatReceiptRpc = "Landoria_Social_GroupChatReceipt"; private const float PositionUpdateInterval = 2f; private const float ChatReceiptTimeout = 15f; private static ZRoutedRpc registeredRpc; private static float nextPositionUpdate; private static bool awaitingInitialState; private static float nextInitialStateRequest; private static readonly Dictionary<string, PendingGroupChat> PendingChats = new Dictionary<string, PendingGroupChat>(); private static readonly Dictionary<long, PendingInvite> PendingInvites = new Dictionary<long, PendingInvite>(); internal static void Update() { EnsureRpcs(); if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null) { return; } if (ZNet.instance.IsServer()) { SocializePlugin.Settings.InitializeServer(SocializePlugin.Log); if (Time.unscaledTime >= nextPositionUpdate) { nextPositionUpdate = Time.unscaledTime + 2f; BroadcastPositionUpdates(); } ExpireGroupChats(); ExpireInvites(); } else if (awaitingInitialState && (Object)(object)Player.m_localPlayer != (Object)null && Time.unscaledTime >= nextInitialStateRequest) { SendInitialStateRequest(); } } internal static void Reset() { registeredRpc = null; nextPositionUpdate = 0f; awaitingInitialState = false; nextInitialStateRequest = 0f; PendingChats.Clear(); PendingInvites.Clear(); GroupState.ClearAll(); SocializePlugin.Settings?.ResetState(); SocialChatSender.ApplyRangesToLoadedTalkers(); } internal static bool IsLocalPlayerInGroup() { if ((Object)(object)Game.instance != (Object)null) { return GroupState.LocalMembers.Contains(Game.instance.GetPlayerProfile().GetPlayerID()); } return false; } internal static bool IsExpectedServer(long sender) { if ((Object)(object)ZNet.instance == (Object)null) { return false; } if (ZNet.instance.IsServer()) { return true; } ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null) { return serverPeer.m_uid == sender; } return false; } internal static void SendChat(string message) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) if (!IsLocalPlayerInGroup()) { Chat instance = Chat.instance; if (instance != null) { ((Terminal)instance).AddString("You are not in a group."); } } else if (!PrivilegeResultExtentions.IsGranted(PlatformManager.DistributionPlatform.PrivilegeProvider.CheckPrivilege((Privilege)3))) { SocializePlugin.Log.LogWarning("Group chat blocked by the sender's text privilege."); Chat instance2 = Chat.instance; if (instance2 != null) { ((Terminal)instance2).AddString("Text communication is not permitted for this account."); } } else { BeginGroupChatPermissionCheck(message); } } private static void BeginGroupChatPermissionCheck(string message) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0044: 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_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0070: 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) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) List<PlayerInfo> list = new List<PlayerInfo>(); ZDOID val = ZNet.instance.LocalPlayerCharacterID; long userID = ((ZDOID)(ref val)).UserID; foreach (PlayerInfo player in ZNet.instance.GetPlayerList()) { val = player.m_characterID; if (((ZDOID)(ref val)).UserID != userID) { HashSet<long> localMembers = GroupState.LocalMembers; val = player.m_characterID; if (localMembers.Contains(((ZDOID)(ref val)).UserID)) { list.Add(player); } } } if (list.Count == 0) { SendChatRequest(message); return; } int remaining = list.Count; bool denied = false; SocializePlugin.Log.LogInfo($"Checking sender permission for group chat against {list.Count} recipient(s)."); foreach (PlayerInfo item in list) { PlayerInfo captured = item; TextPermissionService.Check(captured.m_userInfo.m_id, isSender: true, delegate(RelationsManagerPermissionResult result) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) SocializePlugin.Log.LogInfo($"Sender permission result for group member '{captured.m_name}': {result}."); if (!RelationsManagerPermissionResultExtentions.IsGranted(result)) { denied = true; } int num = remaining; remaining = num - 1; if (remaining == 0) { if (denied) { Chat instance = Chat.instance; if (instance != null) { ((Terminal)instance).AddString("Group message is not permitted for every connected member."); } SocializePlugin.Log.LogWarning("Group chat cancelled because at least one sender permission check failed."); } else { SendChatRequest(message); } } }); } } private static void SendChatRequest(string message) { EnsureRpcs(); if (ZRoutedRpc.instance != null && !((Object)(object)Game.instance == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null)) { UserInfo val = default(UserInfo); string text = default(string); Chat.GetChatMessageData(message, true, ref val, ref text); ZPackage val2 = NewRequest("chat", text); SocializePlugin.Log.LogInfo($"Sending group chat request (length={text.Length})."); ZRoutedRpc.instance.InvokeRoutedRPC("Landoria_Social_GroupRequest", new object[1] { val2 }); } } internal static void SendRequest(string action, string argument) { EnsureRpcs(); if (ZRoutedRpc.instance != null && !((Object)(object)Game.instance == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null)) { ZPackage val = NewRequest(action, argument); ZRoutedRpc.instance.InvokeRoutedRPC("Landoria_Social_GroupRequest", new object[1] { val }); } } private static ZPackage NewRequest(string action, string argument) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(action); val.Write(Game.instance.GetPlayerProfile().GetPlayerID()); val.Write(Game.instance.GetPlayerProfile().GetName()); val.Write(argument ?? ""); UserInfo.GetLocalUser().Serialize(ref val); return val; } internal static void RequestInitialState() { awaitingInitialState = true; SendInitialStateRequest(); } private static void SendInitialStateRequest() { nextInitialStateRequest = Time.unscaledTime + 2f; SocializePlugin.Log.LogDebug("Requesting initial group state from the server."); SendRequest("state", ""); } internal static void Dispatch(long sender, long playerId, string playerName, string action, string argument, UserInfo user) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) if (!TryValidateIdentity(sender, playerId, playerName, user, out var connected, out var reason)) { SocializePlugin.Log.LogWarning($"Rejected group action '{action}' from peer={sender}: {reason}"); if (action != "state") { SendMessage(sender, "Group action rejected because the player identity could not be verified."); } return; } SocializePlugin.Log.LogInfo($"Processing group action '{action}' from '{connected.m_name}' (peer={sender})."); if (RegisterSession(sender, playerId)) { BroadcastArrival(playerName); } switch (action) { case "state": SendSnapshot(sender, playerId); break; case "invite": Invite(sender, playerId, playerName, argument); break; case "invite-received": ConfirmInviteReceipt(sender, playerId, argument); break; case "accept": Accept(sender, playerId, playerName, argument); break; case "reject": Reject(sender, playerId, argument); break; case "leave": Leave(sender, playerId); break; case "remove": Remove(sender, playerId, argument); break; case "promote": Promote(sender, playerId, argument); break; case "info": SendInfo(sender, playerId); break; case "chat": SendGroupChat(sender, playerId, argument, user); break; default: SocializePlugin.Log.LogWarning($"Ignored unknown group action '{action}' from peer={sender}."); break; } } internal static void ReadResponse(ZPackage package) { switch (package.ReadString()) { case "message": { Chat instance = Chat.instance; if (instance != null) { ((Terminal)instance).AddString(package.ReadString()); } break; } case "arrival": ShowArrival(package.ReadString()); break; case "snapshot": ReadSnapshot(package); break; case "positions": ReadPositionUpdate(package); break; case "invite": ShowInvite(package.ReadString(), package.ReadString()); break; case "groupChat": ReadGroupChat(package); break; case "groupChatResult": ReadGroupChatResult(package); break; } } private static void EnsureRpcs() { if (RpcRegistry.RegisterIfChanged(ref registeredRpc, RegisterRpcs)) { GroupState.ClearAll(); SocializePlugin.Settings?.ResetState(); SocialChatSender.ApplyRangesToLoadedTalkers(); } } private static void RegisterRpcs(ZRoutedRpc rpc) { rpc.Register<ZPackage>("Landoria_Social_GroupRequest", (Action<long, ZPackage>)GroupRpc.RPC_Request); rpc.Register<ZPackage>("Landoria_Social_GroupResponse", (Action<long, ZPackage>)GroupRpc.RPC_Response); rpc.Register<Vector3, UserInfo>("Landoria_Social_GroupPingRequest", (Action<long, Vector3, UserInfo>)GroupRpc.RPC_PingRequest); rpc.Register<string, int>("Landoria_Social_GroupChatReceipt", (Action<long, string, int>)GroupRpc.RPC_ChatReceipt); } internal static void RelayPing(long sender, Vector3 position, UserInfo user) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) if (!GroupState.PeerPlayers.TryGetValue(sender, out var value)) { SocializePlugin.Log.LogDebug("Group ping ignored because the sender is not registered."); return; } SocialGroup socialGroup = GroupState.GetGroup(value); if (socialGroup == null) { SocializePlugin.Log.LogDebug("Group ping ignored because the sender is not in a group."); return; } if (!TryGetAuthoritativeUser(sender, user, out var user2)) { SocializePlugin.Log.LogWarning($"Group ping ignored because peer={sender} supplied an invalid identity."); return; } int num = 0; foreach (long key in socialGroup.Members.Keys) { long num2 = FindPeer(key); if (num2 != 0L) { ZRoutedRpc.instance.InvokeRoutedRPC(num2, "ChatMessage", new object[4] { position, 3, user2, "" }); num++; } } SocializePlugin.Log.LogDebug($"Relayed group ping from {GetPlayerName(value)} to {num} member(s)."); } private static void Invite(long sender, long inviter, string inviterName, string targetName) { long num = FindPeerByName(targetName); long playerId; GroupDecision groupDecision = GroupPolicy.CanInviteTarget(TryGetPlayerForPeer(num, out playerId)); if (!groupDecision.Allowed) { SendMessage(sender, groupDecision.Message); return; } GroupDecision groupDecision2 = GroupInvitationPolicy.TryInvite(GroupState.GetGroup(inviter), inviter, playerId, GroupState.PlayerGroups.ContainsKey(playerId), GroupState.Invitations); if (!groupDecision2.Allowed) { if (groupDecision2.Message != null) { SendMessage(sender, groupDecision2.Message); } return; } ZPackage val = NewResponse("invite"); val.Write(inviter.ToString()); val.Write(inviterName); PendingInvites[playerId] = new PendingInvite { InviterPeer = sender, TargetName = targetName, SentAt = Time.realtimeSinceStartup }; ZRoutedRpc.instance.InvokeRoutedRPC(num, "Landoria_Social_GroupResponse", new object[1] { val }); SocializePlugin.Log.LogInfo($"Group invitation queued from peer={sender} for peer={num}; awaiting display receipt."); } private static void ConfirmInviteReceipt(long sender, long playerId, string inviterText) { if (!long.TryParse(inviterText, out var result) || !GroupState.Invitations.TryGetValue(playerId, out var value) || result != value) { SocializePlugin.Log.LogWarning($"Invalid group invitation receipt from peer={sender} for inviter={inviterText}."); return; } long peer = FindPeer(result); PendingInvites.Remove(playerId); SendMessage(peer, "Group invitation delivered."); SocializePlugin.Log.LogInfo($"Group invitation from player={result} displayed for player={playerId}."); } private static void Accept(long sender, long playerId, string playerName, string inviterText) { PendingInvites.Remove(playerId); GroupAcceptanceResult groupAcceptanceResult = GroupAcceptancePolicy.Accept(playerId, playerName, inviterText, GroupState.Invitations, GetOrCreateGroup, GroupState.PlayerGroups); if (!groupAcceptanceResult.Accepted) { SendMessage(sender, groupAcceptanceResult.Message); } else { BroadcastChange(groupAcceptanceResult.Group, playerName + " joined the group."); } } private static SocialGroup GetOrCreateGroup(long inviter) { SocialGroup socialGroup = GroupState.GetGroup(inviter); if (socialGroup != null) { if (socialGroup.Leader != inviter) { return null; } return socialGroup; } socialGroup = new SocialGroup { Id = GroupState.GetNextGroupId(), Leader = inviter }; socialGroup.AddMember(inviter, GetPlayerName(inviter)); GroupState.Groups[socialGroup.Id] = socialGroup; GroupState.PlayerGroups[inviter] = socialGroup.Id; return socialGroup; } private static void Reject(long sender, long playerId, string inviterText) { PendingInvites.Remove(playerId); GroupState.Invitations.Remove(playerId); SendMessage(sender, "Group invitation rejected."); if (long.TryParse(inviterText, out var result)) { SendMessage(FindPeer(result), GetPlayerName(playerId) + " rejected the group invitation."); } } private static void Leave(long sender, long playerId) { SocialGroup socialGroup = GroupState.GetGroup(playerId); if (socialGroup == null) { SendMessage(sender, "You are not in a group."); return; } string text = socialGroup.Members[playerId]; GroupState.PlayerGroups.Remove(playerId); GroupRemovalResult groupRemovalResult = GroupLifecyclePolicy.Remove(socialGroup, playerId); BroadcastMembers(groupRemovalResult.RemainingMembers, text + " left the group."); ApplyRemoval(socialGroup, groupRemovalResult); SendSnapshot(sender, playerId); SendMessage(sender, "You left the group."); BroadcastSnapshots(socialGroup); } private static void Remove(long sender, long actor, string targetName) { SocialGroup socialGroup = GroupState.GetGroup(actor); long num = FindMember(socialGroup, targetName); if (ValidateLeaderAction(sender, actor, targetName, socialGroup, num) && ValidateRemoveTarget(sender, actor, num)) { string text = socialGroup.Members[num]; long peer = FindPeer(num); GroupState.PlayerGroups.Remove(num); GroupRemovalResult groupRemovalResult = GroupLifecyclePolicy.Remove(socialGroup, num); BroadcastMembers(groupRemovalResult.RemainingMembers, text + " was removed from the group."); ApplyRemoval(socialGroup, groupRemovalResult); SendSnapshot(peer, num); SendMessage(peer, "You were removed from the group."); BroadcastSnapshots(socialGroup); } } private static void Promote(long sender, long actor, string targetName) { SocialGroup socialGroup = GroupState.GetGroup(actor); long num = FindMember(socialGroup, targetName); GroupDecision groupDecision = GroupPromotionPolicy.TryPromote(socialGroup, actor, num, targetName); if (!groupDecision.Allowed) { SendMessage(sender, groupDecision.Message); return; } Broadcast(socialGroup, socialGroup.Members[num] + " is now the group leader."); BroadcastSnapshots(socialGroup); } private static bool ValidateLeaderAction(long sender, long actor, string targetName, SocialGroup group, long target) { GroupDecision groupDecision = GroupPolicy.CanTargetMember(group, actor, target, targetName); if (!groupDecision.Allowed) { SendMessage(sender, groupDecision.Message); } return groupDecision.Allowed; } private static bool ValidateRemoveTarget(long sender, long actor, long target) { return ValidateTargetDecision(sender, GroupPolicy.CanRemove(actor, target)); } private static bool ValidateTargetDecision(long sender, GroupDecision decision) { if (!decision.Allowed) { SendMessage(sender, decision.Message); } return decision.Allowed; } private static void ApplyRemoval(SocialGroup group, GroupRemovalResult result) { if (!result.Disbanded) { return; } foreach (long remainingMember in result.RemainingMembers) { GroupState.PlayerGroups.Remove(remainingMember); SendMessage(FindPeer(remainingMember), "The group was disbanded."); SendSnapshot(FindPeer(remainingMember), remainingMember); } GroupState.Groups.Remove(group.Id); } private static void SendGroupChat(long sender, long actor, string message, UserInfo user) { SocialGroup socialGroup = GroupState.GetGroup(actor); GroupChatResult groupChatResult = GroupChatPolicy.Prepare(socialGroup, actor, message, (long member) => FindPeer(member) != 0, (string name, string result) => result); if (!groupChatResult.Broadcast) { SendMessage(sender, groupChatResult.Message); return; } if (!TryGetAuthoritativeUser(sender, user, out var user2)) { SocializePlugin.Log.LogWarning($"Rejected group chat from peer={sender}: identity mismatch."); SendMessage(sender, "Group message rejected because the sender identity could not be verified."); return; } string text = Guid.NewGuid().ToString("N").Substring(0, 12); PendingGroupChat pendingGroupChat = new PendingGroupChat { Sender = sender, User = user2, Message = groupChatResult.Message, SentAt = Time.realtimeSinceStartup }; foreach (long key in socialGroup.Members.Keys) { long num = FindPeer(key); if (num != 0L && num != sender) { pendingGroupChat.Waiting.Add(num); } } PendingChats[text] = pendingGroupChat; foreach (long item in pendingGroupChat.Waiting) { SendGroupChatDelivery(item, text, user2, groupChatResult.Message); } SocializePlugin.Log.LogInfo($"Group chat [{text}] accepted from peer={sender}; waiting for {pendingGroupChat.Waiting.Count} receipt(s)."); if (pendingGroupChat.Waiting.Count == 0) { CompleteGroupChat(text, pendingGroupChat); } } private static void SendGroupChatDelivery(long peer, string requestId, UserInfo user, string message) { ZPackage val = NewResponse("groupChat"); val.Write(requestId); user.Serialize(ref val); val.Write(message); ZRoutedRpc.instance.InvokeRoutedRPC(peer, "Landoria_Social_GroupResponse", new object[1] { val }); } private static void ReadGroupChat(ZPackage package) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) string requestId = package.ReadString(); UserInfo user = new UserInfo(); user.Deserialize(ref package); string message = package.ReadString(); SocializePlugin.Log.LogInfo($"Group chat [{requestId}] received from '{user.GetDisplayName()}' (length={message.Length}); checking permission."); TextPermissionService.Check(user.UserId, isSender: false, delegate(RelationsManagerPermissionResult result) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) CompleteGroupChatReceive(requestId, user, message, result); }); } private static void CompleteGroupChatReceive(string requestId, UserInfo user, string message, RelationsManagerPermissionResult result) { //IL_000b: 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_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Invalid comparison between Unknown and I4 //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Expected I4, but got Unknown SocializePlugin.Log.LogInfo($"Group chat [{requestId}] permission result: {result}."); if (RelationsManagerPermissionResultExtentions.IsGranted(result) && (Object)(object)Chat.instance != (Object)null) { string text = message.Replace('<', ' ').Replace('>', ' '); if ((int)result == 1) { CensorShittyWords.Filter(text, ref text); } ((Terminal)Chat.instance).AddString(ChatFormatting.FormatGroup(user.GetDisplayName(), text)); SocializePlugin.Log.LogInfo("Group chat [" + requestId + "] displayed; sending receipt."); } else if (RelationsManagerPermissionResultExtentions.IsGranted(result)) { result = (RelationsManagerPermissionResult)3; } ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("Landoria_Social_GroupChatReceipt", new object[2] { requestId, (int)result }); } } internal static void ReceiveChatReceipt(long sender, string requestId, RelationsManagerPermissionResult result) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) if (!PendingChats.TryGetValue(requestId, out var value) || !value.Waiting.Remove(sender)) { SocializePlugin.Log.LogWarning($"Ignoring unknown group chat receipt [{requestId}] from peer={sender}."); return; } if (!RelationsManagerPermissionResultExtentions.IsGranted(result)) { value.Rejected++; } SocializePlugin.Log.LogInfo($"Group chat [{requestId}] receipt from peer={sender}: {result}; remaining={value.Waiting.Count}."); if (value.Waiting.Count == 0) { CompleteGroupChat(requestId, value); } } private static void CompleteGroupChat(string requestId, PendingGroupChat pending) { PendingChats.Remove(requestId); ZPackage val = NewResponse("groupChatResult"); val.Write(requestId); val.Write(pending.Rejected == 0); pending.User.Serialize(ref val); val.Write(pending.Message); val.Write(pending.Rejected); ZRoutedRpc.instance.InvokeRoutedRPC(pending.Sender, "Landoria_Social_GroupResponse", new object[1] { val }); } private static void ReadGroupChatResult(ZPackage package) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown string text = package.ReadString(); bool num = package.ReadBool(); UserInfo val = new UserInfo(); val.Deserialize(ref package); string text2 = package.ReadString(); int num2 = package.ReadInt(); if (num) { string message = text2.Replace('<', ' ').Replace('>', ' '); Chat instance = Chat.instance; if (instance != null) { ((Terminal)instance).AddString(ChatFormatting.FormatGroup(val.GetDisplayName(), message)); } SocializePlugin.Log.LogInfo("Group chat [" + text + "] confirmed and shown to the sender."); } else { Chat instance2 = Chat.instance; if (instance2 != null) { ((Terminal)instance2).AddString("Group message was not delivered to every member."); } SocializePlugin.Log.LogWarning($"Group chat [{text}] completed with {num2} rejected delivery attempt(s)."); } } private static void ExpireGroupChats() { List<string> list = null; foreach (KeyValuePair<string, PendingGroupChat> pendingChat in PendingChats) { if (Time.realtimeSinceStartup - pendingChat.Value.SentAt >= 15f) { (list ?? (list = new List<string>())).Add(pendingChat.Key); } } if (list == null) { return; } foreach (string item in list) { PendingGroupChat pendingGroupChat = PendingChats[item]; PendingChats.Remove(item); SendMessage(pendingGroupChat.Sender, "Group message was not confirmed by every member."); SocializePlugin.Log.LogWarning($"Group chat [{item}] timed out with {pendingGroupChat.Waiting.Count} missing receipt(s)."); } } private static void ExpireInvites() { List<long> list = null; foreach (KeyValuePair<long, PendingInvite> pendingInvite2 in PendingInvites) { if (Time.realtimeSinceStartup - pendingInvite2.Value.SentAt >= 15f) { (list ?? (list = new List<long>())).Add(pendingInvite2.Key); } } if (list == null) { return; } foreach (long item in list) { PendingInvite pendingInvite = PendingInvites[item]; PendingInvites.Remove(item); GroupState.Invitations.Remove(item); SendMessage(pendingInvite.InviterPeer, "Group invitation to " + pendingInvite.TargetName + " was not confirmed."); SocializePlugin.Log.LogWarning($"Group invitation to player={item} timed out waiting for display confirmation."); } } private unsafe static bool TryGetAuthoritativeUser(long sender, UserInfo claimed, out UserInfo user) { //IL_0012: 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) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0068: 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_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: 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_007a: Expected O, but got Unknown user = null; if (claimed == null || !TryGetPlayerInfo(sender, out var player)) { return false; } if (player.m_name != claimed.Name || ((object)(*(PlatformUserID*)(&player.m_userInfo.m_id))/*cast due to .constrained prefix*/).ToString() != ((object)Unsafe.As<PlatformUserID, PlatformUserID>(ref claimed.UserId)/*cast due to .constrained prefix*/).ToString()) { return false; } user = new UserInfo { Name = player.m_name, UserId = player.m_userInfo.m_id }; return true; } private static bool TryGetPlayerInfo(long peer, out PlayerInfo player) { //IL_005d: 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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance != (Object)null) { foreach (PlayerInfo player2 in ZNet.instance.GetPlayerList()) { ZDOID characterID = player2.m_characterID; if (((ZDOID)(ref characterID)).UserID == peer) { player = player2; return true; } } } player = default(PlayerInfo); return false; } private static bool IsPlayerIdClaimedByOtherPeer(long peer, long playerId) { foreach (KeyValuePair<long, long> peerPlayer in GroupState.PeerPlayers) { if (peerPlayer.Key != peer && peerPlayer.Value == playerId) { return true; } } return false; } private static bool TryValidateIdentity(long peer, long playerId, string playerName, UserInfo claimed, out PlayerInfo connected, out string reason) { if (!TryGetPlayerInfo(peer, out connected)) { reason = "the peer is not present in the server player list yet"; return false; } if (claimed == null) { reason = "the request did not contain a platform identity"; return false; } if (connected.m_name != playerName || connected.m_name != claimed.Name) { reason = "name mismatch (connected='" + connected.m_name + "', player='" + playerName + "', platform='" + claimed.Name + "')"; return false; } string text = ((object)Unsafe.As<PlatformUserID, PlatformUserID>(ref connected.m_userInfo.m_id)/*cast due to .constrained prefix*/).ToString(); string text2 = ((object)Unsafe.As<PlatformUserID, PlatformUserID>(ref claimed.UserId)/*cast due to .constrained prefix*/).ToString(); if (text != text2) { reason = "platform user mismatch (connected='" + text + "', claimed='" + text2 + "')"; return false; } if (IsPlayerIdClaimedByOtherPeer(peer, playerId)) { reason = $"player id '{playerId}' is already associated with another peer"; return false; } reason = null; return true; } private static void SendInfo(long sender, long playerId) { SocialGroup socialGroup = GroupState.GetGroup(playerId); SendMessage(sender, GroupInfoPolicy.Build(socialGroup, (long member) => FindPeer(member) != 0)); } private static void BroadcastChange(SocialGroup group, string message) { Broadcast(group, message); BroadcastSnapshots(group); SocializePlugin.Log.LogInfo(message); } internal static void BeginPeerSession(long peer) { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && GroupState.PeerPlayers.ContainsKey(peer)) { DisconnectPeer(peer); } } internal static void DisconnectPeer(long peer) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && GroupState.PeerPlayers.TryGetValue(peer, out var value)) { GroupState.PeerPlayers.Remove(peer); RemovePlayerSession(value); } } private static bool RegisterSession(long peer, long playerId) { int num; if (GroupState.PeerPlayers.TryGetValue(peer, out var value)) { num = ((value == playerId) ? 1 : 0); if (num != 0) { goto IL_0028; } } else { num = 0; } if (value != 0L) { RemovePlayerSession(value); } RemovePlayerSession(playerId); goto IL_0028; IL_0028: GroupState.PeerPlayers[peer] = playerId; return num == 0; } private static void RemovePlayerSession(long playerId) { RemovePeerMappings(playerId); RemoveInvitations(playerId); SocialGroup socialGroup = GroupState.GetGroup(playerId); if (socialGroup != null) { string text = socialGroup.Members[playerId]; GroupState.PlayerGroups.Remove(playerId); GroupRemovalResult groupRemovalResult = GroupLifecyclePolicy.Remove(socialGroup, playerId); BroadcastMembers(groupRemovalResult.RemainingMembers, text + " left the group."); ApplyRemoval(socialGroup, groupRemovalResult); BroadcastSnapshots(socialGroup); } } private static void RemovePeerMappings(long playerId) { foreach (long item in new List<long>(GroupState.PeerPlayers.Keys)) { if (GroupState.PeerPlayers[item] == playerId) { GroupState.PeerPlayers.Remove(item); } } } private static void RemoveInvitations(long playerId) { GroupState.Invitations.Remove(playerId); PendingInvites.Remove(playerId); foreach (long item in new List<long>(GroupState.Invitations.Keys)) { if (GroupState.Invitations[item] == playerId) { GroupState.Invitations.Remove(item); PendingInvites.Remove(item); } } } private static void BroadcastSnapshots(SocialGroup group) { if (group == null) { return; } foreach (long key in group.Members.Keys) { SendSnapshot(FindPeer(key), key); } } private static void BroadcastPositionUpdates() { foreach (SocialGroup value in GroupState.Groups.Values) { foreach (long key in value.Members.Keys) { SendPositionUpdate(FindPeer(key), value); } } } private static void SendPositionUpdate(long peer, SocialGroup group) { if (peer == 0L || group == null) { return; } ZPackage val = NewResponse("positions"); val.Write(group.Members.Count); foreach (KeyValuePair<long, string> member in group.Members) { val.Write(member.Key); val.Write(member.Value); GroupMapSharing.WritePosition(val, member.Key); } ZRoutedRpc.instance.InvokeRoutedRPC(peer, "Landoria_Social_GroupResponse", new object[1] { val }); } private static void SendSnapshot(long peer, long playerId) { if (peer == 0L) { return; } SocialGroup socialGroup = GroupState.GetGroup(playerId); ZPackage val = NewResponse("snapshot"); val.Write(socialGroup?.Leader ?? 0); val.Write(socialGroup?.Members.Count ?? 0); if (socialGroup != null) { foreach (KeyValuePair<long, string> member in socialGroup.Members) { val.Write(member.Key); val.Write(member.Value); GroupMapSharing.WritePosition(val, member.Key); } } SocializePlugin.Settings.WriteState(val); ZRoutedRpc.instance.InvokeRoutedRPC(peer, "Landoria_Social_GroupResponse", new object[1] { val }); } private static void ReadSnapshot(ZPackage package) { awaitingInitialState = false; package.ReadLong(); GroupState.LocalMembers.Clear(); GroupMapSharing.Clear(); int num = package.ReadInt(); for (int i = 0; i < num; i++) { long num2 = package.ReadLong(); string name = package.ReadString(); GroupState.LocalMembers.Add(num2); GroupMapSharing.ReadPosition(package, num2, name); } SocializePlugin.Settings.ReadState(package); SocialChatSender.ApplyRangesToLoadedTalkers(); } private static void ReadPositionUpdate(ZPackage package) { int num = package.ReadInt(); for (int i = 0; i < num; i++) { long playerId = package.ReadLong(); string name = package.ReadString(); GroupMapSharing.ReadPosition(package, playerId, name); } } private static void ShowInvite(string inviterId, string inviterName) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_004e: Expected O, but got Unknown //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown InvitationPresentation presentation = InvitationPresentationPolicy.Build(inviterName); UnifiedPopup.Push((PopupBase)new YesNoPopup(presentation.Title, presentation.Message, (PopupButtonCallback)delegate { RespondToInvite(presentation.AcceptAction, inviterId); }, (PopupButtonCallback)delegate { RespondToInvite(presentation.RejectAction, inviterId); }, false, false)); SendRequest("invite-received", inviterId); } private static void RespondToInvite(string action, string inviterId) { UnifiedPopup.Pop(); SendRequest(action, inviterId); } private static void Broadcast(SocialGroup group, string message) { if (group == null) { return; } foreach (long key in group.Members.Keys) { SendMessage(FindPeer(key), message); } } private static void BroadcastMembers(IEnumerable<long> members, string message) { foreach (long member in members) { SendMessage(FindPeer(member), message); } } private static void BroadcastArrival(string playerName) { foreach (long item in new List<long>(GroupState.PeerPlayers.Keys)) { ZNetPeer peer = ZNet.instance.GetPeer(item); if (peer != null && peer.IsReady()) { SendArrival(item, playerName); } } SocializePlugin.Log.LogInfo(playerName + " arrived on the server."); } private static void SendArrival(long peer, string playerName) { if (peer == 0L) { ShowArrival(playerName); return; } ZPackage val = NewResponse("arrival"); val.Write(playerName ?? ""); ZRoutedRpc.instance.InvokeRoutedRPC(peer, "Landoria_Social_GroupResponse", new object[1] { val }); } private static void ShowArrival(string playerName) { string message = Localization.instance.Localize("$text_player_arrived"); Chat instance = Chat.instance; if (instance != null) { ((Terminal)instance).AddString(ChatFormattingPolicy.FormatArrival(playerName, message)); } } private static void SendMessage(long peer, string message) { if (peer == 0L) { Chat instance = Chat.instance; if (instance != null) { ((Terminal)instance).AddString(message); } } else { ZPackage val = NewResponse("message"); val.Write(message ?? ""); ZRoutedRpc.instance.InvokeRoutedRPC(peer, "Landoria_Social_GroupResponse", new object[1] { val }); } } private static ZPackage NewResponse(string type) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(type); return val; } private static long FindMember(SocialGroup group, string name) { if (group == null) { return 0L; } foreach (KeyValuePair<long, string> member in group.Members) { if (string.Equals(member.Value, name, StringComparison.OrdinalIgnoreCase)) { return member.Key; } } return 0L; } private static long FindPeerByName(string name) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null) { return 0L; } foreach (PlayerInfo player in ZNet.instance.GetPlayerList()) { if (string.Equals(player.m_name, name, StringComparison.OrdinalIgnoreCase)) { ZDOID characterID = player.m_characterID; return ((ZDOID)(ref characterID)).UserID; } } return 0L; } private static bool TryGetPlayerForPeer(long peer, out long playerId) { playerId = 0L; if (peer != 0L) { return GroupState.PeerPlayers.TryGetValue(peer, out playerId); } return false; } private static long FindPeer(long playerId) { foreach (KeyValuePair<long, long> peerPlayer in GroupState.PeerPlayers) { if (peerPlayer.Value == playerId && (Object)(object)ZNet.instance != (Object)null) { ZNetPeer peer = ZNet.instance.GetPeer(peerPlayer.Key); if (peer != null && peer.IsReady()) { return peerPlayer.Key; } } } return 0L; } private static string GetPlayerName(long playerId) { SocialGroup socialGroup = GroupState.GetGroup(playerId); if (socialGroup != null && socialGroup.Members.TryGetValue(playerId, out var value)) { return value; } foreach (KeyValuePair<long, long> peerPlayer in GroupState.PeerPlayers) { if (peerPlayer.Value == playerId) { ZNetPeer peer = ZNet.instance.GetPeer(peerPlayer.Key); if (peer != null) { return peer.m_playerName; } } } return playerId.ToString(); } } internal sealed class InvitationPresentation { internal string Title; internal string Message; internal string AcceptAction; internal string RejectAction; } internal static class InvitationPresentationPolicy { internal static InvitationPresentation Build(string inviterName) { return new InvitationPresentation { Title = "Group invitation", Message = inviterName + " invited you to a group.", AcceptAction = "accept", RejectAction = "reject" }; } } internal static class RpcRegistry { internal static bool RegisterIfChanged(ref ZRoutedRpc registered, Action<ZRoutedRpc> register) { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null || instance == registered) { return false; } registered = instance; register(instance); return true; } } [HarmonyPatch(typeof(Minimap), "Update")] internal static class UpdateMapPingVisibilityPatch { private static void Postfix(Minimap __instance) { HidePublicPositionTogglePatch.UpdateVisibility(__instance); } } [HarmonyPatch(typeof(ZNet), "SetPublicReferencePosition")] internal static class DisablePublicPositionPatch { private static void Prefix(ref bool pub) { pub = MapSharingPolicy.GetPublicPosition(SocializePlugin.Settings.RestrictPublicPositions, pub); } } [HarmonyPatch(typeof(ZNet), "GetOtherPublicPlayers")] internal static class ShowGroupMembersOnMapPatch { private static void Postfix(List<PlayerInfo> playerList) { GroupMapSharing.AddGroupMembers(playerList); } } [HarmonyPatch(typeof(Minimap), "Start")] internal static class HidePublicPositionTogglePatch { private static void Postfix(Minimap __instance) { UpdateVisibility(__instance); } internal static void UpdateVisibility(Minimap minimap) { UpdatePositionVisibility(minimap); if (!((Object)(object)minimap.m_pingImageObject == (Object)null)) { GetPingButton(minimap).SetActive(MapSharingPolicy.CanSendPublicPing(SocializePlugin.Settings.RestrictPublicPings, GroupService.IsLocalPlayerInGroup())); } } private static void UpdatePositionVisibility(Minimap minimap) { Toggle publicPosition = minimap.m_publicPosition; if (!((Object)(object)publicPosition == (Object)null)) { bool flag = !SocializePlugin.Settings.RestrictPublicPositions; if (!flag) { publicPosition.isOn = false; } ((Component)publicPosition).gameObject.SetActive(flag); SetDedicatedContainerVisibility(minimap, publicPosition, flag); } } private static GameObject GetPingButton(Minimap minimap) { Button componentInParent = ((Component)minimap.m_pingImageObject).GetComponentInParent<Button>(true); if ((Object)(object)componentInParent != (Object)null && !IsMapRoot(minimap, ((Component)componentInParent).gameObject)) { return ((Component)componentInParent).gameObject; } Transform parent = ((Component)minimap.m_pingImageObject).transform.parent; if ((Object)(object)parent != (Object)null && !IsMapRoot(minimap, ((Component)parent).gameObject) && ((Component)parent).GetComponentsInChildren<RawImage>(true).Length == 0) { return ((Component)parent).gameObject; } return ((Component)minimap.m_pingImageObject).gameObject; } private static void SetDedicatedContainerVisibility(Minimap minimap, Toggle toggle, bool visible) { Transform parent = ((Component)toggle).transform.parent; if (!((Object)(object)parent == (Object)null) && !IsMapRoot(minimap, ((Component)parent).gameObject)) { bool num = ((Component)parent).GetComponentsInChildren<Toggle>(true).Length == 1; bool flag = ((Component)parent).GetComponentsInChildren<RawImage>(true).Length != 0; if (num && !flag) { ((Component)parent).gameObject.SetActive(visible); } } } private static bool IsMapRoot(Minimap minimap, GameObject target) { if (!((Object)(object)target == (Object)(object)minimap.m_largeRoot) && !((Object)(object)target == (Object)(object)minimap.m_smallRoot) && !((Object)(object)target == (Object)(object)minimap.m_mapLarge)) { return (Object)(object)target == (Object)(object)minimap.m_mapSmall; } return true; } } internal static class MapSharingPolicy { internal static bool CanSendPublicPing(bool restricted, bool isInGroup) { return !restricted || isInGroup; } internal static bool GetPublicPosition(bool restricted, bool requested) { if (!restricted) { return requested; } return false; } internal static bool ShouldAddGroupMember(long playerId, long localPlayerId, ISet<long> visiblePlayerIds) { if (playerId != localPlayerId) { return !visiblePlayerIds.Contains(playerId); } return false; } } [BepInPlugin("Landoria.Socialize", "Landoria.Socialize", "1.0.16")] public sealed class SocializePlugin : LandoriaPlugin { private const string PluginGuid = "Landoria.Socialize"; private const string PluginName = "Landoria.Socialize"; private const string PluginVersion = "1.0.16"; internal static ModLog Log { get; private set; } internal static SocializeSettings Settings { get; private set; } private void Awake() { Log = InitializePlugin("Landoria.Socialize"); Settings = new SocializeSettings(); Log.LogInfo("Landoria.Socialize 1.0.16 is loaded."); } private void Update() { TextPermissionService.Update(); GroupService.Update(); TargetPingService.Update(); PrivateChat.Update(); } private void OnDestroy() { GroupService.Reset(); TargetPingService.Reset(); PrivateChat.Reset(); TextPermissionService.Reset(); Log?.LogInfo("Landoria.Socialize 1.0.16 is unloaded."); ShutdownPlugin(); Settings = null; Log = null; } } internal sealed class SocializeSettings { private const float FixedShoutDistance = 70f; private const float FixedSayDistance = 15f; private bool serverInitialized; internal bool RestrictPublicPositions { get; private set; } internal bool RestrictPublicPings { get; private set; } internal float ShoutDistance { get; private set; } internal float SayDistance { get; private set; } internal SocializeSettings() { ResetState(); } internal void InitializeServer(ModLog logger) { if (!serverInitialized && ServerRole.IsDedicatedServer) { serverInitialized = true; LogSettings(logger); } } private void LogSettings(ModLog logger) { logger.LogInfo("Effective map settings: restrictPublicPositions=" + $"{RestrictPublicPositions}, restrictPublicPings={RestrictPublicPings}."); logger.LogInfo($"Effective chat settings: shoutDistance={ShoutDistance}, " + $"sayDistance={SayDistance}."); } internal void WriteState(ZPackage package) { package.Write(RestrictPublicPositions); package.Write(RestrictPublicPings); package.Write(ShoutDistance); package.Write(SayDistance); } internal void ReadState(ZPackage package) { RestrictPublicPositions = package.ReadBool(); RestrictPublicPings = package.ReadBool(); ShoutDistance = package.ReadSingle(); SayDistance = package.ReadSingle(); } internal void ResetState() { if (!serverInitialized) { RestrictPublicPositions = true; RestrictPublicPings = true; ShoutDistance = 70f; SayDistance = 15f; } } } internal sealed class SocialGroup { internal const int MaximumSize = 5; internal int Id; internal long Leader; internal readonly Dictionary<long, string> Members = new Dictionary<long, string>(); private readonly List<long> memberOrder = new List<long>(); internal void AddMember(long playerId, string playerName) { if (!Members.ContainsKey(playerId)) { memberOrder.Add(playerId); } Members[playerId] = playerName; } internal void RemoveMember(long playerId) { Members.Remove(playerId); memberOrder.Remove(playerId); } internal long GetOldestMember() { foreach (long item in memberOrder) { if (Members.ContainsKey(item)) { return item; } } return 0L; } } internal static class TargetPingService { private sealed class PendingPing { internal long TargetPeer; internal string TargetName; internal string Message; internal Terminal Context; internal float SentAt; } private const string MessageRpc = "Landoria_Social_TargetPingMessage"; private const string ReceiptRpc = "Landoria_Social_TargetPingReceipt"; private const float ReceiptTimeoutSeconds = 15f; private static readonly MethodInfo AddInworldText = AccessTools.Method(typeof(Chat), "AddInworldText", (Type[])null, (Type[])null); private static readonly Dictionary<string, PendingPing> Pending = new Dictionary<string, PendingPing>(); private static ZRoutedRpc registeredRpc; internal static void Update() { EnsureRpcs(); List<string> list = null; foreach (KeyValuePair<string, PendingPing> item in Pending) { if (Time.realtimeSinceStartup - item.Value.SentAt >= 15f) { (list ?? (list = new List<string>())).Add(item.Key); } } if (list == null) { return; } foreach (string item2 in list) { PendingPing pendingPing = Pending[item2]; Pending.Remove(item2); SocializePlugin.Log.LogWarning("Target ping [" + item2 + "] to '" + pendingPing.TargetName + "' timed out."); Terminal context = pendingPing.Context; if (context != null) { context.AddString("Ping to " + pendingPing.TargetName + " was not confirmed."); } } } internal static void Reset() { Pending.Clear(); registeredRpc = null; } internal static bool Send(string targetName, string message, Terminal context) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) PlayerInfo target; bool num = Tr