This guide will help you install and setup the required tools to run Rasa.NET. There are a few steps:
- Download, install, and setup the required tooling
- Download and install the game
- Download or clone the code
- Build and run the code from Visual Studio
- Launch the game
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
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
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 --versionand verify10.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.
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
rootuser password to something unique when prompted after installation
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
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
.exeand chooseSend to > Desktop (create shortcut) - Go to the desktop and right-click on the newly created shortcut and choose
Properties - Select the
Shortcuttab - Edit the
Targetbox to append/NoPatch /AuthServer=localhost:2106to the end of the file path.
- Right-click on the
The game client needs to be version 1.16.5.0. If you have questions, join the Discord.
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
If you want to use a MySql database, here are the steps required to get things to work.
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 MySQL80underMySQL Connectionsthat 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
rasaauthand clickApply - Repeat this two more times for
rasacharandrasaworld
- In the new tab, name the first schema
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 Nametorasa - Set the
Limit Hosts to Matchingtolocalhost - Set the password to
rasa - Select the
Administrative Rolestab and checkDBA - Click
Apply
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.
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.slnfile in the code repository - Create a file named
appsettings.env.json/databasesettings.env.jsonin 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.
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).
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.
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).
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.
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.
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_statusishealthywhile the server has finished loading, is listening for players, has not been shut down, and its world loop ticked withinApiConfig.LoopStallSeconds(default15).app_server_statusishealthywhile the link to the Auth server is up and logged in.currentconnectionsis the number of players holding a slot, the number the server list shows.maxconnectionsisServerInfoConfig.MaxPlayers,peakconnectionsthe most there have been since the server started, anduptimesecondsthe 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.
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
Endpointsit is off. An entry turns it on unless it says"Enabled": false, which is what the shipped file has; an entry that leavesEnabledout counts as on. Rest.Publicnever makes it public. Only"Public": truein its own entry does, and then anyone who can reach the port can create accounts.- Its own
ApiKeyopens it, and so does the globalApiKey. 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>" }
}
}
}
}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"}
]}nameis the character's own name andfamilyNamethe family name of the account's characters.classIdis the class's number andclassNameits name (Recruit,Soldier,Specialist,Commando, and so on).stateisloggedinorcharacterselectionfor an account that has not picked a character, whose character fields are allnull, andloading,ingameorteleportingfor one that has.- The list is read once a second from the world, so it can be a second old.
countcan be below/serverstatus'scurrentconnections: 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/usersonlinePOST /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) orfamilyName, one of them and not both. A family name is matched as the in-game.kickmatches it, case aside.reasonis what the player is told; it may be left out, and may be 256 characters.adminis 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/kickuserPOST /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.
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"}
]}typeishelprequestorbugreport;statusisopen,resolvedorcancelled(withdrawn by the player).accountNameis the account's login andfamilyNameits family name;characterNameis the character that filed it. Each isnullfor an account or character no longer there.mapContextIdandpositionare where the player stood when they filed it.createdAtis UTC.resolutionis a game master's answer, empty until there is one.noticePendingistruewhile 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" }petitionIdis a number, or a number in a string. Only an open petition is answered or closed./respondpetition'sresponsemay be 255 characters. It is written as the petition'sresolution, in place of any answer before it, and the petition stays open.adminis who is answering, as you name them, up to 64 characters./respondpetitionalso 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'sadminIdis 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 MESSAGEline, "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/closepetitionEvery 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,tokenorapikeyin it, or ends incode, is written as***- the password of/addaccount, the code and token of the/ingameexchange. A key sent in a header is never written. - A body is kept up to 16000 characters;
body_lengthis 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;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.
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.
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.
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.
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
Iterationsis safe. Each hash records its own count, and an account is rehashed to the current one at its next login. - Adding a
Pepperlater is safe. Accounts hashed without one are given it at their next login. - Changing or removing a
Pepperonce 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.
Rasa.Game\Config owns the server settings loaded from src\Rasa.Game\appsettings.json:
GameConfigowns 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 is0by default, which disables those packets.QueueConfig,CommunicatorConfig,ServerInfoConfig, andSocketAsyncConfigown their existing queue, auth-communicator, server-list, and socket settings.GameDataConfigowns the races offered at character creation (EnabledRacesandAlwaysUnlockHybrids, see Races and hybrids), startup server flags, the knowledge-base JSON path, andNavMeshPath. The default navigation directory isnavmesh.
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.
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".
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.
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 restoredotnet tool restoredotnet 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 SqliteWorldContextdotnet ef migrations has-pending-model-changes --project src\Rasa.DBL --startup-project src\Rasa.Game --context MySqlWorldContextdotnet ef migrations has-pending-model-changes --project src\Rasa.DBL --startup-project src\Rasa.Game --context SqliteCharContextdotnet 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=MySqlAuthContextdotnet ef database update --context=MySqlCharContextdotnet 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=MySqlAuthContextmigrates MySqlAuthContext to "MigrationName".dotnet ef database update 0 --context=MySqlAuthContextrollback 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=MySqlAuthContextdotnet ef migrations list --context=MySqlCharContextdotnet ef migrations list --context=MySqlWorldContext
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=MySqlAuthContextdotnet 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=MySqlAuthContextdotnet 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 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.
You should be ready to compile Rasa.NET and run the servers.
- Launch Visual Studio and open the
Rasa.NET.slnfile 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.Authproject viaDebug > Start without Debugging - Run the
Rasa.Gameproject viaDebug > 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.
From the repository root:
dotnet restore
dotnet build --no-restore
dotnet test --no-build
dotnet run --project src\Rasa.Auth\Rasa.Auth.csproj --no-buildStart 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 trueUse 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.
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 navmeshUse --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.
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"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.
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.