Exemplo n.º 1
0
        /// <summary>
        /// Adds a command to the collection.
        /// </summary>
        public void AddCommand(PluginCommand pluginCommand)
        {
            lock (_lock)
            {
                // First check for duplicate command names.
                foreach (var command in Commands)
                {
                    string duplicateCommandName = null;
                    foreach (var commandName1 in pluginCommand.CommandNames)
                    {
                        foreach (var commandName2 in command.CommandNames)
                        {
                            if (string.Equals(commandName1, commandName2, StringComparison.CurrentCultureIgnoreCase))
                            {
                                VChatPlugin.LogError($"Command with the name \"{Prefix}{duplicateCommandName}\" already exists, types {command.Type} and {pluginCommand.Type}.");
                                duplicateCommandName = commandName1;
                                break;
                            }
                        }
                    }
                }

                // Add the command, we currently don't care if a duplicate command name is found.
                _commands.Add(pluginCommand);
            }
        }
Exemplo n.º 2
0
        /// <summary>
        /// When the client receives a greeting from the server.
        /// This makes us aware that the server is running VChat.
        /// </summary>
        private static void OnClientMessage(long senderId, string version)
        {
            if (senderId == ZNet.instance.GetServerPeer()?.m_uid)
            {
                VChatPlugin.Log($"Received a greeting from the server ({senderId}) that's running on {VChatPlugin.Name} {version}.");

                // Property to determine if both sides have VChat installed.
                if (!HasLocalPlayerReceivedGreetingFromServer)
                {
                    HasLocalPlayerReceivedGreetingFromServer = true;
                    ServerVersion = version;
                }

                // Send a response to the server if we haven't yet done so.
                if (!HasLocalPlayerGreetedToServer)
                {
                    SendToServer();
                    HasLocalPlayerGreetedToServer = true;
                }
            }
            else
            {
                VChatPlugin.Log($"Ignoring a greeting received from a client with id {senderId} and version {version}.");
            }
        }
Exemplo n.º 3
0
        public static void Prefix(ref Player __instance)
        {
            // This is only for clients. Add a welcome message to the chat box displaying the status of VChat.
            if (!HasSentServerPluginStatusMessage)
            {
                var chat = Chat.instance;
                if (chat != null)
                {
                    // Notify the server connection status.
                    if (GreetingMessage.HasLocalPlayerGreetedToServer && GreetingMessage.HasLocalPlayerReceivedGreetingFromServer)
                    {
                        chat.AddString($"<color=lime>[{VChatPlugin.Name}] Connected to the server-wide instance of {VChatPlugin.Name}, server version of {GreetingMessage.ServerVersion}.</color>");
                    }
                    else if (GreetingMessage.HasLocalPlayerGreetedToServer && !GreetingMessage.HasLocalPlayerReceivedGreetingFromServer)
                    {
                        chat.AddString($"<color=white>[{VChatPlugin.Name}] was not found on the server, messages sent to the global chat can only be seen by players with {VChatPlugin.Name} installed.</color>");
                    }
                    else
                    {
                        chat.AddString($"<color=red>[{VChatPlugin.Name}] Has encountered an issue greeting the server.</color>");
                    }
                }
                else
                {
                    VChatPlugin.LogError($"Could not write the VChat welcome message because Chat is not yet initialised.");
                }

                HasSentServerPluginStatusMessage = true;
            }
        }
Exemplo n.º 4
0
        private static bool Prefix(ref Chat __instance, ref string user, ref string text, ref Talker.Type type)
        {
            // Pings should not add a message to the chat.
            if (type == Talker.Type.Ping)
            {
                return(false);
            }

            __instance.AddString(VChatPlugin.GetFormattedMessage(new CombinedMessageType(type), user, text));
            return(false);
        }
Exemplo n.º 5
0
 public static void Prefix(ref ZNet __instance)
 {
     // Reset the server greeting variables when the client disconnects.
     // These will be set once the client connects to a server.
     if (!ZNet.m_isServer)
     {
         VChatPlugin.Log("Resetting variables for local player");
         PlayerPatchOnSpawned.HasSentServerPluginStatusMessage = false;
         GreetingMessage.ResetClientVariables();
     }
 }
Exemplo n.º 6
0
 /// <summary>
 /// Register the global message commands, this should be called when ZNet initialises.
 /// </summary>
 public static void Register()
 {
     VChatPlugin.Log($"Registering custom routed messages for global chat.");
     if (ZNet.m_isServer)
     {
         ZRoutedRpc.instance.Register(GlobalChatHashName, new RoutedMethod <Vector3, int, string, string>(OnGlobalMessage_Server).m_action);
     }
     else
     {
         ZRoutedRpc.instance.Register(GlobalChatHashName, new RoutedMethod <Vector3, int, string, string>(OnGlobalMessage_Client).m_action);
     }
 }
Exemplo n.º 7
0
 /// <summary>
 /// Register the global message commands, this should be called when ZNet initialises.
 /// </summary>
 public static void Register()
 {
     VChatPlugin.Log($"Registering custom routed messages for greetings.");
     if (ZNet.m_isServer)
     {
         ZRoutedRpc.instance.Register(GreetingHashName, new RoutedMethod <string>(OnServerMessage).m_action);
     }
     else
     {
         ZRoutedRpc.instance.Register(GreetingHashName, new RoutedMethod <string>(OnClientMessage).m_action);
     }
 }
Exemplo n.º 8
0
 /// <summary>
 /// Send a VChat greeting message to the server
 /// </summary>
 public static void SendToServer()
 {
     if (!ZNet.m_isServer)
     {
         var parameters = new object[] { VChatPlugin.Version };
         ZRoutedRpc.instance.InvokeRoutedRPC(ZNet.instance.GetServerPeer().m_uid, GreetingHashName, parameters);
         HasLocalPlayerGreetedToServer = true;
     }
     else
     {
         VChatPlugin.LogError($"Cannot send the greeing to ourself, are we missing a server check?");
     }
 }
Exemplo n.º 9
0
        public static bool Prefix(ref ZRoutedRpc __instance, ref RoutedRPCData data)
        {
            if (ZNet.m_isServer && data?.m_methodHash == GlobalMessages.TalkerSayHashCode)
            {
                // Read local say chat messages for users not connected to VChat.
                // Messages that fit the global chat command name will be redirected as global chat messages.
                if (GreetingMessage.PeerInfo.TryGetValue(data.m_senderPeerID, out GreetingMessagePeerInfo peerInfo) &&
                    !peerInfo.HasReceivedGreeting)
                {
                    try
                    {
                        var senderPeer = ZNet.instance.GetPeer(data.m_senderPeerID);
                        var package    = new ZPackage(data.m_parameters.GetArray());
                        var ctype      = package.ReadInt();
                        var playerName = package.ReadString();
                        var text       = package.ReadString();

                        if (ctype == (int)Talker.Type.Normal)
                        {
                            var globalChatCommand = VChatPlugin.CommandHandler.FindCommand(PluginCommandType.SendGlobalMessage);
                            if (VChatPlugin.CommandHandler.IsValidCommandString(text, globalChatCommand, out text))
                            {
                                VChatPlugin.Log($"Redirecting local message to global chat from peer {data.m_senderPeerID} \"({senderPeer?.m_playerName})\" with message \"{text}\".");

                                // Redirect this message to the global chat channel.
                                foreach (var peer in ZNet.instance.GetConnectedPeers())
                                {
                                    // Exclude the sender, otherwise it'd probably just be annoying.
                                    if (peer.m_uid != data.m_senderPeerID)
                                    {
                                        GlobalMessages.SendGlobalMessageToPeer(peer.m_uid, (int)GlobalMessageType.RedirectedGlobalMessage, senderPeer?.m_refPos ?? new Vector3(), senderPeer?.m_playerName ?? playerName, text);
                                    }
                                }

                                // Intercept message so that other connected users won't receive the same message twice.
                                data.m_methodHash = GlobalMessages.InterceptedSayHashCode;
                                return(false);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        VChatPlugin.LogError($"Error reading Talker.Say message for unconnected VChat user ({data.m_senderPeerID}): {ex}");
                    }
                }
            }

            return(true);
        }
Exemplo n.º 10
0
        /// <summary>
        /// Attempt to find the command based on the type, this should technically never fail.
        /// </summary>
        public PluginCommand FindCommand(PluginCommandType type)
        {
            lock (_lock)
            {
                foreach (var command in Commands)
                {
                    if (command.Type == type)
                    {
                        return(command);
                    }
                }
            }

            VChatPlugin.LogError($"Could not find the command for {type}, is this a new unhandled command?");
            return(null);
        }
Exemplo n.º 11
0
 /// <summary>
 /// Triggered when the server receives a global message from a client .
 /// </summary>
 /// <remarks>Player name and position are primarily here for future work, player aliases for example.</remarks>
 /// <param name="senderId">The peer id of the sender</param>
 /// <param name="pos">The reported position</param>
 /// <param name="type">Reserved for future use</param>
 /// <param name="callerName">The reported player name</param>
 /// <param name="text">the message, without a playername or formatting.</param>
 private static void OnGlobalMessage_Server(long senderId, Vector3 pos, int type, string callerName, string text)
 {
     if (senderId != ZNet.instance.GetServerPeer()?.m_uid)
     {
         var globalMessageType = (GlobalMessageType)type;
         if (globalMessageType == GlobalMessageType.StandardMessage || globalMessageType == GlobalMessageType.RedirectedGlobalMessage)
         {
             try
             {
                 // Sender should always be found but who knows what can happen within a few milliseconds, though I bet its still cached should that player disconnect.. safety first.
                 // We simply apply the position and player name the server knows rather than the reported values first.
                 var peer = ZRoutedRpc.instance?.GetPeer(senderId);
                 if (peer?.m_server == false)
                 {
                     // Loop through every connected peer and redirect the received message, including the original sender because the code is currently set so that the client knows that it's been sent.
                     foreach (var connectedPeer in ZNet.instance.GetConnectedPeers())
                     {
                         if (connectedPeer != null && !connectedPeer.m_server && connectedPeer.IsReady() && connectedPeer.m_socket?.IsConnected() == true)
                         {
                             VChatPlugin.Log($"Routing global message to peer {connectedPeer.m_uid} \"({connectedPeer.m_playerName})\" with message \"{text}\".");
                             SendGlobalMessageToPeer(connectedPeer.m_uid, type, peer?.m_refPos ?? pos, peer?.m_playerName ?? callerName, text);
                         }
                     }
                 }
                 else
                 {
                     VChatPlugin.LogWarning($"Received a global chat message from a peer identified as a server, id {senderId} \"{peer.m_playerName}\"");
                 }
             }
             catch (Exception ex)
             {
                 VChatPlugin.LogError($"Failed to InvokeRoutedRPC for global message ({senderId}|{text}): {ex}");
             }
         }
         else
         {
             VChatPlugin.LogWarning($"A global message type with value of {type} could not be parsed. Please check if there are any updates available.");
         }
     }
     else
     {
         VChatPlugin.LogWarning($"Received a greeting from a peer with the server id...");
     }
 }
Exemplo n.º 12
0
        private static void Prefix(ref Chat __instance, ref Chat.WorldTextInstance wt)
        {
            // Apply floating text and colour.
            // This item gets added in Chat.AddInworldText, but there's a lot of code to handle if I were to intercept it, Postfix on that method works too but for I feel like the update method makes sense.
            foreach (var messageInfoPair in VChatPlugin.ReceivedMessageInfo.ToList())
            {
                var messageInfo = messageInfoPair.Value;
                if (messageInfo?.Equals(wt) == true)
                {
                    wt.m_text            = messageInfo.Text;
                    wt.m_textField.color = VChatPlugin.GetTextColor(new CombinedMessageType(wt.m_type));
                    // Not required but setting the floating text anyway.
                    wt.m_textField.text = messageInfo.Text;

                    VChatPlugin.ReceivedMessageInfo.TryRemove(messageInfoPair.Key, out UserMessageInfo _);
                    return;
                }
            }
        }
Exemplo n.º 13
0
        public static void Prefix(ref ZNet __instance, ref ZRpc rpc, ref ZDOID characterID)
        {
            // Client sends the character id to the server before it spawns.
            if (ZNet.m_isServer)
            {
                ZNetPeer peer = __instance.GetPeer(rpc);
                if (peer != null)
                {
                    if (!SpawnedPlayers.Contains(peer.m_uid))
                    {
                        bool isPlayerConnectedWithVChat = false;
                        if (GreetingMessage.PeerInfo.TryGetValue(peer.m_uid, out GreetingMessagePeerInfo peerInfo))
                        {
                            isPlayerConnectedWithVChat = peerInfo.HasReceivedGreeting;
                        }

                        // Send a local chat message to the player if it does not have VChat installed.
                        if (!isPlayerConnectedWithVChat)
                        {
                            VChatPlugin.Log($"Player \"{peer.m_playerName}\" ({peer.m_uid}) spawned that does not have VChat installed, sending mod information...");

                            var messages = new[]
                            {
                                $"This server runs {VChatPlugin.Name} {VChatPlugin.Version}, We detected that you do not have this mod installed.",
                                $"You can find the latest version on {VChatPlugin.RepositoryUrl}",
                                "Global chat messages will be sent in your local chat channel.",
                                $"Type {VChatPlugin.Settings.CommandPrefix}{VChatPlugin.Settings.GlobalChatCommandName.FirstOrDefault()} [text] to send a message to the global chat.",
                            };

                            foreach (var message in messages)
                            {
                                object[] parameters = new object[] { peer.GetRefPos(), (int)Talker.Type.Normal, VChatPlugin.Name, message };
                                ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "ChatMessage", parameters);
                            }
                        }

                        SpawnedPlayers.Add(peer.m_uid);
                    }
                }
            }
        }
Exemplo n.º 14
0
 /// <summary>
 /// Triggered when the client receives a global message from the server.
 /// </summary>
 /// <remarks>Player name and position are primarily here for future work, player aliases for example.</remarks>
 /// <param name="senderId">The peer id of the sender</param>
 /// <param name="pos">The position</param>
 /// <param name="type">Reserved for future use</param>
 /// <param name="playerName">The player name</param>
 /// <param name="text">the message, without a playername or formatting.</param>
 private static void OnGlobalMessage_Client(long senderId, Vector3 pos, int type, string playerName, string text)
 {
     // If the client is connected to a server-sided instance of VChat then only accept messages from the server.
     if (!GreetingMessage.HasLocalPlayerReceivedGreetingFromServer || senderId == ZNet.instance.GetServerPeer()?.m_uid)
     {
         VChatPlugin.Log($"Received a global message from {playerName} ({senderId}) on location {pos} with message \"{text}\".");
         if (Chat.instance != null)
         {
             var formattedMessage = VChatPlugin.GetFormattedMessage(new CombinedMessageType(CustomMessageType.Global), playerName, text);
             Chat.instance?.AddString(formattedMessage);
         }
         else
         {
             VChatPlugin.LogWarning($"Received a message but Chat instance is undefined.");
         }
     }
     else
     {
         VChatPlugin.LogWarning($"Ignoring a global message received from a client, reported values: {senderId} \"{playerName}\" on location {pos} with message \"{text}\".");
     }
 }
Exemplo n.º 15
0
        /// <summary>
        /// Get the latest github release tag name from a repository.
        /// </summary>
        /// <param name="author">The owner of the repository</param>
        /// <param name="repositoryName">The repository name</param>
        /// <param name="isPrerelease">Returns if the found release is a pre-release</param>
        /// <param name="includePrerelease">Whether or not prereleases should be returned</param>
        /// <returns>The tag name of the release</returns>
        public static string GetLatestGithubRelease(string author, string repositoryName, out bool isPrerelease, bool includePrerelease = false)
        {
            isPrerelease = false;
            try
            {
                var webClient = new WebClient();
                webClient.Headers.Add("User-Agent: VChat-version-checker");
                var jsonString = webClient.DownloadString($"{GithubApiBaseUri}/repos/{author}/{repositoryName}/releases");

                // Bit hacky but this way I won't have to include a json library.
                while (true)
                {
                    int pos = 0;

                    // Get the version name, aka the tag.
                    var tagName = jsonString.Between("\"tag_name\":\"", "\"", out pos, pos);
                    if (string.IsNullOrEmpty(tagName))
                    {
                        return(null);
                    }

                    // See if this is a prerelease.
                    var isPrereleaseString = jsonString.Between("\"prerelease\":", ",", out pos, pos);
                    bool.TryParse(isPrereleaseString, out isPrerelease);
                    if (includePrerelease || !isPrerelease)
                    {
                        return(tagName);
                    }

                    jsonString = new string(jsonString.Skip(pos).ToArray());
                }
            }
            catch (Exception ex)
            {
                VChatPlugin.LogWarning($"Unable to find the github release for {author}/{repositoryName}: {ex}");
            }

            return(null);
        }
Exemplo n.º 16
0
        /// <summary>
        /// When the server receives a greeting from the client.
        /// This makes us aware that the user has VChat installed.
        /// </summary>
        private static void OnServerMessage(long senderId, string version)
        {
            if (senderId != ZNet.instance.GetServerPeer()?.m_uid)
            {
                var peer = ZRoutedRpc.instance?.GetPeer(senderId);
                if (peer != null)
                {
                    VChatPlugin.Log($"Greeting received from client \"{peer?.m_playerName}\" ({senderId}) with version {version}.");
                    GreetingMessagePeerInfo peerInfo;
                    if (PeerInfo.TryGetValue(senderId, out GreetingMessagePeerInfo previousGreeting))
                    {
                        peerInfo                     = previousGreeting;
                        peerInfo.Version             = version;
                        peerInfo.HasReceivedGreeting = true;
                    }
                    else
                    {
                        peerInfo = new GreetingMessagePeerInfo()
                        {
                            PeerId              = senderId,
                            Version             = version,
                            HasReceivedGreeting = true,
                            HasSentGreeting     = false,
                        };
                    }

                    PeerInfo.AddOrUpdate(senderId, peerInfo, (long oldKey, GreetingMessagePeerInfo oldValue) => peerInfo);
                }
                else
                {
                    VChatPlugin.LogWarning($"Received greeting from an unconnected peer with id {senderId}.");
                }
            }
            else
            {
                VChatPlugin.LogWarning($"Received a greeting from a peer with the server id...");
            }
        }
Exemplo n.º 17
0
        /// <summary>
        /// Send a global message to the server.
        /// </summary>
        public static void SendGlobalMessageToServer(string text, GlobalMessageType type = GlobalMessageType.StandardMessage)
        {
            var localPlayer = Player.m_localPlayer;

            if (localPlayer != null)
            {
                long peerId = ZRoutedRpc.Everybody;

                // If server-sided VChat is found, send the message directly to the server.
                var serverPeer = ZNet.instance?.GetServerPeer();
                if (GreetingMessage.HasLocalPlayerReceivedGreetingFromServer && serverPeer != null)
                {
                    peerId = ZNet.instance.GetServerPeer().m_uid;
                }

                var parameters = new object[] { localPlayer.GetHeadPoint(), (int)type, localPlayer.GetPlayerName(), text };
                ZRoutedRpc.instance.InvokeRoutedRPC(peerId, GlobalChatHashName, parameters);
            }
            else
            {
                VChatPlugin.LogError($"Could not send global message because the player is undefined (text: \"{text}\").");
            }
        }
Exemplo n.º 18
0
        /// <summary>
        /// Send a VChat greeting message to a client, this should only be called on a server.
        /// </summary>
        public static void SendToClient(long peerId)
        {
            if (ZNet.m_isServer)
            {
                var parameters = new object[] { VChatPlugin.Version };
                ZRoutedRpc.instance.InvokeRoutedRPC(peerId, GreetingHashName, parameters);

                // Add or update peer info
                GreetingMessagePeerInfo peerInfo;
                if (PeerInfo.TryGetValue(peerId, out GreetingMessagePeerInfo previousGreeting))
                {
                    if (previousGreeting.HasSentGreeting)
                    {
                        VChatPlugin.LogWarning($"Player \"{ZNet.instance.GetPeer(peerId)?.m_playerName}\" ({peerId}) has already been greeted, but sending anyway.");
                    }

                    peerInfo = previousGreeting;
                    peerInfo.HasSentGreeting = true;
                }
                else
                {
                    peerInfo = new GreetingMessagePeerInfo()
                    {
                        PeerId              = peerId,
                        Version             = null,
                        HasReceivedGreeting = false,
                        HasSentGreeting     = true,
                    };
                }

                PeerInfo.AddOrUpdate(peerId, peerInfo, (long oldKey, GreetingMessagePeerInfo oldValue) => peerInfo);
            }
            else
            {
                VChatPlugin.LogWarning($"Cannot send the greeing to a client.");
            }
        }
Exemplo n.º 19
0
        private static void Postfix(ref Chat __instance)
        {
            var chat = __instance;

            // Listen on value changed, this updated the input field and carrot text color to match the targeted channel.
            __instance.m_input.onValueChanged.AddListener((text) =>
            {
                if (chat.m_input != null)
                {
                    VChatPlugin.UpdateCurrentChatTypeAndColor(chat.m_input, text);
                }
            });

            // Listen when the chat field is closed, this resets the position of the message history (arrow up & down handler)
            __instance.m_input.onEndEdit.AddListener((text) =>
            {
                if (string.IsNullOrEmpty(chat.m_input.text))
                {
                    VChatPlugin.MessageSendHistoryIndex = 0;
                }
            });

            // Enable chat window click-through.
            if (VChatPlugin.Settings.EnableClickThroughChatWindow && __instance.m_chatWindow != null)
            {
                __instance.m_chatWindow.ChangeClickThroughInChildren(VChatPlugin.Settings.EnableClickThroughChatWindow);
            }

            // Set the hide delay.
            __instance.m_hideDelay = VChatPlugin.Settings.ChatHideDelay;

            // Update the input colour since we may not be on local.
            VChatPlugin.UpdateChatInputColor(__instance.m_input, VChatPlugin.LastChatType);

            // Get the latest release of VChat and notify the user when the chat is starting up for the first time.
            var latestReleaseVersion = GithubHelper.GetLatestGithubRelease(VChatPlugin.RepositoryAuthor, VChatPlugin.RepositoryName);

            if (!string.IsNullOrEmpty(latestReleaseVersion))
            {
                List <string> messages = new();
                if (VersionHelper.IsNewerVersion(VChatPlugin.Version, latestReleaseVersion))
                {
                    messages.AddRange(new[] {
                        $"You are running on an older version of {VChatPlugin.Name} ({VChatPlugin.Version}).",
                        $"version {latestReleaseVersion} has been released, see {VChatPlugin.RepositoryUrl}",
                    });
                }
                else
                {
                    messages.Add($"{VChatPlugin.Name} {VChatPlugin.Version} is loaded and up to date.");
                }

                foreach (var msg in messages)
                {
                    __instance.AddString($"[{VChatPlugin.Name}] {msg}");
                }
            }

            // Reset the server plugin status as we may have changed server from a single player world to a dedicated server.
            PlayerPatchOnSpawned.HasSentServerPluginStatusMessage = false;
        }