C# plugins: the native tier the Lua API maps onto - a plugin compiled against KcdMp.Api # C# > Game modes as C# plugins - the native tier the Lua API is built on, compiled against KcdMp.Api and loaded by the server. A KCD:MP game mode can be a **C# class library** instead of a Lua script: a `.dll` compiled against the server folder's `sdk/KcdMp.Api.dll`, placed in a folder of its own under `gamemodes/` and named in `server.toml` (`script = "gamemodes/arena/Arena.dll"`). It is the **native tier** - the Lua API is built on exactly these interfaces, so anything Lua can do a plugin can do with the same semantics, typed, and with the whole of .NET behind it: a database, a web service, a package from NuGet. The built-in freeroam the server falls back to is one. | | | |---|---| | [Server API](/csharp/server/) | building a plugin, loading it, a database or web service behind it, `IGameMode` (the callbacks), `IServerApi` (the functions), `IPlayer`, `IWorldEntity`, `IZone`, `IHudText`, `IParty`, the records and enums | There is no client tier in C#: a mode's client half is a [Lua client script](/lua/client/) whatever the server side is written in. The behaviour of every call is documented once, on the [Lua pages](/lua/server/); the C# page maps the names and types. [Setting up](/getting-started/setting-up/) has the server folder, the first run and the editor; the [reference](/reference/) has the keys the functions take. # Server API (C#) > Writing a game mode as a C# plugin - the project, loading it, a database behind it, and every member of IGameMode, IServerApi, IPlayer, IWorldEntity, IZone, IHudText and IParty mapped to its Lua page. A plugin is a class library with one public class that implements `IGameMode` and has a parameterless constructor. The server loads it in its own load context - the plugin's private dependencies stay private, `KcdMp.Api` resolves to the server's copy - and calls it exactly as it calls a Lua mode: every method on the **simulation thread**, one tick at a time, so a plugin may keep plain fields and touch `IServerApi` freely from inside its methods, and must not from anywhere else. The one door for other threads is `Post` ([below](#slow-work-databases-and-web-requests)). The behaviour of each call is documented on the [Lua pages](/lua/server/); this page maps the C# names and types onto them, and the IDE shows each member's summary (`KcdMp.Api.xml` ships next to the DLL). ## The project The server folder ([Setting up](/getting-started/setting-up/) has it whole, and the first run) has two places for this: ``` KcdMp-server/ sdk/KcdMp.Api.dll what a plugin builds against (KcdMp.Api.xml next to it: the IDE shows every member's description) gamemodes/arena/Arena.dll where a plugin lives: its own folder under gamemodes/, next to the Lua modes ``` The plugin is a .NET class library that references `sdk/KcdMp.Api.dll` - the server's own copy is the one that runs, so the reference is not copied along, and the DLL to build against is the one of the server version you run (a member the running server does not have fails at the call). NuGet packages are added as usual; they travel with the plugin. ```xml net10.0 enable enable ../KcdMp-server/sdk/KcdMp.Api.dll false ``` `dotnet publish` puts the plugin, its `.deps.json` and every package it uses in one folder - publish straight into the mode's folder and name the DLL in `server.toml`: ```bash dotnet publish -c Release -o ../KcdMp-server/gamemodes/arena ``` ```toml [gamemode] script = "gamemodes/arena/Arena.dll" # or "gamemodes/arena/Arena.dll:MyModes.ArenaMode" when the assembly has several IGameMode types ``` ```bash KcdMp.Server --gamemode gamemodes/arena/Arena.dll ``` The same on Linux and Windows; the server folder is laid out identically on both. `/reload` and `ReloadGameMode` reload a plugin like a script: the old load context is dropped, the file is read again, `OnInit` runs anew and every player is announced again (`[gamemode] watch` follows a `.lua` file only). Whatever the server itself has loaded - `KcdMp.Api`, the runtime, its own packages - is shared with the plugin; everything else the plugin brings is its own. ```csharp using KcdMp.Api; public sealed class HelloMode : IGameMode { private IServerApi _api = null!; private IHudText? _clock; public string Name => "hello"; public void OnInit(IServerApi api) { _api = api; _clock = api.CreateHudText(0.99f, 0.02f, "", 0xFFFF40FF, 1f, HudAlign.Right); } public void OnShutdown() { } public void OnPlayerConnect(IPlayer player) => _api.Broadcast(0xFFD070FF, $"{player.Name} joined"); public SpawnPoint? OnPlayerRequestSpawn(IPlayer player) { var s = _api.DefaultSpawn; return s with { X = s.X + 1.5f * (player.Id % 16) }; // null would hold the player until SpawnPlayer } public void OnPlayerSpawn(IPlayer player) => _clock?.Show(player); public bool OnPlayerText(IPlayer player, string text) => true; public bool OnPlayerCommand(IPlayer player, string command, string args) { if (command != "hello") return false; _api.SendClientMessage(player, 0xFFD070FF, $"Hello, {player.Name}"); return true; } public void OnPlayerDisconnect(IPlayer player, string reason) { } public void OnTick(float dtSeconds) { if (_clock is not null && _api.Tick % 60 == 0) _clock.Text = TimeSpan.FromSeconds(_api.TimeOfDay).ToString(@"hh\:mm"); } } ``` The Lua conventions hold: metres, degrees (`0` = facing +Y, counter-clockwise), `0xRRGGBBAA` colours, milliseconds. Where the Lua API takes a `pid` or an entity `id`, C# takes the `IPlayer` / `IWorldEntity` object; `api.GetPlayer(id)` and `api.GetEntity(netId)` translate, and both answer `null` once the player or entity is gone - keep the id, not the object, across ticks if in doubt. ## Slow work: databases and web requests A plugin's callbacks run inside the tick, so a query that takes 40 ms inside `OnPlayerConnect` stalls every player for 40 ms - and nothing in `IServerApi` may be touched from another thread. The one member made for other threads is **`void Post(Action action)`**: the action runs on the simulation thread at the start of the next tick, before `OnTick`, in posting order, and inside it the whole API is legal again. So the slow part runs on a task of the plugin's own and posts its result back. Any .NET database driver or HTTP client works; MySQL through the `MySqlConnector` package, for example: ```csharp using KcdMp.Api; using MySqlConnector; // dotnet add package MySqlConnector public sealed class VisitsMode : IGameMode { private IServerApi _api = null!; private MySqlDataSource _db = null!; private readonly CancellationTokenSource _stopping = new(); public string Name => "visits"; public void OnInit(IServerApi api) { _api = api; _db = new MySqlDataSource("Server=127.0.0.1;Database=kcdmp;User=kcdmp;Password=..."); } public void OnShutdown() { _stopping.Cancel(); // a reload replaces the plugin: tasks still running must not post into the new one _db.Dispose(); // ... and the pool must go, or the old copy lingers } public void OnPlayerConnect(IPlayer player) { var id = player.Id; // read what you need now; the objects stay on the simulation thread var name = player.Name; _ = Task.Run(async () => { int visits; try { await using var cmd = _db.CreateCommand("SELECT visits FROM players WHERE name = @name"); cmd.Parameters.AddWithValue("@name", name); visits = Convert.ToInt32(await cmd.ExecuteScalarAsync(_stopping.Token) ?? 0); } catch (Exception ex) when (ex is MySqlException or OperationCanceledException) { if (!_stopping.IsCancellationRequested) _api.Post(() => _api.Log($"players table: {ex.Message}")); // the log too return; } if (_stopping.IsCancellationRequested) return; _api.Post(() => { if (_api.GetPlayer(id) is { } p && p.Name == name) // ids are reused: the slot may hold someone else by now _api.SendClientMessage(p, 0xFFD070FF, $"Welcome back, {name} - visit {visits + 1}"); }); }); } public void OnPlayerDisconnect(IPlayer player, string reason) { var name = player.Name; _ = Task.Run(async () => // a write nobody waits for needs no Post { try { await using var cmd = _db.CreateCommand( "INSERT INTO players (name, visits) VALUES (@name, 1) ON DUPLICATE KEY UPDATE visits = visits + 1"); cmd.Parameters.AddWithValue("@name", name); await cmd.ExecuteNonQueryAsync(_stopping.Token); } catch (Exception ex) when (ex is MySqlException or OperationCanceledException) { if (!_stopping.IsCancellationRequested) _api.Post(() => _api.Log($"players table: {ex.Message}")); } }); } public SpawnPoint? OnPlayerRequestSpawn(IPlayer player) => _api.DefaultSpawn; public void OnPlayerSpawn(IPlayer player) { } public bool OnPlayerText(IPlayer player, string text) => true; public void OnTick(float dtSeconds) { } } ``` The rules in one place: - Read the `IPlayer` / `IWorldEntity` values you need (`Id`, `Name`, `Position` ...) before the task starts; the objects and every `IServerApi` member other than `Post` stay on the simulation thread - `Log` included. - Come back through `Post` with the **id**, and check the player is still the one you meant: `GetPlayer(id)` answers `null` once they left, and a freed slot is given to the next player. - Catch inside the task. An exception a task swallows is lost silently; an exception inside a posted action is the mode's, like one in any callback. - `Post` from the simulation thread itself is allowed too: the action simply runs on the next tick. Actions posted from one thread run in the order they were posted; an action that posts another gets it a tick later. - A reload drops what the old plugin still had queued and calls its `OnShutdown`: cancel your tasks there, as above, so a late answer does not run under the new mode. ## IGameMode - the callbacks Every member has a default that does nothing (or lets the action through), so a mode implements the ones it needs. `Name`, `OnInit`, `OnShutdown`, `OnPlayerConnect`, `OnPlayerRequestSpawn`, `OnPlayerSpawn`, `OnPlayerText`, `OnPlayerDisconnect` and `OnTick` are abstract - a mode must have them. | C# | Lua page | |---|---| | `string Name { get; }` | [`SetGameModeText`](/lua/server/functions/setgamemodetext/) - the mode's name | | `void OnInit(IServerApi api)` | [`OnGameModeInit`](/lua/server/callbacks/ongamemodeinit/) - keep `api` | | `void OnShutdown()` | [`OnGameModeExit`](/lua/server/callbacks/ongamemodeexit/) | | `void OnTick(float dtSeconds)` | [`OnTick`](/lua/server/callbacks/ontick/) | | `void OnPlayerConnect(IPlayer)` | [`OnPlayerConnect`](/lua/server/callbacks/onplayerconnect/) | | `SpawnPoint? OnPlayerRequestSpawn(IPlayer)` | [`OnPlayerRequestSpawn`](/lua/server/callbacks/onplayerrequestspawn/) - return the point, or `null` to hold the player for `SpawnPlayer` | | `void OnPlayerSpawn(IPlayer)` | [`OnPlayerSpawn`](/lua/server/callbacks/onplayerspawn/) | | `bool OnPlayerText(IPlayer, string text)` | [`OnPlayerText`](/lua/server/callbacks/onplayertext/) - `false` swallows the line | | `bool OnPlayerCommand(IPlayer, string command, string args)` | [`OnPlayerCommandText`](/lua/server/callbacks/onplayercommandtext/) - `true` = handled | | `void OnPlayerDisconnect(IPlayer, string reason)` | [`OnPlayerDisconnect`](/lua/server/callbacks/onplayerdisconnect/) | | `void OnPlayerLogin(IPlayer)` | [`OnPlayerLogin`](/lua/server/callbacks/onplayerlogin/) | | `void OnPlayerMount(IPlayer, IWorldEntity horse)`, `OnPlayerDismount` | [`OnPlayerMount`](/lua/server/callbacks/onplayermount/), [`OnPlayerDismount`](/lua/server/callbacks/onplayerdismount/) | | `bool OnPlayerPickup(IPlayer, IWorldEntity item)` | [`OnPlayerPickup`](/lua/server/callbacks/onplayerpickup/) - `false` refuses | | `void OnPlayerDrop(IPlayer, IWorldEntity item)` | [`OnPlayerDrop`](/lua/server/callbacks/onplayerdrop/) | | `bool OnPlayerUseItem(IPlayer, string itemClass, float health, string buffGuid)` | [`OnPlayerUseItem`](/lua/server/callbacks/onplayeruseitem/) - `false` cancels | | `void OnPlayerDrunk(IPlayer, bool drunk)` | [`OnPlayerDrunk`](/lua/server/callbacks/onplayerdrunk/) | | `float OnPlayerDamage(IPlayer victim, IPlayer? attacker, float damage, int zone, int bodyPart)` | [`OnPlayerDamage`](/lua/server/callbacks/onplayerdamage/) - return the damage to apply, `0` cancels | | `float OnPlayerHit(IPlayer victim, IPlayer? attacker, in HitInfo hit)` | the same hit with everything the model resolved (`HitInfo` below); the default hands it to `OnPlayerDamage` | | `void OnPlayerInjury(IPlayer victim, IPlayer? attacker, BodyPart part)` | [`OnPlayerInjury`](/lua/server/callbacks/onplayerinjury/) | | `void OnPlayerDeath(IPlayer victim, IPlayer? attacker)` | [`OnPlayerDeath`](/lua/server/callbacks/onplayerdeath/) | | `void OnFightStart(IPlayer a, IPlayer b, string reason)`, `OnFightEnd` | [`OnFightStart`](/lua/server/callbacks/onfightstart/), [`OnFightEnd`](/lua/server/callbacks/onfightend/) | | `void OnActorArrive(IWorldEntity actor)` | [`OnActorArrive`](/lua/server/callbacks/onactorarrive/) | | `float OnActorHit(IWorldEntity actor, IPlayer attacker, in HitInfo hit)` | [`OnActorDamage`](/lua/server/callbacks/onactordamage/) - return the damage, `0` cancels | | `void OnActorDeath(IWorldEntity actor, IPlayer? attacker)` | [`OnActorDeath`](/lua/server/callbacks/onactordeath/) | | `void OnActorInjury(IWorldEntity actor, IPlayer? attacker, BodyPart part)` | [`OnActorInjury`](/lua/server/callbacks/onactorinjury/) | | `float OnActorAttack(IWorldEntity actor, IPlayer victim, in HitInfo hit)` | [`OnActorAttack`](/lua/server/callbacks/onactorattack/) - the default hands it to `OnPlayerHit` without an attacker | | `void OnPlayerEnterZone(IPlayer, IZone)`, `OnPlayerLeaveZone` | [`OnPlayerEnterZone`](/lua/server/callbacks/onplayerenterzone/), [`OnPlayerLeaveZone`](/lua/server/callbacks/onplayerleavezone/) | | `void OnClientEvent(IPlayer, string name, string payload)` | [`OnClientEvent`](/lua/server/callbacks/onclientevent/) | | `bool OnPlayerUseDoor(IPlayer, string key, bool open, bool locked)` | [`OnPlayerUseDoor`](/lua/server/callbacks/onplayerusedoor/) - `false` refuses | | `bool OnPlayerOpenContainer(IPlayer, string key)`, `void OnPlayerCloseContainer(IPlayer, string key)` | [`OnPlayerOpenContainer`](/lua/server/callbacks/onplayeropencontainer/), [`OnPlayerCloseContainer`](/lua/server/callbacks/onplayerclosecontainer/) | | `bool OnPlayerAuditViolation(IPlayer, string itemClass, int amount, int allowed)` | [`OnPlayerAuditViolation`](/lua/server/callbacks/onplayerauditviolation/) - `false` vouches | | `bool OnPartyInvite(IPlayer from, IPlayer target)` | [`OnPartyInvite`](/lua/server/callbacks/onpartyinvite/) - `false` refuses the invitation | | `void OnPartyInviteResponse(IPlayer from, IPlayer target, string answer)` | [`OnPartyInviteResponse`](/lua/server/callbacks/onpartyinviteresponse/) | | `void OnPartyCreate(IParty, IPlayer leader)`, `void OnPartyDisband(IParty, string reason)` | [`OnPartyCreate`](/lua/server/callbacks/onpartycreate/), [`OnPartyDisband`](/lua/server/callbacks/onpartydisband/) | | `void OnPlayerJoinParty(IParty, IPlayer, string reason)`, `OnPlayerLeaveParty` | [`OnPlayerJoinParty`](/lua/server/callbacks/onplayerjoinparty/), [`OnPlayerLeaveParty`](/lua/server/callbacks/onplayerleaveparty/) | | `void OnPartyLeaderChange(IParty, IPlayer leader, IPlayer previous)` | [`OnPartyLeaderChange`](/lua/server/callbacks/onpartyleaderchange/) | ## IServerApi - the functions Grouped as the Lua index is; the Lua page has the behaviour. **Server, threads and timers** - `string Level`, `string LevelName` (the level as a player reads it: Trosky, Kuttenberg, Sedletz Monastery; `Levels.DisplayName(id)` is the static helper), `int MaxPlayers`, `long Tick`, `uint TimeMs`, `SpawnPoint DefaultSpawn`, `void Log(string)`, `void ReloadGameMode(string reason = "requested")`, `void Post(Action)` (the one member for other threads: the action runs on the next tick, [above](#slow-work-databases-and-web-requests)). There is no timer API: a plugin counts ticks in `OnTick` or keeps its own `TimeMs` deadlines ([`SetTimer`](/lua/server/functions/settimer/) is the Lua tier's own). **Players** - `IReadOnlyList Players`, `IPlayer? GetPlayer(int id)`, `void SpawnPlayer(IPlayer, SpawnPoint at)` ([`SetSpawnInfo`](/lua/server/functions/setspawninfo/) + [`SpawnPlayer`](/lua/server/functions/spawnplayer/) in one: `SpawnPoint(X, Y, Z, YawDeg, ClothingPreset = "", WeaponPreset = "", Appearance = "")`), `void SetPlayerPos(IPlayer, float x, float y, float z, float? yawDeg = null)`, `void Kick(IPlayer, string reason)`, `void SetPlayerNameplate(IPlayer, string)`, `void SetPlayerColour(IPlayer, uint)`, `void SetPlayerTeam(IPlayer, int)` (`IPlayer.NoTeam`), `void TogglePlayerControllable(IPlayer, bool)`, `void SetPlayerVirtualWorld(IPlayer, int)`, `void SetPlayerAdmin(IPlayer, bool)`, `IReadOnlyList Admins`. **Chat, GameText and HUD** - `void SendClientMessage(IPlayer, uint colour, string)`, `void Broadcast(uint colour, string)` ([`SendClientMessageToAll`](/lua/server/functions/sendclientmessagetoall/)), `void GameText(IPlayer, string, int durationMs, GameTextStyle = Centre)`, `void GameTextForAll(...)`, `IHudText? CreateHudText(float x, float y, string text, uint colour = white, float scale = 1, HudAlign = Left)`, `void DestroyHudText(IHudText)`, `IReadOnlyList HudTexts`, `IHudText? GetHudText(int id)`. **Script events and state bags** - `void SendClientEvent(IPlayer, string name, string payload)`, `void SendClientEventToAll(...)`; `IReadOnlyDictionary GlobalState`, `string? GetGlobalState(string)`, `bool SetGlobalState(string, string?)`, `GetPlayerState` / `SetPlayerState(IPlayer, ...)`, `GetEntityState` / `SetEntityState(IWorldEntity, ...)`; the limits are `IServerApi.StateValueMax` (1024) and `StateKeysMax` (64). **Storage** - `IPlayer.Data` and `IWorldEntity.Data` / `IZone.Data` (`IDictionary`, the private bags); `SavedPlayer? GetSavedPlayer(string name)`, `string? GetSavedData(string name, string key)`, `void SetSavedData(string name, string key, string? value)` (by **name**, not player); `string? GetServerData(string)`, `void SetServerData(string, string?)`. **Vitals, stats, buffs, drink** - `void SetPlayerHealth(IPlayer, float)`, `void SetPlayerStamina(IPlayer, float)`, `void HealPlayer(IPlayer)`; `bool SetPlayerStat(IPlayer, string, int)`, `bool SetPlayerSkill(...)`, `byte GetPlayerStat(IPlayer, string)`, `byte GetPlayerSkill(...)`; `bool GivePlayerBuff(IPlayer, string buff, float seconds = 0)`, `bool RemovePlayerBuff`, `bool HasPlayerBuff`, `void ClearPlayerBuffs`, `string? ResolveBuff(string key)`, `BuffInfo? GetBuffInfo(string key)`, `IReadOnlyList FindBuffs(string pattern, int max = 10)`, `IReadOnlyList ClaimedBuffClasses`, `void ClaimBuffClasses(IEnumerable)`; `float GetPlayerAlcohol(IPlayer)`, `bool IsPlayerDrunk(IPlayer)`, `void SetPlayerAlcohol(IPlayer, float)`. The readers are properties of `IPlayer` (below). **Combat** - `bool StartFight(IPlayer a, IPlayer b)`, `void EndFight(IPlayer a, IPlayer b)`, `bool AreFighting(IPlayer a, IPlayer b)`, `IReadOnlyList GetOpponents(IPlayer)`, `string GetPlayerWeapon(IPlayer)`. **Items and catalogues** - `void GivePlayerItem(IPlayer, string itemClass, int amount)`, `IReadOnlyList GetPlayerInventory(IPlayer)`; `string? ResolveItemClass(string key)`, `ItemInfo? GetItemInfo(string key)`, `IReadOnlyList FindItems(string pattern, int max = 10)`; `string? ResolveHorseSoul(string key)`, `SoulInfo? GetSoulInfo(string key)`, `IReadOnlyList FindSouls(string pattern, int max = 10)`; `string? ResolveMesh(string key)`, `IReadOnlyList FindMeshes(string pattern, int max = 10)`. **World entities** - `IReadOnlyList Entities`, `IWorldEntity? GetEntity(int netId)`, `IWorldEntity? CreateEntity(EntityKind kind, string template, float x, float y, float z, float yawDeg, IPlayer? controller = null, int virtualWorld = -1)` (a horse: `template` = the soul key or `""`; a pickup: the item key), `bool DestroyEntity(IWorldEntity)`, `void SetEntityPos(IWorldEntity, float x, float y, float z, float? yawDeg = null)`, `bool SetEntityController(IWorldEntity, IPlayer?)`, `void SetEntityVirtualWorld(IWorldEntity, int)`, `bool MountPlayer(IPlayer, IWorldEntity horse)`, `IWorldEntity? GetPlayerMount(IPlayer)`, `IWorldEntity? CreateProp(string mesh, float x, float y, float z, float yawDeg, float scale = 1, bool rigid = false, int virtualWorld = -1)`, `IWorldEntity? CreateDog(IPlayer who, string soul = "", float x = NaN, float y = NaN, float z = NaN, float yawDeg = NaN)`, `IWorldEntity? GetPlayerDog(IPlayer)`. **NPC actors** - `IWorldEntity? CreateActor(string soul, float x, float y, float z, float yawDeg, string name = "", string clothingPreset = "", string weaponPreset = "", int virtualWorld = 0)`, `bool MoveActor(IWorldEntity, float x, float y, float z, float speed)`, `bool StopActor(IWorldEntity)`, `bool TurnActor(IWorldEntity, float yawDeg)`, `bool SetActorAnim(IWorldEntity, string clip, bool loop = true)`, `bool SetActorHealth(IWorldEntity, float)`, `bool HealActor(IWorldEntity)`, `bool SetActorHostile(IPlayer, IWorldEntity, bool)`; the wounds are `IWorldEntity.Injuries` and `.Bleeding`. **Effects and sounds** - `int SpawnEffect(string effect, float x, float y, float z, float scale = 1, float dirX = 0, float dirY = 0, float dirZ = 1, int virtualWorld = 0)`, `int SpawnDecal(string material, float x, float y, float z, float size = 1, float seconds = 0, float normalX = 0, float normalY = 0, float normalZ = 1, int virtualWorld = 0)`, `int PlaySound(string trigger, float x, float y, float z, float seconds = 0, int virtualWorld = 0)` - each returns how many players got it. **Zones** - `IZone? CreateZone(float x1, float y1, float z1, float x2, float y2, float z2)`, `IZone? CreateCircleZone(float x, float y, float radius, float zMin = 1, float zMax = 0)`, `void DestroyZone(IZone)`, `IReadOnlyList Zones`, `IZone? GetZone(int id)`. **Parties** ([the parties guide](/lua/server/parties/)) - `bool InviteToParty(IPlayer from, IPlayer target, out string reason)` ([`InviteToParty`](/lua/server/functions/invitetoparty/): the reason is the Lua tier's second value), `bool AcceptPartyInvite(IPlayer)`, `bool DeclinePartyInvite(IPlayer)`, `IPlayer? GetPartyInviter(IPlayer, out float secondsLeft)` ([`GetPlayerPartyInvite`](/lua/server/functions/getplayerpartyinvite/)), `bool AddPlayerToParty(IPlayer host, IPlayer)`, `bool RemovePlayerFromParty(IPlayer, string reason = "left")`, `bool SetPartyLeader(IParty, IPlayer)`, `bool DisbandParty(IParty)`, `IParty? GetPlayerParty(IPlayer)`, `IParty? GetParty(int id)`, `IReadOnlyList Parties`, `int PartyMaxSize` (`[party] max_size`; the Lua [`GetPartySize`](/lua/server/functions/getpartysize/) / [`IsPartyFull`](/lua/server/functions/ispartyfull/) are `IParty.Members.Count` against it), `bool ArePartyMembers(IPlayer a, IPlayer b)`, `void SetPartyName(IParty, string)`, `bool SetPartyMemberLabel(IPlayer, string)`, `bool ShowPartyFrames(IPlayer, bool shown)`, `bool SendPartyMessage(IParty, uint colour, string)`. **The world** - `double TimeOfDay` / `void SetTimeOfDay(double seconds)` (game **seconds** since midnight - the Lua [`GetWorldTime`](/lua/server/functions/getworldtime/) divides by 3600), `float TimeRatio` / `SetTimeRatio`, `float Rain` / `SetRain`, `string Weather`, `bool SetWeather(string presetOrProfile, float blendSeconds = -1)`, `IReadOnlyDictionary WeatherPresets`, `float? TerrainHeight(float x, float y)`. **World data** ([the guides](/guides/#the-games-data)) - the navigation mesh: `bool HasNavmesh`, `IReadOnlyList? FindPath(Vector3 from, Vector3 to)` (the corners of the walk, null off the mesh or unreachable), `bool IsReachable(Vector3, Vector3)`, `float? NavmeshHeight(Vector3)`, `Vector3? NearestNavmeshPoint(Vector3)`; the collision geometry: `bool HasCollision`, `RayHit? RayCast(Vector3 from, Vector3 to)` (the nearest surface: `Point`, `Normal`, `Distance`, the owning entity's `EntityClass` / `EntityName` / `EntityId`, the `Material`), `bool IsLineOfSight(Vector3, Vector3)`, `float? GroundZ(Vector3)`. Metres, the level's frame (Z up); the doors the server knows are open let a ray through. **Doors and containers** - `IReadOnlyList Doors`, `bool GetDoorState(string key, out bool open, out bool locked)`, `void SetDoorState(string key, bool open, bool locked)`, `IReadOnlyList Containers`, `IReadOnlyList? GetContainerItems(string key)`, `void SetContainerItems(string key, IEnumerable)`, `IPlayer? GetContainerUser(string key)`. **Bans** - `void Ban(IPlayer, string reason, int seconds = 0)`, `void BanName(string, string reason, int seconds = 0)`, `void BanAddress(...)`, `bool Unban(string nameOrAddress)`, `string? IsBanned(string nameOrAddress)`. ## IPlayer The player as the mode sees them; the readers of the Lua tier are properties here. | Property | Lua | |---|---| | `int Id`, `string Name`, `string Address`, `int Ping`, `string ClientVersion`, `string GameBuildHash` | [`GetPlayerName`](/lua/server/functions/getplayername/), [`GetPlayerIP`](/lua/server/functions/getplayerip/), [`GetPlayerPing`](/lua/server/functions/getplayerping/) | | `bool IsInWorld`, `Vector3 Position`, `float YawDeg`, `Vector3 Velocity`, `uint ReportsReceived` | [`IsPlayerInWorld`](/lua/server/functions/isplayerinworld/), [`GetPlayerPos`](/lua/server/functions/getplayerpos/) ... | | `bool IsRegistered`, `bool IsLoggedIn`, `bool IsAdmin` | [`IsPlayerRegistered`](/lua/server/functions/isplayerregistered/), [`IsPlayerLoggedIn`](/lua/server/functions/isplayerloggedin/), [`IsPlayerAdmin`](/lua/server/functions/isplayeradmin/) | | `float Health`, `float MaxHealth`, `bool IsDead`, `float Stamina`, `float MaxStamina`, `int Injuries` (a bit per part: 1 head, 2 torso, 4 left arm, 8 right arm, 16 left leg, 32 right leg), `float Bleeding`, `float Healing` | [`GetPlayerHealth`](/lua/server/functions/getplayerhealth/) ..., [`GetPlayerInjuries`](/lua/server/functions/getplayerinjuries/), [`GetPlayerBleeding`](/lua/server/functions/getplayerbleeding/) | | `float Alcohol`, `bool IsDrunk` | [`GetPlayerAlcohol`](/lua/server/functions/getplayeralcohol/), [`IsPlayerDrunk`](/lua/server/functions/isplayerdrunk/) | | `IReadOnlyList Equipment`, `IReadOnlyList Inventory`, `int AuditViolations` | [`GetPlayerEquipment`](/lua/server/functions/getplayerequipment/), [`GetPlayerInventory`](/lua/server/functions/getplayerinventory/), [`GetPlayerAuditViolations`](/lua/server/functions/getplayerauditviolations/) | | `IReadOnlyList Buffs` | [`GetPlayerBuffs`](/lua/server/functions/getplayerbuffs/) | | `IDictionary Data`, `IReadOnlyDictionary State` | [`SetPlayerData`](/lua/server/functions/setplayerdata/), [`GetPlayerStates`](/lua/server/functions/getplayerstates/) | | `int Team`, `uint Colour`, `string Nameplate` | [`GetPlayerTeam`](/lua/server/functions/getplayerteam/), [`GetPlayerColour`](/lua/server/functions/getplayercolour/), [`GetPlayerNameplate`](/lua/server/functions/getplayernameplate/) | | `IParty? Party`, `string PartyLabel` | [`GetPlayerParty`](/lua/server/functions/getplayerparty/), [`GetPartyMemberLabel`](/lua/server/functions/getpartymemberlabel/) | | `IReadOnlyList Zones` | [`GetPlayerZones`](/lua/server/functions/getplayerzones/) | | `int VirtualWorld`, `bool IsControllable` | [`GetPlayerVirtualWorld`](/lua/server/functions/getplayervirtualworld/), [`IsPlayerControllable`](/lua/server/functions/isplayercontrollable/) | ## IWorldEntity `int NetId`, `EntityKind Kind` (`Player`, `Horse`, `Item`, `Npc`, `Prop`, `Dog`), `string Template` (the item class, the soul, the mesh path), `Vector3 Position`, `float YawDeg`, `Vector3 Velocity`, `IPlayer? Controller`, `IPlayer? Rider`, `int VirtualWorld`, `uint CreatedAt`, `uint LastReportAt` ([`GetEntityIdleTime`](/lua/server/functions/getentityidletime/) is `TimeMs - LastReportAt`), `IDictionary Data`, `IReadOnlyDictionary State`, `float Scale`, `bool Rigid` (props), `string Name`, `bool IsMoving`, `float Health`, `bool IsDead`, `int Injuries` (a bit per part, as `IPlayer.Injuries`), `float Bleeding` (actors). ## IZone, IHudText and IParty `IZone`: `int Id`, `bool IsCircle`, `MinX .. MaxZ`, `CentreX`, `CentreY`, `Radius`, `bool Contains(float x, float y, float z)`, `IReadOnlyList Players`, `IDictionary Data` - [`GetZoneInfo`](/lua/server/functions/getzoneinfo/), [`GetZonePlayers`](/lua/server/functions/getzoneplayers/), [`SetZoneData`](/lua/server/functions/setzonedata/). `IHudText`: `int Id`, `float X`, `float Y`, `string Text`, `uint Colour`, `float Scale`, `HudAlign Align` (settable - a change is re-sent), `IReadOnlyList ShownTo`, `void Show(IPlayer)`, `void Hide(IPlayer)`, `void ShowForAll()`, `void HideForAll()`, `bool IsShownTo(IPlayer)` - [`CreateHudText`](/lua/server/functions/createhudtext/) and its family. `IParty`: `int Id` (never reused while the server runs), `IPlayer Leader`, `IReadOnlyList Members` (in join order), `string Name`, `IDictionary Data` (the mode's private bag, gone with the party) - [`GetPartyLeader`](/lua/server/functions/getpartyleader/), [`GetPartyMembers`](/lua/server/functions/getpartymembers/), [`GetPartyName`](/lua/server/functions/getpartyname/), [`SetPartyData`](/lua/server/functions/setpartydata/). ## Records and enums | Type | | |---|---| | `SpawnPoint(float X, float Y, float Z, float YawDeg, string ClothingPreset = "", string WeaponPreset = "", string Appearance = "")` | where and as what a player spawns | | `SavedPlayer(string Name, int Visits, double PlayTimeSeconds, DateTime LastSeenUtc, bool HasPosition, float X, float Y, float Z, float YawDeg)` | [`GetSavedPlayer`](/lua/server/functions/getsavedplayer/) | | `ContainerItem(string ItemClass, int Amount, int Health = 100)` | one stack of a container or an inventory | | `HitInfo(float Damage, float Raw, int Zone, BodyPart BodyPart, int DamageType, string WeaponClass, string WeaponName, float Defense, bool Exhausted)` with `DamageTypeName` (`"stab"`, `"slash"`, `"smash"`) | everything the model resolved for a hit | | `ItemInfo(int Id, string Name, string ItemClass, string Category, string DisplayName, float Weight, float Price)` | [`GetItemInfo`](/lua/server/functions/getiteminfo/) | | `SoulInfo(int Id, string Name, string SoulGuid, string Archetype)` | [`GetSoulInfo`](/lua/server/functions/getsoulinfo/) | | `BuffInfo(int Id, string Name, string BuffGuid, string Class, string DisplayName, float Duration)` | [`GetBuffInfo`](/lua/server/functions/getbuffinfo/) | | `MeshInfo(int Id, string Path)` with `Name` | [`FindMeshes`](/lua/server/functions/findmeshes/) | | `RayHit(Vector3 Point, Vector3 Normal, float Distance, string EntityClass, string EntityName, uint EntityId, byte Material)` | [`RayCast`](/lua/server/functions/raycast/) | | `enum BodyPart { Unknown, Head, Torso, ArmLeft, ArmRight, LegLeft, LegRight }` | the body parts `0` .. `6` | | `enum EntityKind : byte { Player, Horse, Item, Npc, Prop, Dog }` | [entity kinds](/lua/server/constants/#entity-kinds) | | `enum HudAlign : byte { Left, Centre, Right }`, `enum GameTextStyle : byte { Centre, Top, Lower }` | [constants](/lua/server/constants/) |