Skip to content

Change the interface

Everything KCD:MP draws on a player’s screen - the chat at the foot of it, the list of players that Tab shows, the names over heads, the party frames, the notices a mode sends - is a component with a name. A game mode can change two things about a component, for one player or for everyone:

  • who draws it - the drawer: native is KCD:MP’s own piece, the default; hidden draws nothing; on a server with a web interface, default is KCD:MP’s web version of the piece and a frame’s name hands it to your own page;
  • how it is laid out - the component’s options: the chat’s corner and number of lines, whether Tab holds or toggles the scoreboard, where the party frames sit, the colour of the name plates, which of the game’s own HUD pieces show, and a few more.

A server that calls none of these functions looks exactly as it always did. The calls are SetPlayerInterface and SetInterfaceForAll for the drawer, SetPlayerInterfaceOptions and SetInterfaceOptionsForAll for the options, and GetPlayerInterface to read a drawer back. The C# names are the same, on IServerApi (below).

Component What it is May be hidden Web version
chat the lines at the foot of the screen and the box a player types in yes yes
scoreboard the list of players that Tab shows yes yes
notices the toasts a mode sends: ShowInfoText, ShowNotification, ShowGameLog and ShowObjectiveEvent yes yes
party the party frames down the side of the screen yes yes
emotes the emote wheel (G) and the point (T) yes yes
hudtext GameText lines and HUD texts yes yes
sleep the line that follows a sleep in a bed yes yes
nameplates the names, health bars, injury marks and chat bubbles over players’ heads yes no
compass the blips of markers and party members on the game’s compass yes no
death the box a player who has died sees no yes
dialog the message boxes a player must answer or read - a kick, a refused join, a question no yes
loading the loading screen no yes, chosen in ui.toml
shop the vendor’s list panel no yes
voice the marks that show who is talking no yes
hud the game’s own bars and prompts no no

The six that may not be hidden are always drawn as they are; asking for hidden there is refused with the reason the shop cannot be hidden (or the death ..., the hud ...).

There are two everywhere, written in any case:

  • native - KCD:MP draws the component. It is what every component starts as.
  • hidden - nothing draws it and its keys stop working (two exceptions: a hidden scoreboard still takes Tab, which the game never gets during a session, and the party invitation’s accept and decline keys stay). What the piece does behind the scenes carries on: a hidden chat still collects the lines that arrive, a hidden scoreboard still knows who is on the server, hidden party frames are still a party. Choosing native again shows it as it was. What hidden does to each component is below.

To give a drawer back, say native: there is no word for “as everyone else has it”.

A server with a web interface has two more for every component with a web version (the table above):

  • default - KCD:MP’s own web version of the component, styled by the interface’s theme: the web chat has channel tabs and suggests commands as the player types, the web scoreboard shows the columns of SetScoreboardColumns.
  • the name of a frame of the interface - your own page draws the component: it receives the component’s lines, roster or texts and its keys, and calls its actions (Make a web interface).

A player whose game cannot draw the web drawer - the web layer is off or failed, the page is not ready yet - gets the native piece meanwhile, and the web one the moment it can draw. The loading screen’s drawer is chosen in the interface’s ui.toml alone, since it shows before anything the mode sends can arrive.

An option is a text under a key; the server checks every one against the range below and refuses a call with a wrong word in it - nothing changes at all, not even the good keys of the same call. Numbers may be written with a decimal point (kept to three places); a colour is a text of six hex digits, "F6E890" or "#F6E890", not a number; "default" (or an empty text) takes a key back to its default. The other components have no options.

Sizes and margins are pixels on a screen 1080 pixels tall. On a taller or shorter screen the piece grows or shrinks with the height, and a wider screen only gives it more room across: margin_x = 12 is the same fraction of the screen’s height for every player (a 2560x1080 screen and a 1920x1080 screen both get 12 pixels). The scaling stops below 432 and above 2160 rows. anchor picks the corner or edge the piece is attached to; the margins count from the edges it touches. top and bottom are centred across, left and right centred down and center both - a centred direction has no margin.

Option Values Default What it does
anchor top-left, top, top-right, left, center, right, bottom-left, bottom, bottom-right bottom-left where the chat is attached
margin_x 0 to 960 12 the gap to the left or right edge
margin_y 0 to 540 64 the gap to the top or bottom edge
width 300 to 1200 560 how wide a line is before it wraps
lines a whole number, 4 to 20 10 how many lines show at once; Page Up and Page Down scroll back by half of them
backing_alpha 0 to 100 45 how dark the frame behind the lines is while the box is closed (0 none, 100 solid)
backing_alpha_open 0 to 100 85 the same while the box is open or the chat is scrolled back
font_scale 0.6 to 1.6 1 the size of the text, times the usual size
Option Values Default What it does
mode hold, toggle hold hold shows the board while Tab is held; toggle opens it with Tab and closes it with Tab or Esc (a menu or the loading screen closes it too)
title text, at most 64 characters empty replaces the KCD:MP - <server name> part of the title line; the number of players and the page stay after it. Empty keeps the usual title. A longer text is refused, not cut
Option Values Default What it does
anchor top-left, left, bottom-left, top-right, right, bottom-right top-left which edge the column of frames sits against
margin_x 0 to 960 24 the gap to the left or right edge
margin_y 0 to 1000 173 the gap to the top or bottom edge - 173 is where the column has always started, a sixth of the way down
show_stamina true, false true the stamina bar of each member
Option Values Default What it does
color six hex digits F6E890 the colour of a player’s name when the mode gave them none (SetPlayerColor)
font_scale 0.5 to 2 1 the size of the names, the chat bubbles and the trade prompt, times the usual size
bar_width 40 to 240 120 how wide the health bar is
Option Values Default What it does
span 30 to 180 90 the degrees of the horizon the compass strip covers - a smaller number spreads the blips further apart

One switch for each of the game’s own HUD pieces, 1 shown (the default) or 0 hidden (true, false, on, off, yes and no are read the same): compass, stats (the health and stamina bars), subtitles, hints (the “press a key” prompts), cursor (the crosshair dot and the item card), buffs, info_text, game_log, common_event, fancy_event and tutorial_message. SetPlayerHudVisible switches one piece for one player and says what each piece is; the last five are where the mode’s own toasts show, so hiding them hides those too.

  • chat - the lines and the frame are not drawn. Enter does not open the chat box - the key goes to the game - and Page Up and Page Down are left alone, so a player whose chat is hidden cannot type into it. Use it for a mode that draws its own chat from a client script (which sends what the player types to the server with SendServerEvent), or for a stretch where nobody should talk; a chat of your own on a web page is a frame drawer instead. Lines that arrive meanwhile are kept, so showing the chat again shows them; a box that is open when the chat is hidden closes.
  • scoreboard - the board is never shown and Tab does nothing (KCD:MP still takes the key, the game does not get it).
  • notices - the game’s own toasts from ShowInfoText, ShowNotification, ShowGameLog and ShowObjectiveEvent are dropped. Tutorial boxes (ShowTutorial, and with them the party invitation and the death box) and the calls that hide or clear a notice still work.
  • party - the party frames are not drawn. This is the same switch as ShowPartyFrames: hidden is ShowPartyFrames(pid, false), native is true, and each reads the other. The invitation box and its Y and N keys stay.
  • emotes - the wheel never opens and G and T go back to the game. The wheel’s list on the server is untouched, and an emote a mode plays with PlayPlayerEmote still plays.
  • hudtext - GameText lines and HUD texts are not drawn.
  • sleep - the line after a sleep in a bed is not drawn; the sleep itself works as before.
  • nameplates - names, health bars, injury marks and chat bubbles are not drawn. The “[E] Trade” prompt over a vendor stays.
  • compass - the blips are not drawn; the compass itself is the game’s.

What a player sees is their own setting if the mode made one for them, else the setting made for everyone, else the default.

  • SetInterfaceForAll and SetInterfaceOptionsForAll hold for whoever joins later as well, and they replace what the mode set for one player - the drawer of that component, or those keys - so a call for everyone is a clean start.
  • For one player, native overrides a hidden made for everyone. There is no “as everyone has it” for a drawer; for an option, "default" is exactly that: it takes the key back to what everyone else has (the setting made for everyone, else the option’s own default). A "default" in the call for everyone takes it back to the option’s own default.
  • The party starts from [party] frames in server.toml; SetInterfaceForAll("party", ...) changes what a newcomer gets from then on.
  • A reload of the mode puts everything back - every drawer native, every option at its default. Set the interface where the mode is made again: for everyone in OnGameModeInit, for one player in OnPlayerConnect, and both run again after a reload.
  • A player who leaves and comes back starts again from the settings made for everyone; a mode that remembers a player’s own layout keeps it in its database and sets it in OnPlayerConnect.
  • Several calls in one tick reach the player as one change, and a call that changes nothing sends nothing. A player who joins gets the settings made for everyone before their game has finished loading.
  • GetPlayerInterface returns two words for a component, what was asked for and what draws it now; with native and hidden they are always the same, and with a web drawer the second is native while the player’s game cannot draw the web one.

A refused call answers false and the reason in words a person can read, ready for a log or a chat line:

Reason When
not connected the player is not on the server
unknown component 'x' no component of that name
unknown option 'x' for the chat no option of that name on that component
the shop cannot be hidden the component has no hidden drawer
another reason for a drawer a word that is not a drawer of that component - the reason says what is wrong with it
the compass has no web version yet, no interface is loaded, no such frame in the interface a web drawer the component or the server cannot take
lines must be a whole number from 4 to 20 a number out of its range or not a whole number where one is needed (width must be a number from 300 to 1200)
anchor must be one of top-left, top, ... a word that is not on the option’s list
show_stamina must be true or false a flag that is neither
color must be a colour of six hex digits, like F6E890 a colour that is not six hex digits
title must be at most 64 characters a title that is too long
-- the chat in the top-right corner with twelve lines, a scoreboard that Tab opens and closes,
-- and a /plates command that switches the name plates off and on for whoever types it
function OnGameModeInit()
-- for everyone, and for whoever joins later; a word the server cannot take answers false and the reason
local ok, why = SetInterfaceOptionsForAll("chat", { anchor = "top-right", margin_x = 16, margin_y = 24, lines = 12 })
if not ok then Log("chat layout refused: " .. why) end
SetInterfaceOptionsForAll("scoreboard", { mode = "toggle", title = "The Kuttenberg Inn" })
end
function OnPlayerCommandText(pid, cmd, args)
if cmd ~= "plates" then return false end
local _, drawer = GetPlayerInterface(pid, "nameplates")
local hide = drawer ~= "hidden"
SetPlayerInterface(pid, "nameplates", hide and "hidden" or "native")
SendClientMessage(pid, COLOR_SERVER, "Name plates " .. (hide and "off" or "on") .. ".")
return true
end

A player’s own layout, for one player, is the same call with a pid:

SetPlayerInterfaceOptions(pid, "chat", { lines = 6, width = 420, font_scale = 0.8 }) -- a smaller chat
SetPlayerInterfaceOptions(pid, "chat", { lines = "default", width = "default" }) -- and back to what everyone has

The members are on IServerApi - SetPlayerInterface, GetPlayerInterface, SetInterfaceForAll, SetPlayerInterfaceOptions and SetInterfaceOptionsForAll; the setters return true or false with the reason in an out string?, and the options are a IReadOnlyDictionary<string, string> - every value is text, as in the tables above. GetPlayerInterface returns (string Requested, string Effective)?, null for a player who is not connected or a component that does not exist.

using KcdMp.Api;
public sealed class InterfaceMode : IGameMode // the members every mode needs (Name, OnShutdown ...) are left out here
{
private IServerApi _api = null!;
public void OnInit(IServerApi api)
{
_api = api;
// for everyone, and for whoever joins later: the chat top-right with twelve lines, a scoreboard Tab opens and closes
var chat = new Dictionary<string, string> { ["anchor"] = "top-right", ["margin_x"] = "16", ["margin_y"] = "24", ["lines"] = "12" };
if (!api.SetInterfaceOptionsForAll("chat", chat, out var why))
api.Log($"chat layout refused: {why}");
api.SetInterfaceOptionsForAll("scoreboard", new Dictionary<string, string> { ["mode"] = "toggle" }, out _);
}
public bool OnPlayerCommand(IPlayer player, string command, string args)
{
if (command != "plates") return false;
// /plates: the name plates off and on for the player who types it
var hidden = _api.GetPlayerInterface(player, "nameplates")?.Effective == "hidden";
_api.SetPlayerInterface(player, "nameplates", hidden ? "native" : "hidden", out _);
return true;
}
}
  • A hidden chat cannot be typed into: Enter goes to the game. Hide it only for a mode that has its own way to take text.
  • Every word is checked on the server before anything is sent, so a refused call is safe to make from a command a player can type - log the reason, or tell the player.
  • The interface is the mode’s: a reload resets it, so it belongs in OnGameModeInit and OnPlayerConnect.
  • The party frames have one switch with two names: [party] frames / ShowPartyFrames and the party component are the same thing.