Skip to Content
Modlock

Settings, metrics and boards

Let players choose, count what they do and rank them.

A setting is a value a player chooses, a metric is a count of what players do, and a board is a ranking of players. You declare each in the manifest and the host stores them, so your mod needs no database of its own.

Settings

Declare a setting in mod.json, then read it. Every value is text, and a bot reads each setting's default.

import { settingNumber, settingOn } from 'modlock'

const layout = player.setting('layout') // 'full' or 'compact'
const sounds = settingOn(player, 'sounds') // a switch, as a boolean
const volume = settingNumber(player, 'volume') // a number
player.setSetting('layout', 'full')
local layout = player:setting("layout") -- "full" or "compact"
local sounds = player:setting("sounds") == "true"
local volume = tonumber(player:setting("volume"))
player:setSetting("layout", "full")
layout = player.setting("layout")  # "full" or "compact"
sounds = player.setting("sounds") == "true"
volume = float(player.setting("volume") or 0)
player.set_setting("layout", "full")
layout, _ := p.Setting("layout") // "full" or "compact"
sounds, _ := p.SettingOn("sounds") // a switch, as a bool
volume, _ := p.SettingNumber("volume") // a number
_ = p.SetSetting("layout", "full")

A player may change a setting outside the mod, such as on a profile page. The setting-changed event reports the change:

import { onSettingChanged } from 'modlock'

onSettingChanged((player, key, value) => {
  if (key === 'layout') {
    player.chat(`Layout is now ${value}.`)
  }
})
modlock.onSettingChanged(function(player, key, value)
	if key == "layout" then
		player:chat(`Layout is now {value}.`)
	end
end)
@modlock.on_setting_changed
def changed(player: modlock.Player, key: str, value: str) -> None:
    if key == "layout":
        player.chat(f"Layout is now {value}.")
mod.OnSettingChanged(func(p mod.Player, key, value string) {
	if key == "layout" {
		_ = p.Chat("Layout is now " + value + ".")
	}
})

Metrics

A metric is a running total for each player: a count, a sum or a maximum. Declare it in mod.json, then add to it. The optional label splits one metric into several totals.

player.addMetric('round.wins', 1)
player.addMetric('hud.opened', 1, 'compact')
player:addMetric("round.wins", 1)
player:addMetric("hud.opened", 1, "compact")
player.add_metric("round.wins", 1)
player.add_metric("hud.opened", 1, "compact")
_ = p.AddMetric("round.wins", 1, nil)
label := "compact"
_ = p.AddMetric("hud.opened", 1, &label)

When no host stores metrics, modlock dev logs each session's totals when the session ends.

Boards

A board ranks players against each other. Declare it in mod.json. Report each score with a value for each of the board's partitions.

import { command, readBoard, reportScore } from 'modlock'

reportScore(player, 'aim.score', 4210, {
  partition: { scenario: 'tracking', difficulty: 'hard' },
})

// Show the top five.
command('top', (player) => {
  for (const row of readBoard('aim.score', { limit: 5 })?.rows ?? []) {
    player.chat(`${row.rank}. ${row.name} ${row.value}`)
  }
})
modlock.reportScore(player, "aim.score", 4210, {
	partition = { scenario = "tracking", difficulty = "hard" },
})

-- Show the top five.
modlock.command("top", function(player)
	local page = modlock.readBoard("aim.score", { limit = 5 })
	for _, row in if page then page.rows else {} do
		player:chat(`{row.rank}. {row.name} {row.value}`)
	end
end)
modlock.report_score(
    player, "aim.score", 4210, partition={"scenario": "tracking", "difficulty": "hard"}
)


# Show the top five.
@modlock.command("top")
def top(player: modlock.Player, args: str) -> None:
    page = modlock.read_board("aim.score", limit=5)
    for row in page.rows if page is not None else []:
        player.chat(f"{row.rank}. {row.name} {row.value}")
_ = p.ReportScore(&mod.ScoreReport{
	Board:     "aim.score",
	Value:     4210,
	Partition: map[string]string{"scenario": "tracking", "difficulty": "hard"},
})

// Show the top five.
mod.Command("top", func(p mod.Player, args string) {
	page, err := mod.ReadBoard(&mod.BoardQuery{Board: "aim.score", Limit: 5})
	if err != nil {
		return
	}
	for _, row := range page.GetRows() {
		_ = p.Chat(fmt.Sprintf("%d. %s %d", row.GetRank(), row.GetName(), row.GetValue()))
	}
})
CallDoes
reportScore(player, board, value, options?)Reports one score. Returns whether the host accepted it. A host without boards accepts none, and the score goes to the mod's log instead.
reportMatch(board, places, options?)Reports a whole match's places to a rated board. Players who tie share a place.
readBoard(board, read?)Reads a page of a board, ten rows by default, as the host last read it. A page the host has not read yet comes back loading.

Python spells them in snake_case with keyword options. Go spells them Player.ReportScore, ReportMatch and ReadBoard, and returns the host's error where the others return false.

On this page