Knight docs
EN ID
GitHub

Language Guide

Syntax, declarations, control flow and data.

Declarations

A .sa file contains configuration, callbacks, helper functions and variables.

knight
system::config() { ... };           // server.cfg
player::connect(int playerid) { };  // callback
function add(int a, int b) { return a + b; };
let total = 0;                      // variable
Warning
The trailing ; after a callback, function or config block is required.

Variables

knight
let hp = 100;       // int
let speed = 1.5;    // float
let name = "CJ";    // string
let online = true;  // bool

hp = 90;            // reassign
hp += 5;            // compound: += -= *= /= %=

Operators

OperatorMeaning
+ - * / %Arithmetic; + also joins strings
== != ~=Equality (~= is an alias for !=)
< <= > >=Comparison
and or notLogical; also &&, ||, !
+= -= *= /= %=Compound assignment
& | ^ ~ << >>Bitwise and/or/xor/not, shifts

Control flow

knight
player::command("/rank", int score) {
    if (score >= 100) {
        SendClientMessage(playerid, "Veteran");
    } else if (score >= 50) {
        SendClientMessage(playerid, "Regular");
    } else {
        SendClientMessage(playerid, "Rookie");
    }
};

Loops

All the usual loop forms are available. break leaves the nearest loop or switch; continue jumps to the next iteration.

knight
// while
let i = 0;
while (i < 10) {
    i += 1;
}

// do / while: runs at least once
do {
    i -= 1;
} while (i > 0);

// C-style for
for (let n = 0; n < 5; n += 1) {
    if (n == 3) { break; }
}

// iterate an array
let names = ["amy", "bob", "cid"];
for (let name in names) {
    SendClientMessage(playerid, "Hi {name}");
}

// iterate an array with the index
for (let index, name in names) {
    SendClientMessage(playerid, "{index}: {name}");
}

// iterate a map (keys are visited in sorted order)
let scores = { "amy": 10, "bob": 20 };
for (let key in scores) {
    SendClientMessage(playerid, "{key} = {scores[key]}");
}

// skip some iterations
for (let n = 0; n < 20; n += 1) {
    if (n % 2 == 0) { continue; }
    if (n > 15) { break; }
}

switch

switch compares one value against case labels. There is no fall-through: the first matching arm runs, then the switch ends, so you never need break at the end of an arm.

knight
player::command("/class", int id) {
    switch (id) {
        case 0:
            SendClientMessage(playerid, "Medic");
        case 1:
            SendClientMessage(playerid, "Police");
        default:
            SendClientMessage(playerid, "Civilian");
    }
};

Arrays and maps

knight
let scores = [10, 20, 30];
let player = { "name": "CJ", "score": 0 };

scores[0] = 15;
player["score"] = 42;

let first = scores[0];
let n = len(scores);

push(scores, 40);            // mutate in place: [10, 20, 30, 40]
let last = pop(scores);      // 40, scores is now [10, 20, 30]
let part = scores[1:3];      // [20, 30]
let sorted = sort([3, 1, 2]); // [1, 2, 3]

Literals

knight
let hex = 0xFF00FF00;   // hex int
let wait = 500ms;       // duration -> milliseconds
let pos = [1.0, 2.0, 3.0];
let meta = { "hp": 100 };

Comments and includes

knight
// line comment
/* block comment
   spanning lines */

#include <knight>              // optional; natives are built in
#include "lib/shared.sa"       // include another file

Includes are resolved relative to the including file. Circular includes are rejected with an error.

Functions

Use function to define a reusable helper. Functions can also be called by name from timers and database callbacks.

knight
function clampHealth(float value) {
    if (value < 0.0) { return 0.0; }
    if (value > 100.0) { return 100.0; }
    return value;
}

player::spawn(int playerid) {
    SetPlayerHealth(playerid, clampHealth(150.0));
};

Parameters: types, defaults and rest

Function parameters are typed and checked at compile time. Trailing parameters may have default values, and a final ...rest parameter collects any extra arguments into an array.

knight
function spawnAt(Player target, float x, float y, float z = 3.0) { ... }
function total(int first, ...rest) {
    let sum = first;
    for (let n in rest) { sum += n; }
    return sum;
}

spawnAt(playerid, 100.0, 200.0);       // z defaults to 3.0
total(1, 2, 3, 4);                     // 10
Note
Defaults are evaluated when the function is called. Passing the wrong type, too few arguments or too many arguments is a compile error.

Constants

knight
const MAX_HP = 100;
const RATIO: float = 0.5;

function cap(float hp) { return hp > MAX_HP ? MAX_HP : hp; }

Assigning to a const is rejected at compile time.

Calling a function by name

CallRemoteFunction(name, ...args) and its alias CallLocalFunction call a top-level function by its name. This is useful when the function is chosen at runtime, and it keeps Pawn code portable.

knight
function add(int a, int b) { return a + b; }
function isBig(int n) { return n > 10; }

let sum = CallRemoteFunction("add", 2, 3);      // 5
let big = CallLocalFunction("isBig", 50);       // 1
let x = CallRemoteFunction("missing");          // 0 (logged)

The result is always an int, like PAWN: numbers truncate, true/false become 1/0, a string returns its length, an object handle returns its id and null returns 0. Calling a name that does not exist writes a warning to the log and returns 0.

Scopes

A variable declared at module level is shared by every callback for the whole server lifetime. A variable declared inside a block only exists there.

knight
let total = 0;                  // module scope

player::connect(int playerid) {
    let local = 5;              // callback scope
    total = total + local;      // module variable is writable
};

Loop safety

Loops are bounded. A single callback, command or timer may run at most 10 million iterations; exceeding that stops the callback with loop iteration limit exceeded and leaves the server responsive, so one bad line cannot freeze it.

Warning
The budget resets for every callback, command and timer. A legitimate loop over players or arrays is far below the limit.

Truthiness

ValueTruthy?
true / non-zero numberyes
false / 0 / 0.0no
non-empty stringyes
empty string ""no
nullno
non-empty array / mapyes

Operator precedence

From highest to lowest:

LevelOperators
1! not - + ~ (unary)
2* / %
3+ -
4<< >>
5< <= > >=
6== != ~=
7&
8^
9|
10and &&
11or ||
12? : (ternary)

Use parentheses when the intent is not obvious.

Keywords

KeywordMeaning
letDeclare a variable
if / elseConditional
returnReturn from a function or callback
functionDeclare a helper function
true / falseBoolean literals
nullNo value
and / or / notLogical operators
while / do / forLoops
inRange iteration in for
break / continueLoop control
switch / case / defaultMulti-way branch (no fall-through)

Absent by design