Server API (C#)
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). The behaviour of each call is documented on the
Lua pages; 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
Section titled “The project”The server folder (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 modesThe 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.
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net10.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> <ItemGroup> <Reference Include="KcdMp.Api"> <HintPath>../KcdMp-server/sdk/KcdMp.Api.dll</HintPath> <Private>false</Private> </Reference> </ItemGroup></Project>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:
dotnet publish -c Release -o ../KcdMp-server/gamemodes/arena[gamemode]script = "gamemodes/arena/Arena.dll" # or "gamemodes/arena/Arena.dll:MyModes.ArenaMode" when the assembly has several IGameMode typesKcdMp.Server --gamemode gamemodes/arena/Arena.dllThe 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.
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
Section titled “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:
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/IWorldEntityvalues you need (Id,Name,Position…) before the task starts; the objects and everyIServerApimember other thanPoststay on the simulation thread -Logincluded. - Come back through
Postwith the id, and check the player is still the one you meant:GetPlayer(id)answersnullonce 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.
Postfrom 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
Section titled “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 - the mode’s name |
void OnInit(IServerApi api) |
OnGameModeInit - keep api |
void OnShutdown() |
OnGameModeExit |
void OnTick(float dtSeconds) |
OnTick |
void OnPlayerConnect(IPlayer) |
OnPlayerConnect |
SpawnPoint? OnPlayerRequestSpawn(IPlayer) |
OnPlayerRequestSpawn - return the point, or null to hold the player for SpawnPlayer |
void OnPlayerSpawn(IPlayer) |
OnPlayerSpawn |
bool OnPlayerText(IPlayer, string text) |
OnPlayerText - false swallows the line |
bool OnPlayerCommand(IPlayer, string command, string args) |
OnPlayerCommandText - true = handled |
void OnPlayerDisconnect(IPlayer, string reason) |
OnPlayerDisconnect |
void OnPlayerLogin(IPlayer) |
OnPlayerLogin |
void OnPlayerMount(IPlayer, IWorldEntity horse), OnPlayerDismount |
OnPlayerMount, OnPlayerDismount |
bool OnPlayerPickup(IPlayer, IWorldEntity item) |
OnPlayerPickup - false refuses |
void OnPlayerDrop(IPlayer, IWorldEntity item) |
OnPlayerDrop |
bool OnPlayerUseItem(IPlayer, string itemClass, float health, string buffGuid) |
OnPlayerUseItem - false cancels |
void OnPlayerDrunk(IPlayer, bool drunk) |
OnPlayerDrunk |
float OnPlayerDamage(IPlayer victim, IPlayer? attacker, float damage, int zone, int bodyPart) |
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 |
void OnPlayerDeath(IPlayer victim, IPlayer? attacker) |
OnPlayerDeath |
void OnFightStart(IPlayer a, IPlayer b, string reason), OnFightEnd |
OnFightStart, OnFightEnd |
void OnActorArrive(IWorldEntity actor) |
OnActorArrive |
float OnActorHit(IWorldEntity actor, IPlayer attacker, in HitInfo hit) |
OnActorDamage - return the damage, 0 cancels |
void OnActorDeath(IWorldEntity actor, IPlayer? attacker) |
OnActorDeath |
void OnActorInjury(IWorldEntity actor, IPlayer? attacker, BodyPart part) |
OnActorInjury |
float OnActorAttack(IWorldEntity actor, IPlayer victim, in HitInfo hit) |
OnActorAttack - the default hands it to OnPlayerHit without an attacker |
void OnPlayerEnterZone(IPlayer, IZone), OnPlayerLeaveZone |
OnPlayerEnterZone, OnPlayerLeaveZone |
void OnClientEvent(IPlayer, string name, string payload) |
OnClientEvent |
bool OnPlayerUseDoor(IPlayer, string key, bool open, bool locked) |
OnPlayerUseDoor - false refuses |
bool OnPlayerOpenContainer(IPlayer, string key), void OnPlayerCloseContainer(IPlayer, string key) |
OnPlayerOpenContainer, OnPlayerCloseContainer |
bool OnPlayerAuditViolation(IPlayer, string itemClass, int amount, int allowed) |
OnPlayerAuditViolation - false vouches |
bool OnPartyInvite(IPlayer from, IPlayer target) |
OnPartyInvite - false refuses the invitation |
void OnPartyInviteResponse(IPlayer from, IPlayer target, string answer) |
OnPartyInviteResponse |
void OnPartyCreate(IParty, IPlayer leader), void OnPartyDisband(IParty, string reason) |
OnPartyCreate, OnPartyDisband |
void OnPlayerJoinParty(IParty, IPlayer, string reason), OnPlayerLeaveParty |
OnPlayerJoinParty, OnPlayerLeaveParty |
void OnPartyLeaderChange(IParty, IPlayer leader, IPlayer previous) |
OnPartyLeaderChange |
IServerApi - the functions
Section titled “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). There is no timer API: a plugin counts ticks
in OnTick or keeps its own TimeMs deadlines (SetTimer is the Lua tier’s own).
Players - IReadOnlyList<IPlayer> Players, IPlayer? GetPlayer(int id), void SpawnPlayer(IPlayer, SpawnPoint at)
(SetSpawnInfo + 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<string> Admins.
Chat, GameText and HUD - void SendClientMessage(IPlayer, uint colour, string), void Broadcast(uint colour, string)
(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<IHudText> HudTexts, IHudText? GetHudText(int id).
Script events and state bags - void SendClientEvent(IPlayer, string name, string payload), void SendClientEventToAll(...);
IReadOnlyDictionary<string, string> 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<string, object?>, 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<BuffInfo> FindBuffs(string pattern, int max = 10),
IReadOnlyList<string> ClaimedBuffClasses, void ClaimBuffClasses(IEnumerable<string>); 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<IPlayer> GetOpponents(IPlayer), string GetPlayerWeapon(IPlayer).
Items and catalogues - void GivePlayerItem(IPlayer, string itemClass, int amount), IReadOnlyList<ContainerItem> GetPlayerInventory(IPlayer);
string? ResolveItemClass(string key), ItemInfo? GetItemInfo(string key), IReadOnlyList<ItemInfo> FindItems(string pattern, int max = 10);
string? ResolveHorseSoul(string key), SoulInfo? GetSoulInfo(string key), IReadOnlyList<SoulInfo> FindSouls(string pattern, int max = 10);
string? ResolveMesh(string key), IReadOnlyList<MeshInfo> FindMeshes(string pattern, int max = 10).
World entities - IReadOnlyList<IWorldEntity> 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<IZone> Zones, IZone? GetZone(int id).
Parties (the parties guide) - bool InviteToParty(IPlayer from, IPlayer target, out string reason)
(InviteToParty: the reason is the Lua tier’s second value), bool AcceptPartyInvite(IPlayer),
bool DeclinePartyInvite(IPlayer), IPlayer? GetPartyInviter(IPlayer, out float secondsLeft) (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<IParty> Parties, int PartyMaxSize
([party] max_size; the Lua GetPartySize / 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 divides by 3600), float TimeRatio / SetTimeRatio, float Rain / SetRain,
string Weather, bool SetWeather(string presetOrProfile, float blendSeconds = -1), IReadOnlyDictionary<string, string> WeatherPresets,
float? TerrainHeight(float x, float y).
World data (the guides) - the navigation mesh: bool HasNavmesh, IReadOnlyList<Vector3>? 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<string> Doors, bool GetDoorState(string key, out bool open, out bool locked),
void SetDoorState(string key, bool open, bool locked), IReadOnlyList<string> Containers, IReadOnlyList<ContainerItem>? GetContainerItems(string key),
void SetContainerItems(string key, IEnumerable<ContainerItem>), 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
Section titled “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, GetPlayerIP, GetPlayerPing |
bool IsInWorld, Vector3 Position, float YawDeg, Vector3 Velocity, uint ReportsReceived |
IsPlayerInWorld, GetPlayerPos … |
bool IsRegistered, bool IsLoggedIn, bool IsAdmin |
IsPlayerRegistered, IsPlayerLoggedIn, 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 …, GetPlayerInjuries, GetPlayerBleeding |
float Alcohol, bool IsDrunk |
GetPlayerAlcohol, IsPlayerDrunk |
IReadOnlyList<string> Equipment, IReadOnlyList<ContainerItem> Inventory, int AuditViolations |
GetPlayerEquipment, GetPlayerInventory, GetPlayerAuditViolations |
IReadOnlyList<string> Buffs |
GetPlayerBuffs |
IDictionary<string, object?> Data, IReadOnlyDictionary<string, string> State |
SetPlayerData, GetPlayerStates |
int Team, uint Colour, string Nameplate |
GetPlayerTeam, GetPlayerColour, GetPlayerNameplate |
IParty? Party, string PartyLabel |
GetPlayerParty, GetPartyMemberLabel |
IReadOnlyList<IZone> Zones |
GetPlayerZones |
int VirtualWorld, bool IsControllable |
GetPlayerVirtualWorld, IsPlayerControllable |
IWorldEntity
Section titled “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 is TimeMs - LastReportAt),
IDictionary<string, object?> Data, IReadOnlyDictionary<string, string> 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
Section titled “IZone, IHudText and IParty”IZone: int Id, bool IsCircle, MinX .. MaxZ, CentreX, CentreY, Radius, bool Contains(float x, float y, float z),
IReadOnlyList<IPlayer> Players, IDictionary<string, object?> Data - GetZoneInfo,
GetZonePlayers, SetZoneData.
IHudText: int Id, float X, float Y, string Text, uint Colour, float Scale, HudAlign Align (settable - a change is
re-sent), IReadOnlyList<IPlayer> ShownTo, void Show(IPlayer), void Hide(IPlayer), void ShowForAll(), void HideForAll(),
bool IsShownTo(IPlayer) - CreateHudText and its family.
IParty: int Id (never reused while the server runs), IPlayer Leader, IReadOnlyList<IPlayer> Members (in join order), string Name,
IDictionary<string, object?> Data (the mode’s private bag, gone with the party) - GetPartyLeader,
GetPartyMembers, GetPartyName,
SetPartyData.
Records and enums
Section titled “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 |
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 |
SoulInfo(int Id, string Name, string SoulGuid, string Archetype) |
GetSoulInfo |
BuffInfo(int Id, string Name, string BuffGuid, string Class, string DisplayName, float Duration) |
GetBuffInfo |
MeshInfo(int Id, string Path) with Name |
FindMeshes |
RayHit(Vector3 Point, Vector3 Normal, float Distance, string EntityClass, string EntityName, uint EntityId, byte Material) |
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 |
enum HudAlign : byte { Left, Centre, Right }, enum GameTextStyle : byte { Centre, Top, Lower } |
constants |
