Sirius Emulator - From Scratch - C# 6.0

Status
Not open for further replies.

Seriosk

Programmer;
Oct 29, 2016
256
105
Windows is pretty useless when it comes to having a webserver on it.

Sent from my SM-G928F using Tapatalk
I agree.

Updates:
- Finished AI manager
- Finished GameMapping
- Finished PathFinding
- Finished RoomManager and basic methods in RoomClient
- Finished a few more packets (even unhandled ones)
- Utilities separated to different types
- Recoded the logging (didn't like it)
- Sockets are now Asynchronous

Fixed up the database library a little, no longer runs a simple query to check for working connection.
Code:
public bool WorkingConnection()
{
    try
    {
        using (var databaseConnection = GetConnection())
        {
            databaseConnection.OpenConnection();
            databaseConnection.CloseConnection();
        }
    }
    catch (MySqlException)
    {
        return false;
    }

    return true;
}

Recoded the ServerProcessWorker (updating title)
Code:
internal sealed class ServerProcessWorker : IDisposable
{
    private Timer _serverTimer;
    private readonly int _timerInterval;
    private DateTime _lastUpdate;
    private bool _disposing;

    public ServerProcessWorker()
    {
        _timerInterval = 10000;
    }

    public void Start()
    {
        _serverTimer = new System.Threading.Timer(e => CheckStatus(), null, TimeSpan.Zero, TimeSpan.FromMilliseconds(_timerInterval));
    }

    private void CheckStatus()
    {
        if ((DateTime.Now - _lastUpdate).TotalMilliseconds <= _timerInterval )
        {
            return;
        }

        var serverUptime = DateTime.Now - Sahara.GetServer().GetServerInformation().ServerStarted;

        var days = serverUptime.Days + " day" + (serverUptime.Days != 1 ? "s" : "") + ", ";
        var hours = serverUptime.Hours + " hour" + (serverUptime.Hours != 1 ? "s" : "") + ", and ";
        var minutes = serverUptime.Minutes + " minute" + (serverUptime.Minutes != 1 ? "s" : "");
        var uptimeString = days + hours + minutes;

        Console.Title = Sahara.GetServer().GetServerInformation().ServerName + " - Uptime: " + uptimeString;

        _lastUpdate = DateTime.Now;
    }

    public void Dispose()
    {
        if (_disposing)
        {
            return;
        }

        _disposing = true;

        _serverTimer.Dispose();
        _serverTimer = null;
    }
}

Logging
Code:
internal sealed class LogManager : IDisposable, ILog
{
    private readonly List<string> _logsToSave;
    private readonly ConcurrentDictionary<LogType, ConsoleColor> _logColors;
    private readonly string _errorLogsFile;
    private bool _isDisposing;

    public LogManager()
    {
        _logsToSave = new List<string>();

        _logColors = new ConcurrentDictionary<LogType, ConsoleColor>();
        _logColors.TryAdd(LogType.Information, ConsoleColor.White);
        _logColors.TryAdd(LogType.Debug, ConsoleColor.Cyan);
        _logColors.TryAdd(LogType.Error, ConsoleColor.Red);
        _logColors.TryAdd(LogType.Warning, ConsoleColor.Yellow);

        _errorLogsFile = "/logs/error_logs.txt";
    }

    public void Information(string log)
    {
        Log(LogType.Information, log);
    }

    public void Debug(string log)
    {
        Log(LogType.Debug, log);
    }

    public void Error(string log, Exception exception = null)
    {
        if (exception != null)
        {
            HandleException(exception);
        }

        Log(LogType.Error, log);
    }

    public void Warn(string log)
    {
        Log(LogType.Warning, log);
    }

    private void Log(LogType logType, string log)
    {
        var oldConsoleColor = Console.ForegroundColor;
        SetColor(logType);
        Console.WriteLine(" " + DateTime.Now.ToString("HH:mm:ss") + " [" + logType.ToString() + "] " + log);
        Console.ForegroundColor = oldConsoleColor;
    }

    private void HandleException(Exception exception)
    {
        _logsToSave.Add("Error occurred at " + DateTime.Now.ToString("HH:mm:ss"));
        _logsToSave.Add(exception.Message);
        _logsToSave.Add(exception.StackTrace);
        _logsToSave.Add(string.Empty);
    }

    private void SetColor(LogType logType)
    {
        ConsoleColor colorToSet;

        if (_logColors.TryGetValue(logType, out colorToSet))
        {
            Console.ForegroundColor = colorToSet;
            return;
        }

        Console.ForegroundColor = ConsoleColor.White;
    }

    private void SaveLogs()
    {
        if (Utilities.IsFileLocked(new FileInfo(_errorLogsFile)))
        {
            return;
        }

        var stringBuilder = new StringBuilder();

        foreach (var errorLog in _logsToSave)
        {
            stringBuilder.AppendLine(errorLog);
        }

        File.WriteAllText(_errorLogsFile, stringBuilder.ToString());
    }

    public void Dispose()
    {
        if (_isDisposing)
        {
            return;
        }

        _isDisposing = true;

        SaveLogs();
    }
}
 
UPDATES
GameClycing completed

Each Room has its own Cycle class (RoomCycle) that will cycle through things every 500ms.

Coded a caching system to rely less on MySQL
Instead of making calls to MySQL for various functions (Plus Emulator did this a lot) I have chosen to code a caching system that will cache pretty much anything. This will hopefully improve speed, performance and rely less on calling MySQL queries everywhere on Utility functions that rely on MySQL giving us the information.

Enhanced MySQL pooling
MySQL pooling has been implemented, this means that the connections will now be pooled instead of having to re-establish a new connection every time. I guess you could look at it as caching in a way.

Live Staff Updates
Now this is a feature that was given by me from a member of the community. Say you have a staff member online and you want to derank them but don't want to cause too much fuss in doing so, once their rank is reset to 1 in the database it will automatically update in-game without having to reload.

Please remember that all these ideas are ideas given by people who wanted them and all these features are easily enabled or disabled, you don't have to use them. Saharas main purpose was to listen to the users input unlike other emulator creators did, hopefully we can accomplish that goal.
 

Jaden

not so active
Aug 24, 2014
886
263
I agree.

Updates:
- Finished AI manager
- Finished GameMapping
- Finished PathFinding
- Finished RoomManager and basic methods in RoomClient
- Finished a few more packets (even unhandled ones)
- Utilities separated to different types
- Recoded the logging (didn't like it)
- Sockets are now Asynchronous

Fixed up the database library a little, no longer runs a simple query to check for working connection.
Code:
public bool WorkingConnection()
{
    try
    {
        using (var databaseConnection = GetConnection())
        {
            databaseConnection.OpenConnection();
            databaseConnection.CloseConnection();
        }
    }
    catch (MySqlException)
    {
        return false;
    }

    return true;
}

Recoded the ServerProcessWorker (updating title)
Code:
internal sealed class ServerProcessWorker : IDisposable
{
    private Timer _serverTimer;
    private readonly int _timerInterval;
    private DateTime _lastUpdate;
    private bool _disposing;

    public ServerProcessWorker()
    {
        _timerInterval = 10000;
    }

    public void Start()
    {
        _serverTimer = new System.Threading.Timer(e => CheckStatus(), null, TimeSpan.Zero, TimeSpan.FromMilliseconds(_timerInterval));
    }

    private void CheckStatus()
    {
        if ((DateTime.Now - _lastUpdate).TotalMilliseconds <= _timerInterval )
        {
            return;
        }

        var serverUptime = DateTime.Now - Sahara.GetServer().GetServerInformation().ServerStarted;

        var days = serverUptime.Days + " day" + (serverUptime.Days != 1 ? "s" : "") + ", ";
        var hours = serverUptime.Hours + " hour" + (serverUptime.Hours != 1 ? "s" : "") + ", and ";
        var minutes = serverUptime.Minutes + " minute" + (serverUptime.Minutes != 1 ? "s" : "");
        var uptimeString = days + hours + minutes;

        Console.Title = Sahara.GetServer().GetServerInformation().ServerName + " - Uptime: " + uptimeString;

        _lastUpdate = DateTime.Now;
    }

    public void Dispose()
    {
        if (_disposing)
        {
            return;
        }

        _disposing = true;

        _serverTimer.Dispose();
        _serverTimer = null;
    }
}

Logging
Code:
internal sealed class LogManager : IDisposable, ILog
{
    private readonly List<string> _logsToSave;
    private readonly ConcurrentDictionary<LogType, ConsoleColor> _logColors;
    private readonly string _errorLogsFile;
    private bool _isDisposing;

    public LogManager()
    {
        _logsToSave = new List<string>();

        _logColors = new ConcurrentDictionary<LogType, ConsoleColor>();
        _logColors.TryAdd(LogType.Information, ConsoleColor.White);
        _logColors.TryAdd(LogType.Debug, ConsoleColor.Cyan);
        _logColors.TryAdd(LogType.Error, ConsoleColor.Red);
        _logColors.TryAdd(LogType.Warning, ConsoleColor.Yellow);

        _errorLogsFile = "/logs/error_logs.txt";
    }

    public void Information(string log)
    {
        Log(LogType.Information, log);
    }

    public void Debug(string log)
    {
        Log(LogType.Debug, log);
    }

    public void Error(string log, Exception exception = null)
    {
        if (exception != null)
        {
            HandleException(exception);
        }

        Log(LogType.Error, log);
    }

    public void Warn(string log)
    {
        Log(LogType.Warning, log);
    }

    private void Log(LogType logType, string log)
    {
        var oldConsoleColor = Console.ForegroundColor;
        SetColor(logType);
        Console.WriteLine(" " + DateTime.Now.ToString("HH:mm:ss") + " [" + logType.ToString() + "] " + log);
        Console.ForegroundColor = oldConsoleColor;
    }

    private void HandleException(Exception exception)
    {
        _logsToSave.Add("Error occurred at " + DateTime.Now.ToString("HH:mm:ss"));
        _logsToSave.Add(exception.Message);
        _logsToSave.Add(exception.StackTrace);
        _logsToSave.Add(string.Empty);
    }

    private void SetColor(LogType logType)
    {
        ConsoleColor colorToSet;

        if (_logColors.TryGetValue(logType, out colorToSet))
        {
            Console.ForegroundColor = colorToSet;
            return;
        }

        Console.ForegroundColor = ConsoleColor.White;
    }

    private void SaveLogs()
    {
        if (Utilities.IsFileLocked(new FileInfo(_errorLogsFile)))
        {
            return;
        }

        var stringBuilder = new StringBuilder();

        foreach (var errorLog in _logsToSave)
        {
            stringBuilder.AppendLine(errorLog);
        }

        File.WriteAllText(_errorLogsFile, stringBuilder.ToString());
    }

    public void Dispose()
    {
        if (_isDisposing)
        {
            return;
        }

        _isDisposing = true;

        SaveLogs();
    }
}
 
UPDATES
GameClycing completed

Each Room has its own Cycle class (RoomCycle) that will cycle through things every 500ms.

Coded a caching system to rely less on MySQL
Instead of making calls to MySQL for various functions (Plus Emulator did this a lot) I have chosen to code a caching system that will cache pretty much anything. This will hopefully improve speed, performance and rely less on calling MySQL queries everywhere on Utility functions that rely on MySQL giving us the information.

Enhanced MySQL pooling
MySQL pooling has been implemented, this means that the connections will now be pooled instead of having to re-establish a new connection every time. I guess you could look at it as caching in a way.

Live Staff Updates
Now this is a feature that was given by me from a member of the community. Say you have a staff member online and you want to derank them but don't want to cause too much fuss in doing so, once their rank is reset to 1 in the database it will automatically update in-game without having to reload.

Please remember that all these ideas are ideas given by people who wanted them and all these features are easily enabled or disabled, you don't have to use them. Saharas main purpose was to listen to the users input unlike other emulator creators did, hopefully we can accomplish that goal.
Jw why give every room it's own cycle instead of just using 1 timer then cycling through each room instance on a calculated interval?

Also, why are you using a Concurrent Dictionary for storing readonly values that are constant + small?

I don't really understand your reasons for the LogManager either.
 

Seriosk

Programmer;
Oct 29, 2016
256
105
Jw why give every room it's own cycle instead of just using 1 timer then cycling through each room instance on a calculated interval?

Also, why are you using a Concurrent Dictionary for storing readonly values that are constant + small?

I don't really understand your reasons for the LogManager either.
I'm using a concurrent dictionary as the dictionary will be accessed from multiple threads. As for the cycle idea I guess you got me there, I'll change my code up tonight. As for the LogManager I was going to use log4net but I was in the mood to code a LogManager of my own.
 

Jaden

not so active
Aug 24, 2014
886
263
I'm using a concurrent dictionary as the dictionary will be accessed from multiple threads. As for the cycle idea I guess you got me there, I'll change my code up tonight. As for the LogManager I was going to use log4net but I was in the mood to code a LogManager of my own.
Even still, accessing the data from a regular dictionary will do no different because the data in the dictionary will never be modified.
 

Seriosk

Programmer;
Oct 29, 2016
256
105
Even still, accessing the data from a regular dictionary will do no different because the data in the dictionary will never be modified.
Thank you, I wasn't aware it only applied if the data was modified.
 
Finished:
  • Quests
  • Permissions
  • Subscriptions
  • Support
  • Talents
  • Landing View
  • Games
I have also coded a PlayerFigure.cs file which will come in handy when RP functionality is introduced and you want to do some advanced things with the figure, current methods:
GetHair()
GetFace()
GetBody()
GetClothes()
GetShoes()
IsTopless()
IsTrouserless()
WearingHat()

in the future you could possible do something such as (I don't really know the exact figure code for checking but you guys can do that.
PHP:
if (Player.GetPlayerData().PlayerFigure.GetBody() == "idk-the-code-for-here")
{
    // The user has a six pack...
}

I'm really not sure how Habbo figuring works but I am sure someone will be able to shine some light on that when the time comes. This class can pretty much be extended to a lot more if you're smart about it.

Small updates include converting many for each loops into LINQ and removing redundant class qualifiers.
 
Last edited:

Jaden

not so active
Aug 24, 2014
886
263
Thank you, I wasn't aware it only applied if the data was modified.
 
Finished:
  • Quests
  • Permissions
  • Subscriptions
  • Support
  • Talents
  • Landing View
  • Games
I have also coded a PlayerFigure.cs file which will come in handy when RP functionality is introduced and you want to do some advanced things with the figure, current methods:
GetHair()
GetFace()
GetBody()
GetClothes()
GetShoes()
IsTopless()
IsTrouserless()
WearingHat()

in the future you could possible do something such as (I don't really know the exact figure code for checking but you guys can do that.
PHP:
if (Player.GetPlayerData().PlayerFigure.GetBody() == "idk-the-code-for-here")
{
    // The user has a six pack...
}

I'm really not sure how Habbo figuring works but I am sure someone will be able to shine some light on that when the time comes. This class can pretty much be extended to a lot more if you're smart about it.

Small updates include converting many for each loops into LINQ and removing redundant class qualifiers.
Foreach loops are faster than LINQ in many cases (keep that in mind)
 

Seriosk

Programmer;
Oct 29, 2016
256
105
Foreach loops are faster than LINQ in many cases (keep that in mind)
Thank you, I'll keep that in mind.

I haven't really been doing much work recently, I have just been improving the code quality with small improvements and a lot of converting to string interpolation because I was too dumb to do that from the off.

Damn, I wish there was a VS extension that converted all my strings to string interpolation for me :( I've been using ReSharper recently but even the brain of that can't help me, code rush is actually the only extension that picks up string interpolation possibility (defaultly, not sure if ReSharper can be configured to do it?), I would use string.format but I prefer the look of string interpolation, and it gets converted to string.format on compilation anyway.

I really need to start using public fields instead of a private field with a public expression body, but I really like the look of a private field + public expression body, not sure which is better for performance, @Jaden ? obviously less code is better but is there any significant different.
 

Pinkman

Posting Freak
Jul 27, 2016
814
193
Ok a live version will be up and I will start making a compatible cms with this :)
 

Jaden

not so active
Aug 24, 2014
886
263
Thank you, I'll keep that in mind.

I haven't really been doing much work recently, I have just been improving the code quality with small improvements and a lot of converting to string interpolation because I was too dumb to do that from the off.

Damn, I wish there was a VS extension that converted all my strings to string interpolation for me :( I've been using ReSharper recently but even the brain of that can't help me, code rush is actually the only extension that picks up string interpolation possibility (defaultly, not sure if ReSharper can be configured to do it?), I would use string.format but I prefer the look of string interpolation, and it gets converted to string.format on compilation anyway.

I really need to start using public fields instead of a private field with a public expression body, but I really like the look of a private field + public expression body, not sure which is better for performance, @Jaden ? obviously less code is better but is there any significant different.
If your variable is dynamic then use auto properties, otherwise it seems cleaner to use an expression body when the value is predefined.

The only drastic difference I can point out with in response to your question is how the properties would respond when the value corresponds or relies on a static property.
 

Seriosk

Programmer;
Oct 29, 2016
256
105
Not really done much on this recently, but I have 2 weeks off college the coming week so hopefully I could get some development in during that time off.

Coded Language Locales
Code:
using System.Collections.Concurrent;
using System.Data;
using System.Diagnostics;

namespace Sahara.Base.Game.Habbo.Other
{
    public sealed class GameMessages
    {
        private readonly ConcurrentDictionary<string, string> _languageLocales;

        internal GameMessages()
        {
            var stopwatch = Stopwatch.StartNew();

            _languageLocales = new ConcurrentDictionary<string,string>();
            InitializeLocales();

            stopwatch.Stop();
            Sahara.GetServer().GetLogManager().Information("Loaded Locales [" + stopwatch.ElapsedMilliseconds + "ms]");
        }

        private void InitializeLocales()
        {
            using (var mysqlConnection = Sahara.GetServer().GetDatabaseManager().GetConnection())
            {
                mysqlConnection.SetQuery("SELECT * FROM `server_locale`");
                var localesTable = mysqlConnection.GetTable();

                if (localesTable == null)
                {
                    return;
                }

                foreach (DataRow localesRow in localesTable.Rows)
                {
                    _languageLocales.TryAdd(localesRow["key"].ToString(), localesRow["value"].ToString());
                }
            }
        }

        internal bool TryGetLocale(string key, out string value)
        {
            return _languageLocales.TryGetValue(key, out value);
        }
    }
}

As I said before, I'll keep the same kind-of code structure as Plus as well as the same database structure so it'll be easier for new users to use. Language Locales aren't sent as a notification if they weren't sent (in noob language.. if you deleted a language locale in your database, it wont even try and send the notification), example below

Code:
string notificationLocale;

if (Sirius.GetServer().GetGameManager().GetLanguageLocale().TryGetLocale("user_not_found", out notificationLocale))
{
    player.SendGameNotification(notificationLocale);
}

You could always do this, if you really wanted too...
Code:
string notificationLocale = "Locale wasn't found, you could enter a custom message here.";
Sirius.GetServer().GetGameManager().GetLanguageLocale().TryGetLocale("user_not_found", out notificationLocale);
player.SendGameNotification(notificationLocale);
 
I'm back with some real updates. I have recently been working on a small thing called Sirius Mus Manager, and before you guys see the word Sirius and wonder 'what the hell', I have renamed Sahara to Sirius, as I preferred the name.

I finally implemented mus

PwLlV3z.png


Disconnection notifications:
e91d3aef68b749d3ba6de3603ce6a193.png


Sending data:
9ab296ef95474db6afcf4ed09515c279.png

As you can see, I show examples of connecting, disconnect, and maximum connections above in screenshots.

How does maximum connections work?
Code:
# Mus Configuration
mus.max_connections=10
mus.allowed_hosts=127.0.0.1,192.168.0.1
There is also a limit on 1 connection per IP, I haven't added a configuration element for that as I do't think it needs one, why would you need multiple connections from the same IP? unless you're doing something like public access to your mus etc.

I rushed the Mus Manager thing, it is literally the fastest program I have ever made, the code is below if you want to look at it..
Code:
private static void Main(string[] args)
{
    Console.Title = "Sirius Mus Test";
    try
    {
        _s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        _s.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 2345));

        Console.WriteLine("Type something to mus...");
        var sendToMus = Console.ReadLine();

        var bytesToSend = Encoding.Default.GetBytes(sendToMus);
        _s.BeginSend(bytesToSend, 0, bytesToSend.Length, 0, OnSend, null);

        Console.WriteLine("sent to MUS at " + DateTime.Now.ToLongTimeString());

        while (true)
        {
            Console.ReadKey();
        }
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
        Console.WriteLine(e.StackTrace);
    }
}

Code Snippets?
Code:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Sirius.Core.Logging;

namespace Sirius.Core.Mus
{
    internal sealed class MusManager : IDisposable
    {
        private readonly Socket _musSocket;
        private readonly LogManager _log;
        private readonly ConcurrentDictionary<string, MusConnection> _musConnections;
        private readonly List<string> _allowedIps;
        private readonly Timer _timer;
        private bool _disposing;

        internal MusManager()
        {
            var stopwatch = Stopwatch.StartNew();

            _musSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _log = Sirius.GetServer().GetLogManager();
            _musConnections = new ConcurrentDictionary<string, MusConnection>();
            _allowedIps = new List<string>();
            _timer = new Timer(e => CheckConnections(), null, TimeSpan.Zero, TimeSpan.FromMilliseconds(10000));

            var allowedIpsString = Sirius.GetServer().GetConfigManager().GetConfigElement("mus.allowed_hosts");

            if (allowedIpsString.Contains(","))
            {
                foreach (var ipSplit in allowedIpsString.Split(','))
                {
                    _allowedIps.Add(ipSplit);
                }
            }
            else
            {
                _allowedIps.Add(allowedIpsString);
            }

            Listen();

            stopwatch.Stop();
            _log.Information("Loaded Mus Socket [" + stopwatch.ElapsedMilliseconds + "ms]");
        }

        private void CheckConnections()
        {
            var disconnectedCount = 0;

            foreach (var musConnection in _musConnections.Values.Where(musConnection => !musConnection.StillConnected()))
            {
                musConnection.Dispose();
                disconnectedCount++;
            }

            if (disconnectedCount > 0)
            {
                Sirius.GetServer().GetLogManager().Information("Disconnected " + disconnectedCount + " inactive mus connection" + (disconnectedCount != 0 ? "s" : "") + ".");
            }
        }

        private void Listen()
        {
            try
            {
                _musSocket.Bind(new IPEndPoint(IPAddress.Any, 2345));
                _musSocket.Listen(50);
                _musSocket.BeginAccept(OnNewMusConnection, _musSocket);
            }
            catch (SocketException socketException)
            {
                _log.Error("Failed to start sockets: " + socketException.Message, socketException);
            }
        }

        private void OnNewMusConnection(IAsyncResult asyncResult)
        {
            try
            {
                var server = (Socket)asyncResult.AsyncState;
                var client = server.EndAccept(asyncResult);
                var clientIp = client.RemoteEndPoint.ToString().Split(':')[0];

                if (_musConnections.Count >= Convert.ToInt32(Sirius.GetServer().GetConfigManager().GetConfigElement("mus.max_connections")) || _musConnections.ContainsKey(clientIp))
                {
                    _log.Information("Connection from mus rejected: too many connections.");
                    return;
                }

                _log.Debug("Connection from mus received. [" + clientIp + "]");

                if (!_musConnections.TryAdd(clientIp, new MusConnection(client, clientIp)))
                {
                    _log.Error("Failed to create new mus connection.");
                }
            }
            catch (SocketException socketException)
            {
                _log.Error("Failed to accept mus connection: " + socketException.Message, socketException);
            }
            finally
            {
                _musSocket?.BeginAccept(OnNewMusConnection, _musSocket);
            }
        }

        public void TryRemoveConnection(string ipAddress)
        {
            MusConnection connectionTemp;

            if (_musConnections.TryRemove(ipAddress, out connectionTemp))
            {
                Sirius.GetServer().GetLogManager().Information("Mus connection has left. [" + ipAddress + "]");
            }
        }

        public void Dispose()
        {
            if (_musSocket == null || _disposing)
            {
                return;
            }

            _disposing = true;

            _musSocket.Shutdown(SocketShutdown.Both);
            _musSocket.Close();
            _musSocket.Dispose();

            _musConnections.Clear();

            foreach (var musConnection in _musConnections.Values)
            {
                musConnection.Dispose();
            }
        }
    }
}
Code:
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Sirius.Core.Utility;

namespace Sirius.Core.Mus
{
    internal sealed class MusConnection : IDisposable
    {
        private readonly Socket _socket;
        private readonly string _ipAddress;
        private readonly byte[] _buffer;
        private bool _disposing;

        internal MusConnection(Socket socket, string ipAddress)
        {
            _socket = socket;
            _ipAddress = ipAddress;
            _buffer = new byte[1024];

            try
            {
                _socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, OnReceiveData, _socket);
            }
            catch
            {
                _socket.Shutdown(SocketShutdown.Both);
                _socket.Close();
                _socket.Dispose();
            }
        }

        private void OnReceiveData(IAsyncResult asyncResult)
        {
            try
            {
                var receivedBytes = 0;

                try
                {
                    receivedBytes = _socket.EndReceive(asyncResult);
                }
                catch
                {
                    Dispose();
                    return;
                }

                var data = Encoding.Default.GetString(_buffer, 0, receivedBytes);

                if (data.Length < 1)
                {
                    return;
                }

                Sirius.GetServer().GetLogManager().Information("Mus Connection: " + data);
            }
            catch (Exception exception)
            {
                Sirius.GetServer().GetLogManager().Error("Error receiving mus data: " + exception.Message, exception);
            }
            finally
            {
                _socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, OnReceiveData, _socket);
            }
        }

        public bool StillConnected()
        {
            return Utilities.SocketConnected(_socket);
        }

        public void Dispose()
        {
            if (_disposing)
            {
                return;
            }

            _disposing = true;

            Sirius.GetServer().GetMusManager().TryRemoveConnection(_ipAddress);

            _socket.Shutdown(SocketShutdown.Both);
            _socket.Close();
            _socket.Dispose();
        }
    }
}

I had a crash earlier so this code isn't 100% and it isn't finished, it's just the example I used and it'll be edited a fair bit before production.

If you're also wondering about how I check if a socket is still connected, here is that method.
Code:
public static bool SocketConnected(Socket socket)
{
    return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
}
 
Hello. I haven't done anything much since my last post except clean up some of the code. I have switched out long switch statemenets (PlusEMU has many, go look) that basically return a string from an enum value and replaced it with this simple method that will do most of the work in 10% of the lines.

Example:
Code:
internal static class InteractionTypes
{
    internal static InteractionType GetTypeFromString(string pType)
    {
        var enumNames = Enum.GetNames(typeof(InteractionType));
        var enumValueName = enumNames.FirstOrDefault(x => pType.IndexOf(x, StringComparison.OrdinalIgnoreCase) >= 0);

        if (enumValueName != null)
        {
            return (InteractionType) Enum.Parse(typeof(InteractionType), enumValueName);
        }
        
        return InteractionType.None;
    }
}
 

MayoMayn

BestDev
Oct 18, 2016
1,423
683
Not really done much on this recently, but I have 2 weeks off college the coming week so hopefully I could get some development in during that time off.

Coded Language Locales
Code:
using System.Collections.Concurrent;
using System.Data;
using System.Diagnostics;

namespace Sahara.Base.Game.Habbo.Other
{
    public sealed class GameMessages
    {
        private readonly ConcurrentDictionary<string, string> _languageLocales;

        internal GameMessages()
        {
            var stopwatch = Stopwatch.StartNew();

            _languageLocales = new ConcurrentDictionary<string,string>();
            InitializeLocales();

            stopwatch.Stop();
            Sahara.GetServer().GetLogManager().Information("Loaded Locales [" + stopwatch.ElapsedMilliseconds + "ms]");
        }

        private void InitializeLocales()
        {
            using (var mysqlConnection = Sahara.GetServer().GetDatabaseManager().GetConnection())
            {
                mysqlConnection.SetQuery("SELECT * FROM `server_locale`");
                var localesTable = mysqlConnection.GetTable();

                if (localesTable == null)
                {
                    return;
                }

                foreach (DataRow localesRow in localesTable.Rows)
                {
                    _languageLocales.TryAdd(localesRow["key"].ToString(), localesRow["value"].ToString());
                }
            }
        }

        internal bool TryGetLocale(string key, out string value)
        {
            return _languageLocales.TryGetValue(key, out value);
        }
    }
}

As I said before, I'll keep the same kind-of code structure as Plus as well as the same database structure so it'll be easier for new users to use. Language Locales aren't sent as a notification if they weren't sent (in noob language.. if you deleted a language locale in your database, it wont even try and send the notification), example below

Code:
string notificationLocale;

if (Sirius.GetServer().GetGameManager().GetLanguageLocale().TryGetLocale("user_not_found", out notificationLocale))
{
    player.SendGameNotification(notificationLocale);
}

You could always do this, if you really wanted too...
Code:
string notificationLocale = "Locale wasn't found, you could enter a custom message here.";
Sirius.GetServer().GetGameManager().GetLanguageLocale().TryGetLocale("user_not_found", out notificationLocale);
player.SendGameNotification(notificationLocale);
 
I'm back with some real updates. I have recently been working on a small thing called Sirius Mus Manager, and before you guys see the word Sirius and wonder 'what the hell', I have renamed Sahara to Sirius, as I preferred the name.

I finally implemented mus

PwLlV3z.png


Disconnection notifications:
e91d3aef68b749d3ba6de3603ce6a193.png


Sending data:
9ab296ef95474db6afcf4ed09515c279.png

As you can see, I show examples of connecting, disconnect, and maximum connections above in screenshots.

How does maximum connections work?
Code:
# Mus Configuration
mus.max_connections=10
mus.allowed_hosts=127.0.0.1,192.168.0.1
There is also a limit on 1 connection per IP, I haven't added a configuration element for that as I do't think it needs one, why would you need multiple connections from the same IP? unless you're doing something like public access to your mus etc.

I rushed the Mus Manager thing, it is literally the fastest program I have ever made, the code is below if you want to look at it..
Code:
private static void Main(string[] args)
{
    Console.Title = "Sirius Mus Test";
    try
    {
        _s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        _s.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 2345));

        Console.WriteLine("Type something to mus...");
        var sendToMus = Console.ReadLine();

        var bytesToSend = Encoding.Default.GetBytes(sendToMus);
        _s.BeginSend(bytesToSend, 0, bytesToSend.Length, 0, OnSend, null);

        Console.WriteLine("sent to MUS at " + DateTime.Now.ToLongTimeString());

        while (true)
        {
            Console.ReadKey();
        }
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
        Console.WriteLine(e.StackTrace);
    }
}

Code Snippets?
Code:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Sirius.Core.Logging;

namespace Sirius.Core.Mus
{
    internal sealed class MusManager : IDisposable
    {
        private readonly Socket _musSocket;
        private readonly LogManager _log;
        private readonly ConcurrentDictionary<string, MusConnection> _musConnections;
        private readonly List<string> _allowedIps;
        private readonly Timer _timer;
        private bool _disposing;

        internal MusManager()
        {
            var stopwatch = Stopwatch.StartNew();

            _musSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _log = Sirius.GetServer().GetLogManager();
            _musConnections = new ConcurrentDictionary<string, MusConnection>();
            _allowedIps = new List<string>();
            _timer = new Timer(e => CheckConnections(), null, TimeSpan.Zero, TimeSpan.FromMilliseconds(10000));

            var allowedIpsString = Sirius.GetServer().GetConfigManager().GetConfigElement("mus.allowed_hosts");

            if (allowedIpsString.Contains(","))
            {
                foreach (var ipSplit in allowedIpsString.Split(','))
                {
                    _allowedIps.Add(ipSplit);
                }
            }
            else
            {
                _allowedIps.Add(allowedIpsString);
            }

            Listen();

            stopwatch.Stop();
            _log.Information("Loaded Mus Socket [" + stopwatch.ElapsedMilliseconds + "ms]");
        }

        private void CheckConnections()
        {
            var disconnectedCount = 0;

            foreach (var musConnection in _musConnections.Values.Where(musConnection => !musConnection.StillConnected()))
            {
                musConnection.Dispose();
                disconnectedCount++;
            }

            if (disconnectedCount > 0)
            {
                Sirius.GetServer().GetLogManager().Information("Disconnected " + disconnectedCount + " inactive mus connection" + (disconnectedCount != 0 ? "s" : "") + ".");
            }
        }

        private void Listen()
        {
            try
            {
                _musSocket.Bind(new IPEndPoint(IPAddress.Any, 2345));
                _musSocket.Listen(50);
                _musSocket.BeginAccept(OnNewMusConnection, _musSocket);
            }
            catch (SocketException socketException)
            {
                _log.Error("Failed to start sockets: " + socketException.Message, socketException);
            }
        }

        private void OnNewMusConnection(IAsyncResult asyncResult)
        {
            try
            {
                var server = (Socket)asyncResult.AsyncState;
                var client = server.EndAccept(asyncResult);
                var clientIp = client.RemoteEndPoint.ToString().Split(':')[0];

                if (_musConnections.Count >= Convert.ToInt32(Sirius.GetServer().GetConfigManager().GetConfigElement("mus.max_connections")) || _musConnections.ContainsKey(clientIp))
                {
                    _log.Information("Connection from mus rejected: too many connections.");
                    return;
                }

                _log.Debug("Connection from mus received. [" + clientIp + "]");

                if (!_musConnections.TryAdd(clientIp, new MusConnection(client, clientIp)))
                {
                    _log.Error("Failed to create new mus connection.");
                }
            }
            catch (SocketException socketException)
            {
                _log.Error("Failed to accept mus connection: " + socketException.Message, socketException);
            }
            finally
            {
                _musSocket?.BeginAccept(OnNewMusConnection, _musSocket);
            }
        }

        public void TryRemoveConnection(string ipAddress)
        {
            MusConnection connectionTemp;

            if (_musConnections.TryRemove(ipAddress, out connectionTemp))
            {
                Sirius.GetServer().GetLogManager().Information("Mus connection has left. [" + ipAddress + "]");
            }
        }

        public void Dispose()
        {
            if (_musSocket == null || _disposing)
            {
                return;
            }

            _disposing = true;

            _musSocket.Shutdown(SocketShutdown.Both);
            _musSocket.Close();
            _musSocket.Dispose();

            _musConnections.Clear();

            foreach (var musConnection in _musConnections.Values)
            {
                musConnection.Dispose();
            }
        }
    }
}
Code:
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Sirius.Core.Utility;

namespace Sirius.Core.Mus
{
    internal sealed class MusConnection : IDisposable
    {
        private readonly Socket _socket;
        private readonly string _ipAddress;
        private readonly byte[] _buffer;
        private bool _disposing;

        internal MusConnection(Socket socket, string ipAddress)
        {
            _socket = socket;
            _ipAddress = ipAddress;
            _buffer = new byte[1024];

            try
            {
                _socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, OnReceiveData, _socket);
            }
            catch
            {
                _socket.Shutdown(SocketShutdown.Both);
                _socket.Close();
                _socket.Dispose();
            }
        }

        private void OnReceiveData(IAsyncResult asyncResult)
        {
            try
            {
                var receivedBytes = 0;

                try
                {
                    receivedBytes = _socket.EndReceive(asyncResult);
                }
                catch
                {
                    Dispose();
                    return;
                }

                var data = Encoding.Default.GetString(_buffer, 0, receivedBytes);

                if (data.Length < 1)
                {
                    return;
                }

                Sirius.GetServer().GetLogManager().Information("Mus Connection: " + data);
            }
            catch (Exception exception)
            {
                Sirius.GetServer().GetLogManager().Error("Error receiving mus data: " + exception.Message, exception);
            }
            finally
            {
                _socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, OnReceiveData, _socket);
            }
        }

        public bool StillConnected()
        {
            return Utilities.SocketConnected(_socket);
        }

        public void Dispose()
        {
            if (_disposing)
            {
                return;
            }

            _disposing = true;

            Sirius.GetServer().GetMusManager().TryRemoveConnection(_ipAddress);

            _socket.Shutdown(SocketShutdown.Both);
            _socket.Close();
            _socket.Dispose();
        }
    }
}

I had a crash earlier so this code isn't 100% and it isn't finished, it's just the example I used and it'll be edited a fair bit before production.

If you're also wondering about how I check if a socket is still connected, here is that method.
Code:
public static bool SocketConnected(Socket socket)
{
    return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
}
 
Hello. I haven't done anything much since my last post except clean up some of the code. I have switched out long switch statemenets (PlusEMU has many, go look) that basically return a string from an enum value and replaced it with this simple method that will do most of the work in 10% of the lines.

Example:
Code:
internal static class InteractionTypes
{
    internal static InteractionType GetTypeFromString(string pType)
    {
        var enumNames = Enum.GetNames(typeof(InteractionType));
        var enumValueName = enumNames.FirstOrDefault(x => pType.IndexOf(x, StringComparison.OrdinalIgnoreCase) >= 0);

        if (enumValueName != null)
        {
            return (InteractionType) Enum.Parse(typeof(InteractionType), enumValueName);
        }
       
        return InteractionType.None;
    }
}
Looks good man! Keep it up. I will definitely make this emulator compatible with my CMS once it's released.
 

Seriosk

Programmer;
Oct 29, 2016
256
105

Looks good man! Keep it up. I will definitely make this emulator compatible with my CMS once it's released.
Thanks bro, would love to see a decent quality CMS besides rev for this

First command coded:
Code:
using System;
using System.Text;

namespace Sirius.Base.Game.Habbo.Commands.Commands.Habbo
{
    using Other.Communication.Packets.Outgoing.Notifications;

    internal class InfoCommand : ICommand
    {
        internal void ProcessCommand(Player player)
        {
            var serverName = Sirius.GetServer().GetServerInformation().ServerName;
            var serverVersion = Sirius.GetServer().GetServerInformation().ServerVersion;
            var serverCreator = Sirius.GetServer().GetServerInformation().ServerCreator;
            var swfVersion = Sirius.GetServer().GetServerInformation().HabboSwfRevision;

            var notificationStringBuilder = new StringBuilder();

            var serverUptime = DateTime.Now - Sirius.GetServer().GetServerInformation().ServerStarted;

            var days = serverUptime.Days + " day" + (serverUptime.Days != 1 ? "s" : "") + ", ";
            var hours = serverUptime.Hours + " hour" + (serverUptime.Hours != 1 ? "s" : "") + ", and ";
            var minutes = serverUptime.Minutes + " minute" + (serverUptime.Minutes != 1 ? "s" : "");

            notificationStringBuilder.Append(serverName + " v" + serverVersion + " [Running on " + swfVersion + "]");
            notificationStringBuilder.Append(string.Empty);
            notificationStringBuilder.Append("Thanks/Congrats:");
            notificationStringBuilder.Append(string.Empty);
            notificationStringBuilder.Append("- " + serverCreator + " [Creator]");
            notificationStringBuilder.Append(string.Empty);
            notificationStringBuilder.Append("Uptime:");
            notificationStringBuilder.Append(string.Empty);
            notificationStringBuilder.Append("- " + days + hours + minutes);

            player.SendMessage(new MotdNotificationComposer(notificationStringBuilder.ToString()));
        }
    }
}

Interface:
Code:
namespace Sirius.Base.Game.Habbo.Commands.Commands
{
    using Other.Players;

    internal interface ICommand
    {
        void ProcessCommand(Player player);
        string PermissionRequired { get; }
        int NumberOfParameters { get; }
        string CommandParameters { get; }
        string CommandDescription { get; }
        bool CommandCooldown { get; }
        int CommandCooldownMs { get; }
    }
}

Unlike Plus, you can now define multiple command triggers in the same dictionary entry
Code:
_commands.Add("info|about|serverinfo", new InfoCommand());

Added these to each ICommand file:
Code:
public string PermissionRequired => "";

public int NumberOfParameters => 0;

public string CommandParameters => "";

public string CommandDescription => "Lets you view information about the server.";

It automatically checks if the parameters isn't equal to 'NumberOfParameters' and sends this:
Code:
player.SendWhisper("Invalid command syntax for command " + commandName + " -- " + CommandParameters + "");

Command permission is exactly the same as Plus's way, and the description is just what ever is displayed inside :commands

Automatic cool down system for every command.
Each ICommand now has this:
Code:
public bool CommandHasCooldown => true;

public int CommandCooldownMs => 5000;

It's obvious what this does...

Ever wanted to make a command like something below?
Code:
@mycommand <username>

Code:
>>somecommand 10

Yeah, well you no longer have to be stuck to : anymore, but you will have to even hard code : before each command name for example:
Code:
_commands.Add(":mycommand", new MyCommand());
_commands.Add("@home", new AtHomeCommand());
_commands.Add(".ilovefish", new ILoveFishCommand());
 
Last edited:

Liam

discord.gg/hivehub
Staff member
FindRetros Moderator
Apr 10, 2013
1,184
714
Good luck, hopefully you can finish and release it.
 

Seriosk

Programmer;
Oct 29, 2016
256
105
Looking good buddy. I might also code a cms from new framework for this :p
Thank you, it will be compatible with RevCMS with a few small changes but feel free to try and create a CMS for it.

Good luck, hopefully you can finish and release it.
Thank you, and yes sir.

Updates:
I was being really stupid declaring lots of fields that didn't need to be there in that CommandHandler.cs, I have removed:
Code:
public bool CommandHasCooldown => true;

And it just checks if this doesn't equal 0, if it doesn't then there is a cooldown?
Code:
public int CommandCooldownMs => 5000;

Also done the same with parameters, no ParameterCount field now, it just splits the actual parameter string.

On some more serious news..
Start commenting here or sending me a private message with what commands you would like to see in this emulator, I'll probably code most of them as long as their smart and will be used by many.

Commands can now have multiple permissions/rights:
Code:
if (command.PermissionRequired.Contains(" "))
{
    if (command.PermissionRequired.Split(' ').Any(permission => !player.GetPlayerData().GetPermissions().HasCommand(command.PermissionRequired) &&
                                                                !player.GetPlayerData().GetPermissions().HasRight(command.PermissionRequired)))
    {
        return false;
    }
}
 
Last edited:

Seriosk

Programmer;
Oct 29, 2016
256
105
Awesome work man! Keep up the great work. Can't wait to see until it releases.
Thankyou.

At 14:44 Sirius has been completed, thank you to everyone who accompanied me on this journey. I want to say thanks to @SpreedBlood for his continuous support, ideas, suggestions and much more. I want to thank @JynX , and others. You know who you all..

I also want to say thank you to @Sledmore for releasing his Plus when he did, I got a fair amount of intelligence about Habbo methods from his emulator, and a lot of the inspiration in Sirius came from Plus. I also use his encryption and HabooEncryption.dll, and not forgetting AStart.dll, and I would often look at how he did something, so thanks for that.

System Information
Version => 0.1.0
SWF Build => PRODUCTION-201601012205-226667486

Bugs Found
- Room creation doesn't save, will look in to it.

Missing features/todo:
- Need to clean up Navigator
- Logging doesn't save to a file

Code Snippets (Mainly core stuff), will try and get more up soon




Just to show, I have minimized almost every switch statement (3 value switch statements) with a bool operator method
Code:
public static NavigatorSearchAllowance GetSearchAllowanceByString(string categoryType)
        {
            return categoryType == "SHOW_MORE" ? NavigatorSearchAllowance.ShowMore : categoryType == "GO_BACK" ? NavigatorSearchAllowance.GoBack : NavigatorSearchAllowance.Nothing;
        }

        public static int GetIntegerValue(NavigatorSearchAllowance searchAllowance)
        {
            return searchAllowance == NavigatorSearchAllowance.ShowMore ? 1 : searchAllowance == NavigatorSearchAllowance.GoBack ? 2 : 0;
        }

        public static NavigatorViewMode GetViewModeByString(string viewMode)
        {
            return viewMode.ToUpper() == "THUMBNAIL" ? NavigatorViewMode.Thumbnail : NavigatorViewMode.Regular;
        }

        public static bool SocketConnected(Socket socket)
        {
            return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
        }

        public static string GetMergedString(IEnumerable<string> parts, int start)
        {
            return string.Join(" ", parts.Skip(start));
        }

        public static bool IsInteger(string inputString)
        {
            return int.TryParse("123", out int tempInt);
        }

I did promise roleplay functionality so I want to get a fair amount of that done before actually releasing it. I've just started on this, I decided to make separate command handlers for RP and Hotel as the RP will contain a lot of extra fields inside the IGameCommand interface and command classes such as:
Code:
private readonly int _requiredBusinessId => 4;
private readonly int _requiredBusinessRank => 6;
private readonly GovernmentType _govRequired => GovernmentType.None;
private readonly BusinessType _requiredBusinessType => businessType.Retail;
private readonly bool _forceWorking => true;

The above fields are just a sketch, storyboard what ever you want to call it, but it will contain a more extended command management system.

3db0e880cad146f9a68bb3cacc63ddc7.png

It's totally not much, but I will keep you updated and possibly most more updates tonight.

RoleplayData.cs
Code:
namespace Sirius.Base.Game.Roleplay.Players
{
    using System;
    using System.Collections.Generic;
    using System.Data;
    using Other.GameClients;

    public class RoleplayData : IDisposable
    {
        private readonly Player _player;
        private readonly Dictionary<string, string> _cachedPlayerData;
        private bool _disposing;

        public void Dispose()
        {
            try
            {
                if (_disposing)
                {
                    return;
                }

                _disposing = true;

                using (var mysqlConnection = Sirius.GetServer().GetDatabaseManager().GetConnection())
                {
                    var saveQuery = "UPDATE `users` SET ";
                    var foreachIndex = 0;

                    foreach (var dataPair in _cachedPlayerData)
                    {
                        foreachIndex++;

                        saveQuery += "`" + dataPair.Key + "` = @" + dataPair.Key + (foreachIndex < _cachedPlayerData.Count ? ", " : " LIMIT 1");
                        mysqlConnection.AddParameter(dataPair.Key, dataPair.Value);
                    }

                    if (foreachIndex == 0)
                    {
                        return;
                    }

                    mysqlConnection.SetQuery(saveQuery, false);
                    mysqlConnection.RunQuery();
                }
            }
            catch (Exception exception)
            {
                Sirius.GetServer().GetLogManager().Error("Error saving roleplay data for " + _player.GetPlayerData().Username + ": " + exception.Message);
            }
        }

        public RoleplayData(Player player)
        {
            _cachedPlayerData = new Dictionary<string, string>();
            _player = player;

            LoadPlayerData(player.GetPlayerData().Id);
        }

        private void LoadPlayerData(int userId)
        {
            try
            {
                using (var mysqlConnection = Sirius.GetServer().GetDatabaseManager().GetConnection())
                {
                    mysqlConnection.SetQuery("SELECT * FROM srp_user_statistics` WHERE `user_id` = @userId");
                    mysqlConnection.AddParameter("userId", userId);
                    var playerTable = mysqlConnection.GetTable();

                    if (playerTable == null)
                    {

                        return;
                    }

                    foreach (DataRow roleplayRow in playerTable.Rows)
                    {
                        foreach (DataColumn column in playerTable.Columns)
                        {
                            _cachedPlayerData.Add(column.ColumnName, Convert.ToString(roleplayRow[column]));
                        }
                    }
                }
            }
            catch (Exception exception)
            {
                Sirius.GetServer().GetLogManager().Error("Error during login for user " + userId + ": " + exception.Message);
            }
        }

        private string SelectColumn(string columnName, bool fromDatabase)
        {
            if (_cachedPlayerData.ContainsKey(columnName) && !fromDatabase)
            {
                return _cachedPlayerData[columnName];
            }
            using (var mysqlConnection = Sirius.GetServer().GetDatabaseManager().GetConnection())
            {
                mysqlConnection.SetQuery("SELECT `" + columnName + "` FROM `users` WHERE `id` = @userId");
                mysqlConnection.AddParameter("userId", _player.GetPlayerData().Id);

                var columnValue = mysqlConnection.GetString();
                _cachedPlayerData.Add(columnName, columnValue);

                return columnValue;
            }
        }

        public int SelectColumnAsInt(string columnName, bool fromDatabase)
        {
            return Convert.ToInt32(SelectColumn(columnName, fromDatabase));
        }

        public void UpdateColumn(string columnName, string newValue, bool inDatabase)
        {
            if (_cachedPlayerData.ContainsKey(columnName))
            {
                _cachedPlayerData[columnName] = newValue;
            }

            if (!inDatabase)
            {
                return;
            }

            UpdateColumnWithNewValue(columnName, newValue);
        }

        private void UpdateColumnWithNewValue(string columnName, string newValue)
        {
            using (var mysqlConnection = Sirius.GetServer().GetDatabaseManager().GetConnection())
            {
                mysqlConnection.SetQuery("UPDATE `users` SET  `" + columnName + "` = @newValue WHERE `id` = @userId");
                mysqlConnection.AddParameter("newValue", newValue);
                mysqlConnection.AddParameter("userId", _player.GetPlayerData().Id);
                mysqlConnection.RunQuery();
            }
        }
    }
}

@Velaski is also now contributing on the Sirius Roleplay edition.

Not sure about this method, I may try and find a better way:
Code:
public void Dispose()
{
    try
    {
        if (_disposing)
        {
            return;
        }

        _disposing = true;

        using (var mysqlConnection = Sirius.GetServer().GetDatabaseManager().GetConnection())
        {
            var saveQuery = "UPDATE `users` SET ";
            var foreachIndex = 0;

            foreach (var dataPair in _cachedPlayerData)
            {
                foreachIndex++;

                saveQuery += "`" + dataPair.Key + "` = @" + dataPair.Key + (foreachIndex < _cachedPlayerData.Count ? ", " : " LIMIT 1");
                mysqlConnection.AddParameter(dataPair.Key, dataPair.Value);
            }

            if (foreachIndex == 0)
            {
                return;
            }

            mysqlConnection.SetQuery(saveQuery, false);
            mysqlConnection.RunQuery();
        }
    }
    catch (Exception exception)
    {
        Sirius.GetServer().GetLogManager().Error("Error saving roleplay data for " + _player.GetPlayerData().Username + ": " + exception.Message);  
    }
}
 

Seriosk

Programmer;
Oct 29, 2016
256
105
Started working on client sided and server sided WebSockets, here is a sneek peak of a simple client side system
PHP:
window.ws = new wsImpl('ws://localhost:8181/');

var hasConnected = false;

function startWebSockets() {
    ws.onmessage = function (messageEvent) {
        onReceiveMessage(messageEvent.Data);
    };

    ws.onopen = function () {
        onConnectionOpened();
    };

    ws.onclose = function () {
        onConnectionClosed();
    }
}

function onReceiveMessage(messageData) {
    alert('You received a message from the WebSocket server: ' + messageData);
}

function onConnectionOpened() {
    console.log('Connected to the WebSocket server.');
    hasConnected = true;
}

function onConnectionClosed() {
    if (!hasConnected) {
        console.log('Failed to connect to the WebSocket server.');
    }
    else {
        console.log('Your connection to the WebSocket server was unexpectedly closed.');
    }
}

startWebSockets();
 
Status
Not open for further replies.

Users who are viewing this thread

Top