Skip to content

Latest commit

 

History

History
903 lines (696 loc) · 62.7 KB

setup.md

File metadata and controls

903 lines (696 loc) · 62.7 KB

Setup Guide

This guide will help you install and setup the required tools to run Rasa.NET. There are a few steps:

  1. Download, install, and setup the required tooling
  2. Download and install the game
  3. Download or clone the code
  4. Build and run the code from Visual Studio
  5. Launch the game

Download, install, and setup the required tooling

The following tools are required to setup, build, and run Rasa.NET:

  • .NET SDK 10.0.401 (pinned in the repository's global.json)
  • Optional: a Visual Studio release that supports this .NET 10 SDK
  • Database System, either:
    • MySQL Server and Workbench or
    • Sqlite (no tools required, but a Sqlite DB browser might help)
  • Git

Install Visual Studio

There is a single Visual Studio solution that contains the projects needed for Rasa.NET. Use a Visual Studio release that supports .NET 10, or build with the .NET CLI below. Visual Studio 2017 cannot build this solution.

  • Download Visual Studio. Choose the license that works for you
  • Run the installer after it's downloaded
  • In the Workloads selection, select .NET desktop development
  • Continue the installation and let it finish

Install .NET 10

All solution projects target .NET 10, including Rasa.Missions. Install the exact SDK selected by global.json; SDK roll-forward is disabled so local builds, CI and Docker use the same version.

  • Download the .NET 10 SDK and install version 10.0.401. The SDK includes the runtime.
  • From the repository root, run dotnet --version and verify 10.0.401.

The supported portable deployment identifiers are win-x64, osx-x64 and linux-x64. These preserve the Windows, macOS and Linux x64 deployment families, not support for the obsolete operating-system versions named by the old .NET 5 identifiers. Use an operating system supported by .NET 10.

Optional: Install MySQL Server and MySQL Workbench

Rasa.NET uses MySQL or Sqlite to store game data. Sqlite requires no separate database server; startup applies both schema and mission-data migrations. If you want to use MySql, download and install MySQL Community Server following these steps:

  • Download the MySQL Installer for Windows
  • Run the installer after it's downloaded
  • The default options are fine, but you can customize the install and choose only MySQL Sever and MySQL Workbench.
  • For help, follow the documentation
  • Set the root user password to something unique when prompted after installation

Install Git

Git is a version control system and will allow you to download the code. Alternatively, if you're not planning on developing for Rasa.NET you can download a .zip of the code directly from GitHub.

  • Download Git
  • Run the installer. If you're not sure of what options to select, choose the defaults

Download and install the game

You'll need the game client. Below is the demo version which was made freely available.

  • Refer to the forums to download and install the game client.
  • Create a shortcut to the game client on the desktop. You'll need to find the executable in the path where you installed the game
    • Right-click on the .exe and choose Send to > Desktop (create shortcut)
    • Go to the desktop and right-click on the newly created shortcut and choose Properties
    • Select the Shortcut tab
    • Edit the Target box to append /NoPatch /AuthServer=localhost:2106 to the end of the file path.

Version 1.16.5.0 Required

The game client needs to be version 1.16.5.0. If you have questions, join the Discord.

Download or clone the code

Download Rasa.NET from GitHub or use Git to clone the project (recommended).

  • Launch a command prompt or Git bash terminal
  • Change directories to where you want the source to download to, i.e. cd C:\Projects
  • Run the command git clone https://github.com/InfiniteRasa/Rasa.NET.git

Optional: Setup MySql

If you want to use a MySql database, here are the steps required to get things to work.

Setup the database

If you're using MySql, before you can build and run the code, there are some configuration steps required for the database.

  • Launch MySQL Workbench and select your Local instance MySQL80 under MySQL Connections that was created during install
  • Enter your root password that you created to connect to it
  • In the Navigator panel on the left, right-click in the blank space and choose Create Schema
    • In the new tab, name the first schema rasaauth and click Apply
    • Repeat this two more times for rasachar and rasaworld

Add a database user for the servers

The auth and game servers are pre-configured to use the user rasa to connect. You'll need to add this user to your database instance.

  • In MySQL Workbench, go to Server > Users and Privileges
  • Click Add Account
  • Set the Login Name to rasa
  • Set the Limit Hosts to Matching to localhost
  • Set the password to rasa
  • Select the Administrative Roles tab and check DBA
  • Click Apply

Custom configuration

RASA.NET uses configuration files for important settings the servers need to work. These files are named

  • appsettings.json for application specific settings
  • databasesettings.json for connection to the database

Changing the configuration is at least required for the database settings.

How to overwrite configuration for your enviroment

If you want to overwrite one or multiple settings from the appsettings.json of Rasa.Auth or Rasa.Game or the databasesettings.json of Rasa.DBL, do the following:

  • Launch Visual Studio and open the Rasa.NET.sln file in the code repository
  • Create a file named appsettings.env.json/databasesettings.env.json in the root of the respective project or copy and rename the existing file
  • The new file will be shown as subelement of the original file
  • Use the new file to overwrite settings from appsettings.json/databasesettings.json. Keep property naming and json structure. You don't need to add settings that you don't want to change. For example, to overwrite GameConfig.PublicAddress in the Rasa.Game settings, use
{
  "GameConfig":{
    "PublicAddress": "<new value>"
  }
}
  • The env.json files is ignored in git. Keep it that way, this configuration applies only for your development enviroment.

REST and in-game APIs

Rasa.Game exposes HTTP endpoints through the shared ApiConfig.Rest listener. The in-game endpoints are grouped under /ingame. Each in-game endpoint is disabled by default and must be enabled explicitly in ApiConfig.Rest.Endpoints.

{
  "ApiConfig": {
    "Rest": {
      "Enabled": true,
      "Public": false,
      "JwtSecret": "",
      "JwtTokenLifetimeSeconds": 86400,
      "Endpoints": {
        "ingame/session/exchange": {
          "Enabled": true
        },
        "ingame/items": {
          "Enabled": true
        },
        "ingame/items/categories": {
          "Enabled": true
        },
        "ingame/items/{id}": {
          "Enabled": true
        }
      }
    }
  }
}

JwtSecret is optional. When it is empty, the server generates a random process-local signing secret; set it to a value of at least 32 UTF-8 bytes if issued tokens should remain valid across server restarts. Keep an overridden secret out of source control; appsettings.env.json is the recommended place for it. A suitable value can be generated with openssl rand -hex 32.

The REST listener uses port 8104 by default. An Admin-or-higher game client starts in-game API authentication with .ingameapiauth; the server returns a short-lived one-time exchange code through that authenticated game connection, and the injected UI sends it to POST /ingame/session/exchange to receive a JWT. The item catalog is available under /ingame/items, /ingame/items/categories, and /ingame/items/{id} and independently requires permission to use .giveitem. Issued in-game JWTs default to a 24-hour lifetime (JwtTokenLifetimeSeconds: 86400).

Squad voice chat

Rasa.Game runs the voice server the game client's built-in squad voice chat connects to. It is configured in the VoiceConfig section of its appsettings.json:

Setting Default Meaning
Enabled true false turns squad voice chat off; clients are told it is unavailable and never try to connect. A missing section also means off.
PublicAddress "" Host or IP clients connect to for voice. Empty uses GameConfig.PublicAddress.
BindAddress 0.0.0.0 Local address the UDP socket binds to.
Port 8103 UDP port. Forward it as UDP on your router or firewall (docker-compose maps 8103/udp).
MaxTalkTime 60 Seconds of talk time shown on the player's talk-time bar. Display only.
TalkTimeRegen 1 Seconds of talk time regained per second of silence.
TimeoutSeconds 30 A voice connection silent this long is dropped.
TokenLifetimeSeconds 60 How long a voice login token stays valid.
LogSessions true Log voice logins, logouts and refusals.

Changes are picked up when the file is reloaded; a new BindAddress or Port restarts the voice listener. Type voice on the game server console to see who is connected. Players talk with the game's push-to-talk key while in a squad, with voice enabled in their options.

Message of the day

Rasa.Game shows players a message in the client's message-of-the-day window as they enter the world. It is configured in the MessageOfTheDay section of its appsettings.json:

Setting Default Meaning
Text Welcome to the Infinite Rasa server. The message, in English. Every client falls back to it. Empty sends no message at all.
ShowEveryLogin false false: a player sees each message once, at the first login after it changes (the client remembers the last one it showed). true: every login shows it.
Translations {} The message in other languages, by the client's language id. A client set to a language with no entry gets Text. Used only while ShowEveryLogin is false.

The language ids are 2 Korean, 3 Japanese, 4 Chinese, 5 French, 6 German, 7 Italian, 8 Spanish, 9 Portuguese and 10 Russian. English is 1 and always comes from Text; an entry for 1 under Translations is ignored.

{
  "MessageOfTheDay": {
    "Text": "Welcome to the server.",
    "ShowEveryLogin": false,
    "Translations": {
      "5": "Bienvenue sur le serveur.",
      "6": "Willkommen auf dem Server."
    }
  }
}

The message is sent once per connection, when the player first enters the world. A change to the file applies at once, without a restart, and goes to everyone already in the world. A missing section uses the default text. Type motd on the game server console to see the message in force; the GM command .motd shows it to you as players get it (see the GM command reference).

Item modules

A weapon's Steal and Debuff Resist modules fire on a hit by chance, and the client has no number for the chance. It is set in the ItemModules section of Rasa.Game's appsettings.json:

Setting Default Meaning
StealChancePercent 10 Chance in percent, on each hit of the weapon that does damage, that each Steal Health, Power, Adrenaline or Armor module in it takes its amount. 0 or less: never. 100: every hit.
ResistDebuffChancePercent 10 The same for each Debuff Resist module putting its debuff on the target.
{
  "ItemModules": {
    "StealChancePercent": 10,
    "ResistDebuffChancePercent": 10
  }
}

The chance is per hit, so a weapon that hits more often fires them more often. A change to the file applies at once, without a restart; a missing section uses the defaults. How long a resist debuff lasts is not a setting: it is arg2, in seconds, of the module's row in the world database's module_effect table (10 as migrated), and is read at startup. What each module does is in the GM command reference.

Races and hybrids

Which races a player can pick in the character creation window is set in the GameDataConfig section of Rasa.Game's appsettings.json:

Setting Default Meaning
EnabledRaces [ 1, 2, 3, 4 ] The races the server offers at all, by id: 1 Human, 2 Forean Hybrid, 3 Brann Hybrid, 4 Thrax Hybrid. Human is always offered, listed or not; an id that is not a race is dropped, with a line in the log. A missing setting offers all four; an empty list [] offers Human only.
AlwaysUnlockHybrids true true (or a missing setting): every race in EnabledRaces is offered to every account. false: an account is offered Human and the hybrids it has unlocked by completing their missions, and still only those in EnabledRaces.
{
  "GameDataConfig": {
    "EnabledRaces": [ 1, 2, 3, 4 ],
    "AlwaysUnlockHybrids": false
  }
}

A race that is not offered shows in the window locked, with the client's own "Unlock this hybrid by completing certain missions in game." The server refuses it too, so a modified client cannot create or clone a character of that race.

With AlwaysUnlockHybrids set to false, a hybrid is unlocked for the whole account, all of its characters, when one of its characters completes the mission whose finishing text grants it:

Hybrid Mission Where
Forean 1861 Traitor on the Run Elder Q'uoa, Thoria Das (Concordia Divide)
Brann 1851 Remedy Connant, Baylor Base (Torden Mires)
Thrax 1899 Genome Sweet Genome Zupa Eugin, Viddea Quarter, Staal (Ligo Crucible)

The player is told "Hybrid Access Granted" as the mission's reward is handed over. The unlock is kept in the hybrid_unlocks column of the character database's account table (1 Forean, 2 Brann, 4 Thrax, added together) and is recorded whatever the setting, so turning AlwaysUnlockHybrids off later still leaves each account what it has earned. None of the three missions is in the server's mission content yet, so with the setting off nobody can unlock a hybrid until they are added.

A change to either setting applies at once, without a restart: a player sees it the next time they reach the character selection screen. A character already made keeps its race whatever the settings say.

REST API

Rasa.Game can report its status, create accounts, and let the game tools read and change creature flags and loot pools, over HTTP on a port of its own. It is configured in the ApiConfig.Rest section of its appsettings.json:

Setting Default Meaning
Enabled true false closes the listener. A missing ApiConfig section also means off.
BindAddress 0.0.0.0 Local address the listener binds to. Empty or 0.0.0.0 binds every interface.
Port 8104 TCP port (the compose files map 8104).
Public false true lets the endpoints answer without a key. An endpoint with a Public of its own goes by that instead.
ApiKey "" A key every endpoint accepts. Empty for none.
JwtSecret "" Signs the tokens the /ingame endpoints go by; see REST and in-game APIs above. Empty makes a new secret at every start.
JwtTokenLifetimeSeconds 86400 How long such a token lasts.
AllowedIps [] The addresses that may ask: single addresses (203.0.113.7) and ranges (10.0.0.0/8), IPv4 or IPv6. Empty answers every address. An entry that is no address and no range allows nobody and is logged as an error.
AllowedOrigins [] The web pages that may use the API from a browser, by origin: "null" for a page opened from a file on disk, as the game tools are; "http://tools.example" for one served from there; "*" for any. Empty lets no page use it. It opens only endpoints that want a key; see Game tools below.
Endpoints see below Settings for one endpoint, by its name.
Tls off HTTPS in place of HTTP on the same port; see below.

As shipped the API listens but answers nobody: Public is false and no key is set, so /healthcheck and /serverstatus answer 401 until you set an ApiKey or make them public. The startup log lists every endpoint as off, public, key or endpoint auth.

A key is sent in the X-API-Key header, or as Authorization: Bearer <key>.

The /ingame endpoints take no key, and are the ones the startup log calls endpoint auth: Rest.Public, Rest.ApiKey and a Public or ApiKey in their own entries do nothing for them. POST /ingame/session/exchange takes the one-time code that .ingameapiauth gives an Admin in the game, and the others take the token it answers with, as Authorization: Bearer <token>. AllowedIps applies to them as it does to the rest.

Endpoint Answer
GET /healthcheck {"game_server_status":"healthy","app_server_status":"healthy"}, each healthy or unhealthy. The status is 200 when both are healthy and 503 when either is not, with the same body.
GET /serverstatus {"uptimeseconds":45000,"currentconnections":3,"peakconnections":9,"maxconnections":1024}
POST /addaccount Creates a login; see below. Off until it is turned on.
POST /ingame/session/exchange, GET /ingame/items, GET /ingame/items/categories, GET /ingame/items/{id} The in-game API; see REST and in-game APIs above. Each is off until it is turned on.
GET /monsterflags, POST /updatemonsterflags The flags of the creature classes, read and changed; see Game tools below. Off until turned on.
GET /lootpools, POST /updatelootpools Canonical weighted ordinary-loot profiles, read and edited; see Game tools below. Off until turned on.
GET /usersonline Every account online, with the character it is playing; see Who is online below. Off until turned on.
POST /kickuser Disconnects an account that is online; see Kicking a player below. Off until turned on.
GET /getopenpetitions, GET /getallpetitions, POST /respondpetition, POST /closepetition Players' petitions, read, answered and closed; see Petitions below. Off until turned on.
POST /sendmessage A game master's message to a player who is online; see Messaging a player below. Off until turned on.
  • game_server_status is healthy while the server has finished loading, is listening for players, has not been shut down, and its world loop ticked within ApiConfig.LoopStallSeconds (default 15).
  • app_server_status is healthy while the link to the Auth server is up and logged in.
  • currentconnections is the number of players holding a slot, the number the server list shows. maxconnections is ServerInfoConfig.MaxPlayers, peakconnections the most there have been since the server started, and uptimeseconds the time since it opened its ports.

A request is refused with 403 from an address not on AllowedIps, 404 for a path that names no endpoint or one that is off, 405 for the wrong method, and 401 for a missing or wrong key or, at an /ingame endpoint, a code or token that is missing, wrong or no longer good.

Each entry under Endpoints (healthcheck, serverstatus, addaccount, the /ingame ones, monsterflags, updatemonsterflags, lootpools, updatelootpools, usersonline, kickuser, getopenpetitions, getallpetitions, respondpetition, closepetition and sendmessage) has:

Setting Default Meaning
Enabled true false makes the endpoint a 404.
Public not set Whether this endpoint answers without a key. Left out, Rest.Public decides.
ApiKey "" A key for this endpoint alone. The global ApiKey still opens it.

healthcheck and serverstatus are on without an entry. The /ingame endpoints are off without one, as addaccount is, and Enabled is the only setting of theirs that counts. To give a monitor a key that opens nothing else, leave the global ApiKey empty and set an ApiKey on those two entries.

Creating accounts

POST /addaccount makes a login as the Auth console's create command does. The accounts are the Auth server's, so Game hands the request on over its link to Auth, which has to be connected. The body is JSON, sent as application/json:

{ "email": "test@test.com", "username": "test", "password": "test" }

The user name may be 14 bytes and the password 16, the most the game client can send; the user name has no spaces in it, and the e-mail has to look like an address.

Status Meaning
201 {"result":"created","username":"test","account_id":12}
400 The body is not that JSON, or the details will not do; error says what is wrong.
409 {"error":"username taken"} or {"error":"email taken"}
415 The body was not sent as application/json.
500 The Auth server could not store the account; its log says why.
503 The Auth server is not connected.
504 The Auth server did not answer in time. The account may or may not have been created.

Because it changes something, this endpoint is stricter than the other two:

  • With no entry under Endpoints it is off. An entry turns it on unless it says "Enabled": false, which is what the shipped file has; an entry that leaves Enabled out counts as on.
  • Rest.Public never makes it public. Only "Public": true in its own entry does, and then anyone who can reach the port can create accounts.
  • Its own ApiKey opens it, and so does the global ApiKey. Hand the global key only to those who may create accounts, or leave it empty and give each endpoint its own.
{
  "ApiConfig": {
    "Rest": {
      "Endpoints": {
        "healthcheck": { "ApiKey": "<key for the monitor>" },
        "serverstatus": { "ApiKey": "<key for the monitor>" },
        "addaccount": { "Enabled": true, "ApiKey": "<key for creating accounts>" }
      }
    }
  }
}

Who is online

GET /usersonline lists every account logged in to this game server, at the character screen or playing, by account id, with the character it is playing:

{"count":2,"users":[
  {"accountId":1,"characterId":12,"name":"Ellie","familyName":"Hart","classId":2,"className":"Soldier","level":15,"state":"ingame"},
  {"accountId":3,"characterId":null,"name":null,"familyName":null,"classId":null,"className":null,"level":null,"state":"characterselection"}
]}
  • name is the character's own name and familyName the family name of the account's characters. classId is the class's number and className its name (Recruit, Soldier, Specialist, Commando, and so on).
  • state is loggedin or characterselection for an account that has not picked a character, whose character fields are all null, and loading, ingame or teleporting for one that has.
  • The list is read once a second from the world, so it can be a second old. count can be below /serverstatus's currentconnections: that also counts players the queue is sending to the world port, who have not logged in here yet.

It names accounts and characters, so it is off until its own entry turns it on, and Rest.Public never makes it public, as for addaccount. Give it a key of its own:

"usersonline": { "Enabled": true, "ApiKey": "<key for the player list>" }
curl -H "X-API-Key: <key for the player list>" http://127.0.0.1:8104/usersonline

Kicking a player

POST /kickuser disconnects an account that is online, as the console's kick does: every connection of it, in the world or at the character screen, after telling the player "You have been disconnected by a game master: ". The body is JSON, sent as application/json:

{ "accountId": 12, "reason": "griefing", "admin": "Atomsk" }
{ "familyName": "Hart", "reason": "griefing", "admin": "Atomsk" }
  • accountId (a number, or a number in a string) or familyName, one of them and not both. A family name is matched as the in-game .kick matches it, case aside.
  • reason is what the player is told; it may be left out, and may be 256 characters.
  • admin is who is doing it, as you name them; it is required, and may be 64 characters. It goes in the server log's line for the kick, with the address the request came from, and in the API log.

Every answer of its own is 200:

Answer Meaning
{"kicked":true,"accountId":12,"familyName":"Hart"} Kicked.
{"kicked":false,"error":"not online"} Nobody of that account is online.
{"kicked":false,"error":"..."} The request will not do (no target, both, no admin, not JSON, not sent as application/json), or the game server is not ready or did not get to it within 10 seconds.

An address that is not allowed, a missing key and the rest are refused as for any endpoint. The API may kick any account, game masters too, as the console may. It changes something, so it is off until its own entry turns it on and is never public by Rest.Public; give it a key of its own:

"kickuser": { "Enabled": true, "ApiKey": "<key for kicking>" }
curl -X POST -H "X-API-Key: <key for kicking>" -H "Content-Type: application/json" \
     -d '{"familyName":"Hart","reason":"griefing","admin":"Atomsk"}' http://127.0.0.1:8104/kickuser

Messaging a player

POST /sendmessage sends a game master's message to a player who is online, as .message does: a yellow ADMIN MESSAGE line in their chat, which no player can filter out, and the client's AFS Message notice - a flashing icon that stays until they click it and read the whole message in the Message of the Day window. The body is JSON, sent as application/json:

{"familyName":"Hart","text":"Your character was moved to the hospital.","admin":"Atomsk"}

accountId (a number, or a number in a string) or familyName, one and not both, as for /kickuser; text, up to 1000 characters; admin, who is sending it, up to 64; and important, true to open the client's modal dialog with an OK button in place of the AFS Message (every player gets it; it holds the screen until they press OK); and title, the dialog's, up to 64 characters ("Message from a game master" if left out). Every answer of its own is 200: {"sent":true,"accountId":12,"familyName":"Hart"}, or {"sent":false,"error":"not online"} - nothing is kept for a player who is away. The AFS Message is the message of the day's own slot, so a newer one of either replaces an unread older one.

Petitions

GET /getopenpetitions lists the petitions players have filed (Help, Report Bug, /petition and /bug) that are still open, and GET /getallpetitions every petition whatever its status, newest first:

{"count":1,"petitions":[
  {"id":12,"type":"helprequest","status":"open","accountId":3,"accountName":"atomsk","familyName":"Hart",
   "characterId":40,"characterName":"Ellie","summary":"Stuck","body":"Under the bridge in ...",
   "mapContextId":1220,"position":{"x":1.5,"y":2,"z":-3.25},"resolution":"","noticePending":false,
   "createdAt":"2026-10-14T09:30:00Z"}
]}
  • type is helprequest or bugreport; status is open, resolved or cancelled (withdrawn by the player).
  • accountName is the account's login and familyName its family name; characterName is the character that filed it. Each is null for an account or character no longer there.
  • mapContextId and position are where the player stood when they filed it. createdAt is UTC.
  • resolution is a game master's answer, empty until there is one. noticePending is true while the player has yet to be told of an answer or a close.

POST /respondpetition answers a petition, and POST /closepetition marks one resolved. The bodies are JSON, sent as application/json:

{ "petitionId": 12, "admin": "Atomsk", "response": "The quest is fixed; log in again and try it." }
{ "petitionId": 12, "adminId": "Atomsk" }
  • petitionId is a number, or a number in a string. Only an open petition is answered or closed.
  • /respondpetition's response may be 255 characters. It is written as the petition's resolution, in place of any answer before it, and the petition stays open. admin is who is answering, as you name them, up to 64 characters.
  • /respondpetition also takes "close": true, to answer and close it in one step - the player is told once, "Your petition #12 has been resolved: ..." - and "important": true, to show a player who is in the world the answer in the client's modal dialog with an OK button (titled "Bug Report #12" or "GM Help #12", as it was filed) in place of the AFS Message. A player who is away gets the usual notice when they arrive.
  • /closepetition's adminId is who is closing it, a name or a number, up to 64 characters. The petition keeps its answer.
  • The player is told as a game master's message is (see Messaging a player above): a yellow ADMIN MESSAGE line, "A game master has answered your petition #12: ..." or "Your petition #12 has been resolved: ...", and the AFS Message notice holding it. The client has no window of its own for a petition's answer. At once when they are in the world; otherwise when they next arrive in it, one line for each petition as it stands then, and one AFS Message holding them all.
  • The admin goes in the server log's line for it, with the address the request came from, and the request in the API log.

Every answer of its own is 200:

Answer Meaning
{"responded":true,"petitionId":12,"status":"open","playerTold":true} Answered. playerTold is false for a player who will be told when they arrive.
{"closed":true,"petitionId":12,"status":"resolved","playerTold":false} Closed.
{"responded":false,"error":"no such petition"} There is no petition of that id.
{"closed":false,"error":"the petition is resolved, not open"} It was closed already, or withdrawn (cancelled).
{"responded":false,"error":"..."} The request will not do (no petition id, no admin, no response or one over 255 characters, not JSON, not sent as application/json), or the game server is not ready or did not get to it within 10 seconds.

They name accounts and carry what players wrote, and two of them change something, so all four are off until their own entries turn them on and are never public by Rest.Public:

"getopenpetitions": { "Enabled": true, "ApiKey": "<key for petitions>" },
"getallpetitions": { "Enabled": true, "ApiKey": "<key for petitions>" },
"respondpetition": { "Enabled": true, "ApiKey": "<key for petitions>" },
"closepetition": { "Enabled": true, "ApiKey": "<key for petitions>" }
curl -H "X-API-Key: <key for petitions>" http://127.0.0.1:8104/getopenpetitions
curl -X POST -H "X-API-Key: <key for petitions>" -H "Content-Type: application/json" \
     -d '{"petitionId":12,"admin":"Atomsk","response":"Fixed; try it again."}' http://127.0.0.1:8104/respondpetition
curl -X POST -H "X-API-Key: <key for petitions>" -H "Content-Type: application/json" \
     -d '{"petitionId":12,"adminId":"Atomsk"}' http://127.0.0.1:8104/closepetition

The API log

Every request the REST API is sent is written to the character database's api_log table once it has been answered, refused ones too: created_at (UTC), the address it came from, method, path, query, the answer's status, the body of a POST, body_length, and the start of the answer (response, up to 2000 characters).

  • Secrets are not kept: in a body or an answer, the value of any field whose name has password, secret, token or apikey in it, or ends in code, is written as *** - the password of /addaccount, the code and token of the /ingame exchange. A key sent in a header is never written.
  • A body is kept up to 16000 characters; body_length is how long it was.
  • A connection that sends nothing, or never finishes a TLS handshake, asked for nothing and is not on it. Nor is the status port, which is not the REST API.
  • It is always on, and nothing is ever taken out of it.
select created_at, address, method, path, status, body from api_log order by id desc limit 50;

Game tools

The editors in the repository's gametools folder are web pages that read and change a running server through four endpoints. Each speaks its editor's own file, so what a GET returns can be loaded into the editor and what the editor saves can be sent with POST:

Endpoint What it does
GET /monsterflags Every creature class with its flags (creature_class_flag): {"schema":"rasa.creature_flags/1","source":"server","creatures":[{"class_id":6032,"class_name":"Bane_Amoeboid_v1","flags":[5,71]}]}
POST /updatemonsterflags {"creatures":[{"class_id":6032,"flags":[5,71,142]}]} gives each class named exactly these flags, in the database and in the running server. A class not named is not touched.
GET /lootpools Actual canonical profiles, groups, weighted candidates, map/creature bindings, policies and provenance. Format rasa-weighted-loot-profiles, version 2; see the weighted contract.
POST /updatelootpools That same weighted contract. Replaces named profiles' groups/items and upserts bindings/policies, retaining unnamed content. Native validation and one World transaction precede catalog publication. Legacy flat percentages are rejected, never converted or archived.
Status Meaning
200 Read, or changed: {"result":"updated",...} with counts, and "warnings" when there is something to remark on.
400 The body is not that JSON, or names a class, flag, item or creature the server has not got. error says so and problems lists what is wrong. Nothing was changed.
415 The body was not sent as application/json.
500 Database save failed. Durable and published loot are unchanged; check the server log and retry.
503 The server has not finished starting, or nonempty inactive archives block weighted-loot readiness (with diagnostics).

Group admission uses integer basis points (10000 = 100%). Candidate weights are relative selections after admission, not independent drop percentages. Quantity and roll bounds are inclusive. Native stack limits, item usability and required-map coverage are checked by the canonical catalog. See ordinary loot for the frozen Wilderness v1 data and explicit no-item profiles. Apply the reviewed migrations before startup; MySQL uses the explicit migration workflow below.

All four follow the rules of /addaccount: off with no entry of their own, never public because Rest.Public is, opened by their own ApiKey or the global one.

A browser will not let a web page send a key to another address, or read the answer, unless the server says the page may. AllowedOrigins is that list, and it is empty as shipped. The game tools are opened from disk, which a browser calls the origin null:

{
  "ApiConfig": {
    "Rest": {
      "AllowedOrigins": [ "null" ],
      "Endpoints": {
        "monsterflags": { "Enabled": true, "ApiKey": "<key for reading>" },
        "updatemonsterflags": { "Enabled": true, "ApiKey": "<key for changing>" },
        "lootpools": { "Enabled": true, "ApiKey": "<key for reading>" },
        "updatelootpools": { "Enabled": true, "ApiKey": "<key for changing>" }
      }
    }
  }
}

An origin on the list may use every endpoint that wants a key, and no endpoint that answers without one: a public endpoint is never opened to pages, since any page a browser inside your network happened to load could then use it. The /ingame endpoints, which go by a code and a token of their own in place of a key, are not opened to pages either. A page that is refused is logged with its origin and the reason.

HTTPS

The Tls section turns the port from HTTP to HTTPS:

Setting Default Meaning
Enabled false true makes the port speak HTTPS and nothing else.
CertificatePath "" The certificate: a PKCS#12 file (.pfx, .p12) with its private key inside, or a PEM file, which may hold the chain after the certificate, and the key.
CertificatePassword "" The password of the PKCS#12 file, or of an encrypted PEM key. Empty for none.
KeyPath "" The PEM private key, when it is not in the certificate's file.
MinimumProtocol Tls12 Tls12 accepts TLS 1.2 and 1.3; Tls13 accepts TLS 1.3 alone.

Without TLS, keys and the passwords sent to /addaccount cross the network as they are: keep the port on a network you trust, or turn TLS on. With TLS on, a certificate that cannot be loaded leaves the API off; it never falls back to HTTP. A connection from an address not on AllowedIps is then closed before the handshake, in place of the 403. The certificate's files are looked at again every minute and on every configuration reload, so a renewed certificate is taken up without a restart.

Requests and reloads

Every connection carries one request and is closed after its answer. A request has 5 seconds, 64 connections are served at once, and the request's headers and a POST's body may each be 8 KB. The two game tools endpoints that take a body take up to 8 MB, only read one that large once the request's key has been checked, and then give it a minute to arrive. A request turned away while its body is still being sent is answered, and the rest of the body is read and dropped so that the sender gets to the answer.

curl -H "X-API-Key: <key>" http://127.0.0.1:8104/healthcheck
curl -H "Authorization: Bearer <key>" http://127.0.0.1:8104/serverstatus
curl -X POST -H "X-API-Key: <key>" -H "Content-Type: application/json" -d "{\"email\":\"test@test.com\",\"username\":\"test\",\"password\":\"test\"}" http://127.0.0.1:8104/addaccount

These are written for a command prompt or a Unix shell; Windows PowerShell quotes the JSON differently.

Changes are picked up when the file is reloaded: keys, Public, the endpoints' entries, AllowedIps and AllowedOrigins count from the next request, and a new BindAddress or Port reopens the listener. A listener that cannot open its port stays off, and the game server starts without it.

Status port

Rasa.Game also answers on a plain TCP port, for a monitor that does not speak HTTP. It is configured in the ApiConfig.StatusPort section of its appsettings.json:

Setting Default Meaning
Enabled true false closes the port. A missing ApiConfig section also means off.
BindAddress 0.0.0.0 Local address the listener binds to. Empty or 0.0.0.0 binds every interface.
Port 8105 TCP port (the compose files map 8105).
AllowedIps [] The addresses that may ask: single addresses and ranges, as for the REST API. Empty answers every address; a connection from any other address is closed unanswered.

Connect and send anything - one byte is enough, and what it is does not matter - and the server answers with the whole status as one line of JSON, then closes:

{"game_server_status":"healthy","app_server_status":"healthy","uptimeseconds":45000,"currentconnections":3,"peakconnections":9,"maxconnections":1024}

The fields are those of /healthcheck and /serverstatus above. Nothing is answered until something has been sent.

The status port has no key. As shipped it is on and answers any address that can reach it, so put your monitor's address in AllowedIps, keep the port behind your firewall, or turn it off. Changes are picked up when the file is reloaded, as for the REST API.

Password hashing

Rasa.Auth stores each account's password as a PBKDF2-HMAC-SHA256 hash with a salt of the account's own. The PasswordHashConfig section of its appsettings.json sets the rest:

Setting Default Meaning
Pepper "" A secret mixed into every password hash and kept out of the database, so a stolen copy of the account table cannot be cracked without it. Use a long random value. Empty for none.
Iterations 600000 PBKDF2 iterations. 0, or a missing value, means 600,000; anything under 100,000 is raised to 100,000. More iterations make every login, and every guess at a password, cost more work.
  • Changing Iterations is safe. Each hash records its own count, and an account is rehashed to the current one at its next login.
  • Adding a Pepper later is safe. Accounts hashed without one are given it at their next login.
  • Changing or removing a Pepper once it is set locks out every account hashed with it, until the old value is put back: a hash cannot be turned back into the password to redo. Set it once, and keep a copy where a reinstall will not lose it.

appsettings.json is tracked by git, so set the pepper in appsettings.env.json (see above), which is not.

The Auth server logs what is in force at startup and whenever it changes (Passwords: PBKDF2-HMAC-SHA256, 600000 iterations, per-account salt, pepper set.), and logs an error when a pepper is changed or removed while it runs. A change applies from the next login, without a restart. Accounts made with the console's create command and through /addaccount are hashed the same way. Hashes from before PBKDF2, a single SHA-256, are still accepted and are replaced at the account's next login.

Game configuration ownership

Rasa.Game\Config owns the server settings loaded from src\Rasa.Game\appsettings.json:

  • GameConfig owns the public game endpoint, listener backlog, the 60-second transfer acknowledgement timeout, the 6-metre corpse-looting distance, and the optional GM performance-metrics interval. The metrics interval is 0 by default, which disables those packets.
  • QueueConfig, CommunicatorConfig, ServerInfoConfig, and SocketAsyncConfig own their existing queue, auth-communicator, server-list, and socket settings.
  • GameDataConfig owns the races offered at character creation (EnabledRaces and AlwaysUnlockHybrids, see Races and hybrids), startup server flags, the knowledge-base JSON path, and NavMeshPath. The default navigation directory is navmesh.

Action and action-level behavior is loaded from the world data tables. Missions load enabled definitions and typed scene bindings installed in the World database by migrations. They are not activated by adding an environment setting or copying JSON into the server directory. See mission data migrations. Do not add environment keys for auction, crafting or missions unless the owning code first adds a supported configuration property.

For the exact transfer and loot validation rules and focused checks, see the world regression guide.

Database configuration

There are three databases: Auth (accounts), Char (character and scene state), and World (shared definitions and migrated mission content). EF Core supports MySql and Sqlite providers. Connection settings are defined in src\Rasa.DBL\databasesettings.json and its environment-specific override. Sqlite is suitable for development and small servers; configure its file paths and start the servers normally.

To setup your database settings, have a look in "Custom configuration" to learn how to create an enviroment specific settings file.

To use Sqlite with EF Core, set Provider to Sqlite. Sqlite uses only the value database of the configuration to create a file named <database>.db; supply a base path without the extension. Pending migrations run automatically at the corresponding server's startup, including the complete Bootcamp content. An absolute database base path avoids choosing different files when launching from different working directories. No preexisting World file or publication script is required.

To access a MySql server with EF Core, set Provider to MySql. You need to provide host, port, database, user, password and timeout. The runtime and design-time providers read databasesettings.json, optional databasesettings.env.json, and then environment variables, in that order. Environment variables use double underscores to represent nested keys, e.g. Databases__Provider=MySql and Databases__Auth__Host=mysql. Unspecified values retain the JSON defaults. For MySql, apply pending migrations explicitly before starting the servers. At startup each server checks its MySql databases and logs an error naming the migrations one has not had; it starts anyway, and what those migrations add fails when it is used.

Docker users can instead use the MySQL Compose setup: it provisions the three schemas and runs explicit migration jobs using the same published or locally built application image. The one-shot auth --migrate operation applies Auth migrations; game --migrate applies Char and World migrations. Normal auth and game startup still never automatically migrates MySQL. These commands refuse to run when the configured provider is SQLite. For an existing MySQL installation, back up and stop live servers before running them; successful migration is required before resuming gameplay.

If you want to add additional migrations as part of a feature, see "Creating migrations".

Working with the databases and EF Core

The databases are kept up to date with EF Core. The compatible package set is EF Core/SQLite/Design 9.0.20 with Pomelo MySQL 9.0.0, running on .NET 10. Pomelo 9 supports EF Core 9, not EF Core 10; upgrade these providers together. EF Core 9 support ends November 10, 2026, so this dependency choice needs review before that date. MySQL 8.0 and 8.4 are supported by the provider.

MySQL schema names up to the server's 64-character limit are supported. Rasa preserves Pomelo's migration-lock names for schemas up to 45 characters and uses a deterministic, case-normalized SHA256 lock name for longer schemas. This keeps migration synchronization and history intact without renaming databases. The naming override uses Pomelo 9's protected lock-name hook; revalidate it when upgrading the provider.

Applying migrations

This section describes how to apply migrations to your MySql database as well as how to add additional migrations if you changed the data model in a way that requires an update to the database.

First restore the solution and the repository-local EF tool from the repository root. The manifest pins dotnet-ef to the same version as EF Core; its runtime roll-forward allows the tool to run with only .NET 10 installed. Do not install an unpinned global tool.

  • Open powershell
  • dotnet restore
  • dotnet tool restore
  • dotnet ef --version (expected: 9.0.20)

Before upgrading an existing database, back it up and test these commands on a disposable copy. Keep __EFMigrationsHistory; do not use EnsureCreated, delete the database, or suppress pending-model errors to bypass an upgrade failure. SQLite applies migrations automatically on server startup; MySQL requires the commands below before starting the servers.

Before declaring content or gameplay work ready, also check for provider/model drift from the repository root:

  • dotnet ef migrations has-pending-model-changes --project src\Rasa.DBL --startup-project src\Rasa.Game --context SqliteWorldContext
  • dotnet ef migrations has-pending-model-changes --project src\Rasa.DBL --startup-project src\Rasa.Game --context MySqlWorldContext
  • dotnet ef migrations has-pending-model-changes --project src\Rasa.DBL --startup-project src\Rasa.Game --context SqliteCharContext
  • dotnet ef migrations has-pending-model-changes --project src\Rasa.DBL --startup-project src\Rasa.Game --context MySqlCharContext

If required mission content is broken, Rasa.Game now logs each actionable mission diagnostic and refuses to print Server ready! until the content is fixed. Apply the matching World migrations and deploy the required C# mission scripts; there is no separate active-release requirement.

Ordinary loot also validates and copies migrated World rows before Game accepts clients. The paired 20261203000100_Add_ordinary_loot_tables schema and 20261203000200_Wilderness_ordinary_loot_v1 data migration make map 1220 require complete ordinary bindings. Invalid content blocks readiness; applying matching migrations and restarting loads the catalog, without a live-reload command. See ordinary-loot deployment and tuning.

The preceding 20261203000000_Archive_flat_loot_pools transition preserves incoming numeric flat-pool configurations as inactive legacy_flat_* tables. It follows the unchanged incoming 20261202000000_Place_bootcamp_base_npcs tail. Nonempty archives block ordinary-loot readiness until an explicit authoring decision resolves them; no percentages become weights and no rows are discarded. The gametools loot editor/API uses only the version-2 weighted contract and never writes those archives. See the archive and authoring boundary.

The .NET 10 platform update itself requires no schema change. Apply the repository's migrations in their recorded order, subject to the fresh-database boundary below.

Now navigate to the folder of the Rasa.DBL project:

  • cd Path\to\Rasa.Net\src\Rasa.DBL

To apply any pending migrations, execute the following commands:

  • dotnet ef database update --context=MySqlAuthContext
  • dotnet ef database update --context=MySqlCharContext
  • dotnet ef database update --context=MySqlWorldContext

Explicitly providing the context is required as we have to work with different contexts according to database and provider.

You can also migrate to any specific migration (forward or backward) by passing the migration name or the index/number of the migration as an argument. Obviously, this works with other DbContexts, too:

  • dotnet ef database update "MigrationName" --context=MySqlAuthContext migrates MySqlAuthContext to "MigrationName".
  • dotnet ef database update 0 --context=MySqlAuthContext rollback any migration on MySqlAuthContext, essentially resetting the database to an empty state.

To see existing migrations and if they are applied to your database, use one of the following:

  • dotnet ef migrations list --context=MySqlAuthContext
  • dotnet ef migrations list --context=MySqlCharContext
  • dotnet ef migrations list --context=MySqlWorldContext

Creating migrations

Basically, we differentiate between two types of migrations:

  • Migrations that change the model / schema of the database
  • Migrations that add or remove data to or from the database

Mission definitions, rewards, routes and scene bindings belong in C# data migrations, using shared helpers where appropriate. See mission authoring. Keep schema changes separate, and add a new migration instead of modifying previously applied migration data.

Ordinary loot chances and candidate pools are data too. Use a new paired World data migration, not edits to an applied seed/helper or gameplay constants. World history is forward-dated through 20261203000200; a scaffold's current-date ID may sort before installed content. Before using it, assign an ID after the current tail and keep both providers' filenames/designer attributes consistent. See the verified tuning example and ordering steps.

Always ensure, that a migration only does one or the other. This is very easy, as we use code first approach. If you're developing a feature that requires an update to the schema of one or more of the databases, change the entry classes in RASA.DBL/Structures or add new entries by creating the class and adding a DbSet to the respective DbContext. Then, create a migration applying those changes by executing the following commands:

Historical exception: the incoming 20261120000000_Add_item_modules and 20261121000000_Add_module_crafting pairs combine table creation and seed insertion. Their applied IDs and operations are preserved; do not repeat that coupling in new migrations.

  • dotnet ef migrations add <Name_of_the_Migration> --context=MySqlAuthContext
  • dotnet ef migrations add <Name_of_the_Migration> --context=SqliteAuthContext

If you realize something is wrong with the created migrations and you want to remove and recreate the last migration, execute the following commands:

  • dotnet ef migrations remove --context=MySqlAuthContext
  • dotnet ef migrations remove --context=SqliteAuthContext

Always add migrations for MySql and Sqlite for your changes.

As the code generated by migrations is database provider specific, seperate migrations for MySql and Sqlite are required. Examples for such differences are:

  • Autoincrementing a primary key in Sqlite only works with the type integer
  • Sqlite does not know unsigned numbers
  • Sqlite does not know an explicit datetime type but instead uses TEXT

As already said, we use code first approach, so do not change the generated migration or model snapshot files that apply changes to the database model. With code first, you have the following methods to manipulate the output of the migration generator to your disposal (in order of priority):

  • Try to work with Annotations in the Entry classes as much as possible. Examples: -- [Column("column_name", TypeName = "varchar(40)")] sets a columns name and data type -- [Required] makes a column "not null"
  • If no annotation exists for your use case but the changes work for MySql and Sqlite, use the OnModelCreating method in the corresponding abstract base DbContext (AuthContext). Examples: -- .HasDefaultValue(<some value>) sets a default value for a column
  • If the change of the model needs to distinguish between MySql and Sqlite, try to move them to the database provider specific implementations of IDbContextPropertyModifier. For examples, see the implementations of this interface and how it is used.
  • If that does not work, put them in the OnModelCreating methods of the corresponding derived DbContexts (MySqlAuthContext, SqliteAuthContext, MySqlCharContext, SqliteCharContext, MySqlWorldContext, SqliteWorldContext).

If, on the other hand, you use a migration to provide default data that needs to be imported into the database, you just create an empty migration. Ensure you didn't change any entry classes and add the migrations as descibed above. The created migration will be empty and you can use them do add data. As an example how this can be implemented for MySql and Sqlite can be seen in 20201218081744_Preload_ItemTemplate_PlayerExp_RandomName.

Mission data migrations

Mission content follows the same deployment flow as other database content. SQLite startup creates missing files and applies pending schema/data migrations. MySQL requires the normal explicit dotnet ef database update commands before starting Game. Both providers use shared C# mission-data helpers.

The mission baseline consolidates its 162 development-time migration steps into six, counting SQLite and MySQL separately. Migrations already on development remain unchanged. The Wilderness rollout appends paired World migrations after the complete PR105 history.

Database New migrations for each provider
Auth None; the MySQL identity correction is snapshot metadata only
Char ConsolidatedCharacterSchema
World ConsolidatedWorldSchema, then SeedWorldContent

The consolidated baseline targets fresh databases. It does not upgrade databases that recorded the removed branch migration IDs, convert experimental mission releases, or backfill intermediate character saves. Use fresh database paths, or remove your own disposable files when you intend to start over. Do not rewrite __EFMigrationsHistory to make an old branch database appear compatible. The server does not delete databases or reset characters.

The supported rollout targets are a fresh merged database and an existing PR105 database. PR105's final World migration is 20261103000000_Snowball_stacks_not_unique. All 17 Wilderness migration pairs follow it, in the order below; each timestamp is identical for SQLite and MySQL. The Wilderness-only World updates do not reset existing PR105 Char assignments, inventory, flags or history.

Timestamp Wilderness migration
20261104000000 WildernessOpeningWorld
20261104000100 NativeMissionCategory
20261104000200 WildernessAliaOpening
20261104000300 WildernessHubWorld
20261104000400 RelatedMissionFailureAction
20261104000500 WildernessRewardEquipment
20261104000600 WildernessAdditionalWorld
20261104000700 WildernessSpawnStatistics
20261104000800 WildernessAliaBranches
20261104000900 WildernessSniperPlacement
20261104001000 WildernessElohPinhole
20261104001100 WildernessLandingZone
20261104001200 WildernessSupportedRewards
20261104001300 WildernessTwinPillars
20261104001400 WildernessRanjaGorge
20261104001500 WildernessDaghdasUrn
20261104001600 WildernessEvidenceCapacity

The earlier September Wilderness migration IDs were unshipped and are not an upgrade source for this integration. Their development databases are disposable; use fresh configured paths rather than rewriting __EFMigrationsHistory. Intermediate-upgrade fixtures use the November lineage and retain their own active mission progress. That regression coverage does not convert old experimental or September Wilderness saves.

Mission-authored World creatures, pools and attack rows now use the allocated 630001..630199 namespace, leaving PR105's Divide 530xxx rows untouched. Char outcome flags 530002 and 530003 are separate identities and do not move. PR105's Add_armor_values supplies native itemclass.max_hp armor values before Wilderness starts. WildernessRewardEquipment is a no-op compatibility marker and cannot insert duplicates or delete those PR105-owned rows on rollback.

SeedWorldContent installs shared World content, including the five enabled Bootcamp missions and their private experience bindings. Both providers call WorldContentDataV1; schema and data remain separate. Subsequent content changes should add new migrations rather than edit this seed. Required script and content validation remains part of startup. Read the authoring guide and data/script reference when adding or changing missions.

Build and run the code from Visual Studio

You should be ready to compile Rasa.NET and run the servers.

  • Launch Visual Studio and open the Rasa.NET.sln file in the code repository
  • If you have to overwrite the default database connection parameters, see "Custom configuration" and "Database configuration"
  • Build the solution
  • For MySQL, apply pending migrations; SQLite runs them automatically on startup
  • Run the Rasa.Auth project via Debug > Start without Debugging
  • Run the Rasa.Game project via Debug > Start Debugging
  • Alternatively, define multiple start projects as follows:
    • Right click on the solution
    • Click "Set StartUp Projects"
    • Choose "Multiple startup projects"
    • Set "Action" for both projects wether you want to start with or without debugging
  • Of course, you can also build the project and start the Auth server from the bin directory.

Build and test from the command line

From the repository root:

dotnet restore
dotnet build --no-restore
dotnet test --no-build
dotnet run --project src\Rasa.Auth\Rasa.Auth.csproj --no-build

Start Game in a second terminal with dotnet run --project src\Rasa.Game\Rasa.Game.csproj --no-build. For a self-contained deployment, publish each server with a portable identifier, for example:

dotnet publish src\Rasa.Auth\Rasa.Auth.csproj -c Release -r win-x64 --self-contained true
dotnet publish src\Rasa.Game\Rasa.Game.csproj -c Release -r win-x64 --self-contained true

Use osx-x64 or linux-x64 for the other deployment targets.

Game publishes include compiled migration data, mission scripts and navigation assets. Use the published executable's --check-mission-assets diagnostic from an unrelated working directory to check asset discovery without starting listeners or opening databases.

Navigation and navmesh assets

Rasa.Navigation is the runtime query library, Rasa.NavMesh is the offline builder, and Rasa.ClientData reads the installed client's map and mesh data. The repository contains 77 generated .nav files under navmesh. Rasa.Game loads matching files at startup from GameDataConfig.NavMeshPath. For the default navmesh setting, it checks the working directory, the application's directory, then the repository root when running from a source checkout. Explicit custom paths remain relative to the working directory and are not replaced by this discovery.

Game publishes include the navigation assets. The startup log reports the resolved folder and loaded-map count. Restart the server after updating assets so new private map instances inherit the loaded mesh. Scripted routes such as Alister's move require navigation and refuse to start without it. A failed query on a loaded mesh never becomes a straight line through geometry; ordinary creatures on maps that intentionally have no mesh retain their legacy movement.

To rebuild all navmeshes from a local 1.16.5.0 client installation:

dotnet run --project src\Rasa.NavMesh\Rasa.NavMesh.csproj --no-build -- --client "C:\Games\Tabula Rasa" --out navmesh

Use --map adv_foreas_concordia_wilderness to rebuild one map. Generated files are inputs to Rasa.Game; no game client or live database is required to compile the navigation projects.

Database compatibility tests

The compatibility tests verify the pinned SDK, all solution project targets, the retained navigation project/package references, cryptographic fixtures, connection-string-specific MySQL server-version caching, and deterministic migration-lock names for schemas through MySQL's 64-character limit. The MySQL configuration tests use a fixed server version and do not connect to a database.

dotnet test src\Rasa.Test\Rasa.Test.csproj --filter "FullyQualifiedName~Compatibility"

Create a game user

The authentication server can be used to create a user by running a command in the terminal. The usage is: create <email> <username> <password>. Running this command will create a new user in the database that you can use to login with the game client.

  • Run the command in the authentication termain. i.e. create test@test.com test test
    • You can use any username / password that you want to create an account.

Launch the game

If the server consoles launched correctly, you should be ready to start the game client.

  • Start the game client using the shortcut you created earlier
  • Login with the user you created for the game above

A first-login server crash is reported in InfiniteRasa/Rasa.NET#45. The automated protocol checks do not reproduce the complete native-client first-load sequence. If you encounter it, capture the server error and frame boundaries, restart Rasa.Game, and retry without treating the workaround as acceptance. See the protocol regression guide.