Parties
Players group up, the server keeps the group and shows every member the others’ health and stamina in party frames on the left of the screen, and the game mode decides everything else. Every callback and function below has its own page in the index.
What a party is
Section titled “What a party is”A small group of players - five by default - with a leader. It comes into being when the first invitation is
accepted and ends when it falls to one member: there is no party of one. A player is in at most one party and has at most
one invitation pending. Any member may invite; whether that stays so is the mode’s rule (see OnPartyInvite).
The server does these things by itself:
- No friendly fire. A hit between two members is refused before it ever reaches
OnPlayerDamage, the way a hit between teammates is, and starts no fight.[party] friendly_fire = truegives it back. - The frames. Each member sees the others as a column of frames under the game’s HUD: name, a label the mode may set, health and stamina bars, injury and bleeding marks, the leader’s name in gold, greyed while dead or loading. The bars stay live at any distance - a member who walks off, or is in another virtual world, is still on the frame. Nothing on the frame says where a member is: no distance, no direction.
- The invitation. The invited player gets a toast with a countdown and two keys to accept or decline (Y and N;
KcdMp_party_keys <accept> <decline>in their console changes them); the mode’s own/acceptcommand answers the same invitation. An invitation times out after[party] invite_timeoutseconds and is withdrawn when the party fills or disbands. - The leader. When the leader leaves, the next member in join order leads (
[party] leader_leaves = "disband"ends the party instead). A disconnect leaves the party at once; nothing is remembered across sessions.
Everything a player types is the mode’s. The server ships no party commands: /invite, /accept, /decline, /leave,
/kick, /leader, /p are a dozen lines of the mode (the example below), which is where every rule lives - who may
invite whom, a level gate, leader-only invitations, what the party chat looks like. The shipped freeroam mode carries these
commands as its example; a server owner keeps, changes or drops them.
A client script sees the party too - KcdMp.party and the hook KcdMp.on_party_change in
the client API - so a mode that wants frames of its own hides the server’s with
ShowPartyFrames(pid, false) and draws from that.
Parties and teams are separate things: a party is a group with a leader, frames and invitations; a team is a number that turns friendly fire off. A mode may use both.
Callbacks
Section titled “Callbacks”| Callback | When |
|---|---|
OnPartyInvite(from, target) |
before the invitation is sent; return false refuses it - the place for leader-only invitations or any other rule |
OnPartyInviteResponse(from, target, answer) |
"accepted", "declined", "timeout", or "cancelled" (the party filled or disbanded first, or a side disconnected) |
OnPartyCreate(party, leader) |
the group exists; fires before the first two joins |
OnPlayerJoinParty(party, pid, reason) |
every member arrives through here, the leader too: "create", "invite" or "mode" |
OnPlayerLeaveParty(party, pid, reason) |
"left", "kicked", "disconnect" or "disband" |
OnPartyLeaderChange(party, pid, previous) |
a hand-over, by the mode or by the leader leaving |
OnPartyDisband(party, reason) |
"empty" (down to one member), "mode" or "leader"; fires last, after every leave |
Functions
Section titled “Functions”A party is a number; ids are never reused while the server runs, so an id a mode kept never points at a newer group.
| Function | Returns | What it does |
|---|---|---|
InviteToParty(from, target) |
true, or false, reason |
sends the invitation; reason is "not connected", "self", "in a party", "full", "pending" or "refused" (OnPartyInvite said no) |
AcceptPartyInvite(pid) |
boolean |
the mode’s /accept; joins the inviter’s party, creating it when they have none |
DeclinePartyInvite(pid) |
boolean |
the mode’s /decline |
GetPlayerPartyInvite(pid) |
from, seconds or nil |
the pending invitation |
AddPlayerToParty(host, pid) |
boolean |
no invitation: puts pid into host’s party, making it when there is none - a queue, a raid maker |
RemovePlayerFromParty(pid, reason) |
boolean |
"left" (the default) or "kicked" |
SetPartyLeader(party, pid) |
boolean |
pid must be a member |
DisbandParty(party) |
boolean |
everyone leaves with "disband" |
GetPlayerParty(pid) |
number or nil |
|
GetPartyLeader(party) |
number or nil |
|
GetPartyMembers(party) |
table of pids |
in join order |
GetPartySize(party) / IsPartyFull(party) |
number / boolean |
against [party] max_size |
ArePartyMembers(a, b) |
boolean |
for the mode’s own rules - loot, chat, a duel refused between friends |
GetParties() |
table of party ids |
|
SetPartyName(party, text) / GetPartyName(party) |
the title over the frames; "" none |
|
SetPartyMemberLabel(pid, text) / GetPartyMemberLabel(pid) |
boolean / string |
a line under the name in the frame - a role, a score; "" none |
ShowPartyFrames(pid, shown) |
boolean |
per player; [party] frames is the default for everyone |
SendPartyMessage(party, colour, text) |
boolean |
one chat line to every member - the /p command is the mode’s |
SetPartyData(party, key, value) / GetPartyData(party, key) |
the mode’s private storage on a party, gone with it |
The C# tier has the same names on IServerApi and IGameMode, with IParty (Id, Leader, Members, Name, Data) and IPlayer.Party / IPlayer.PartyLabel.
Configuration
Section titled “Configuration”[party] in server.toml (the reference page):
| Key | Default | Meaning |
|---|---|---|
max_size |
5 |
members per party |
invite_timeout |
30 |
seconds until a pending invitation times out |
frames |
true |
the frames are on for everyone until the mode says otherwise per player |
leader_leaves |
"next" |
"next" hands the lead to the next member in join order; "disband" ends the party |
friendly_fire |
false |
true lets members hurt each other |
A mode’s commands
Section titled “A mode’s commands”What the shipped freeroam mode carries - /invite <name>, /accept, /decline, /leave, /kick <name>,
/leader <name>, /disband and /p <text> - with GetPlayerId for the name and the
false, reason of InviteToParty for the refusal:
function OnPlayerCommandText(pid, cmd, args) if cmd == "invite" then local target = GetPlayerId(args) if not target then return SendClientMessage(pid, COLOUR_RED, "No such player.") end local ok, reason = InviteToParty(pid, target) if ok then SendClientMessage(pid, COLOUR_SERVER, "Invited " .. GetPlayerName(target) .. ".") else SendClientMessage(pid, COLOUR_RED, "Cannot invite: " .. reason .. ".") end return true elseif cmd == "accept" then if not AcceptPartyInvite(pid) then SendClientMessage(pid, COLOUR_RED, "Nobody invited you.") end return true elseif cmd == "decline" then DeclinePartyInvite(pid) return true elseif cmd == "leave" then if not RemovePlayerFromParty(pid, "left") then SendClientMessage(pid, COLOUR_RED, "You are in no party.") end return true elseif cmd == "kick" or cmd == "leader" then local party, target = GetPlayerParty(pid), GetPlayerId(args) if not party or GetPartyLeader(party) ~= pid then return SendClientMessage(pid, COLOUR_RED, "You lead no party.") end if not target or GetPlayerParty(target) ~= party then return SendClientMessage(pid, COLOUR_RED, "Not in your party.") end if cmd == "kick" then RemovePlayerFromParty(target, "kicked") else SetPartyLeader(party, target) end return true elseif cmd == "disband" then local party = GetPlayerParty(pid) if party and GetPartyLeader(party) == pid then DisbandParty(party) end return true elseif cmd == "p" then local party = GetPlayerParty(pid) if not party then return SendClientMessage(pid, COLOUR_RED, "You are in no party.") end SendPartyMessage(party, 0x80C0FFFF, "[Party] " .. GetPlayerName(pid) .. ": " .. args) return true end return falseend
function OnPartyInvite(from, target) SendClientMessage(target, COLOUR_SERVER, GetPlayerName(from) .. " invites you to a party - /accept or /decline.")end
function OnPlayerJoinParty(party, pid, reason) SendPartyMessage(party, COLOUR_SERVER, GetPlayerName(pid) .. " joined the party.")end
function OnPlayerLeaveParty(party, pid, reason) if reason ~= "disband" then SendPartyMessage(party, COLOUR_SERVER, GetPlayerName(pid) .. " left the party (" .. reason .. ").") endendLeader-only invitations are one more line at the top of OnPartyInvite:
if GetPlayerParty(from) and GetPartyLeader(GetPlayerParty(from)) ~= from then return false end.
