Client packages
C# also runs in the game client. Code in Client/ runs on every player that has the
NanosSharp client installed, code in Shared/ runs on both sides.
my-gamemode/
├── Package.toml
├── Server/Gamemode.cs runs on the server
├── Client/Hud.cs runs on every player
├── Client/Index.lua Lua fallback for players without NanosSharp
└── Shared/Teams.cs runs on both sides
public sealed class Hud : Package
{
protected override void OnStart()
{
Events.SubscribeRemote("ScoreChanged", (long score) => Log.Info($"Score: {score}"));
Input.OnKeyPress += (key, _) =>
{
if (key == "F")
{
Events.CallRemote("RequestScore");
}
return true;
};
}
}
The client API is generated from the game client just like the server API: Client, Input, Viewport, Sky,
Trace, WebUI, Widget, Canvas, Sound and every other client class are available with types and documentation.
How client code is built
The server sends the .cs files of Client/ and Shared/ to the players together with the rest of the package. The
NanosSharp client compiles them on the player's machine when the package loads. There is no build step on your side.
Shared/ is compiled into both sides. Use the SERVER and CLIENT symbols for code that only belongs to one of them:
public static class Teams
{
public const int MaxPlayers = 16;
#if SERVER
public static void Assign(Player player) => player.SetValue("team", Player.All.Count() % 2);
#endif
}
The sandbox
Client code runs on the computers of your players, so it is compiled in a sandbox. The compiler checks every type and member the package uses against an allow list and rejects the package if anything else is used.
| Allowed | Not allowed |
|---|---|
| The whole NanosSharp API | System.IO, files and processes |
Collections, LINQ, StringBuilder, regular expressions | Reflection, Activator, expression trees |
async/await, Task, timers from NanosSharp | Threads, GC, Environment.Exit |
System.Text.Json, System.Numerics, math, dates | HttpClient and other networking from .NET |
| Exceptions and the usual attributes | unsafe, dynamic, P/Invoke, Marshal, Unsafe |
A rejected package logs every violation and does not start. The game API itself stays available as usual, for example
HTTP for web requests and ScriptFile for the files nanos world allows Lua to access.
Client packages cannot reference NuGet packages or ship .dll files, and they have no hot reload.
Differences to server packages
OnStartruns one tick after the package loaded, when the game has finished setting up the package.Package.Title,Version,GetFilesand the persistent data are available as on the server.- Lua interop works the same:
Exports.Get,Exports.Set,Exports.Calland[Export].
Talking to the server
The client sends to the server, the server sends to one or all players. The handlers on the server receive the sending player as the first parameter.
Events.CallRemote("Vote", "nanos-world::BlankMap");
Events.CallRemoteUnreliable("Aim", Viewport.ViewportSize);
Events.SubscribeRemote("Vote", (Player player, string map) => Log.Info($"{player.Name} voted {map}"));
Events.CallRemote("ScoreChanged", player, 120L);
CallRemote and BroadcastRemote are delivered reliably and in order. The Unreliable variants are cheaper but the
message may be lost or arrive out of order, use them for frequent values that are replaced by the next one anyway.
Players without NanosSharp
Players can join without the NanosSharp client. They download the .cs files like any other file, but nothing runs
them. A package can ship a Lua fallback for these players. NanosSharp tells Lua whether the C# code of a package is
running:
if Client.GetValue("NanosSharp:" .. Package.GetName()) then
return
end
Console.Log("This server works best with NanosSharp: https://nanossharp.dasdarki.de")
The value is only set when the C# code of this package actually started. It is not set when the player has no NanosSharp client, when the server does not run NanosSharp or when the client and the server speak different protocol versions.
Knowing who has NanosSharp
When a player is ready, the server asks the client for its NanosSharp version. The result is available on the server:
NanosSharpClients.Connected += (player, client) => Log.Info($"{player.Name} runs NanosSharp {client.Version}");
NanosSharpClients.Missing += player => player.SendMessage("Install NanosSharp for the full experience.");
if (player.HasNanosSharp)
{
Events.CallRemote("ShowCSharpHud", player);
}
A player counts as missing when a Lua fallback reports it, when the protocol versions differ or when the client does not answer within the configured time. A Lua fallback can report a missing client right away instead of letting the server wait:
if not Client.GetValue("NanosSharp") then
Events.SubscribeRemote("nanossharp:handshake-request", function()
Events.CallRemote("nanossharp:handshake")
end)
end
Requiring NanosSharp
A server can kick players without a compatible client:
[clients]
required = true
handshake_timeout_seconds = 30
kick_message = "This server requires NanosSharp {version}. Download it at https://nanossharp.dasdarki.de"
{version} is replaced with the NanosSharp version of the server. See Configuration.