Pawn Language Basics for AMX Mod X Beginners

January 28, 2026 Daemon666 8 min read 15 Aufrufe

AMX Mod X plugins are written in Pawn, a small embedded language that looks like C but is not C. The differences that trip up experienced programmers are specific and worth learning up front: there is essentially one numeric type, strings are just arrays, and there is no main() — the engine calls named public functions. Get these five concepts right and the rest of the API is just function lookups.

1. Cells, tags, and floats

Every variable in Pawn is a 32-bit cell. There is no separate int, float, or bool type — there are tags that reinterpret the cell. A plain cell is an integer; the Float: tag makes it a float:

new count = 5              // integer cell
new Float:speed = 250.0    // float cell, note the Float: tag
new bool:ready = true      // bool tag

Mixing tags without conversion produces a tag mismatch warning. Convert explicitly with float() and floatround():

new Float:half = float(count) / 2.0
new rounded = floatround(speed)

Float math uses normal operators in modern Pawn, but tag discipline is strict — treat a warning as an error.

2. Strings are arrays of cells

Pawn has no string type. A string is a char-tagged or plain cell array, null-terminated. You size it yourself:

new name[32]
new message[192]

You never assign a string with =. Use the string functions and charsmax(), which is sizeof(arr) - 1 — the size minus the null terminator:

copy(message, charsmax(message), "Hello")
formatex(message, charsmax(message), "Player %s has %d frags", name, frags)

Compare strings with equal(), never ==. == on arrays compares nothing useful:

if (equal(cmd, "menu")) { /* ... */ }

3. Variable declaration and scope

Declare locals with new. There is no int x = 0 — it is new x = 0. Globals are just new at file scope. Constants use const or #define:

#define MAX_CLIENTS 32
new const VERSION[] = "1.0.0"
new g_iKills[MAX_CLIENTS + 1]     // global array, index by player id

Player IDs run 1–32, so player arrays are sized [33] (index 0 unused) — that + 1 is deliberate.

4. Function kinds: public, native, stock, forward

The keyword in front of a function tells the compiler who calls it:

  • public — callable by the engine/AMXX (event handlers, command callbacks, plugin_init). Must be public or the engine cannot find it.
  • native — a function implemented in a module (C++), declared in an include. You call these; you do not write their bodies.
  • stock — a helper that is only compiled into the plugin if you actually use it. No warning if unused.
  • forward — a declaration of a public that AMXX will call (used in includes).

A handler you register must be public and its name must match the string you registered:

public plugin_init() {
    register_plugin("My Plugin", "1.0", "Me")
    register_clcmd("say /hp", "cmd_hp")
}
public cmd_hp(id) {
    client_print(id, print_chat, "You have %d HP", get_user_health(id))
    return PLUGIN_HANDLED
}

5. Control flow and operators

Familiar C syntax: if/else, for, while, switch. One assignment trap: = assigns, == compares — the compiler warns on if (x = 5) but people still do it. Loop over players by id:

for (new i = 1; i <= get_maxplayers(); i++) {
    if (!is_user_connected(i))
        continue
    client_print(i, print_chat, "Round starting")
}

switch uses case without fall-through, and multiple values share a case with commas:

switch (weapon) {
    case CSW_AWP, CSW_SCOUT: client_print(id, print_chat, "Sniper")
    default: client_print(id, print_chat, "Other")
}

6. Cvars and tasks the AMXX way

Two idioms you will use in almost every plugin. Register a cvar once and read it through its handle — caching the pointer is faster than looking it up by name every time:

new g_pEnabled

public plugin_init() {
    register_plugin("Demo", "1.0", "Me")
    g_pEnabled = register_cvar("demo_enabled", "1")
}

public some_event(id) {
    if (!get_pcvar_num(g_pEnabled))
        return
    // ...
}

Run code later or on a repeat with set_task. The time is a Float:, and a flag like "b" makes it loop:

set_task(5.0, "show_advert", _, _, _, "b")   // every 5 seconds, forever

public show_advert() {
    client_print(0, print_chat, "Visit our website")
}

Return values matter for command callbacks: PLUGIN_HANDLED stops the game from also processing the input (so a chat trigger does not leak to public chat), while PLUGIN_CONTINUE lets it through. These are constants from amxmodx.inc, not numbers to memorise.

Common mistakes

  • tag mismatch — mixing Float: and integer cells; convert with float()/floatround().
  • array must be indexed — you used a string array where a single cell was expected, or compared strings with ==.
  • Handler never fires — the function is not public, or its name does not match the registered string exactly.
  • Buffer overflow / garbage text — you sized a string too small or passed the wrong length to formatex; always use charsmax().

Next step

With the syntax in hand, write something that runs: follow Write Your First AMX Mod X Plugin, then compile it to .amxx. For real natives, keep the AMXX include files open — every function above is declared in amxmodx.inc, cstrike.inc, or fun.inc.

Mitwirkende: Daemon666 ✦
Teilen: