Group Details Private

administrators

Member List

  • Greetings everyone,

    We are almost at the end of the millestone V0.2 and new features and bug fixes should be released to the test server by the end of this month (may 2019). Check out what has been planned and done for version 0.2 here.

    Now, we need to plan version 0.3 that will be released by the end of June 2019.

    We are trying our best to release at least, one version per month, including new features and bug fixes. I open this topic and create a poll to ask you, dear community, if there is any feature that you would like to see in version 0.3 of Rhisis.

    Also, we are trying to make Rhisis a community-driven project, where you, members and contributors decide what you want to see in Rhisis.

    Thank you for your time and patience regarding the project.

    posted in Rhisis Discussions
  • Greetings everyone,

    After some feedback of users (via pm or here in this discord) that have tested the server, we now know what we need to improve and I've been reorganizing the project and make priorities on the tasks we need to achieve.

    I've opened the first Rhisis milestone, so you can track the evolution of the tasks: https://github.com/Eastrall/Rhisis/milestone/1

    We are going to focus on the following points:

    • āœ” Complete the battle system (Gain experience, level up, boost statistics)
    • āœ” Complete the drop system
    • šŸ”„ Implement the Death System and the Resurection system (lodelight, no skill yet šŸ˜‰ )
    • āŒ Improve the Inter Server communication system thanks to the upcoming Sylver library (Upgrade of Ether.Network)
    • āœ” Implement WSAD moves and jump system
    • āœ” Review real-time position calculation for monsters and players
    • šŸ”„ Bug fixes

    The end of the milestone have been set to the end of May 2019. This date is not fixed. It can vary considering the availability of every contributor of the project.

    On the client side, we are also planning to make some changes, pack the resources into a folder, and some "package" files to make the download easier with the Andromeda Launcher.

    I know that it's been a long time since we are developing the project, and some of you might be a little disappointed by the lack of systems developed and ready to use on the test server. Note that we work on our free time to improve ourselves and learn more about the game development.

    Thank you for all of you that have tested the server and made feedback. We now have a clear idea of what you expect about Rhisis.

    --
    Cheers,
    Eastrall

    posted in Announcements
  • Greetings everyone,

    This is it, after a long time of development, the Rhisis test server is finally opening! You can now play on our official server and help us track bugs, and improve the emulator quality.

    Register and download the client

    You can now register an account on our web site and download the Andromeda Launcher to download and update the game client: Download Andromeda

    Server informations

    We are trying to demonstrate that a FLYFF server can run on a Linux environment system with the smallest amount of resources, compared to the FLYFF official files. Thus, we have setup a small VPS with the minimal resources for a small cost. Below, the characteristics of the server:

    Server type: VPS (Virtual Private Server)
    OS: Debian 9
    CPU cores: 1 vCore
    RAM: 4 GB
    Rhisis Version: 0.1

    Features available

    • User authentication
    • View user's characters list
    • Create a new character
    • Delete a character
    • Join the world
    • Walk
    • Chat with other players
    • Monster AI (Move / attack)
    • Shop System (You can buy and sell items from the shops)
    • Inventory System (You can move items, equip/unequip items)
    • Trade items with other players
    • Send mails (only message and gold)
    • Character customization system (change face / hair)

    More informations about the features available: Version 0.1 features


    We hope to see you in game and help us improve Rhisis! If you are facing a bug or an unexpected behavior, we would appriciate if you report it in the Bug Tracker section so we can take a closer look to the issue and fix it for the next versions.

    --

    Cheers,
    Eastrall

    posted in Announcements
  • This guide will help you to set up a Rhisis server instance on a Debian 9 environment. We will first see how to install the dependencies and then we will install and configure a Rhisis instance.

    There is two ways of installing Rhisis. From the source code or from an official release.

    Install Rhisis from source-code

    In this section we will see how to install a Rhisis server using the latest sources and configure the server.

    Prerequisites

    Before you get started, you will need to install some packages on your Debian system.
    First of all, update all of you existing packages by typing the following commands:

    $> sudo apt-get update
    $> sudo apt-get upgrade
    

    The apt-get update command will download the list of packages from the Debian repositories, but will not install any package.
    The apt-get upgrade actually installs newer versions of the packages you have. After updating the lists, the package manager knows about available updates for the software you have installed.

    Now that our Debian environment seems up-to-date, let's install some packages to run Rhisis. We will need to install git to checkout the source code, dotnet to execute the Rhisis servers and mysql-server to store the server's data.

    $> sudo apt-get install git
    

    Once git is installed, you can now install .NET Core.
    You can find all the installation instructions here: .NET Core SDK install instructions

    .NET Core

    To install .NET Core SDK, type the following commands to register the Microsoft key, product repository and install all dependencies, type the following commands:

    $> wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg
    $> sudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/
    $> wget -q https://packages.microsoft.com/config/debian/9/prod.list  
    $> sudo mv prod.list /etc/apt/sources.list.d/microsoft-prod.list  
    $> sudo chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg  
    $> sudo chown root:root /etc/apt/sources.list.d/microsoft-prod.list
    

    Now the product repository is set up, run an apt-get update to update the package list and then install dotnet-sdk

    $> sudo apt-get update
    $> sudo apt-get install dotnet-sdk-2.2
    

    ā„¹ļø if you are facing issues with the following error: E: The method driver /usr/lib/apt/methods/https could not be found., please install the apt-transport-https with the following command:

    $> sudo apt-get install apt-transport-https
    

    And then re-execute the sudo apt-get install dotnet-sdk-2.2 command.

    To make sure .NET Core is installed run the following command and you shall see the following output:

    $> dotnet --version
    2.2.XXX
    

    MySQL Server

    ā„¹ļø *You can find the MySQL Server install instructions here: MySQL Server install

    First of all, we will need to download the repository from the MySQL official website and then install it:

    $> cd /tmp/
    $> wget https://dev.mysql.com/get/mysql-apt-config_0.8.12-1_all.deb
    

    Now that the repository is downloaded, we can install it using the dpkg command:

    $> sudo dpkg -i mysql-apt-config*
    

    During the installation, you'll be presented with a configuration screen where you can specify which version of MySQL you'd prefer, along with an option to install repositories for other MySQL-related tools. The defaults will add the repository information for the latest stable version of MySQL and nothing else. This is what we want, so use the down arrow to navigate to the Ok menu option and hit ENTER.

    MySQL repository is now installed on our Debian environment, we can now run a apt-get update to update the package list:

    $> sudo apt-get update
    

    Then, install the mysql-server package:

    $> sudo apt install mysql-server
    

    During installation, you will be asked to enter the root password, be sure to choose a strong password. Also, you can choose the Use Strong Password Encryption option.

    Once it is installed, check if the mysql server is running correcly:

    $> systemctl status mysql
    ā— mariadb.service - MariaDB 10.1.37 database server
       Loaded: loaded (/lib/systemd/system/mariadb.service; enabled; vendor preset: enabled)
       Active: [active (running)] since Sun 2019-03-03 10:22:42 CET; 26s ago
         Docs: man:mysqld(8)
               https://mariadb.com/kb/en/library/systemd/
     Main PID: 22203 (mysqld)
       Status: "Taking your SQL requests now..."
       CGroup: /system.slice/mariadb.service
               ā””ā”€22203 /usr/sbin/mysqld
    

    ā„¹ļø If the mysql-server didn't asked you for the root password, run the following command to secure your mysql server installation: mysql_secure_installation

    Download and build Rhisis

    Now that git and dotnet is installed, we will need to download the Rhisis source code and compile in order to run the servers.
    Create a folder wherever you want in your disk.

    :info: For this guide, I will use the /home/rhisis/ folder.

    Navigate to /home/rhisis/ and type the following commands to download the sources:

    $> cd /home/rhisis/
    $> git clone https://github.com/Eastrall/Rhisis.git server
    

    The first cd command will Change Directory to the /home/rhisis folder.
    Then, the git clone ADDRESS server will download the sources into the server folder.

    Now again, go to the server directory with the cd command:

    $> cd server/
    

    If you type the command ls -la you can see the content of the current folder:

    $> ls -la
    total 100
    drwxr-xr-x  8 root root  4096 Mar  2 12:27 .
    drwxr-xr-x  3 root root  4096 Mar  2 12:27 ..
    drwxr-xr-x  4 root root  4096 Mar  2 12:27 bin
    -rw-r--r--  1 root root  1174 Mar  2 12:27 CHANGELOG.md
    -rw-r--r--  1 root root  3289 Mar  2 12:27 CONTRIBUTING.md
    drwxr-xr-x  3 root root  4096 Mar  2 12:27 docs
    drwxr-xr-x  8 root root  4096 Mar  2 12:27 .git
    -rw-r--r--  1 root root  2546 Mar  2 12:27 .gitattributes
    -rw-r--r--  1 root root  3760 Mar  2 12:27 .gitignore
    -rw-r--r--  1 root root 35141 Mar  2 12:27 LICENSE
    -rw-r--r--  1 root root  3815 Mar  2 12:27 README.md
    -rw-r--r--  1 root root  8014 Mar  2 12:27 Rhisis.sln
    drwxr-xr-x  4 root root  4096 Mar  2 12:27 script
    drwxr-xr-x 11 root root  4096 Mar  2 12:27 src
    drwxr-xr-x  4 root root  4096 Mar  2 12:27 test
    -rw-r--r--  1 root root   979 Mar  2 12:27 .travis.yml
    

    Alright, we now have the sources. Let's compile Rhisis in release mode using the dotnet build command:

    $> dotnet build --configuration Release
    

    .NET Core will install the solution's packages and build the entire solution.

    Server configuration

    To setup the Rhisis emulator, including the database, we are going to use the Rhisis CLI tool.
    Before running the tool, we are going to create a symbolic link to the Rhisis CLI binary, navigate to the bin/ folder and execute the following command:

    $> chmod +x ./rhisis-cli
    

    Now, our Rhisis CLI is ready to use. To setup the emulator and database type the following command:

    $> ./rhisis-cli setup
    

    The Rhisis CLI will prompt several fields you will need to specify in order to configure the login, cluster, world server and the database access.
    As for the database configuration, you will be asked which provider to use, simply choose MySQL. Then, enter your MySQL account and password (root, or any other one you have created), finally, choose a database name and continue the installation.

    ā„¹ļø The Rhisis CLI will create the configuration files for login, cluster, world and database, and save the configuration into the config folder.

    Create a test account

    To create an account on the server we have configured, you can use the Rhisis CLI user command:

    $> ./rhisis-cli user create
    

    This command will ask you for an username, password, password salt (use "kikugalanet"), and the authority (simple player, GM or admin).

    Server services

    In order to maintain the servers online and restart them if one of them crash, we will need to create 3 services using Debian systemd.

    More informations about Debian systemd here

    Navigate to folder /etc/systemd/system and create a new file named: rhisis-login and insert this content:

    [Unit]
    Description=Rhisis Login Server
    After=mysql.service
    
    [Service]
    WorkingDirectory=/home/rhisis/server/bin/
    ExecStart=/usr/bin/dotnet run --project /home/rhisis/server/src/Rhisis.Login/Rhisis.Login.csproj -c Release
    Restart=always
    RestartSec=10
    KillSignal=SIGINT
    SyslogIdentifier=rhisis-login
    User=root
    Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
    
    [Install]
    WantedBy=multi-user.target
    

    This script describes the behavior of the service. As you can see, this is the script for the Rhisis Login Server. It will start the server automatically after a system reboot and after the mysql.service started.
    In the [Service] section, we specify the WorkingDirectory and command to execute to start the service (ExecStart).
    We can also see that the server will restart 10 seconds after it crashed.

    As for the the Cluster and World Server, the script is nearly the same:

    rhisis-cluster.service

    [Unit]
    Description=Rhisis Cluster Server
    After=rhisis-login.service
    
    [Service]
    WorkingDirectory=/home/rhisis/server/bin/
    ExecStart=/usr/bin/dotnet run --project /home/rhisis/server/src/Rhisis.Cluster/Rhisis.Cluster.csproj -c Release
    Restart=always
    RestartSec=10
    KillSignal=SIGINT
    SyslogIdentifier=rhisis-cluster
    User=root
    Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
    
    [Install]
    WantedBy=multi-user.target
    

    rhisis-world.service

    [Unit]
    Description=Rhisis World Server
    After=rhisis-cluster.service
    
    [Service]
    WorkingDirectory=/home/rhisis/server/bin/
    ExecStart=/usr/bin/dotnet run --project /home/rhisis/server/src/Rhisis.World/Rhisis.World.csproj -c Release
    Restart=always
    RestartSec=10
    KillSignal=SIGINT
    SyslogIdentifier=rhisis-world
    User=root
    Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
    
    [Install]
    WantedBy=multi-user.target
    

    Once you created the 3 services files, reload the services using the command:

    $> sudo systemctl daemon-reload
    

    Starting the servers

    To start the servers, simply start the services we created previously:

    $> sudo systemctl start rhisis-login
    

    To make sure the server has started, type the following command:

    $> sudo systemctl status rhisis-login
    ā— rhisis-login.service - Rhisis Login Server
       Loaded: loaded (/etc/systemd/system/rhisis-login.service; disabled; vendor preset: enabled)
       Active: [active (running)] since Sun 2019-03-03 16:07:31 CET; 14s ago
     Main PID: 26424 (dotnet)
        Tasks: 30 (limit: 4915)
       CGroup: /system.slice/rhisis-login.service
               ā”œā”€26424 /usr/bin/dotnet run --project /home/rhisis/server/src/Rhisis.Login/Rhisis.Login.csproj -c Release
               ā””ā”€26464 dotnet exec /home/rhisis/server/src/Rhisis.Login/bin/Release/netcoreapp2.2/Rhisis.Login.dll
    

    You should always start the servers in this order:

    1. Login Server (rhisis-login.service)
    2. Cluster Server (rhisis-cluster.service)
    3. World Server (rhisis-world.service)
    posted in Tutorials / Guides
  • Resources are an important part of the Rhisis project. Without them, it would be difficult to know for exemple, items attributes, monsters names, etc... This is why we need to load them inside the Rhisis server and consume them whenever we need.

    Rhisis has a specific and simple way to load resources. This guide will help you understand how to create new loaders and how to use them.

    Create a simple loader

    To create a loader, you need to create a new class and make it inherit from the Rhisis.Core.Resources.IGameResourceLoader. Once this is done, you will need to implement the following methods to match the interface contract:

    • Load() : Loads the resources
    • Dispose() : Dispose and releases the resources.

    Example:

    using Rhisis.Core.Resources;
    
    public class ItemLoader : IGameResourceLoader
    {
    	public void Load()
    	{
    		// Insert loading code here
    	}
    	
    	public void Dispose()
    	{
    		// Insert dispose code here
    	}
    }
    

    Setting up the loader at startup

    Each server has an entry point and a startup class. The WorldServer for instance, has a WorldServerStartup class that contains all the code necessery for startup. It includes configuration files loading, database setup and resource loading.

    public sealed class WorldServerStartup : IProgramStartup
    {
    	// ...
    	private readonly IEnumerable<Type> _loaders = new []
    	{
    		typeof(DefineLoader),
    		typeof(TextLoader),
    		typeof(ItemLoader), // Item loader added
    		// TODO: add more here
    	};
    
    	public void Configure()
    	{
    		// ...
    		GameResources.Instance.Initialize(this._loaders);
    		DependencyContainer.Instance.BuildServiceProvider();
    	}
    
    	public void Run()
    	{
    		try
    		{
    			GameResources.Instance.Load(); // Call loaders in the order defined in the _loaders field.
    		}
    		catch (Exception e)
    		{
    			// ...
    		}
    	}
    }
    

    In the piece of code shown above, we add the ItemLoader to the _loaders private field of the WorldServerStartup class and then we call the GameResources.Instance.Initialize(this._loaders); which will register every loaders.
    When everything is configured, the program calls the Run() method which will call the GameResources.Instance.Load(); and load every loader in the defined order.

    Working with dependency injection

    You have the possibility to inject dependencies into your loaders, like the logger, or other loaders that have been already loaded.

    using Rhisis.Core.Resources;
    
    public class ItemLoader : IGameResourceLoader
    {
    	private readonly ILogger<ItemLoader> _logger;
    	private readonly DefineLoader _defines;
    
    	public ItemLoader(ILogger<ItemLoader> logger, DefineLoader defines)
    	{
    		this._logger = logger;
    		this._defines = defines;
    	}
    
    	public void Load()
    	{
    		this._logger.LogInformations("Loading items...");
    		// Insert loading code here
    	}
    	
    	public void Dispose()
    	{
    		// Insert dispose code here
    	}
    }
    
    posted in Tutorials / Guides
  • You would like to contribute to Rhisis by creating new systems ? Well, you are in right place! This guide will help you find all the informations and tricks you need to develop your own system from scratch.

    You first need to read the Contribution Guidelines in order to have the sources and the repository ready.

    Next thing you need, is to know which system you want to create and add to Rhisis. Let's say you want to create the ChatSystem to be able to talk to other players around you. You will need to identify the packet header that the server receives and process it correctly in order to send information back to the clients.

    āš  The chat system is already done and used as an exemple. If you want to do it again, you can delete all the files/folders related to the chat which are the following:

    • Handlers/ChatHandler.cs
    • System/Chat/
    • Packets/ChatPacket.cs

    Now, let's get started.

    Basics

    You first need to understand the basics of the communication between a client and a server.
    Let's take a simple exemple. You enter a restaurant, the waiter welcomes you and gives you the menu. Then you choose what you want to eat/drink and you give the order to the waiter. He goes back to the kitchen and brings you your drink and food. Then you, eat, drink, pay, and say goodbye.

    In this exemple, you are the Client and the waiter is the Server. You simply ask for something, and the server answers you.

    In FlyFF, the communication between the client and the server is made using a socket and the TCP protocol. If you want more informations, Wikipedia explains it very well : TCP IP Wikipedia

    All of this process is already done in the client side and in the server side (Rhisis). You don't need to create it from scratch, but it's always good to understand what we are talking about šŸ˜‰

    So, the client sends us a request, we are going to handle and processes it and then we will send the answer to the client.

    Handle the incoming request

    When you are connected to Rhisis server, sometimes you receive this warning message in the console:

    [Warning] - Unimplemented packet CHAT(0x00FF0000)
    

    This message tells you that the server has received a packet named CHAT with the header : 0x00FF0000 and it has been ignored by the server and therefore, not handled.

    Before going further, let's explain what is a packet. A packet is "just" a message sent by the client to the server or from the server to the client. This message has an identifier that we call "header". It is used to differentiate messages. For exemple, if the server receives the CHAT header, we will know, just by looking at it that it process the chat action, in the other case if we receive the BATTLE header, it will process an action related to the battle system.

    You can visualize a packet like this image:

    packet

    We can see that the packet (in black) has an header (in blue) and some content (in red).

    Note: All packet header are located in Rhisis.Core/Network/Packets/PacketType.cs.

    To handle the chat packet, you will need to create a new file inside the Handlers folder of the Rhisis.World project. Inside this file create a static class and name it ChatHandler.

    using System;
    
    namespace Rhisis.World.Handlers
    {
        public static class ChatHandler
        {
        }
    }
    

    Next step will be to tell the world server to do an action when he receives the CHAT packet. To do that, you just need to create a static method that takes two parameters : WorldClient and INetPacketStream. We pass this parameters so we can know which client received the packet, and the INetPacketStream that contains the informations sent by the client.

    using Ether.Network.Packets;
    using Rhisis.Core.Network;
    using Rhisis.Core.Network.Packets;
    
    namespace Rhisis.World.Handlers
    {
        public static class ChatHandler
        {
            [PacketHandler(PacketType.CHAT)]
            public static void OnChat(WorldClient client, INetPacketStream packet)
            {
            }
        }
    }
    

    Thanks to the PacketHandler attribute, each time the server will receive the PacketType.CHAT packet, it will call the OnChat method and execute it. This is the entry point of the incoming packet.

    Now that our World server receives and handles the CHAT packet, we will have to read the content of the packet. To know the content of the packet, I usually go to the official files with the packet header ID (0xNNNNNN) or header name and search for what the Neuz sends to the server. For exemple, I have searched PACKETTYPE_CHAT within the official files and found what the Neuz was sending when we type a message on the chat.

    void CDPClient::SendChat( LPCSTR lpszChat )
    {
    	BEFORESENDSOLE( ar, PACKETTYPE_CHAT, DPID_UNKNOWN );
    	ar.WriteString( lpszChat );
    	SEND( ar, this, DPID_SERVERPLAYER );
    }
    

    In the BEFORESENDSOLE instruction it will just say that the PACKETTYPE_CHAT is the packet header. (Blue part in the previous image)
    What we want from this method is the content of the packet located between the BEFORESENDSOLE and SEND instruction. In our case we only have the following lines:

    ar.WriteString( lpszChat );
    

    This line will write the content of lpszChat as a string inside the packet content (Red part in the image).

    Alright, we know that the Neuz send the PACKETTYPE_CHAT header along with a single string as content. Now, let's go back to our handler and read the message from the Neuz.
    To do so, we are going to use the INetPacketStream.Read<T>() method.

    using Ether.Network.Packets;
    using Rhisis.Core.Network;
    using Rhisis.Core.Network.Packets;
    
    namespace Rhisis.World.Handlers
    {
        public static class ChatHandler
        {
            [PacketHandler(PacketType.CHAT)]
            public static void OnChat(WorldClient client, INetPacketStream packet)
            {
                string chatMessage = packet.Read<string>();
            }
        }
    }
    

    In this code, we simply had a line that reads a string from the packet variable and store it into the chatMessage. To check if it's the correct message from the client, you can add a Console.WriteLine(chatMessage); under the packet.Read<string>() line.

    Pretty simple right ? With only a few lines of code, we can handle incoming data from the client.
    From now on, that's where the fun starts, we are going to create the system that will contain the game logic.

    Create the system

    In Rhisis context, a system is where the game logic is written and executed. All systems are located in the Systems folder of the Rhisis.World project.
    Before we get started with our system, you must know that there is two types of systems:

    • Updatable : A system that is executed at every game tick on all map layers.
    • Notifiable : A system that can be called and executed by an entity.

    When you create a new system for Rhisis, you must know which type of system it is. Ask yourself this question : "How am I going to call the game logic ?" If it's related to the player, then it should be a Notifiable system. If it's related to the map, environment, time, then it should be a Updatable system.

    In our case, we want to write a system that interacts with the player. Our ChatSystem will then be : Notifiable.

    Let's get started. To create a new system, create a new folder in the Systems folder of the Rhisis.World project; let's name it "Chat".

    Then, create a new class named ChatSystem. Once you have your ChatSystem class, make it inherit from the ISystem interface.

    using Rhisis.World.Game.Core;
    using Rhisis.World.Game.Core.Systems;
    
    public class ChatSystem : ISystem
    {
        // This property indicates which type of entities can execute this system.
        // In our case, only players can chat.
        public WorldEntityType Type => WorldEntityType.Player;
    
        public void Execute(IEntity entity, SystemEventArgs e)
        {
            // TODO: Chat Game Logicc
        }
    }
    

    ā„¹ As you can see, you must implement the Execute method and Type property.
    The Execute method takes an IEntity as first parameters which will be the entity that is calling the system; and then takes a SystemEventArgs as second parameter.
    The SystemEventArgs is used to pass aditional arguments to the system. To create a new system arguments, you must create a new class and inherit from SystemEventArgs.

    Good, now let's mention that the ChatSystem class is a Rhisis system and give him a system type.

    using Rhisis.World.Game.Core;
    using Rhisis.World.Game.Core.Systems;
    
    [System(SystemType.Notifiable)]
    public class ChatSystem : ISystem
    {
        public WorldEntityType Type => WorldEntityType.Player;
    
        public void Execute(IEntity entity, SystemEventArgs e)
        {
            // TODO: Chat Game Logic
        }
    }
    

    Alright, we have our system ready, let's start writing the game logic. First question is, what do we need for this system ? We already have the entity that is calling the system, and we now need the chat text to send to every players around.

    As I mentioned earlier, we will need to create a new system arguments class. To do so, let's create a new class named ChatEventArgs and make it inherit from SystemEventArgs.

    This new class will need to store the chat message, so you will need to create a new readonly property and a constructor that takes one parameter : the chat message.

    using Rhisis.World.Game.Core.Systems;
    
    public sealed class ChatEventArgs : SystemEventArgs
    {
        public string Message { get; }
        
        public ChatEventArgs(string message)
        {
            this.Message = message;
        }
        
        public override bool CheckArguments() => !string.IsNullOrEmpty(this.Message);
    }
    

    With this class, we will now be able to store the chat message and pass it to the ChatSystem's Execute method.

    Let's go back to the ChatSystem class and check if the system event it is a ChatEventArgs and if his arguments are correct.

    using Rhisis.World.Game.Core;
    using Rhisis.World.Game.Core.Systems;
    
    [System(SystemType.Notifiable)]
    public class ChatSystem : ISystem
    {
        public WorldEntityType Type => WorldEntityType.Player;
    
        public void Execute(IEntity entity, SystemEventArgs e)
        {
            if (!(e is ChatEventArgs chatEvent)) // Check if e is type of ChatEventArgs
                return;  // KO: e is not a ChatEventArgs
            if (!chatEvent.CheckArguments())
                return; // KO: arguments are invalid.
    
            // OK: e is a ChatEventArgs and arguments are valid.
        }
    }
    

    Once we checked the event type and the arguments, we know that our parameters are valid ang we can now write the game logic.

    ā„¹ The ChatSystem can send messages to every player around the player that is calling the system, or process chat commands. For this guide, we will only handle the normal chat message.

    First of all, we are going to check if the player's message start with a /. If so, it's a chat command and will not be covered by this guide. If not, we will send the message to every player around.

    using Rhisis.World.Game.Core;
    using Rhisis.World.Game.Core.Systems;
    
    [System(SystemType.Notifiable)]
    public class ChatSystem : ISystem
    {
        public WorldEntityType Type => WorldEntityType.Player;
    
        public void Execute(IEntity entity, SystemEventArgs e)
        {
            if (!(e is ChatEventArgs chatEvent))
                return; 
            if (!chatEvent.CheckArguments())
                return; 
    
            if (chatEvent.Message.StartsWith("/"))
            {
                // It's a chat command. Not covered by the guide.
            }
            else
            {
                // TODO: send message to every player around.
            }
        }
    }
    

    Alright, let's summarize what we have done so far.

    1. Create the ChatSystem class and make it notifiable.
    2. Create the ChatEventArgs that stores the received chat message and use it in the system logic
    3. Check the system event type and argument
    4. Check if the message is a command or normal message.

    Next step is to send to every player around the system calling player the chat message. To do so, we will need to create a packet.

    ā„¹ All packets files are located in the Packets folder of the Rhisis.World project.

    Go to the Packets folder and create a new file named ChatPackets. This file will contain every packets related to the ChatSystem. Remove the ChatPackets class and replace it with public static partial class WorldPacketFactory.

    ā„¹ This will say that we are sharing the every method contained into the partial WorldPacketFactory class.

    Now, create a static method that takes the 2 parameters :

    • IEntity entity : the current entity sending the packet.
    • string message : the message to send.
    public static partial class WorldPacketFactory
    {
        public static void SendChat(IEntity entity, string message)
        {
        }
    }
    

    Just like the incoming packet, to know what the client is expected, let's make a quick search throught the official files and search for the every Chat occurence. After some minutes of search, we found the CUserMng::AddChat() method that looks like what we are searching for.

    void CUserMng::AddChat( CCtrl* pCtrl, const TCHAR* szChat )
    {
        CAr ar;
    
        ar << GETID( pCtrl ) << SNAPSHOTTYPE_CHAT;
        ar.WriteString( szChat );
        ...
    }
    

    If we take a close look, we see that the packet is storing the control id, which is in Rhisis context, the entity id. Then it stores the snapshot type, which is SNAPSHOTTYPE_CHAT.
    Finally, it writes the szChat string into the packet and then send to every player around.

    Alright, let's do this in C#.

    public static partial class WorldPacketFactory
    {
        public static void SendChat(IEntity entity, string message)
        {
            using (var packet = new FFPacket())
            {
                packet.StartNewMergedPacket(entity.Id, SnapshotType.CHAT);
                packet.Write(message);
    
                SendToVisible(packet, entity, sendToPlayer: true);
            }
        }
    }
    

    As you can see, we create a new FFPacket instance and start a new packet with the entity.Id and the SnapshotType.CHAT just like the official files. Then, we write the message into the packet and finally, we send it to every visible players around entity. Note that we also send the packet to the entity (specified with the sentToPlayer: true flag).

    Our packet seems to be correct, let's go back to the ChatSystem and call the SendChat() method we created to send the chat message.

    using Rhisis.World.Game.Core;
    using Rhisis.World.Game.Core.Systems;
    using Rhisis.World.Packets;
    
    [System(SystemType.Notifiable)]
    public class ChatSystem : ISystem
    {
        public WorldEntityType Type => WorldEntityType.Player;
    
        public void Execute(IEntity entity, SystemEventArgs e)
        {
            if (!(e is ChatEventArgs chatEvent))
                return; 
            if (!chatEvent.CheckArguments())
                return; 
    
            if (chatEvent.Message.StartsWith("/"))
            {
                // It's a chat command. Not covered by the guide.
            }
            else
            {
                WorldPacketFactory.SendChat(entity, chatEvent.Message);
            }
        }
    }
    

    Good, our system is now ready!

    Assembling Handler and System

    Now that we have our system ready, we need to call it whenever we receive a CHAT packet. Open your ChatHandler.cs file, create a new ChatEventArgs by passing it the message and call the ChatSystem.

    using Ether.Network.Packets;
    using Rhisis.Network;
    using Rhisis.Network.Packets;
    using Rhisis.Network.Packets.World;
    using Rhisis.World.Systems.Chat;
     
    namespace Rhisis.World.Handlers
    {
        public static class ChatHandler
        {
            [PacketHandler(PacketType.CHAT)]
            public static void OnChat(WorldClient client, INetPacketStream packet)
            {
                string chatMessage = packet.Read<string>();
                var chatEvent = new ChatEventArgs(chatPacket.Message);
    
                client.Player.NotifySystem<ChatSystem>(chatEvent);
            }
        }
    }
    

    And that's it. Whenever you receive the PacketType.CHAT packet, the handler will notify the ChatSystem and execute its game logic.


    Congratulations, you just created a Rhisis system! šŸŽ‰

    It wasn't that hard thought! šŸ˜

    I hope you enjoyed creating a system on Rhisis, and don't hesitate to ask for help if you want to create a system an contribute to the emulator.

    All the best,
    Eastrall.

    posted in Tutorials / Guides
  • Greetings everyone,

    Today, I am happy to announce the opening of the Rhisis emulator project community forums. This forum has been made so everyone can talk about the project, share their ideas, report bugs found on local or online test server, and of course, improve the emulator.

    We intent to create a community around this project and provide good quality content to help people to create a FLYFF private server easily without using the official files and guide people to contribute to the project by developping new systems or bringing new bug fixes.

    Soon, the official Rhisis server will be open and you will be able to play on our server. Be aware that we are in testing phase and you can experience some frustrating bugs while playing. When you are facing a bug or an unexpected behavior, we would appriciate if you report it in the Bug Tracker section so we can take a closer look to the issue and fix it ASAP.

    We thank you for your support since the begining of the project and we look forward to see you in game!

    --
    Eastrall

    posted in Announcements