Globals

Functions and values available directly in every addon.

functionscreenshot(path?, x?, y?, w?, h?) → nil

Queues a PNG capture. The default — no overlay, no region — saves the raw capture frame, pixel for pixel at the frame's own resolution, straight off the card and after mods' OnFrame mutations: what the reader sees is what lands in the file, whatever size the window is or how the picture is scaled into it. Fulfilled on a background thread, so it never stalls rendering. A relative path is resolved against the app's screenshots/ folder; omit it for an auto-named file.

The other two shapes photograph the rendered window instead: overlay = true (options-table form, screenshot(path, { overlay = true })) captures after the overlay draws, panels and HUD included — the built-in screenshot addon binds this to O, with P on the raw frame. A region (all four of x, y, w, h, in window pixels) also reads from the window, because that is the space its coordinates live in. Window-based shots are scaled by the display fit; only the default form is fixture-grade.

ParamTypeNotes
pathstring (optional)Output path, e.g. "screenshots/shot.png". Omit or pass nil for an auto-named file.
x, ynumber (optional)Top-left corner of the region to capture, in pixels.
w, hnumber (optional)Region width & height, in pixels. Region is used only if all four of x, y, w, h are supplied; otherwise the whole frame is captured.
hook.Add("KeyPress", "mymod", function(key)
    if key == "F12" then
        screenshot("screenshots/grab_" .. os.time() .. ".png")   -- whole frame
    elseif key == "F11" then
        screenshot("screenshots/hud.png", 20, 20, 260, 90)       -- just a region
    end
end)
functionsound.play(path) → boolean

Plays a small WAV once — UI feedback, not a music system. The sound is mixed over the passthrough audio on its own stream and follows the app's output volume, so a muted instance stays silent. path is resolved against the app's data root exactly like image paths ("addons/<mod>/click.wav"); absolute paths and .. are refused. Returns false if the file could not be loaded or played. Keep files short — the whole file is queued at once.

ParamTypeNotes
pathstringA .wav under the data root, usually "addons/<mod>/…". Ship it with the mod like any other asset.
-- a shutter click alongside a screenshot (see the screenshot addon)
screenshot(name)
if sound and sound.play then                -- guard for older engines
    sound.play("addons/" .. MOD_NAME .. "/shutter.wav")
end
functionsound.capture(on) → nil

Start or stop keeping a copy of the console's sound as it passes through to the speakers — for a mod that wants to record it. Nothing is buffered until a mod asks, and switching it off drops whatever was held, so a recorder that is turned off costs nothing. sound.capturing() answers whether it is on.

Why a pull and not a hook. The samples arrive on SDL's audio thread and the Lua state belongs to the main one — dispatching into Lua from there would corrupt it. So the engine holds the bytes and you collect them from your own Think, where calling Lua is safe. The engine's buffer is capped at about two seconds; a mod that stops collecting loses the oldest rather than growing without limit.
functionsound.take() → bytes, channels, rate, bits, isFloat

Everything captured since the last call, plus the format it is in. The bytes are raw interleaved PCM exactly as the passthrough has them — no conversion, so writing them straight into a WAV or an AVI's audio stream is lossless. Returns an empty string when nothing has arrived. Takes no parameters.

ReturnsTypeNotes
bytesstringInterleaved samples. One frame of audio is channels * bits/8 bytes.
channels, rate, bitsnumberTypically 2, 48000, 32.
isFloatbooleanTrue when the samples are IEEE floats — WAVE format tag 3 rather than 1.
-- collected in Think, where calling into Lua is safe
sound.capture(true)
function g:Think()
    local pcm, ch, rate, bits, isFloat = sound.take()
    if #pcm > 0 then keep(pcm, perf.nowMs()) end
end
functionlog(msg) → nil

Writes a line to the console/log stream, prefixed with [mod]. Handy for debugging.

ParamTypeNotes
msgstringThe message to log.
fieldMOD_NAMEstring

The addon's folder name. Also the name of your private data folder (data/<MOD_NAME>/).

fieldMOD_VERSIONstring

What your mod.txt says this copy's version is, as "major.minor.patch""0.0.0" when there is no manifest. Handy for a "did the update land?" line in your own logging.

fieldMOD_SOURCEstring

Which copy of your mod is running: ".ccmod" for the package, "file" for a loose folder. A mod can exist twice on one machine and the versions decide which one wins, so after an update "did my edit take, or the download?" is otherwise only answerable from the startup log. The engine shows this beside the version in the mod's settings header; you get the same string if you want it yourself.

functionoverlay.setTheme(theme) → nil

Install a skin: one table of sections that every native widget falls back to. This is the whole redesign surface — a mod that styles nothing follows the installed skin, a mod that styled something keeps exactly what it asked for, and with no skin everything is the engine's built-in look. Resolution order is always widget style → theme section → built-in default.

Pass nil to take a skin back off. You rarely need to: unloading the mod that called this clears its skin automatically, so removing a skin mod restores the stock look with nothing to undo.

SectionKeys
overlayscrim — the dim drawn behind an open overlay — and bgImage, a full-screen backdrop over it.
panelbg, bgImage, border.
labelfg.
rectbg, bgImage, border. Skinning this restyles every list band at once.
rowbg, bgImage, border — the background of a layout row.
buttonbg, bgHover, bgImage, bgImageHover, border, fg, fgHover, and per role, with the role in the middle: bgActive, bgActiveHover, bgDanger, bgDangerHover, and the matching bgImageActive, fgDangerHover, …. A role key falls back to the role-less one, so bgHover set once covers every button and you only name bgActiveHover where that case should differ.
textboxbg, bgImage, border, borderFocus, fg, placeholder, selection, bgDisabled, borderDisabled, fgDisabled. A disabled field skips its bgImage: dim is the whole message there.
combobg, bgImage, border, fg.
sliderbg (track), fill.
menuThe overlay's own button column: dir ("column" | "row"), anchor ("center" | "top" | "bottom" | "left" | "right"), w, h, gap, margin, font, plus bg, bgHover, bgImage, bgImageHover, border, fg, fgHover.
gameslift — pixels to raise the cartridge bar off the bottom edge, so a bottom menu has room.
Colours are {r,g,b[,a]} in 0..255, the same shape a widget's style uses. Every key is optional; whatever you leave out keeps the built-in value. fgHover defaults to fg, so a skin only sets it where a strong hover fill would otherwise wreck the label's contrast.
Art (bgImage and friends) is a path, the same one d:image() takes — ship it with your mod and point at "addons/" .. MOD_NAME .. "/…", which resolves whether your mod is a loose folder or a downloaded .ccmod. It is stretched to the rect it fills and drawn over that rect's colour, so the colour is the ground beneath any transparency: to let art with rounded corners actually show them, set the fill's alpha to 0. A path that won't load draws nothing and leaves the colour alone.
overlay.setTheme({
    overlay = { scrim = {14,20,27,204} },
    panel   = { bg = {35,38,46,242}, border = {61,68,80,255} },
    button  = { bg = {61,68,80,255}, bgHover = {26,159,255,255},
                bgActive = {26,159,255,255}, fg = {220,222,223,255} },
    menu    = { dir = "row", anchor = "bottom", w = 170, h = 40, gap = 8,
                -- the fill is transparent so the art's corners survive
                bg = {0,0,0,0}, bgHover = {0,0,0,0},
                bgImage      = "addons/" .. MOD_NAME .. "/skin/toolbar.png",
                bgImageHover = "addons/" .. MOD_NAME .. "/skin/toolbar_hover.png" },
})
functionoverlay.getTheme() → table?

The installed skin, or nil. For reading colours out of a skin see overlay.themeColor below — it handles the "no skin" case for you.

functionoverlay.themeColor(section, key, r, g, b, a) → r, g, b, a

One colour out of the installed skin, falling back to the four values you pass. Returns them as four numbers, so it drops straight into d:text / d:rect. This is how something that draws its own pixels — a paint function — follows a skin instead of being the one part of the screen that ignores it. overlay.themeNum(section, key, def) and overlay.themeStr(section, key, def) are the same idea for numbers and strings.

function el.paint(d)
    d:rect(0, 0, 200, 40, overlay.themeColor("panel", "bg", 40, 42, 54, 235))
end
fieldrolestring?

On a button widget: what the button means, leaving what it looks like to the skin. "active" for a toggle that is on or the selected one of a set, "danger" for the ones that delete, "neutral" (the default) for the rest. The theme is consulted per role — bgActive, bgDanger — at DRAW time, which is what lets a skin loaded later restyle a button built earlier. A style.bg of your own still wins over both.

{ type = "button", text = "Sound: ON", role = "active", style = { font = 14 } }
fieldOVERLAY_OPENboolean

Whether the overlay menu is up, right now. Unlike MOD_NAME and friends this one changes as the user opens and closes it, and the engine keeps it current the moment the state changes — read it from anywhere: a Think, a paint, a "KeyPress" handler, a helper three files away. overlay.isOpen() is the same fact as a function call.

It is what a mod filters keybinds on, since the engine no longer decides that for you: a key that would be a nuisance while the menus are up returns early, and a key that is about what is on screen (a screenshot) does not.

hook.Add("KeyPress", "mymod", function(key)
    if OVERLAY_OPEN then return end        -- not while the user is in the menus
    if key == "R" then reset() end
end)

-- and anywhere else, not just in a callback that was handed it:
hook.Add("Think", "mymod_idle", function()
    if not OVERLAY_OPEN then pollTheGame() end
end)
filemod.txt

An optional file at the root of your addon (addons/<name>/mod.txt), read by the engine before any Lua runs. It exists to answer one question: when the same mod is present twice — as a folder you are editing and as a .ccmod you downloaded — which copy loads? The higher version wins, and a tie goes to the package — equal versions mean the folder has not been bumped since that release was cut, so the two are claiming to be the same thing and the published one is the copy that was actually built.

# addons/mymod/mod.txt
version = 1.2.3
No manifest means version 0.0.0, which loses to any stated version. That is what lets a downloaded release take over from a folder you left lying around without moving or renaming anything — and equally, bumping your folder past the release puts you back in charge. Both copies stay on disk either way; the log says which one ran and why. Unknown keys are ignored, so the file can grow later. Comments start with #, and a UTF-8 byte-order mark (what Notepad writes) is tolerated.

Two more keys, both advisory. dependencies lists mods this one needs beside it, optionally with a minimum version; incompatible lists mods it cannot live with. Names are folder names, matched without regard to case, comma separated.

# addons/mymod/mod.txt
version      = 1.2.3
dependencies = pkmn_stats >= 0.3.0, notes
incompatible = steam_skin
Nothing is refused. The engine checks these once, after every mod has loaded, and then says what it found — in the log, and under that mod in Options → Base Mods. It does not stop a mod loading, unload anything, or reorder anything. A mod host that refuses to run your mods because a third party's manifest has a typo in it, or names a dependency you deliberately replaced, is worse than the problem it is solving — and nothing here can tell a real conflict from a stale claim. Load order remains what it has always been: alphabetical by filename, within a mod.
Uploading needs one, and a new one every time. The version in mod.txt is sent with the package when you push a mod to the site, and it becomes the heading of the changelog entry that upload writes. The site refuses a version that mod has used before — two different downloads answering to one version is the thing a version exists to prevent — so bump it before every push. A folder with no version cannot be uploaded at all; the app says so before it packs anything, rather than after sending megabytes. It is also the number the app compares against the site's to decide whether the copy you have installed is the one being served.
functionperf.latencyMs() → number

Current frame-to-screen latency in milliseconds (a smoothed average) — from when the OS timestamps a received frame to when it's presented. This is the software path only; it does not include the capture card + USB latency. Handy for testing your mod's rendering cost. Takes no parameters.

functionperf.fps() → number

Frames presented in the last second. Takes no parameters.

functionperf.nowMs() → number

Milliseconds since the app started — the same clock the frame loop runs on. Use it for anything that has to fit inside a frame; os.clock measures CPU time and is too coarse. Takes no parameters.

One page of the MCCP modding reference. Every section is its own document; search covers all of them.