using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using HarmonyLib; using IronLabs.SharedLib; using Splatform; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("IronLabs.Socialize")] [assembly: AssemblyDescription("Adds persistent groups and expanded chat channels to Valheim.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("IronLabs")] [assembly: AssemblyProduct("IronLabs.Socialize")] [assembly: AssemblyCopyright("Copyright © 2026 End3rbyte")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("DA5DE9DF-7727-4D5F-8274-896B8EB80ED3")] [assembly: AssemblyFileVersion("1.0.2")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.2.17987")] namespace IronLabs.Socialize { internal static class ChatChannelState { private enum Channel { Normal, Shout, Whisper, Group } private static Channel current; private static string whisperTarget = ""; private static bool redirecting; internal static void SetNormal() { current = Channel.Normal; whisperTarget = ""; } internal static void SetShout() { current = Channel.Shout; whisperTarget = ""; } internal static void SetWhisper(string target) { current = Channel.Whisper; whisperTarget = target ?? ""; } internal static void SetGroup() { current = Channel.Group; whisperTarget = ""; } internal static string GetPrompt() { return current switch { Channel.Shout => "Shouting...", Channel.Whisper => "Talking to " + whisperTarget + "...", Channel.Group => "Speaking to the group...", _ => "Speaking...", }; } internal static bool TryRedirect(string text) { if (redirecting || current == Channel.Normal || string.IsNullOrWhiteSpace(text)) { return false; } redirecting = true; try { return Send(text); } finally { redirecting = false; } } private static bool Send(string text) { switch (current) { case Channel.Shout: SocialChatSender.SendShout(text); return true; case Channel.Whisper: return PrivateChat.Send(whisperTarget, text, (Terminal)(object)Chat.instance); case Channel.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() : null); if (!((Object)(object)val == (Object)null)) { val.Say((Type)2, text); } } internal static void ApplyShoutRange(Talker talker) { talker.m_shoutDistance = talker.m_normalDistance * 2f; } } 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_0011: Unknown result type (might be due to invalid IL or missing references) new ConsoleCommand(name, GetDescription(name), handler, 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 (!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 (!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; } private static bool TryParseTarget(string fullLine, out string target, out string message) { target = ""; message = ""; 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 class PrivateChat { internal static bool Send(string targetName, string message, Terminal context) { //IL_0025: 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_005b: Unknown result type (might be due to invalid IL or missing references) if (!TryFindPlayer(targetName, out var player)) { if (context != null) { context.AddString("No connected player named \"" + targetName + "\" was found."); } return false; } if (IsLocalPlayer(player)) { if (context != null) { context.AddString("You cannot whisper yourself."); } return false; } SendToTarget(player, message); string name = Game.instance.GetPlayerProfile().GetName(); ChatFormatting.AddPrivate(context, name, "to " + player.m_name + ": " + message, timestamp: false); return true; } private static void SendToTarget(PlayerInfo target, string message) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) UserInfo val = default(UserInfo); string text = default(string); Chat.GetChatMessageData(message, true, ref val, ref text); ZRoutedRpc.instance.InvokeRoutedRPC(((ZDOID)(ref target.m_characterID)).UserID, "ChatMessage", new object[4] { ((Character)Player.m_localPlayer).GetHeadPoint(), 0, val, text }); } 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; } } internal static class ChatFormatting { private const string GroupColor = "#4A90E2"; private const string PrivateColor = "#2FAE5F"; private const string ShoutColor = "#FFFF00"; internal static string FormatGroup(string sender, string message) { return "" + sender + ": " + message + ""; } internal static void AddPrivate(Terminal terminal, string user, string text, bool timestamp) { terminal.AddString(GetTimestamp(timestamp) + "" + user + FormatPrivateText(text) + ""); } internal static void AddShout(Terminal terminal, string user, string text, bool timestamp) { terminal.AddString(GetTimestamp(timestamp) + "" + user + ": " + text + ""); } internal static void AddPing(Terminal terminal, string user, string target, string message) { string text = (string.IsNullOrEmpty(target) ? ": " : (" to " + target + ": ")); terminal.AddString("" + user + text + "((Ping)) " + 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 FormatPrivateText(string text) { if (!(text ?? "").StartsWith("to ", StringComparison.OrdinalIgnoreCase)) { return ": " + text; } return " " + text; } private static string GetTimestamp(bool enabled) { if (!enabled) { return ""; } return "[" + DateTime.Now.ToString("MM-dd-yyyy HH:mm:ss") + "] "; } } [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(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() { if (GroupService.IsLocalPlayerInGroup()) { return true; } SocializePlugin.Log.LogDebug("Map ping ignored because the local player is not in a group."); return false; } } [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_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 if ((int)type == 2) { SocialChatSender.SendShout(text); return false; } if ((int)type == 1) { return !ChatChannelState.TryRedirect(text); } return true; } } [HarmonyPatch(typeof(Talker), "Awake")] internal static class SocialShoutRangePatch { private static void Postfix(Talker __instance) { SocialChatSender.ApplyShoutRange(__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() ?? ((Component)((Terminal)chat).m_input).GetComponentInChildren(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 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; } 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_0024: Expected O, but got Unknown //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown //IL_0044: 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, (ConsoleOptionsFetcher)null, false, false, false); new ConsoleCommand("g", "[message] sends a message to your group.", new ConsoleEvent(HandleGroupChat), 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 (!IsValid(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 bool IsValid(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; } } private static void ShowHelp(Terminal context) { context.AddString("/group invite - Invites a connected player."); context.AddString("/group leave - Leaves your group."); context.AddString("/group remove - Removes a member."); context.AddString("/group promote - Promotes a member."); context.AddString("/group info - Lists group members."); context.AddString("/g - Sends a group message."); } } internal sealed class SocialGroup { internal int Id; internal long Leader; internal readonly Dictionary Members = new Dictionary(); } internal static class GroupState { internal const int MaximumSize = 5; internal static readonly Dictionary Groups = new Dictionary(); internal static readonly Dictionary PlayerGroups = new Dictionary(); internal static readonly Dictionary PeerPlayers = new Dictionary(); internal static readonly Dictionary Invitations = new Dictionary(); internal static readonly HashSet LocalMembers = new HashSet(); 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 static class GroupMapSharing { private static readonly Dictionary Players = new Dictionary(); 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 players) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) foreach (KeyValuePair player in Players) { if (!IsLocalPlayer(player.Key) && !Contains(players, player.Key)) { players.Add(player.Value); } } } 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 bool Contains(List players, long playerId) { //IL_000b: 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_0011: 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) //IL_0017: Unknown result type (might be due to invalid IL or missing references) foreach (PlayerInfo player in players) { ZDOID characterID = player.m_characterID; if (((ZDOID)(ref characterID)).UserID == playerId) { return true; } } return false; } private static bool IsLocalPlayer(long playerId) { if ((Object)(object)Game.instance != (Object)null) { return Game.instance.GetPlayerProfile().GetPlayerID() == playerId; } return false; } } internal static class GroupRpc { internal static void RPC_Request(long sender, ZPackage package) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && GroupStorage.TryLoad()) { string action = package.ReadString(); long playerId = package.ReadLong(); string playerName = package.ReadString(); string argument = package.ReadString().Trim(); GroupService.Dispatch(sender, playerId, playerName, action, argument); } } internal static void RPC_Response(long sender, ZPackage package) { GroupService.ReadResponse(package); } } internal static class GroupService { internal const string RequestRpc = "IronLabs_Social_GroupRequest"; internal const string ResponseRpc = "IronLabs_Social_GroupResponse"; private static ZRoutedRpc registeredRpc; private static float nextStateRequest; internal static void Update() { EnsureRpcs(); if (!((Object)(object)ZNet.instance == (Object)null) && ZRoutedRpc.instance != null) { if (ZNet.instance.IsServer()) { GroupStorage.TryLoad(); } if ((Object)(object)Player.m_localPlayer != (Object)null && Time.unscaledTime >= nextStateRequest) { nextStateRequest = Time.unscaledTime + 1f; SendRequest("state", ""); } } } internal static void Reset() { registeredRpc = null; nextStateRequest = 0f; GroupStorage.Reset(); GroupState.ClearAll(); } internal static bool IsLocalPlayerInGroup() { if ((Object)(object)Game.instance != (Object)null) { return GroupState.LocalMembers.Contains(Game.instance.GetPlayerProfile().GetPlayerID()); } return false; } internal static void SendChat(string message) { if (!IsLocalPlayerInGroup()) { Chat instance = Chat.instance; if (instance != null) { ((Terminal)instance).AddString("You are not in a group."); } } else { SendRequest("chat", message); } } internal static void SendRequest(string action, string argument) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown EnsureRpcs(); if (ZRoutedRpc.instance != null && !((Object)(object)Game.instance == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null)) { ZPackage val = new ZPackage(); val.Write(action); val.Write(Game.instance.GetPlayerProfile().GetPlayerID()); val.Write(Game.instance.GetPlayerProfile().GetName()); val.Write(argument ?? ""); ZRoutedRpc.instance.InvokeRoutedRPC("IronLabs_Social_GroupRequest", new object[1] { val }); } } internal static void Dispatch(long sender, long playerId, string playerName, string action, string argument) { GroupState.PeerPlayers[sender] = playerId; if (action == null) { return; } switch (action.Length) { case 5: switch (action[0]) { case 's': if (action == "state") { SendSnapshot(sender, playerId); } break; case 'l': if (action == "leave") { Leave(sender, playerId); } break; } break; case 6: switch (action[2]) { case 'v': if (action == "invite") { Invite(sender, playerId, playerName, argument); } break; case 'c': if (action == "accept") { Accept(sender, playerId, playerName, argument); } break; case 'j': if (action == "reject") { Reject(sender, playerId, argument); } break; case 'm': if (action == "remove") { Remove(sender, playerId, argument); } break; } break; case 4: switch (action[0]) { case 'i': if (action == "info") { SendInfo(sender, playerId); } break; case 'c': if (action == "chat") { SendGroupChat(sender, playerId, argument); } break; } break; case 7: if (action == "promote") { Promote(sender, playerId, argument); } 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 "snapshot": ReadSnapshot(package); break; case "invite": ShowInvite(package.ReadString(), package.ReadString()); break; } } private static void EnsureRpcs() { if (RpcRegistry.RegisterIfChanged(ref registeredRpc, RegisterRpcs)) { GroupStorage.Reset(); GroupState.ClearAll(); } } private static void RegisterRpcs(ZRoutedRpc rpc) { rpc.Register("IronLabs_Social_GroupRequest", (Action)GroupRpc.RPC_Request); rpc.Register("IronLabs_Social_GroupResponse", (Action)GroupRpc.RPC_Response); GameModeGroupIntegration.Register(rpc); } private static void Invite(long sender, long inviter, string inviterName, string targetName) { long num = FindPeerByName(targetName); if (!TryGetPlayerForPeer(num, out var playerId)) { SendMessage(sender, "Player not found or not ready."); } else if (CanInvite(sender, inviter, playerId)) { GroupState.Invitations[playerId] = inviter; ZPackage val = NewResponse("invite"); val.Write(inviter.ToString()); val.Write(inviterName); ZRoutedRpc.instance.InvokeRoutedRPC(num, "IronLabs_Social_GroupResponse", new object[1] { val }); SendMessage(sender, "Group invitation sent."); } } private static bool CanInvite(long sender, long inviter, long target) { SocialGroup socialGroup = GroupState.GetGroup(inviter); if (GroupState.PlayerGroups.ContainsKey(target)) { SendMessage(sender, "That player is already in a group."); return false; } if (socialGroup != null && socialGroup.Leader != inviter) { SendMessage(sender, "Only the group leader can invite players."); return false; } if (socialGroup != null && socialGroup.Members.Count >= 5) { SendMessage(sender, "Your group is full."); return false; } return inviter != target; } private static void Accept(long sender, long playerId, string playerName, string inviterText) { if (!long.TryParse(inviterText, out var result) || !GroupState.Invitations.TryGetValue(playerId, out var value) || value != result) { SendMessage(sender, "That group invitation is no longer valid."); return; } SocialGroup orCreateGroup = GetOrCreateGroup(result); if (orCreateGroup == null || orCreateGroup.Members.Count >= 5) { SendMessage(sender, "That group is no longer available."); return; } orCreateGroup.Members[playerId] = playerName; GroupState.PlayerGroups[playerId] = orCreateGroup.Id; GroupState.Invitations.Remove(playerId); SaveAndBroadcast(orCreateGroup, 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.Members[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) { 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]; socialGroup.Members.Remove(playerId); GroupState.PlayerGroups.Remove(playerId); NormalizeAfterRemoval(socialGroup); GroupStorage.Save(); SendMessage(sender, "You left the group."); Broadcast(socialGroup, text + " 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]; socialGroup.Members.Remove(num); GroupState.PlayerGroups.Remove(num); NormalizeAfterRemoval(socialGroup); GroupStorage.Save(); SendMessage(FindPeer(num), "You were removed from the group."); Broadcast(socialGroup, text + " was 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); if (ValidateLeaderAction(sender, actor, targetName, socialGroup, num) && ValidatePromoteTarget(sender, actor, num)) { socialGroup.Leader = num; GroupStorage.Save(); 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) { if (group == null) { SendMessage(sender, "You are not in a group."); return false; } if (group.Leader != actor) { SendMessage(sender, "Only the group leader can do that."); return false; } if (target == 0L) { SendMessage(sender, "Player not found in your group: " + targetName); return false; } return true; } private static bool ValidateRemoveTarget(long sender, long actor, long target) { if (target != actor) { return true; } SendMessage(sender, "You cannot remove yourself."); return false; } private static bool ValidatePromoteTarget(long sender, long actor, long target) { if (target != actor) { return true; } SendMessage(sender, "You are already group leader."); return false; } private static void NormalizeAfterRemoval(SocialGroup group) { if (group.Members.Count <= 1) { foreach (long key in group.Members.Keys) { GroupState.PlayerGroups.Remove(key); SendMessage(FindPeer(key), "The group was disbanded."); SendSnapshot(FindPeer(key), key); } GroupState.Groups.Remove(group.Id); group.Members.Clear(); } else { if (group.Members.ContainsKey(group.Leader)) { return; } using Dictionary.KeyCollection.Enumerator enumerator = group.Members.Keys.GetEnumerator(); if (enumerator.MoveNext()) { long current2 = enumerator.Current; group.Leader = current2; } } } private static void SendGroupChat(long sender, long actor, string message) { SocialGroup socialGroup = GroupState.GetGroup(actor); if (socialGroup == null) { SendMessage(sender, "You are not in a group."); } else if (!HasOtherOnlineMember(socialGroup, actor)) { SendMessage(sender, "No other group member is connected."); } else { Broadcast(socialGroup, ChatFormatting.FormatGroup(socialGroup.Members[actor], message)); } } private static bool HasOtherOnlineMember(SocialGroup group, long actor) { foreach (long key in group.Members.Keys) { if (key != actor && FindPeer(key) != 0L) { return true; } } return false; } private static void SendInfo(long sender, long playerId) { SocialGroup socialGroup = GroupState.GetGroup(playerId); if (socialGroup == null) { SendMessage(sender, "You are not in a group."); return; } List list = new List { "Group members:" }; foreach (KeyValuePair member in socialGroup.Members) { string text = ((FindPeer(member.Key) != 0L) ? "Connected" : "Disconnected"); string text2 = ((member.Key == socialGroup.Leader) ? " - Group Leader" : ""); list.Add(member.Value + " - " + text + text2); } SendMessage(sender, string.Join("\n", list)); } private static void SaveAndBroadcast(SocialGroup group, string message) { GroupStorage.Save(); Broadcast(group, message); BroadcastSnapshots(group); SocializePlugin.Log.LogInfo(message); } private static void BroadcastSnapshots(SocialGroup group) { if (group == null) { return; } foreach (long key in group.Members.Keys) { SendSnapshot(FindPeer(key), key); } } 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 member in socialGroup.Members) { val.Write(member.Key); val.Write(member.Value); GroupMapSharing.WritePosition(val, member.Key); } } ZRoutedRpc.instance.InvokeRoutedRPC(peer, "IronLabs_Social_GroupResponse", new object[1] { val }); } private static void ReadSnapshot(ZPackage package) { 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); } } private static void ShowInvite(string inviterId, string inviterName) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown //IL_003b: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown UnifiedPopup.Push((PopupBase)new YesNoPopup("Group invitation", inviterName + " invited you to a group.", (PopupButtonCallback)delegate { RespondToInvite("accept", inviterId); }, (PopupButtonCallback)delegate { RespondToInvite("reject", inviterId); }, false)); } 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 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, "IronLabs_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 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 peerPlayer in GroupState.PeerPlayers) { if (peerPlayer.Value == playerId) { 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 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 static class GroupStorage { private const string PrefabName = "IronLabsSocialGroupStorage"; private const string DataKey = "IronLabs.Social.Groups"; private static ZDO storage; internal static bool TryLoad() { if (storage != null) { return true; } storage = FindOrCreate(); if (storage == null) { return false; } Load(storage.GetString("IronLabs.Social.Groups", "")); return true; } internal static void Save() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown if (storage == null) { return; } ZPackage val = new ZPackage(); val.Write(GroupState.Groups.Count); foreach (SocialGroup value in GroupState.Groups.Values) { WriteGroup(val, value); } storage.Set("IronLabs.Social.Groups", Convert.ToBase64String(val.GetArray())); GameModeGroupIntegration.Broadcast(); } internal static void Reset() { storage = null; } private static ZDO FindOrCreate() { if (ZDOMan.instance == null || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return null; } List list = new List(); int num = 0; while (!ZDOMan.instance.GetAllZDOsWithPrefabIterative("IronLabsSocialGroupStorage", list, ref num)) { } if (list.Count <= 0) { return Create(); } return list[0]; } private static ZDO Create() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) int stableHashCode = StringExtensionMethods.GetStableHashCode("IronLabsSocialGroupStorage"); Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(1000000f, -10000f, 1000000f); ZDO obj = ZDOMan.instance.CreateNewZDO(val, stableHashCode); obj.Persistent = true; obj.Distant = true; obj.SetPrefab(stableHashCode); SocializePlugin.Log.LogInfo("Created persistent social group storage for the world."); return obj; } private static void Load(string encoded) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown GroupState.ClearServer(); if (string.IsNullOrEmpty(encoded)) { return; } try { ReadGroups(new ZPackage(Convert.FromBase64String(encoded))); SocializePlugin.Log.LogInfo($"Loaded {GroupState.Groups.Count} social group(s)."); } catch (Exception ex) { SocializePlugin.Log.LogError("Could not load social groups: " + ex); } } private static void ReadGroups(ZPackage package) { int num = package.ReadInt(); for (int i = 0; i < num; i++) { SocialGroup socialGroup = ReadGroup(package); GroupState.Groups[socialGroup.Id] = socialGroup; } } private static SocialGroup ReadGroup(ZPackage package) { SocialGroup socialGroup = new SocialGroup { Id = package.ReadInt(), Leader = package.ReadLong() }; int num = package.ReadInt(); for (int i = 0; i < num; i++) { long key = package.ReadLong(); socialGroup.Members[key] = package.ReadString(); GroupState.PlayerGroups[key] = socialGroup.Id; } return socialGroup; } private static void WriteGroup(ZPackage package, SocialGroup group) { package.Write(group.Id); package.Write(group.Leader); package.Write(group.Members.Count); foreach (KeyValuePair member in group.Members) { package.Write(member.Key); package.Write(member.Value); } } } internal static class RpcRegistry { internal static bool RegisterIfChanged(ref ZRoutedRpc registered, Action 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.UpdatePingVisibility(__instance); } } [HarmonyPatch(typeof(ZNet), "SetPublicReferencePosition")] internal static class DisablePublicPositionPatch { private static void Prefix(ref bool pub) { pub = false; } } [HarmonyPatch(typeof(ZNet), "GetOtherPublicPlayers")] internal static class ShowGroupMembersOnMapPatch { private static void Postfix(List playerList) { GroupMapSharing.AddGroupMembers(playerList); } } [HarmonyPatch(typeof(Minimap), "Start")] internal static class HidePublicPositionTogglePatch { private static void Postfix(Minimap __instance) { Toggle publicPosition = __instance.m_publicPosition; if ((Object)(object)publicPosition != (Object)null) { publicPosition.isOn = false; ((Component)publicPosition).gameObject.SetActive(false); HideDedicatedContainer(__instance, publicPosition); } UpdatePingVisibility(__instance); } internal static void UpdatePingVisibility(Minimap minimap) { if (!((Object)(object)minimap.m_pingImageObject == (Object)null)) { GetPingButton(minimap).SetActive(GroupService.IsLocalPlayerInGroup()); } } private static GameObject GetPingButton(Minimap minimap) { Button componentInParent = ((Component)minimap.m_pingImageObject).GetComponentInParent