R-Bus

A horizontally scrolling shooter multiplayer arcade video game.

✈ Cross-Platform

Release

Compil

R-Bus is designed to be cross-platform, available on Windows, Linux, and MacOS.

Screenshots

Connection

LobbySelection

LobbyCreation

⏬ Installation

Installing R-Bus is easy.

Follow the instructions below based on your preferred method and operating system.

There are two ways to install R-Bus:

  • 📦 From packaged binary: Prefer this method if you want to avoid downloading dependencies and compiling R-Bus.
  • 🔨 From source: Choose this method if you're comfortable with handling dependencies and compilation.
From Packaged Binary

📦 From Packaged Binary

🐧 GNU/Linux

:window: Windows

🍎 MacOS

From Source

🔨 From Source

🐧 GNU/Linux

:window: Windows

🍎 MacOS

👾 How to play

KeyAction
Left arrowMove left
Right arrowMove right
Up arrowMove up
Down arrowMove down
SpaceShoot normal bullets
CShoot fast bullets
VShoot bouncing bullets
BShoot penetrating bullets
EscGo back

📖 Documentation

📑 License

R-Bus is licensed under the MIT License.

Created By X-L-R-G-B

logo

This project is a collaborative effort by:

Installation

There are two ways to install R-Bus:

  • From packaged binary
  • From source

Prefer the packaged binary if you want to find yoursel downloading some dependencies and compiling R-Bus. Prefer the packaged binary if you are not a nerd

Else, go ahead and install R-Bus from source.

Packaged binary

Now, let's install R-Bus from packaged binary.

Please select the right page for your operating system

GNU/Linux

curl -Lo 'r-type-linux.sh' \
    'https://github.com/X-R-G-B/R-Bus/releases/latest/download/r-type-linux.sh'
# The following line will Accepts the license
# extract the packaged binary and its dependencies
yes y | bash 'r-type-linux.sh'
cd R-Type-Linux
# your binaries are in ./bin/
ls ./bin/

:warnings: When you want to move this binaries, please move all the folder R-Type-Linux

MacOs

Download https://github.com/X-R-G-B/R-Bus/releases/latest/download/r-type-macos.dmg

And launch the file downloaded.

Windows

Download https://github.com/X-R-G-B/R-Bus/releases/latest/download/r-type-windows.exe

And execute the file downloaded.

Source

Now, let's install R-Bus from source.

Please select the right page for your operating system

GNU/Linux

  • You need to have cmake, and a c++ compiler

hint: you can use https://github.com/aminya/setup-cpp to install it

  • Git clone the project or download the .zip

git clone -b main https://github.com/X-R-G-B/R-Bus.git R-Bus-main

OR

wget https://github.com/X-R-G-B/R-Bus/archive/main.zip && unzip R-Bus-main.zip

  • Change directory

cd R-Bus-main || "Failed to cd to R-Type-Linux, please open an issue: 'https://github.com/X-R-G-B/R-Bus/issues/new?assignees=&labels=bug&projects=&template=install-failed.yml&title=%5BFAIL+INSTALL%5D+-+Title'"

  • Install dependencies

sudo ./scripts/install-deps-linux.sh

  • Build the project

./scripts/compil.sh

Well Done you have your binaries!

Now, you can run it but dont move it anywhere

MacOs

  • You need to have cmake, and a c++ compiler

hint: you can use https://github.com/aminya/setup-cpp to install it

  • Git clone the project or download the .zip

git clone -b main https://github.com/X-R-G-B/R-Bus.git R-Bus-main

OR

wget https://github.com/X-R-G-B/R-Bus/archive/main.zip && unzip R-Bus-main.zip

  • Change directory

cd R-Bus-main || "Failed to cd to R-Type-Linux, please open an issue: 'https://github.com/X-R-G-B/R-Bus/issues/new?assignees=&labels=bug&projects=&template=install-failed.yml&title=%5BFAIL+INSTALL%5D+-+Title'"

  • Install dependencies

./scripts/install-deps-macos.sh

  • Build the project

./scripts/compil.sh

Well Done you have your binaries!

Now, you can run it but dont move it anywhere

Windows

  • You need to have cmake, and a c++ compiler

hint: you can use https://github.com/aminya/setup-cpp to install it

  • Git clone the project or download the .zip

git clone -b main https://github.com/X-R-G-B/R-Bus.git R-Bus-main

OR

wget https://github.com/X-R-G-B/R-Bus/archive/main.zip && unzip R-Bus-main.zip

  • Change directory

cd R-Bus-main || "Failed to cd to R-Bus-main, please open an issue: 'https://github.com/X-R-G-B/R-Bus/issues/new?assignees=&labels=bug&projects=&template=install-failed.yml&title=%5BFAIL+INSTALL%5D+-+Title'"

  • Install dependencies

./scripts/install-deps-windows.ps1 --interactive

  • Build the project

./scripts/compil.ps1 --no-tidy

Well Done you have your binaries!

Now, you can run it but dont move it anywhere

Troubleshooting

If you have any errors, please let us know.

You can fill an issue here https://github.com/X-R-G-B/R-Bus/issues/new/choose and choose a category.

CONTRIBUTING

Here is the instructions if you want to contribute to the project

Submitting a pull request

  1. Fork and clone the repository
  2. Create a new branch: See 'creating a branch'
  3. Make your change, add tests, and make sure the tests still pass
  4. Make sure that the code is codeQL compliant
  5. Make sure that you had documentation to the new added code (obviously compliant with our doc)
  6. Push to your fork and submit a pull request with correct labels added if necessary
  7. Wait for your pull request to be reviewed and merged.

Here are a few things you can do that will increase the likelihood of your pull request being accepted:

  • The security of your code is analysed by codeQL the CI might fail if it's not secure.
  • The style of your code must respect the clang-format style.
    Normally we have the .clang-format if you want for your IDE. If you don't want to use the .clang-format intergration in your IDE, you can still
    launch the script format.sh (Linux and MacOs) or format.ps1 (Windows), this will format automaticlly your code.
    /!\ Warning ! If your code is not clang format compliant your CI will fail and your pr will be refused.
  • Write tests.
  • Keep your change as focused as possible.
    If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
  • Write a commit message that respect the commit norm that we have, see 'Commit message norm'.

Commit message norm

  1. Define your context.
  • CICD
  • CLIENT-GAME
  • DOCUMENTATION
  • POC
  • NETWORK
  • SERVER
  1. Followed by ":" + "action verb"
  • : Add
  • : Fix
  • : Update
  • etc ...
  1. Explain what your commit introduce
  • new feature for client-game
  • bug inside network
  • documentation for CLIENT-GAME
  • etc...
  1. Just after your commit message put a newline to indicate semver
  • PATCH (If your commit introduce a bug fix)
  • MINOR (If your commit introduce a new feature)
  • MAJOR (If your commit introduce a new feature and the older version doesn't work anymore)

/!\ Warning ! If your commit message doesn't respect those rules your pr might not be accepted

Creating a branch

If you need to contribute to our project, you will need to create a branch to submit a pull request.
Basicly, you must indicate what is the context of your branch followed by a '/'.
For exemple :

  • feature/BRANCH-NAME
  • doc/BRANCH-NAME
  • refactor/BRANCH-NAME
  • etc...

That's all you need to do to create a valid branch.

CICD

Compilation test

When you modify the src directory, the workflow https://github.com/X-R-G-B/R-Bus/actions/workflows/compil.yml will run

Documentation

When you modify the docs directory or book.tml, the workflow https://github.com/X-R-G-B/R-Bus/actions/workflows/documentation.yml will run

Format

The code is automatically formatted by the workflow https://github.com/X-R-G-B/R-Bus/actions/workflows/format.yml when your create a pull request.

If it can't be automatically formated, please use clang-format with the script ./scripts/format.sh or ./scripts/format.ps1

Release

On the main branch, and the dev branch, the workflow https://github.com/X-R-G-B/R-Bus/actions/workflows/release.yml will run

In dev branch it don't push the packaged binary or the source code to a release but in the workflow artifact

Network

The communication protocol is documented with an RFC document.

                    Communication Between Client and Server

------------------------------------------------------------------------------

Abstract

    This document describes the communication between the client and the
    server for the R-Bus game.
    If you want to implement a custom client or server, please follow
    miticulously these RFC.

------------------------------------------------------------------------------

Copyright Notice

    This document is licensed under the MIT License with all resources used by
    the R-Bus game.

------------------------------------------------------------------------------

Table of Contents

    1. Introduction
    2. Network Packet Global Structure
        2.1. Header
        2.2. Body
            2.2.1. Action Header
            2.2.2. Action Body
    3. Network Packet Actions
        3.1. Client -> Main Server
            3.1.1. CONNECT_MAIN_SERVER
            3.1.2. LIST_LOBBY
            3.1.3. CREATE_LOBBY
        3.2. Client -> Lobby Server
            3.2.1. CONNECT_LOBBY
            3.2.2. INIT
            3.2.3. READY
            3.2.4. POSITION_RELATIVE
            3.2.5. POSITION_ABSOLUTE
            3.2.6. NEW_MISSILE
            3.2.7. LIFE_UPDATE
            3.2.8. ENEMY_DEATH
            3.2.9. PLAYER_DEATH
            3.2.10. MISSILE_DEATH
            3.2.11. DISCONNECT_LOBBY

        3.3. Main Server -> Client
            3.3.1. CONNECT_MAIN_SERVER_RESP
            3.3.2. LIST_LOBBY
        3.4. Lobby Server -> Client
            3.4.1. CONNECT_LOBBY_RESP
            3.4.2. START_WAVE
            3.4.3. LIFE_UPDATE
            3.4.4. ENEMY_DEATH
            3.4.5. NEW_ENEMY
            3.4.6. NEW_PLAYER
            3.4.6. NEW_MISSILE
            3.4.8. POSITION_ABSOLUTE_BROADCAST
            3.4.9. POSITION_RELATIVE_BROADCAST
            3.4.10. PLAYER_DEATH
            3.4.11. MISSILE_DEATH
            3.4.12. END_GAME
        3.5. Lobby Server -> Main Server
            3.5.1. INFO_LOBBY
    4. References
        3.1. R-Bus
        3.2. RFC
    5. Appendix
        5.1. Header
            5.1.1. Message
            5.1.1. Network

------------------------------------------------------------------------------

1. Introduction

    The purpose of this document is to facilitate the implementation of custom
    clients and servers for the R-Bus game, as well as for the developers of
    the R-Bus game to know how to develope further new actions for the game.
    This is intended to be a reference for the developers of the R-Bus game.
    This is not intended to be a reference for the players of the R-Bus game.

------------------------------------------------------------------------------

2. Network Packet Global Structure

    The packets are compressed using the zstd library before being sent over
    the network using the UDP protocol. Upon receipt, the packets are
    decompressed using the zstd library
    To add some better reliability, each packet sent has a header with some
    information about all the packets received.

2.1. Header

    The header is composed of the following fields:
    - `magick1`
    - `ids_received`
    - `last_id_received`
    - `id`
    - `nb_action`
    - `magick2`

    *** Magick1

    This field is used to know the packet received is a packet sent by and for
    the R-Bus game.

    This field must be of size 1 byte.
    This field must be equal to the ascii `\x01`

    *** Ids Received

    This field is one part of the header that helps to achieve the reliability
    we need to have to ensure all the important packets are received.

    This field must be of size 4 bytes.
    This field is an unsigned integer (so starting from 0 to 2^32)

    To construct these field, here are an example of its implementation in
    pseudo-code:
    - The value of `ids_received` is zero'd
    - Start a loop with a counter that starts at `last_id_received` and end at
    `last_id_received - 16`
    - Shift all the bit of `ids_received` to the left
    - If the packet with the id of the value of the counter is received, add 1
    to the `ids_received`
    - End the loop

    *** Last Id Received

    This field is one part of the header that helps to achieve the reliability
    we need to have to ensure all the important packets are received.

    This field must be of size 4 bytes.
    This field is unsigned (so starting from 0 to 2^32)

    This field need to be set to the last id of the packet received.

    *** Id

    This field is one part of the header that helps to achieve the reliability
    we need to have to ensure all the important packets are received.

    This field must be of size 4 bytes.
    This field is an unsigned integer (so starting from 0 to 2^32)

    This field will start from 0 at start of the connection, and each time a
    packet is sent, it will be incremented by 1.
    When the maximum id is reached, it will be reset to 0.

    *** Nb Action

    This field is used to know how many action have been sent in the same packet.

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)

    *** Magick2

    This field is used to know the packet received is a packet sent by and for
    the R-Bus game.

    This field must be of size 1 byte.
    This field must be equal to the ascii `\x03`

2.2.  Body

    The body is composed of `n` actions. The number of actions that have
    been sent is in the `nb_action` field in the header of the packet.

2.2.1.  Action Header

    The action header is composed of the following fields:
    - `magick`

    *** Magick

    This field describe how to interpret the action body.

    This field must be of size 4 byte.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

2.2.2.  Action Body

    The next bytes in the packet are specified in the list of available
    actions.

------------------------------------------------------------------------------

3. Network Packet Actions

    This section specify each action that a Client, Main Server or Lobby Server
    can send.

3.1. Client -> Main Server

3.1.1. CONNECT_MAIN_SERVER

    To understand this action, the action header `magick` must be equal to `19`

    The action body is composed of the following fields:
    - `magick`

    *** Magick

    This field help to know the packet is realy a connect action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x18`

3.1.2. LIST_LOBBY

    To understand this action, the action header `magick` must be equal to `16`

    The action body is composed of the following fields:
    - `magick`

    *** Magick

    This field help to know the packet is realy a list lobby action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x16`

3.1.3. CREATE_LOBBY

    To understand this action, the action header `magick` must be equal to `23`

    The action body is composed of the following fields:
    - `magick`
    - `name`
    - `gameType`
    - `maxNbPlayer`
    - `ip`
    - `port`

    *** Magick

    This field help to know the packet is realy a create lobby action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x13`

    *** Name

    This field correspond to the name of the lobby.

    This field must be of size 32 bytes.
    Each byte is a signed integer (so starting from -((2^8)/2) to
    +(((2^8)/2)-1)).
    Each byte correspond to the ascii character.
    After the last byte that you set, you must add a null character.
    The null character is the ascii character `\x00`
    The null character must be on the range of the 32 bytes.

    *** Game Type

    This field correspond to the type of the game created.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))
    This field must be equal to one of this value:
    - 0 : classic game

    *** Max Nb Player

    This field correspond to the maximum number of player in the lobby.

    This field must be of size 4 bytes.
    This field is unsigned (so starting from 0 to 2^32)

    *** IP

    This field correspond to the ip of the Main Server.

    This field must be of size 16 bytes.
    Each byte is a signed integer (so starting from -((2^8)/2) to
    +(((2^8)/2)-1)).
    Each byte correspond to the ascii character.
    After the last byte that you set, you must add a null character.
    The null character is the ascii character `\x00`
    The null character must be on the range of the 16 bytes.

    *** Port

    This field correspond to the port of the Main Server.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

3.2. Client -> Lobby Server

3.2.1. CONNECT_LOBBY

    To understand this action, the action header `magick` must be equal to `20`

    The action body is composed of the following fields:
    - `magick`
    
    *** Magick
    
    This field help to know the packet is realy a connect lobby action
    
    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x19`

3.2.2. INIT

    To understand this action, the action header `magick` must be equal to `1`

    This packet initialize the connection to the lobby, and by extension,
    register to the game that will be created in that lobby.

    The Lobby Server will respond to this packet with a `NEW_PLAYER` (3.4.5.)
    action.

    The action body is composed of the following fields:
    - `magick`

    *** Magick

    This field help to know the packet is realy an init action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x06`

3.2.3. READY

    To understand this action, the action header `magick` must be equal to `2`

    This packet tell the Lobby Server that the client is ready to play.
    The Lobby Server will wait that all players connected are ready to start
    the game.

    The action body is composed of the following fields:
    - `magick`

    *** Magick

    This field help to know the packet is realy a ready action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x17`

3.2.4. POSITION_RELATIVE

    To understand this action, the action header `magick` must be equal to `7`

    The action body is composed of the following fields:
    - `magick`
    - `x`
    - `y`

    *** Magick

    This field help to know the packet is realy a position relative action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x08`

    *** X

    This field correspond to the difference between the current position of
    the client and the position of the client 10 milliseconds before.

    This field must be of size 1 bytes.
    This field is signed (so starting from -((2^8)/2) to +(((2^8)/2)-1))

    *** Y

    This field correspond to the difference between the current position of
    the client and the position of the client 10 milliseconds before.

    This field must be of size 1 bytes.
    This field is signed (so starting from -((2^8)/2) to +(((2^8)/2)-1))

3.2.5. POSITION_ABSOLUTE

    To understand this action, the action header `magick` must be equal to `8`

    The action body is composed of the following fields:
    - `magick`
    - `x`
    - `y`

    *** Magick

    This field help to know the packet is realy a position absolute action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x09`

    *** X

    This field correspond to the absolute position of the client.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

    *** Y

    This field correspond to the absolute position of the client.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

3.2.6. NEW_MISSILE

    To understand this action, the action header `magick` must be equal to `9`

    The action body is composed of the following fields:
    - `magick`
    - `x`
    - `y`
    - `missileId`
    - `missileHealth`
    - `missileType`

    *** Magick

    This field help to know the packet is realy a new bullet action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x0d`

    *** X

    This field correspond to the absolute position of the bullet when created.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

    *** Y

    This field correspond to the absolute position of the bullet when created.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

    *** Missile ID
    
    This field correspond to the ID of the missile.
    This is useless because this is the server that will set the ID of the
    missile.
    
    This field must be of size 4 byte.
    This field must be equal to `0`
    
    *** Missile Health
    
    This field correspond to the health of the missile.
    This is useless because this is the server that will set the health of the
    missile.
    
    This field must be of size 4 byte.
    This field must be equal to `0`

    *** Missile Type

    This field correspond to the type of the missile.

    This field must be of size 4 byte.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))
    This field must be equal to one of this value:
    - 0 : classic
    - 1 : fast
    - 2 : bounce
    - 3 : perforant

3.2.7. LIFE_UPDATE

    To understand this action, the action header `magick` must be equal to `5`

    The action body is composed of the following fields:
    - `magick`
    - `playerId`
    - `hp`

    *** Magick

    This field help to know the packet is realy a life update action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x0b`

    *** Player ID

    This field correspond to the ID of the client (the server will verify it).

    This field must be of size 4 bytes.
    This field is unsigned (so starting from 0 to 2^32)
    This field is unique for each client.

    *** HP

    This field correspond to the life of the client.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

3.2.8. ENEMY_DEATH

    To understand this action, the action header `magick` must be equal to `6`

    The action body is composed of the following fields:
    - `magick`
    - `ennemyId`

    *** Magick

    This field help to know the packet is realy an enemy death action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x0c`

    *** Ennemy ID

    This field correspond to the ID of the ennemy that has been killed.

    This field must be of size 4 bytes.
    This field is unsigned (so starting from 0 to 2^32)
    This field is unique for each ennemy.

3.2.9. PLAYER_DEATH

    To understand this action, the action header `magick` must be equal to `14`

    The action body is composed of the following fields:
    - `magick`
    - `playerId`

    *** Magick

    This field help to know the packet is realy a player death action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x11`

    *** Player ID

    This field correspond to the ID of the player that has been killed.

    This field must be of size 4 bytes.
    This field is unsigned (so starting from 0 to 2^32)
    This field is unique for each player.

3.2.10. MISSILE_DEATH

    To understant This action, the aciton header `magick` must be equal to `15`
    
    The action body is composed of ther following fields:
    - `magick`
    - `missileId`

    *** Magick
   
    This field help to know the packet is realy missile death action
    
    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x1c`
    
    *** Missile ID
    
    This field correspond to the ID of the missile that died

    This field must be of size 4 bytes.
    This field is unsigned (so starting from 0 to 2^32)
    This field is unique for each missile.

3.2.11. DISCONNECT_LOBBY

    To understant this aciton, the action header `magick` must be equal to `22`
    
    The action body is composed of the following fields:
    - `magick`
    
    *** Magick
    
    This field help to know the packet is realy a disconnect lobby action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x1b`

3.3. Main Server -> Client

3.3.1. CONNECT_MAIN_SERVER_RESP

    To understand this action, the action header `magick` must be equal to `20`

    The action body is composed of the following fields:
    - `magick`

    *** Magick

    This field help to know the packet is realy a connect main server response

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x14`

3.3.2. LIST_LOBBY

    To understand this action, the action header `magick` must be equal to `20`

    The action body is composed of the following fields:
    - `magick`
    - `name`
    - `maxNbPlayer`
    - `gameType`
    - `lobbyIp`
    - `lobbyPort`
    - `ownerIp`
    - `ownerPort`

    *** Magick

    This field help to know the packet is realy a list lobby action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x12`

    *** Name

    This field correspond to the name of the lobby.

    This field must be of size 32 bytes.
    Each byte is a signed integer (so starting from -((2^8)/2) to
    +(((2^8)/2)-1)).
    Each byte correspond to the ascii character.
    After the last byte that you set, you must add a null character.
    The null character is the ascii character `\x00`
    The null character must be on the range of the 32 bytes.

    *** Max Nb Player

    This field correspond to the maximum number of player in the lobby.

    This field must be of size 4 bytes.
    This field is unsigned (so starting from 0 to 2^32)

    *** Game Type

    This field correspond to the type of the game created.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))
    This field must be equal to one of this value:
    - 0 : classic game

    *** Lobby IP

    This field correspond to the ip of the Lobby Server.

    This field must be of size 16 bytes.
    Each byte is a signed integer (so starting from -((2^8)/2) to
    +(((2^8)/2)-1)).
    Each byte correspond to the ascii character.
    After the last byte that you set, you must add a null character.
    The null character is the ascii character `\x00`
    The null character must be on the range of the 16 bytes.

    *** Lobby Port

    This field correspond to the port of the Lobby Server.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

    *** Owner IP

    This field correspond to the ip of the Main Server.

    This field must be of size 16 bytes.
    Each byte is a signed integer (so starting from -((2^8)/2) to
    +(((2^8)/2)-1)).
    Each byte correspond to the ascii character.
    After the last byte that you set, you must add a null character.
    The null character is the ascii character `\x00`
    The null character must be on the range of the 16 bytes.

    *** Owner Port

    This field correspond to the port of the Main Server.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

3.4. Lobby Server -> Client

3.4.1. CONNECT_LOBBY_RESP

    To understand this action, the action header `magick` must be equal to `21`

    The action body is composed of the following fields:
    - `magick`
    - `isOk`

    *** Magick

    This field help to know the packet is realy a connect lobby response

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x1a`
    
    *** Is Ok
    
    This field correspond to the status of the connection to the lobby
    
    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field is equal to `1` if the connection is ok
    This field is equal to `0` if the connection is not ok

3.4.2. START_WAVE

    To understand this action, the action header `magick` must be equal to `3`

    The action body is composed of the following fields:
    - `magick`
    - `ennemyId`

    *** Magick

    This field help to know the packet is realy a start wave action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x07`

    *** Ennemy Id

    This field correspond to the id of the first ennemy that will spawn and
    that is part of the wave.

    This field must be of size 4 bytes.
    This field is unsigned (so starting from 0 to 2^32)
    This field is unique for each ennemy.

3.4.3. LIFE_UPDATE

    To understand this action, the action header `magick` must be equal to `5`

    The action body is composed of the following fields:
    - `magick`
    - `playerId`
    - `hp`

    *** Magick

    This field help to know the packet is realy a life update action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x0b`

    *** Player ID

    This field correspond to the ID of the client whose life has been updated.

    This field must be of size 4 bytes.
    This field is unsigned (so starting from 0 to 2^32)
    This field is unique for each player.

    *** HP

    This field correspond to the life of the player.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

3.4.4. ENEMY_DEATH

    To understand this action, the action header `magick` must be equal to `6`

    The action body is composed of the following fields:
    - `magick`
    - `enemyId`

    *** Magick

    This field help to know the packet is realy an enemy death action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x0c`

    *** Enemy ID

    This field correspond to the id of the ennemy that has been killed.

    This field must be of size 4 bytes.
    This field is unsigned (so starting from 0 to 2^32)
    This field is unique for each ennemy.

3.4.5. NEW_ENEMY

    To understand this action, the action header `magick` must be equal to `10`

    The action body is composed of the following fields:
    - `magick`
    - `ennemyId`
    - `hp`
    - `x`
    - `y`
    - `ennemyType`

    *** Magick

    This field help to know the packet is realy a new enemy action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x0e`

    *** Ennemy ID

    This field correspond to the id of the new ennemy.

    This field must be of size 4 bytes.
    This field is unsigned (so starting from 0 to 2^32)
    This field is unique for each ennemy.

    *** HP

    This field correspond to the life of the new ennemy.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

    *** X

    This field correspond to the absolute x position of the new ennemy.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

    *** Y

    This field correspond to the absolute y position of the new ennemy.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

    *** Ennemy Type

    This field correspond to the type of the new ennemy.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))
    This field must be equal to one of this value:
    - 0 : classic
    - 1 : terminator

3.4.6. NEW_PLAYER

    To understand this action, the action header `magick` must be equal to `11`

    The action body is composed of the following fields:
    - `magick`
    - `playerId`
    - `x`
    - `y`
    - `hp`
    - `isOtherPlayer`

    *** Magick

    This field help to know the packet is realy an init action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x0a`

    *** Player ID

    This field correspond to the ID of the client that joined.

    This field must be of size 4 bytes.
    This field is unsigned (so starting from 0 to 2^32)
    This field is unique for each client.

    *** X

    This field correspond to the absolute x position of the client.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

    *** Y

    This field correspond to the absolute y position of the client.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

    *** HP

    This field correspond to the life of the client.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

    *** isOtherPlayer

    This field is an information to let you know if this player is you.

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field is equal to `1` if it is you
    This field is equal to `0` if it is not you

3.4.7. NEW_MISSILE

    To understand this action, the action header `magick` must be equal to `9`

    The action body is composed of the following fields:
    - `magick`
    - `x`
    - `y`
    - `missileId`
    - `missileHealth`
    - `missileType`

    *** Magick

    This field help to know the packet is realy a new bullet action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x0d`

    *** X

    This field correspond to the absolute x position of the bullet.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

    *** Y

    This field correspond to the absolute y position of the bullet.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

    *** Missile ID
    
    This field correspond to the ID of the missile.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))
    
    *** Missile Health
    
    This field correspond to the health of the missile.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

    *** Missile Type

    This field correspond to the type of the bullet.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))
    This field must be equal to one of this value:
    - 0 : classic
    - 1 : fast
    - 2 : bounce
    - 3 : perforant

3.4.8. POSITION_ABSOLUTE_BROADCAST

    To understand this action, the action header `magick` must be equal to `13`

    The action body is composed of the following fields:
    - `magick`
    - `x`
    - `y`
    - `playerId`

    *** Magick

    This field help to know the packet is realy a position absolute broadcast
    action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x10`

    *** X

    This field correspond to the absolute x position of the bullet.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

    *** Y

    This field correspond to the absolute y position of the bullet.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

    *** Player Id

    This field correspond to the id of the player.

    This field must be of size 4 bytes.
    This field is unsigned (so starting from 0 to 2^32)
    This field is unique for each player.

3.4.9. POSITION_RELATIVE_BROADCAST

    To understand this action, the action header `magick` must be equal to `12`

    The action body is composed of the following fields:
    - `magick`
    - `x`
    - `y`
    - `playerId`

    *** Magick

    This field help to know the packet is realy a position relative broadcast
    action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x0f`

    *** X

    This field correspond to the relative x position of the bullet since last
    packet send.

    This field must be of size 1 byte.
    This field is signed (so starting from -((2^8)/2) to +(((2^8)/2)-1))

    *** Y

    This field correspond to the relative y position of the bullet since last
    packet send.

    This field must be of size 1 byte.
    This field is signed (so starting from -((2^8)/2) to +(((2^8)/2)-1))

    *** Player Id

    This field correspond to the id of the player.

    This field must be of size 4 bytes.
    This field is unsigned (so starting from 0 to 2^32)
    This field is unique for each player.

3.4.10. PLAYER_DEATH

    To understand this action, the action header `magick` must be equal to `14`

    The action body is composed of the following fields:
    - `magick`
    - `playerId`

    *** Magick

    This field help to know the packet is realy a player death action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x11`

    *** Player Id

    This field correspond to the id of the player.

    This field must be of size 4 bytes.
    This field is unsigned (so starting from 0 to 2^32)
    This field is unique for each player.

3.4.11. MISSILE_DEATH

    To understant This action, the aciton header `magick` must be equal to `15`
    
    The action body is composed of ther following fields:
    - `magick`
    - `missileId`

    *** Magick
   
    This field help to know the packet is realy missile death action
    
    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x16`
    
    *** Missile ID
    
    This field correspond to the ID of the missile that died

    This field must be of size 4 bytes.
    This field is unsigned (so starting from 0 to 2^32)
    This field is unique for each missile.
    
3.4.12. END_GAME

     To understant This action, the aciton header `magick` must be equal to `24`
     
     The action body is composed of ther following fields:
    - `magick`
    
    *** Magick
   
    This field help to know the packet is realy missile death action
    
    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x1d`

3.5. Lobby Server -> Main Server

3.5.1. INFO_LOBBY

    To understand this action, the action header `magick` must be equal to `18`

    This action is used to let the Main Server know the current Lobby Server
    exists.

    The action body is composed of the following fields:
    - `magick`
    - `name`
    - `maxNbPlayer`
    - `gameType`
    - `ip`
    - `port`

    *** Magick

    This field help to know the packet is realy an info lobby action

    This field must be of size 1 byte.
    This field is unsigned (so starting from 0 to 2^8)
    This field must be equal to the ascii `\x15`

    *** Name

    This field correspond to the name of the lobby.

    This field must be of size 32 bytes.
    Each byte is a signed integer (so starting from -((2^8)/2) to
    +(((2^8)/2)-1)).
    Each byte correspond to the ascii character.
    After the last byte that you set, you must add a null character.
    The null character is the ascii character `\x00`
    The null character must be on the range of the 32 bytes.

    *** Max Nb Player

    This field correspond to the maximum number of player in the lobby.

    This field must be of size 4 bytes.
    This field is unsigned (so starting from 0 to 2^32)

    *** Game Type

    This field correspond to the type of the game created.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))
    This field must be equal to one of this value:
    - 0 : classic game

    *** IP

    This field correspond to the ip of the Main Server.

    This field must be of size 16 bytes.
    Each byte is a signed integer (so starting from -((2^8)/2) to
    +(((2^8)/2)-1)).
    Each byte correspond to the ascii character.
    After the last byte that you set, you must add a null character.
    The null character is the ascii character `\x00`
    The null character must be on the range of the 16 bytes.

    *** Port

    This field correspond to the port of the Main Server.

    This field must be of size 4 bytes.
    This field is signed (so starting from -((2^32)/2) to +(((2^32)/2)-1))

------------------------------------------------------------------------------

4.  References

    Link to some mentioned word

4.1. R-Bus

    - The R-Bus game project source code <https://github.com/X-R-G-B/R-Bus>
    - The R-Bus game documentation <https://x-r-g-b.github.io/R-Bus/>

4.2. RFC

    - How to write an RFC <https://www.rfc-editor.org/rfc/rfc7322>

------------------------------------------------------------------------------

5. Appendix

    Files that helps understand the RFC

5.1. Header

    Header files used by the R-Bus game

5.1.1. Message

    Structures used by the R-Bus game inside the ECS
    <https://github.com/X-R-G-B/R-Bus/blob/dev/src/ECS/MessageTypes.h>

5.1.1. Network

    Structures used by the R-Bus game inside the network
    <https://github.com/X-R-G-B/R-Bus/blob/dev/src/Nitwork/Nitwork.h>

UML

Clock

A clock can be used to measure time.

You need to create a clock, it will give you the ID to retrieve it and get ellapsed time

Methods

std::size_t Clock::create();
std::size_t Clock::elapsedSecondesSince(std::size_t id); // 1
std::size_t Clock::elapsedMillisecondsSince(std::size_t id); // 10^-3
std::size_t Clock::elapsedNanosecondsSince(std::size_t id); // 10^-9
void Clock::restart(std::size_t id);

Example

#include "Registry.hpp"

std::size_t id = Registry::getInstance().getClock().create();
std::cout << Registry::getInstance().getClock().elapsedMillisecondsSince(id) << std::endl;
Registry::getInstance().getClock().restart(id);

Maths

The Maths namespace provides a collection of utility functions for precise mathematical operations involving decimal numbers. The namespace includes functions for converting between float and integer representations, performing arithmetic operations with preserved decimal precision, and modifying decimal integers using normal integers and floats. We use a lot of these functions to reduce network traffic by sending integers instead of floats, and to ensure that calculations are performed with the same precision on both the client and server.

You can use it including the following header file:

#include "Maths.hpp"

Constants

static constexpr int Maths::DECIMALS_TO_CONSERVE = 2;

This constant specifies the number of decimal places to preserve during calculations, ensuring precision in decimal arithmetic operations.

For example, if DECIMALS_TO_CONSERVE is 2, we keep 2 decimal precision points. This means that (float)99.99 is converted to (int)9999, and (float) 0.09 is converted to (int)9.

Functions

  • Calculates and returns a multiplier based on the DECIMALS_TO_CONSERVE constant.
static constexpr int Maths::getMultiplier()
// For example, `DECIMALS_TO_CONSERVE = 2` results in `100`
  • Converts a floating-point number to an integer while conserving the specified number of decimals.
int Maths::floatToIntConservingDecimals(const float normalFloat)
// For example, `99.99` becomes `9999` for `DECIMALS_TO_CONSERVE = 2`
  • Converts an integer to a floating-point number with the specified number of decimals.
float Maths::intToFloatConservingDecimals(const int decimalInt)
// For example, `9999` becomes `99.99` for `DECIMALS_TO_CONSERVE = 2`
  • Removes decimals from an integer, producing a truncated integer result.
int Maths::removeIntDecimals(const int decimalInt)
// For example, `9999` becomes `99` for `DECIMALS_TO_CONSERVE = 2`
  • Adds the specified number of decimals to an integer.
int Maths::addIntDecimals(const int normalInt)
// For instance, `99` becomes `9900` for `DECIMALS_TO_CONSERVE = 2`
  • Performs addition on two integers with preserved decimals.
int Maths::additionWithTwoIntDecimals(const int decimalInt, const int otherDecimalInt)
// For example, `9999 + 9999` results in `199.98` for `DECIMALS_TO_CONSERVE = 2`
  • Performs subtraction on two integers with preserved decimals.
int Maths::subtractionWithTwoIntDecimals(const int minuend, const int subtrahend)
// For instance, `9999 - 9999` results in `0` for `DECIMALS_TO_CONSERVE = 2`
  • Performs multiplication on two integers with preserved decimals.
int Maths::multiplicationWithTwoIntDecimals(const int decimalInt, const int otherDecimalInt)
// For example, `9999 * 2` results in `199.98` for `DECIMALS_TO_CONSERVE = 2`
  • Performs division on two integers with preserved decimals.
int Maths::divisionWithTwoIntDecimals(const int dividend, const int divisor)
// For instance, `9999 / 2` results in `49.995` for `DECIMALS_TO_CONSERVE = 2`
  • Adds a normal integer to a decimal integer, modifying the decimal integer in place.
void Maths::addNormalIntToDecimalInt(int &decimalInt, const int normalInt)
// For example, `500 + 5` results in `550` for `DECIMALS_TO_CONSERVE = 2`
  • Subtracts a normal integer from a decimal integer, modifying the decimal integer in place.
void Maths::subNormalIntToDecimalInt(int &decimalInt, const int normalInt)
// For instance, `550 - 5` results in `500` for `DECIMALS_TO_CONSERVE = 2`
  • Adds a floating-point number to a decimal integer, modifying the decimal integer in place.
void Maths::addFloatToDecimalInt(int &decimalInt, const float normalFloat)
// For example, `500 + 5.5` results in `555` for `DECIMALS_TO_CONSERVE = 2`
  • Subtracts a floating-point number from a decimal integer, modifying the decimal integer in place.
void Maths::subFloatToDecimalInt(int &decimalInt, const float normalFloat)
// For example, `500 - 5.5` results in `445` for `DECIMALS_TO_CONSERVE = 2`

Logger

The logging system allows you to print messages of different levels to facilitate tracking and debugging of your game.

Log Levels

  • NoLog: No messages are recorded.
  • Fatal: Only "fatal" level messages are recorded.
  • Error: "error" and "fatal" level messages are recorded.
  • Warn: "warn", "error", and "fatal" level messages are recorded.
  • Info: "info", "warn", "error", and "fatal" level messages are recorded.
  • Debug: "debug", "info", "warn", "error", and "fatal" messages.
  • Trace: "trace", "debug", "info", "warn", "error", and "fatal" messages.

Usage

Watch out: Debug and Trace levels are only available in debug mode.

Header file: #include "Logger.hpp"

Methods

Methods used to log messages of different levels.

void Logger::fatal(const std::string &message);
void Logger::error(const std::string &message);
void Logger::warn(const std::string &message);
void Logger::info(const std::string &message);
void Logger::debug(const std::string &message);
void Logger::trace(const std::string &message);

Callback methods used to subscribe to log messages of different levels.

void Logger::subscribeCallback(LogLevel type, const std::string &name, std::function<void(const std::string &)> callback);
void Logger::unsubscribeCallback(LogLevel type, const std::string &name);

Methods used to change and retrieve the current log level.

void Logger::setLogLevel(LogLevel logLevel);
LogLevel Logger::getLogLevel() const;

Examples

Exemple of how to use the logger to print messages of different levels.

Logger::fatal("fatal message");
Logger::error("error message");
Logger::warn("warn message");
Logger::info("info message");
Logger::debug("debug message");
Logger::trace("trace message");

Exemple of how to use the logger to change and retrieve the current log level.

void Logger::setLogLevel(LogLevel::Info);
LogLevel logLevel = Logger::getLogLevel();

Exemple of how to use the logger to subscribe to log messages of different levels.

Logger::subscribeCallback(LogLevel::Info, "myCallback", [](const std::string &message) {
    std::cout << message << std::endl;
});
Logger::unsubscribeCallback(LogLevel::Info, "myCallback");

Json Class

Introduction

The Json class provides ease in loading and accessing data from JSON files. Thanks to the Singleton design pattern, only one instance of this class exists and can be used all inside the program, preventing unnecessary reloading of files.

Json library

We use the library of nlohmann called json used for modern C++. We choosed this library because of it's simplicity of usage and for the good features inside.

Usage

JsonType available

  • DEFAULT_ENEMY,
  • DEFAULT_PLAYER,
  • DEFAULT_PARALLAX,

Methods

  • Get the instance of the class :
Json &Json::getInstance();
  • Get a data with JsonType :
nlohmann::json Json::getDataByJsonType(JsonType dataType);
  • Get a data with a key :
nlohmann::json Json::getDataByJsonType(const std::string &index, JsonType dataType);
  • Get a data with vector of key :
nlohmann::json Json::getDataByVector(const std::vector<std::string> &indexes, JsonType dataType);
  • Get a data from a json list :
    This function is useful when you want to take a json list inside your json because all of your items of the list are in the vector.
std::vector<nlohmann::json> Json::getDatasByJsonType(const std::vector<std::string> &indexes, JsonType dataType);
  • Get a data but precise the type of what you want with template :
template <typename T>
T Json::getDataFromJson(nlohmann::json jsonData, const std::string &index);
  • Get a data from a vector of json data :
std::vector<nlohmann::json> Json::getDatasFromList(const std::vector<nlohmann::json> &list, const std::string &key);
  • Get a json object from a jsonType and an id :
nlohmann::json Json::getJsonObjectById(JsonType type, const std::string &id, const std::string &arrayName);
  • Get a json list from a json data :
std::vector<nlohmann::json> Json::getDatasFromList(const nlohmann::json &list, const std::string &key);
  • Get a json list from a json data :
std::vector<nlohmann::json> Json::getDatasFromList(const nlohmann::json &list);
  • Check if the data exists in the json :
bool Json::isDataExist(nlohmann::json jsonData, const std::string &index);

Errors handling

All the methods above Log an error with the class Logger and throw an std::runtime_error if the arguments are incorect or the key in the json is not found because if the data is not get correctly the rest of rest programm might crash.

Here's a json with spritePath of enemy missing :

{
    "enemy" : {
    }
}

Here's a code that will try to acces it :

Json::getInstance().getDataByVector({"enemy", "spritePath"},  
    JsonType::DEFAULT_ENEMY)

This won't work and output this :

2023-10-15 13:29:57.813141624 [FATAL] (getDataByVector) Key : spritePath is not valid

However you can handle you proper way the errors with the method isDataExist listed above.

Some examples

Basic example

Here's a json :

{
    "enemy" : {
        "spritePath" : "path"
    }
}

Here's how you can get spritePath data :

std::cout << Json::getInstance().getDataByVector({"enemy", "spritePath"},  
    JsonType::DEFAULT_ENEMY) << std::endl;

Using list data

Here's a new json but with a list inside :

{
    "enemy" : [
        {
            "spritePath" : "path"
        },
        {
            "spritePath" : "path"
        }
    ]
}

You can see that all spritePath are in a list.
Here's how to proceed :

std::vector<nlohmann::json> enemyData =  
    Json::getInstance().getDataByJsonType("enemy", enemyType);

And now you can iterate on your datas :

for (auto &data : enemyData) {
    std::cout << Json::getInstance().getDataFromJson<std::string>(elem, "spritePath") <<
        std::endl;
}

Get a json object from a jsonType and an id

Here's a json file with an array of enemy objects :

{
    "enemy" : [
        {
            "id" : "1",
            "spritePath" : "path"
        },
        {
            "id" : "2",
            "spritePath" : "path"
        }
    ]
}

Here's how to get an enemy object from his id :

    nlohmann::json object = Json::getJsonObjectById(enemyType, "1", "enemy");
    //the returned object will be :
    // {
    //     "id" : "1",
    //     "spritePath" : "path"
    // }

Bullets

We use differents types of bullets in the game. Each type of bullet has its own characteristics. Bullets are defined in the bullets.json file.

Json structure

We use the following structure to define bullets:

{
    "bullets": [
        {
            "id": "classic",
            "velocity": {
                "speedX": 120,
                "speedY": 0
            },
            "spritePath": "assets/R-TypeSheet/WaterBullets.png",
            "soundPath": "assets/Audio/Sounds/laser.ogg",
        },
        {
            "id": "perforant",
            "velocity": {
                "speedX": 400,
                "speedY": 0
            },
            "spritePath": "assets/R-TypeSheet/FireBullets.png",
            "soundPath": "assets/Audio/Sounds/laser2.ogg",
        }
    ]
}

It is composed of an array of bullets. Each bullet has its own id and its own characteristics.

Characteristics

  • id: The id of the bullet. It is used to identify the bullet in the game.
  • damage: The damage of the bullet. It is used to damage the enemies.
  • health: The health of the bullet. It is used to destroy the bullet when it collides with an enemy.
  • velocity: The velocity of the bullet. It is used to move the bullet.
  • physics: The physics of the bullet. It is used to move the bullet.
  • waitTimeBullet: The wait time of the bullet. It is used to wait before shooting again.
  • collisionRect: The collision rectangle of the bullet. It is used to detect collisions with enemies.
  • spritePath: The sprite path of the bullet. It is used to display the bullet.
  • soundPath: The sound path of the bullet. It is used to play the sound of the bullet.
  • spriteRect: The sprite rectangle of the bullet. It is used to display the bullet.
  • animRect: The animation rectangle of the bullet. It is used to display the bullet.

Physics

Physics is represented by an array of strings. Each string represents a physics. You can put as many physics as you want. So far, we have the following physics:

  • zigzag: The bullet moves in a zigzag way.
  • boncing: The bullet bounces on the top and bottom of the screen.
{
    "bullets": [
        {
            "id": "perforant",
            "physics": [
                "zigzag"
            ]
        }
    ]
}

Bullet types

For now, we have 4 types of bullets:

  • classic: The classic bullet.
  • fast: The fast bullet.
  • bounce: The bouncing bullet.
  • perforant: The perforant bullet.

To add a new bullet type, you have to add it in the missileTypes_e enum in the Missile.hpp file. Then, you have to add it in the bulletKeyMap map in the EventsSystems.cpp file.

C++ Client Documentation

Table of Contents

Introduction

The C++ client is a simple game client that uses the Raylib library to handle graphics and audio. The client is designed to be simple and easy to use, but it is also very flexible and can be used to create more complex games.

Raylib Library

We use the Raylib library to handle our graphics and audio. Raylib is a simple and easy-to-use library that provides a wide range of features. The Raylib library is written in C, but we have wrapped it in C++ classes to make it easier to use.

Using Audio in our game client

In your C++ client application, you can manage audio using the Raylib library. This guide explains how to work with sounds and music using our Raylib wrapper classes. Before looking at the other pages, we advise you to look at the rest of this page. Initialize the audio correctly is essential to use it in our client.

Audio Device Management

Functions

void Raylib::initAudioDevice();
void Raylib::closeAudioDevice();
bool Raylib::isAudioDeviceReady();
void Raylib::setMasterVolume(float volume);

Example usage

You need to initialize the audio device before using any audio functions. You can do this by calling the initAudioDevice() function. You can then use the other functions to manage the audio device. If you want to player music, every frame you need to call the UpdateMusicStream() function.

int main() {
    Raylib::Music music("path/to/musicfile.ogg");

    Raylib::initAudioDevice();
    // game loop
    while (!Raylib::WindowShouldClose()) {
        // stuff
        //for every music
        Raylib::UpdateMusicStream(music);
    }
    Raylib::closeAudioDevice();
}

Volume, pitch, and pan

  • The pitch base level is 1.0f (normal pitch).
  • The volume base level is 1.0f (maximum volume).
  • The pan base level is 0.5f (center).

Audio File Formats

Raylib supports the following audio file formats:

  • WAV
  • OGG
  • MP3
  • XM
  • QOA
  • MOD
  • FLAC

Music Class

Constructors

Raylib::Music(const std::string& fileName, float volume = 0.5f);

Create a music object from the specified audio file.

Methods

void Raylib::Music::unload();
bool Raylib::Music::isReady() const;
void Raylib::Music::play() const;
bool Raylib::Music::isPlaying() const;
void Raylib::Music::update() const;
void Raylib::Music::stop() const;
void Raylib::Music::pause() const;
void Raylib::Music::resume() const;
void Raylib::Music::setVolume(float volume) const;
void Raylib::Music::setPitch(float pitch) const;
void Raylib::Music::setPan(float pan) const;
float Raylib::Music::getTimeLength() const;
float Raylib::Music::getTimePlayed() const;
bool Raylib::Music::NeedToPlay() const;
void Raylib::Music::setNeedToPlay(bool needToPlay);
std::string Raylib::Music::getPath() const;

Example usage

Raylib::Music myMusic("path/to/musicfile.ogg");
myMusic.play();
myMusic.setVolume(0.6f);

Sound Class

Constructors

Raylib::Sound(const std::string& fileName, float volume = 0.5f);

Create a sound object from the specified audio file.

Methods

void Raylib::Sound::unload();
void Raylib::Sound::play() const;
void Raylib::Sound::stop() const;
void Raylib::Sound::pause() const;
void Raylib::Sound::resume() const;
bool Raylib::Sound::isPlaying() const;
void Raylib::Sound::setVolume(float volume) const;
void Raylib::Sound::setPitch(float pitch) const;
void Raylib::Sound::setPan(float pan) const;
bool Raylib::Sound::NeedToPlay() const;
void Raylib::Sound::setNeedToPlay(bool needToPlay);
std::string Raylib::Sound::getPath() const;

Example usage

Raylib::Sound mySound("path/to/soundfile.wav");
mySound.play();
mySound.setVolume(0.8f);
  • bool Raylib::isKeyPressed(Raylib::KeyboardKey key); Check if a key has been pressed once.

  • bool Raylib::isKeyDown(Raylib::KeyboardKey key); Check if a key is being pressed.

  • bool Raylib::isKeyReleased(Raylib::KeyboardKey key); Check if a key has been released once.

  • bool Raylib::isKeyUp(Raylib::KeyboardKey key); Check if a key is NOT being pressed.

  • void Raylib::setExitKey(Raylib::KeyboardKey key); Set a key to exit the application.

  • int Raylib::getKeyPressed(); Get the key pressed (keycode).

  • int Raylib::getCharPressed(); Get the last character pressed (unicode).

  • bool Raylib::isMouseButtonPressed(Raylib::MouseButton button); Check if a mouse button has been pressed once.

  • bool Raylib::isMouseButtonDown(Raylib::MouseButton button); Check if a mouse button is being pressed.

  • bool Raylib::isMouseButtonReleased(Raylib::MouseButton button); Check if a mouse button has been released once.

  • bool Raylib::isMouseButtonUp(Raylib::MouseButton button); Check if a mouse button is NOT being pressed.

  • int Raylib::getMouseX(); Get the X position of the mouse cursor.

  • int Raylib::getMouseY(); Get the Y position of the mouse cursor.

  • Vector2 Raylib::getMousePosition(); Get the current position of the mouse cursor.

  • Vector2 Raylib::getMouseDelta(); Get the mouse delta movement.

  • void Raylib::setMousePosition(int x, int y); Set the position of the mouse cursor.

  • void Raylib::setMouseOffset(int offsetX, int offsetY); Set an offset for the mouse position.

  • void Raylib::setMouseScale(float scaleX, float scaleY); Set the scaling factor for the mouse position.

  • float Raylib::getMouseWheelMove(); Get the mouse wheel movement.

  • Vector2 Raylib::getMouseWheelMoveV(); Get the mouse wheel movement as a vector.

  • void Raylib::setMouseCursor(int cursor); Set the mouse cursor style.

Example

if (Raylib::isKeyDown(Raylib::KeyboardKey::KB_RIGHT))
{
    // Move right
}
else if (Raylib::isKeyDown(Raylib::KeyboardKey::KB_LEFT))
{
    // Move left
}

Color Structure

Represents a color.

  • Raylib::Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a); Constructor to create a color with specified r (red), g (green), b (blue), and a (alpha) values.

Color Constants

  • Raylib::DarkGray
  • Raylib::Yellow
  • Raylib::Gold
  • Raylib::Orange
  • Raylib::Pink
  • Raylib::Red
  • Raylib::Maroon
  • Raylib::Green
  • Raylib::Lime
  • Raylib::DarkGreen
  • Raylib::SkyBlue
  • Raylib::Blue
  • Raylib::DarkBlue
  • Raylib::Purple
  • Raylib::Violet
  • Raylib::DarkPurple
  • Raylib::Beige
  • Raylib::Brown
  • Raylib::DarkBrown
  • Raylib::White
  • Raylib::Black
  • Raylib::Blank
  • Raylib::Magenta
  • Raylib::RayWhite

Rectangle Structure

Represents a rectangle.

  • Raylib::Rectangle(float x, float y, float width, float height); Constructor to create a rectangle with specified x, y, width, and height values.

Vector2 Structure

Represents a 2D vector.

  • Raylib::Vector2(float x, float y); Constructor to create a 2D vector with specified x and y values.

Vector3 Structure

Represents a 3D vector.

  • Raylib::Vector3(float x, float y, float z); Constructor to create a 3D vector with specified x, y, and z values.

Vector4 Structure

Represents a 4D vector.

  • Raylib::Vector4(float x, float y, float z, float w); Constructor to create a 4D vector with specified x, y, z, and w values.
  • Raylib::Color Raylib::fade(Raylib::Color color, float alpha); Fade a color by a specified alpha value.

  • int Raylib::colorToInt(Raylib::Color color); Convert a Color to a 32-bit integer.

  • Vector4 Raylib::colorNormalize(Raylib::Color color); Normalize a Color struct to a Vector4.

  • Raylib::Color Raylib::colorFromNormalized(Raylib::Vector4 normalized); Create a Color from a normalized Vector4.

  • Raylib::Color Raylib::getColor(unsigned int hexValue); Create a Color from a hex value.

Enum ConfigFlags

You can use the following flags to configure the window:

  • Raylib::ConfigFlags::ConfigFlags::FLAG_FULLSCREEN_MODE: Set fullscreen mode.
  • Raylib::ConfigFlags::FLAG_WINDOW_RESIZABLE: Allow the window to be resized.
  • Raylib::ConfigFlags::FLAG_WINDOW_UNDECORATED: Disable window decoration (frame and buttons).
  • Raylib::ConfigFlags::FLAG_WINDOW_TRANSPARENT: Allow the window to be transparent.
  • Raylib::ConfigFlags::FLAG_WINDOW_HIDDEN: Hide the window.
  • Raylib::ConfigFlags::FLAG_WINDOW_MINIMIZED: Minimize the window.
  • Raylib::ConfigFlags::FLAG_WINDOW_MAXIMIZED: Maximize the window.
  • Raylib::ConfigFlags::FLAG_WINDOW_UNFOCUSED: Disable window focus.
  • Raylib::ConfigFlags::FLAG_WINDOW_TOPMOST: Set the window to be always on top.
  • Raylib::ConfigFlags::FLAG_WINDOW_HIGHDPI: Enable high-DPI mode.
  • Raylib::ConfigFlags::FLAG_WINDOW_ALWAYS_RUN: Allow the window to run in the background.
  • Raylib::ConfigFlags::FLAG_MSAA_4X_HINT: Enable 4x MSAA.
  • Raylib::ConfigFlags::FLAG_VSYNC_HINT: Enable V-Sync.

Example

Raylib::setWindowState(Raylib::ConfigFlags::WINDOW_RESIZABLE);
Raylib::setWindowState(Raylib::ConfigFlags::WINDOW_RESIZABLE | Raylib::ConfigFlags::FLAG_VSYNC_HINT);
  • void Raylib::showCursor(); Show the cursor.

  • void Raylib::hideCursor(); Hide the cursor.

  • bool Raylib::isCursorHidden(); Check if the cursor is currently hidden.

  • void Raylib::enableCursor(); Enable cursor (unlock it).

  • void Raylib::disableCursor(); Disable cursor (lock it).

  • bool Raylib::isCursorOnScreen(); Check if the cursor is within the game window.

  • void Raylib::clearBackground(Raylib::Color color); Clear the background with a specified color.

  • void Raylib::beginDrawing(); Begin drawing.

  • void Raylib::endDrawing(); End drawing and swap buffers.

  • void Raylib::setTargetFPS(int fps); Set the target frames-per-second.

  • int Raylib::getFPS(); Get the current frames-per-second.

  • float Raylib::getFrameTime(); Get the time in seconds for a frame.

  • double Raylib::getTime(); Get the current time in seconds.

Image Class

Constructors

Raylib::Image(const std::string& fileName);
Raylib::Image(int width, int height, Raylib::Color color);

Create an image object from the specified file or create a blank image with the given width, height, and color.

Methods

bool Raylib::Image::isImageReady() const;
void Raylib::Image::unloadImage();
int Raylib::Image::getWidth() const;
int Raylib::Image::getHeight() const;
int Raylib::Image::getMipmaps() const;
int Raylib::Image::getFormat() const;
void* Raylib::Image::getData() const;

Example usage

Raylib::Image myImage("path/to/imagefile.png");
int width = myImage.getWidth();
std::cout << "Image width: " << width << std::endl;
myImage.unloadImage();

Misc. functions

  • void Raylib::takeScreenshot(const std::string &fileName); Take a screenshot and save it to a file with the specified name.
  • void Raylib::drawPixel(int posX, int posY, Raylib::Color color); Draw a pixel at the specified position with the given color.

  • void Raylib::drawCircle(int centerX, int centerY, float radius, Raylib::Color color); Draw a circle with the specified center, radius, and color.

  • void Raylib::drawRectangle(int posX, int posY, int width, int height, Raylib::Color color); Draw a rectangle at the specified position with the given width, height, and color.

Example usage

Raylib::drawPixel(100, 100, Raylib::RED);
Raylib::drawCircle(200, 200, 50, Raylib::GREEN);
Raylib::drawRectangle(300, 300, 80, 120, Raylib::BLUE);

Sprite Class

Constructors

Raylib::Sprite(const std::string &fileName, float width, float height);
Raylib::Sprite(Image image, float width, float height);

Create a sprite object from the specified file and set its width and height or create a sprite from an image object with the given width and height. The width and height are in percentage of the screen.

Methods

unsigned int Raylib::Sprite::getId() const;
float Raylib::Sprite::getWidth() const;
float Raylib::Sprite::getHeight() const;
int Raylib::Sprite::getTextureWidth() const;
int Raylib::Sprite::getTextureHeight() const;
int Raylib::Sprite::getMipmaps() const;
int Raylib::Sprite::getFormat() const;
void Raylib::Sprite::unloadSprite();

Example usage

Raylib::Sprite mySprite("path/to/spritefile.png", 100.0f, 150.0f);
float spriteWidth = mySprite.getWidth();
std::cout << "Sprite width: " << spriteWidth << std::endl;
mySprite.unloadSprite();

Text Class

Constructors

Raylib::Text(
    std::string text,
    Raylib::Vector2 position = {0, 0},
    float fontSize = 5.0f,
    Raylib::Color color = Raylib::BLACK);

Create a text object with the given text, position, font size, and color.

Methods

void Raylib::Textdraw();
void Raylib::TextdrawEx(float spacing);
void Raylib::TextdrawPro(Raylib::Vector2 origin, float rotation, float spacing);

float Raylib::Text::x() const;
float Raylib::Text::y() const;
float Raylib::Text::getFontSize() const;
void Raylib::Text::setFontSize(float fontSize);
Raylib::Vector2 Raylib::Text::getPosition() const;
void Raylib::Text::setPixelPosition(Raylib::Vector2 position);
void Raylib::Text::setCurrentFontSize(float fontSize);
std::string &Text::getCurrentText();
void Text::setText(const std::string &text)

getPosition is in percentage of the screen size. For each draw, we recommand to compute and set the position in pixel with setPixelPosition to have a responsive text.

Example usage

Raylib::Text myText("Hello, World!", {100, 200}, 20.0f, Raylib::BLUE);
myText.setPixelPosition({300, 400});
myText.draw();
  • void Raylib::initWindow(int width, int height, const std::string &title); Initialize the game window with the specified width, height, and title.

  • bool Raylib::windowShouldClose(); Check if the window should close (user pressed close button or escape key).

  • void Raylib::closeWindow(); Close the game window.

  • bool Raylib::isWindowReady(); Check if the window has been initialized successfully.

  • bool Raylib::isWindowFullscreen(); Check if the window is in fullscreen mode.

  • bool Raylib::isWindowHidden(); Check if the window is currently hidden.

  • bool Raylib::isWindowMinimized(); Check if the window is currently minimized.

  • bool Raylib::isWindowMaximized(); Check if the window is currently maximized.

  • bool Raylib::isWindowFocused(); Check if the window is currently focused.

  • void Raylib::setConfigFlags(ConfigFlags flags); Set configuration flags for the window.

  • bool Raylib::isWindowResized(); Check if the window has been resized.

  • bool Raylib::isWindowState(ConfigFlags flag); Check if a specific window state flag is set.

  • void Raylib::setWindowState(ConfigFlags flag); Set a specific window state flag.

  • void Raylib::clearWindowState(ConfigFlags flags); Clear specific window state flags.

  • void Raylib::toggleFullscreen(); Toggle fullscreen mode.

  • void Raylib::maximizeWindow(); Maximize the window.

  • void Raylib::minimizeWindow(); Minimize the window.

  • void Raylib::setWindowTitle(const std::string &title); Set the title of the window.

  • int Raylib::getScreenWidth(); Get the current screen width.

  • int Raylib::getScreenHeight(); Get the current screen height.

  • int Raylib::getRenderWidth(); Get the current rendering width

  • int Raylib::getRenderHeight(); Get the current rendering height

  • int Raylib::getMonitorWidth(int monitor); Get the width of the specified monitor.

  • int Raylib::getMonitorHeight(int monitor); Get the height of the specified monitor.

  • int Raylib::getMonitorRefreshRate(int monitor); Get the refresh rate of the specified monitor.

  • int Raylib::getCurrentMonitor(); Get the index of the current monitor.

  • void Raylib::setClipboardText(const std::string &text); Set the text to be copied to the clipboard.

  • std::string Raylib::getClipboardText(); Get the text currently copied to the clipboard.

  • void Raylib::setWindowIcon(Image image); Set the window icon.

Contributors

Thanks to this amazing contributors that made this project possible!