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:
nativeis KCD:MP’s own piece, the default;hiddendraws nothing; on a server with a web interface,defaultis 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).
The components
Section titled “The components”| 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 ...).
The drawers
Section titled “The drawers”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. Choosingnativeagain shows it as it was. Whathiddendoes 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 ofSetScoreboardColumns.- 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.
The options
Section titled “The options”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 |
scoreboard
Section titled “scoreboard”| 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 |
nameplates
Section titled “nameplates”| 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 |
compass
Section titled “compass”| 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.
What hidden does
Section titled “What hidden does”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 withSendServerEvent), 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 fromShowInfoText,ShowNotification,ShowGameLogandShowObjectiveEventare 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 asShowPartyFrames:hiddenisShowPartyFrames(pid, false),nativeistrue, 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 withPlayPlayerEmotestill plays.hudtext-GameTextlines 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.
Who sees what
Section titled “Who sees what”What a player sees is their own setting if the mode made one for them, else the setting made for everyone, else the default.
SetInterfaceForAllandSetInterfaceOptionsForAllhold 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,
nativeoverrides ahiddenmade 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
partystarts from[party] framesin 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 inOnPlayerConnect, 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.
GetPlayerInterfacereturns two words for a component, what was asked for and what draws it now; withnativeandhiddenthey are always the same, and with a web drawer the second isnativewhile 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 |
An example in Lua
Section titled “An example in Lua”-- 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 trueendA 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 chatSetPlayerInterfaceOptions(pid, "chat", { lines = "default", width = "default" }) -- and back to what everyone hasThe same in C#
Section titled “The same in C#”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; }}Things to know
Section titled “Things to know”- A hidden
chatcannot 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
OnGameModeInitandOnPlayerConnect. - The party frames have one switch with two names:
[party] frames/ShowPartyFramesand thepartycomponent are the same thing.
