SP MP Native Online Chat for Singeplayer

Users who are viewing this thread

I have been working on my own network architecture to use on my various projects. This mod has been a creation based on that network architecture. In fact, it runs on it.

With this mod enabled your are connected into a global network architecture. Within this architecture you can speak with everyone who is also inside the network at the same time as you. This mod brings your single player experience a little bit close to multiplayer.

Are you stuck at some specific mission? Ask it through the global chat instantaneously.
Do you have any question regarding the game? You may ask it through the global chat instantaneously.
Or are you bored / waiting your the besieged to starve to death? Just chat your time out.


Brought to you by your family friendly, lovely developer Emre Buğday



First I have decided to develop this mod only to test the TCP side of my network architecture to see how well it functions under huge volume of connections etc. Than it grew into a passion project. The server is running on DigitalOcean and utilizes only TCP if anyone wonders.
There is no P2P connection in the mod as this is built upon a architecture that I have been developing for multiplayer games. Server handles all of the connections and data. It receives the message and processes it than sends it back to other clients that are connected. Nothing comes in and out without entering the server. This is implemented first and foremost for security as in game development you can never let the client have any control to prevent cheating.


Here is the mod link:


Source Code:
 
Last edited:
Planned Features:
  • Moderation tools and perms
    • This issue has to be tackled as early as possible to prevent any toxicity. This is my first priority at the moment.
  • Implementing UI
    • This the second highest in the priority list. I couldn't understand how bannerlord handles UI even though I checked their source code and tried to reverse engineer most of the stuff. If I can figure out how to do some UI on Bannerlord than I can start implement a lot quality of life improvements into the mod itself.
  • Chat rooms
    • Simple as it sounds. If this mod goes off in active users than we will definitely need chat rooms to prevent chaos. Especially for language specific chatting.
  • Commands:
    • /help :To see all the commands
    • /block : To not receive any messages from specific person
    • [DONE]/whoisonline :To see the names of the online players
    • [DONE]/onlinecount
    • [DONE]/pm :To send a private message to someone.
    • [DONE] /rename new name: to change your name.
  • [DONE]Switch to Tmux from Screen for terminal instance handling. Screen seems to be not outputting .net Core outputs correctly.



ISSUE LIST:
  • [SOLVED]Server does not output logs to the console if it is running on a GNU Screen instance. This makes debugging almost impossible. This needs to be resolved ASAP.
  • [SOLVED]Server seemed to have crashed today approximately at 1:30PM GMT+03:00 but due to GNU Screen instance not outputting .NET Core console logs I simple don't know the god damn reason.



20.04.2020 09:46 GMT+3:
Major improvements regarding server security. Still has some long way to go. Check out the GitHub page if you want to help me make it even more secure!
 
Last edited:
  • Therefore I need to figure out a way on how to send PMs uniquely. I would really appreciate advice regarding this.

Associate an ID to each client. When you want to PM you send it to the client ID. You can achieve that by assigning an incrementing ID for each connection (whatch out for overflow), or CRC32 for IP + Port of your client.
 
Associate an ID to each client. When you want to PM you send it to the client ID. You can achieve that by assigning an incrementing ID for each connection (whatch out for overflow), or CRC32 for IP + Port of your client.

I am already using unqiue identifiers to identify users at the server logic. I simply use their ip+port as their unique identifier as it is the most unique thing that I can get lol.
My main design issue regarding PM's is lets say that I want to send a PM to you I need to type /pm Ra'Jiska but what if there are multiple people with that name. I don't want to reserve user names as that would require user authentication and I dont want to store passwords on my server.

Edit: Oh I understood what you said now. Okay. You are saying that people should type the incrementing int as their /pm [identifier]. That makes sense. How couldn't I think that lol. Simple solution thanks m8
 
No problem. Is the server side code no more open source ? Wanted to check a few things :smile:

Yeah, I am planning to turn the server side into a commercial project for multiplayer games in the future(after implementing UDP and after making everything %100 async). Until I can find a proper licence which prevents people from using the server for commercial purposes without my permission I plan to keep it private for a while.

Edit: Never mind, I made it public. It's still really on early stages so it wouldn't matter a thing. I really stuck at this legal stuff so it will probably take ages for me to find a proper licence I might as well keep it public in these early development stages...
Here is the repo https://github.com/EmreB99/InteractiveSync
 
Last edited:
Yeah, I am planning to turn the server side into a commercial project for multiplayer games in the future(after implementing UDP and after making everything %100 async). Until I can find a proper licence which prevents people from using the server for commercial purposes without my permission I plan to keep it private for a while. I am not really good with legal stuff and GitHub's standard license lists does not seem to provide such a licence... lol

But in the meantime you can fire away any questions you have ^^

I see. Well, I was more interested in keeping an eye on the security and vulnerability aspect of the project. Concerning your licensing problem, I don't know much about it but you may be interested by having a look at the CC-BY-NC license which allows open source and prevents commercial use.
 
I see. Well, I was more interested in keeping an eye on the security and vulnerability aspect of the project. Concerning your licensing problem, I don't know much about it but you may be interested by having a look at the CC-BY-NC license which allows open source and prevents commercial use.

I have edited the post with an update but it seems I was late, I made the repo public

Edit: Never mind, I made it public. It's still really on early stages so it wouldn't matter a thing. I really stuck at this legal stuff so it will probably take ages for me to find a proper licence I might as well keep it public in these early development stages...
Here is the repo https://github.com/EmreB99/InteractiveSync
 
I am sorry for double posting but this is an important update which needs to be seen by all the users of my mod.
ISSUE LIST:
  • Server seemed to have crashed today approximately at 1:30PM GMT+03:00 but due to GNU Screen instance not outputting .NET Core console logs I simple don't know the god damn reason.
  • This is what caused the crash: https://i.ibb.co/vZ7dxLC/image.png
    • This is the reason why server crashes;
      • These are what caused the crash: *snip* ||| *snip* ||| *snip*
      • Someone or some bug in the client overloaded the server and forced it to dump memory.
      • Since the person who sent those loads of empty bytes to the server also attempted to connect from a second client(as you can see from the screenshots above, the ip is the same but the port isn't. This means that he opened 2 different connections from 1 pc. Also since there is no log of him disconnecting, this proves that the person intentionally opened 2 clients.
        • Now there 2 steps taken during the first initial connection. First is (::0c0+Name) data which renames the client in the server. Second is (::0c1+Bannerlord) which indicates that client is connecting from a Bannerlord client. The person who sent these loads of datas only send (::0c0) so this means he did not use the Bannerlord client instead used my old client that is on my GitHub page. This yet again implicates his intentions.

These issues will be handled as soon as possible. Server code is pretty fresh at this point and with problems like these it will mature up. These are the datas I could find while working atm(Hell yeah multi tasking!). After I finish my work for the day I will be able to deep dive into this mess and implement a proper firewall system for incoming of spam and empty datas. Soon there will also be version controlling system implemented to make sure that only up-to date users can join the server so that they can't abuse the old bugs/glitches that are present in old clients. This might push back some of quality of life features that I have been working on a little bit later in the release schedule, just to let you all know.
 
Last edited by a moderator:
I am sorry for double posting but this is an important update which needs to be seen by all the users of my mod.


These issues will be handled as soon as possible. Server code is pretty fresh at this point and with problems like these it will mature up. These are the datas I could find while working atm(Hell yeah multi tasking!). After I finish my work for the day I will be able to deep dive into this mess and implement a proper firewall system for incoming of spam and empty datas. Soon there will also be version controlling system implemented to make sure that only up-to date users can join the server so that they can't abuse the old bugs/glitches that are present in old clients. This might push back some of quality of life features that I have been working on a little bit later in the release schedule, just to let you all know.

Please, do not post IP addresses or any personal data on forums (especially because there is mine :razz:). I removed the screenshots with IP addresses of your users.

Concerning the crash, I am most likely the reason of this. Concerning the memory dump, this is because your program must have a flaw somewhere that must make some infinite loop or something as I absolutely did not do any stress test. Please, also do not that I did not use any sort of client, but simply opened raw sockets to your server and sent a few random bytes.

Finally, you do not need to configure a firewall but a proper input sanitization in your code, which clearly is not the case yet.

Edit: The no message spam you have received sounds like a common socket closure improper check. You must be reading message from a socket that has actually been closed.
 
Last edited:
Finally, you do not need to configure a firewall but a proper input sanitization in your code, which clearly is not the case yet.

Edit: The no message spam you have received sounds like a common socket closure improper check. You must be reading message from a socket that has actually been closed

Yeah, this is definitely the case. I am planning to add a counter if the user sends more than x amount of data per x second their connection will be terminated by the server.

For the socket closure check, yeah I have been waiting for an a little bit bigger update to push those changes to the current server as I need to restart the server for it to take effect and restarting the server means people needing to relaunch their games etc. But here is the reason for that https://github.com/EmreB99/InteractiveSync/projects/2#card-36584667

I am manually setting a bool to keep client connected/ or terminated. This has some flaws such as you have mentioned. Simply checking if the client is connected via the socket itself will be a better way of checking connectivity. If not connected than stop listening anymore.
I was aware of this issue but waiting for just a little bit bigger update to push them all together, never thought of a scenario like this would occur lol

Thanks for the heads up though, you scared me a lot but also revealed a major flaw in the server code lol :grin:


Edit: Memory dump is also seems to be related to the socket not being closed on the server side as the keepConnection bool sems to turn true indefinetly ends keeps sending datas to everyone lol
 
Last edited:
It's always great to see initiatives like this. I would say this is one step towards achieving Co-op in the future. And it's great to see that Ra'Jiska already checked it out. Really appreciated that you put server code as well.

Just to test a few stuff, I was planning exactly same - messaging - but with P2P architecture by saving myself from the server load and such. And it kinda feels more secure in that way. And I was planning some P2P VoIP for Multiplayer as well.
But regardless, this looks fairly good. What is the peak for active users so far?
 
It's always great to see initiatives like this. I would say this is one step towards achieving Co-op in the future. And it's great to see that Ra'Jiska already checked it out. Really appreciated that you put server code as well.

Just to test a few stuff, I was planning exactly same - messaging - but with P2P architecture by saving myself from the server load and such. And it kinda feels more secure in that way. And I was planning some P2P VoIP for Multiplayer as well.
But regardless, this looks fairly good. What is the peak for active users so far?

The highest number I have seen so far was 26 users. Technically I don't believe P2P is more secure as you are exposing the IP's of the players to others. With a server in the middle you can decide on who gets what and how they get it. Regardless though the actual server is not designed for mods or chat applications but rather for games so that's why it is important to be able to control all the data in the network to me.
Also, yeah Ra'Jiska made me realize a lot of issues that could've been gone unnoticed other wise. As I always say, things may work really well in test/dev environment but when you get to the production stage you will face issues that you have never expected that could've risen during development. Thats why it is really important to always get feedback from outside and Ra'Jiska helped a lot with that ^^

Also currently I am rewriting the entire TCP implementation to make it work async as it was the intended end goal from the beginning.
 
Technically I don't believe P2P is more secure as you are exposing the IP's of the players to others. With a server in the middle, you can decide on who gets what and how they get it.
True but this also depends on your implementation and use case. What I was planning was communication channel among friends and such, not like an entire Bannerlord Universe. So use-case wouldn't be exposing something to random hackers but perhaps your bad-boy friends at most.
You can achieve this via two ways, one would be simplest one, with a pop-up, saying that "This is your IP, give that to whoever wanna join" then the host can act like "controller". Or you could have a very lightweight holder for those interactions where the host can make a request to a server get's and unique identifier and then provides that ID to others and whenever others want to join they can query the lightweight server and gets corresponding address internally. This also gives you the chance to create chat rooms, lobbies etc really easily.


I am already using unqiue identifiers to identify users at the server logic. I simply use their ip+port as their unique identifier as it is the most unique thing that I can get lol.
Regarding Unique Identifier, my suggestion would be this ( from CodeProject which I'm using partially same for some of my projects as well )
C#:
using System;
using System.Management;
using System.Security.Cryptography;
using System.Security;
using System.Collections;
using System.Text;

namespace Security
{
    /// <summary>
    /// Generates a 16 byte Unique Identification code of a computer
    /// Example: 4876-8DB5-EE85-69D3-FE52-8CF7-395D-2EA9
    /// </summary>
    public class FingerPrint 
    {
        private static string fingerPrint = string.Empty;
        public static string Value()
        {
            if (string.IsNullOrEmpty(fingerPrint))
            {
                fingerPrint = GetHash("CPU >> " + cpuId() + "\nBIOS >> " +
            biosId() + "\nBASE >> " + baseId()
                            //+"\nDISK >> "+ diskId() + "\nVIDEO >> " +
            videoId() +"\nMAC >> "+ macId()
                                     );
            }
            return fingerPrint;
        }
        private static string GetHash(string s)
        {
            MD5 sec = new MD5CryptoServiceProvider();
            ASCIIEncoding enc = new ASCIIEncoding();
            byte[] bt = enc.GetBytes(s);
            return GetHexString(sec.ComputeHash(bt));
        }

        private static string GetHexString(byte[] bt)
        {
            string s = string.Empty;
            for (int i = 0; i < bt.Length; i++)
            {
                byte b = bt[i];
                int n, n1, n2;
                n = (int)b;
                n1 = n & 15;
                n2 = (n >> 4) & 15;
                if (n2 > 9)
                    s += ((char)(n2 - 10 + (int)'A')).ToString();
                else
                    s += n2.ToString();
                if (n1 > 9)
                    s += ((char)(n1 - 10 + (int)'A')).ToString();
                else
                    s += n1.ToString();
                if ((i + 1) != bt.Length && (i + 1) % 2 == 0) s += "-";
            }
            return s;
        }
        #region Original Device ID Getting Code
        //Return a hardware identifier
        private static string identifier
        (string wmiClass, string wmiProperty, string wmiMustBeTrue)
        {
            string result = "";
            System.Management.ManagementClass mc =
        new System.Management.ManagementClass(wmiClass);
            System.Management.ManagementObjectCollection moc = mc.GetInstances();
            foreach (System.Management.ManagementObject mo in moc)
            {
                if (mo[wmiMustBeTrue].ToString() == "True")
                {
                    //Only get the first one
                    if (result == "")
                    {
                        try
                        {
                            result = mo[wmiProperty].ToString();
                            break;
                        }
                        catch
                        {
                        }
                    }
                }
            }
            return result;
        }
        //Return a hardware identifier
        private static string identifier(string wmiClass, string wmiProperty)
        {
            string result = "";
            System.Management.ManagementClass mc =
        new System.Management.ManagementClass(wmiClass);
            System.Management.ManagementObjectCollection moc = mc.GetInstances();
            foreach (System.Management.ManagementObject mo in moc)
            {
                //Only get the first one
                if (result == "")
                {
                    try
                    {
                        result = mo[wmiProperty].ToString();
                        break;
                    }
                    catch
                    {
                    }
                }
            }
            return result;
        }
        private static string cpuId()
        {
            //Uses first CPU identifier available in order of preference
            //Don't get all identifiers, as it is very time consuming
            string retVal = identifier("Win32_Processor", "UniqueId");
            if (retVal == "") //If no UniqueID, use ProcessorID
            {
                retVal = identifier("Win32_Processor", "ProcessorId");
                if (retVal == "") //If no ProcessorId, use Name
                {
                    retVal = identifier("Win32_Processor", "Name");
                    if (retVal == "") //If no Name, use Manufacturer
                    {
                        retVal = identifier("Win32_Processor", "Manufacturer");
                    }
                    //Add clock speed for extra security
                    retVal += identifier("Win32_Processor", "MaxClockSpeed");
                }
            }
            return retVal;
        }
        //BIOS Identifier
        private static string biosId()
        {
            return identifier("Win32_BIOS", "Manufacturer")
            + identifier("Win32_BIOS", "SMBIOSBIOSVersion")
            + identifier("Win32_BIOS", "IdentificationCode")
            + identifier("Win32_BIOS", "SerialNumber")
            + identifier("Win32_BIOS", "ReleaseDate")
            + identifier("Win32_BIOS", "Version");
        }
        //Main physical hard drive ID
        private static string diskId()
        {
            return identifier("Win32_DiskDrive", "Model")
            + identifier("Win32_DiskDrive", "Manufacturer")
            + identifier("Win32_DiskDrive", "Signature")
            + identifier("Win32_DiskDrive", "TotalHeads");
        }
        //Motherboard ID
        private static string baseId()
        {
            return identifier("Win32_BaseBoard", "Model")
            + identifier("Win32_BaseBoard", "Manufacturer")
            + identifier("Win32_BaseBoard", "Name")
            + identifier("Win32_BaseBoard", "SerialNumber");
        }
        //Primary video controller ID
        private static string videoId()
        {
            return identifier("Win32_VideoController", "DriverVersion")
            + identifier("Win32_VideoController", "Name");
        }
        //First enabled network card ID
        private static string macId()
        {
            return identifier("Win32_NetworkAdapterConfiguration",
                "MACAddress", "IPEnabled");
        }
        #endregion
    }
}
Or if you don't wanna stir things up, just use System.Management put some semi-unique information together with IP to make sure.
 
True but this also depends on your implementation and use case. What I was planning was communication channel among friends and such, not like an entire Bannerlord Universe. So use-case wouldn't be exposing something to random hackers but perhaps your bad-boy friends at most.
You can achieve this via two ways, one would be simplest one, with a pop-up, saying that "This is your IP, give that to whoever wanna join" then the host can act like "controller". Or you could have a very lightweight holder for those interactions where the host can make a request to a server get's and unique identifier and then provides that ID to others and whenever others want to join they can query the lightweight server and gets corresponding address internally. This also gives you the chance to create chat rooms, lobbies etc really easily.



Regarding Unique Identifier, my suggestion would be this ( from CodeProject which I'm using partially same for some of my projects as well )
C#:
using System;
using System.Management;
using System.Security.Cryptography;
using System.Security;
using System.Collections;
using System.Text;

namespace Security
{
    /// <summary>
    /// Generates a 16 byte Unique Identification code of a computer
    /// Example: 4876-8DB5-EE85-69D3-FE52-8CF7-395D-2EA9
    /// </summary>
    public class FingerPrint
    {
        private static string fingerPrint = string.Empty;
        public static string Value()
        {
            if (string.IsNullOrEmpty(fingerPrint))
            {
                fingerPrint = GetHash("CPU >> " + cpuId() + "\nBIOS >> " +
            biosId() + "\nBASE >> " + baseId()
                            //+"\nDISK >> "+ diskId() + "\nVIDEO >> " +
            videoId() +"\nMAC >> "+ macId()
                                     );
            }
            return fingerPrint;
        }
        private static string GetHash(string s)
        {
            MD5 sec = new MD5CryptoServiceProvider();
            ASCIIEncoding enc = new ASCIIEncoding();
            byte[] bt = enc.GetBytes(s);
            return GetHexString(sec.ComputeHash(bt));
        }

        private static string GetHexString(byte[] bt)
        {
            string s = string.Empty;
            for (int i = 0; i < bt.Length; i++)
            {
                byte b = bt[i];
                int n, n1, n2;
                n = (int)b;
                n1 = n & 15;
                n2 = (n >> 4) & 15;
                if (n2 > 9)
                    s += ((char)(n2 - 10 + (int)'A')).ToString();
                else
                    s += n2.ToString();
                if (n1 > 9)
                    s += ((char)(n1 - 10 + (int)'A')).ToString();
                else
                    s += n1.ToString();
                if ((i + 1) != bt.Length && (i + 1) % 2 == 0) s += "-";
            }
            return s;
        }
        #region Original Device ID Getting Code
        //Return a hardware identifier
        private static string identifier
        (string wmiClass, string wmiProperty, string wmiMustBeTrue)
        {
            string result = "";
            System.Management.ManagementClass mc =
        new System.Management.ManagementClass(wmiClass);
            System.Management.ManagementObjectCollection moc = mc.GetInstances();
            foreach (System.Management.ManagementObject mo in moc)
            {
                if (mo[wmiMustBeTrue].ToString() == "True")
                {
                    //Only get the first one
                    if (result == "")
                    {
                        try
                        {
                            result = mo[wmiProperty].ToString();
                            break;
                        }
                        catch
                        {
                        }
                    }
                }
            }
            return result;
        }
        //Return a hardware identifier
        private static string identifier(string wmiClass, string wmiProperty)
        {
            string result = "";
            System.Management.ManagementClass mc =
        new System.Management.ManagementClass(wmiClass);
            System.Management.ManagementObjectCollection moc = mc.GetInstances();
            foreach (System.Management.ManagementObject mo in moc)
            {
                //Only get the first one
                if (result == "")
                {
                    try
                    {
                        result = mo[wmiProperty].ToString();
                        break;
                    }
                    catch
                    {
                    }
                }
            }
            return result;
        }
        private static string cpuId()
        {
            //Uses first CPU identifier available in order of preference
            //Don't get all identifiers, as it is very time consuming
            string retVal = identifier("Win32_Processor", "UniqueId");
            if (retVal == "") //If no UniqueID, use ProcessorID
            {
                retVal = identifier("Win32_Processor", "ProcessorId");
                if (retVal == "") //If no ProcessorId, use Name
                {
                    retVal = identifier("Win32_Processor", "Name");
                    if (retVal == "") //If no Name, use Manufacturer
                    {
                        retVal = identifier("Win32_Processor", "Manufacturer");
                    }
                    //Add clock speed for extra security
                    retVal += identifier("Win32_Processor", "MaxClockSpeed");
                }
            }
            return retVal;
        }
        //BIOS Identifier
        private static string biosId()
        {
            return identifier("Win32_BIOS", "Manufacturer")
            + identifier("Win32_BIOS", "SMBIOSBIOSVersion")
            + identifier("Win32_BIOS", "IdentificationCode")
            + identifier("Win32_BIOS", "SerialNumber")
            + identifier("Win32_BIOS", "ReleaseDate")
            + identifier("Win32_BIOS", "Version");
        }
        //Main physical hard drive ID
        private static string diskId()
        {
            return identifier("Win32_DiskDrive", "Model")
            + identifier("Win32_DiskDrive", "Manufacturer")
            + identifier("Win32_DiskDrive", "Signature")
            + identifier("Win32_DiskDrive", "TotalHeads");
        }
        //Motherboard ID
        private static string baseId()
        {
            return identifier("Win32_BaseBoard", "Model")
            + identifier("Win32_BaseBoard", "Manufacturer")
            + identifier("Win32_BaseBoard", "Name")
            + identifier("Win32_BaseBoard", "SerialNumber");
        }
        //Primary video controller ID
        private static string videoId()
        {
            return identifier("Win32_VideoController", "DriverVersion")
            + identifier("Win32_VideoController", "Name");
        }
        //First enabled network card ID
        private static string macId()
        {
            return identifier("Win32_NetworkAdapterConfiguration",
                "MACAddress", "IPEnabled");
        }
        #endregion
    }
}
Or if you don't wanna stir things up, just use System.Management put some semi-unique information together with IP to make sure.

A similar P2P approach that we have used for a previous game project was like this;

We were using P2P networking for cost and monetary reasons but letting players send connection requests to each other via their IPs is waaay behind of 2018(that was the date of the project). So we decided to implement a veeeery lightweight matchmaking server. We built it on Node.js. What it did basically is that, it kept the record of online lobbies and the players inside them. When a user wanted to see active game lobbies to join, they send a request to our matchmaking server. It does send a json list back and the client deserializes it. After choosing the server they wanted to join they send a request again to ask for the ip of that server, we send it back and they send a handshake request to the so called lobby(client server). After connecting to that server they send another request to our matchmaking server stating that all is okay, than server drops the active connection. Also in order not to keep the connection if it timed out or failed or their pc crashed for some unknown reason, server sent a ping every 1 minute and expected a pong(usual stuff lol).

Edit: I wasn't part of the team who did developed P2P architecture, I was in the team who did the matchmaking server. Sadly, I never worked on a P2P project lol.
 
After connecting to that server they send another request to our matchmaking server stating that all is okay, than server drops the active connection. Also in order not to keep the connection if it timed out or failed or their pc crashed for some unknown reason, server sent a ping every 1 minute and expected a pong(usual stuff lol)
I would also go with Nodejs right away without wasting too much time on heavy frameworks and overall this logic sounds fairly okay. P2P is great for VoIP and WebRTC like systems because of its basically free compared to other systems. ( For WebRTC there are some robust STUN TURN stuff required tho )
But again, as you already pointed out, it's not great for large scale games and horrible when it comes to stop cheating and such.

Since we are on the mod topic, I would suggest adding some universal statistics for others to get entertained. You don't need TCP for that actually but since you already have the connection and names etc, you can create some statistic page, with information like "100000 looters killed in Calradia so far, 1,020,003,200,201 gold spend on butter, 1000 villages sacked" etc Or more specific when you enter a town "Player XXX won the latest tournament in here", "XXX taken prisoner" etc

It would give some No Man's Sky vibe, Multiplayer but not actually Multiplayer, which might be good.
 
Since we are on the mod topic, I would suggest adding some universal statistics for others to get entertained. You don't need TCP for that actually but since you already have the connection and names etc, you can create some statistic page, with information like "100000 looters killed in Calradia so far, 1,020,003,200,201 gold spend on butter, 1000 villages sacked" etc Or more specific when you enter a town "Player XXX won the latest tournament in here", "XXX taken prisoner" etc

It would give some No Man's Sky vibe, Multiplayer but not actually Multiplayer, which might be good.

This is very similar to my next update on the client side which I am going to push today if I can fix some minor bugs. What it does is basically when you win a tournament, get wounded, marry someone etc. there will be announcements on the chat like "X married with Y, dont forget to congratulate them!". I can easily save them to a permanent database with sqllite than store them on a page but I really suck at front end stuff so no promise on how the quality of the page's rendering lol. This update also includes stuff like interaction between players. Such as sending each other in-game dinars. This it to experiment with the current architecture's ability on player-to-player interaction.
As I've mentioned before, this mod is a huge chance for me to test things out & improve where needed, before using it on a big project ^^
 
Back
Top Bottom