A fast, batteries-included command processor for SA-MP / open.mp.
The simplicity of zcmd, the permissions/help/aliases of y_commands, and the speed of Pawn.CMD — in one pure-Pawn include with zero dependencies.
Every existing command processor makes you trade something:
- zcmd — dead simple, but no permissions, no help, no aliases, no runtime control. You build all of that by hand.
- y_commands (ycmd) — has all those features, but needs the entire YSI framework (megabytes of includes that are brittle on modern/open.mp compilers).
- Pawn.CMD — very fast, but it's a binary plugin (
.dll/.so+ ABI), not pure Pawn. - I-ZCMD — a faster zcmd, but still no built-in permissions/help/aliases.
omp-cmd gives you all the features in a single .inc file, no plugin, no YSI — and it's as fast as the fastest pure-Pawn processors.
#include <open.mp>
// tell omp-cmd how to read a player's permission level (optional - default is 0)
#define OMPCMD_LEVEL_PROVIDER GetPlayerAdminLevel
#include <omp-cmd>
// simple command (zcmd-style) - always allowed
CMD:pos(playerid, params[])
{
new Float:x, Float:y, Float:z; GetPlayerPos(playerid, x, y, z);
new m[48]; format(m, sizeof m, "%.1f %.1f %.1f", x, y, z);
return OReply(playerid, m);
}
// gated command with built-in permission level + self-describing help
OCMD:ban(playerid, params[], help)
{
if (help) return OHelp(playerid, 3, "/ban <id> <reason> - ban a player");
Kick(strval(params));
return OReply(playerid, "Banned.");
}
// alias: /b runs /ban
OALIAS(b, ban)That's it. OCMD:ban requires level 3 (omp-cmd checks OMPCMD_LEVEL_PROVIDER for you and
denies with a message if they're too low). OHelp both returns the level and
prints the usage — one line gates and documents the command.
Migrating from y_commands?
XCMD:,XHelp,XReplyandXALIASexist as drop-in aliases, so old handlers keep compiling.
| omp-cmd | zcmd | I-ZCMD | y_commands | Pawn.CMD | |
|---|---|---|---|---|---|
Simple CMD: syntax |
✅ | ✅ | ✅ | ✅ | ✅ |
| Permission levels (built-in) | ✅ | ❌ | ❌ | ✅ | ✅ |
| Self-describing help | ✅ | ❌ | ❌ | ✅ | ✅ |
| Aliases | ✅ | ❌ | ❌ | ✅ | ✅ |
| Runtime enable/disable | ✅ | ❌ | ❌ | ✅ | ✅ |
| Pure Pawn (no plugin) | ✅ | ✅ | ✅ | ✅ | ❌ |
| No framework/dependency | ✅ | ✅ | ✅ | ❌ (YSI) | ❌ (plugin) |
| Single file | ✅ | ✅ | ✅ | ❌ | ❌ |
Dispatch cost per command. There are two measurement contexts — modern
open.mp on Apple-silicon (mine, reproducible with test/benchmark.pwn)
and old SA-MP 32-bit hardware (community-published). Absolute times across the two
contexts are not comparable (~20× hardware difference), so each group is shown
with its own zcmd-relative figure.
| Processor | per-command | vs zcmd | features | dependency | source |
|---|---|---|---|---|---|
| omp-cmd (case-sensitive) | 0.60 µs | 1.02× ⚡ | ✅ full | none | measured¹ |
| zcmd | 0.61 µs | 1.00× | ❌ | none | measured¹ |
| I-ZCMD | 0.66 µs | 0.92× | ❌ | none | measured¹ |
| omp-cmd (case-insensitive, default) | 0.90 µs | 0.68× | ✅ full | none | measured¹ |
| — published numbers below (old SA-MP HW; ratios only) — | |||||
| zcmd | ~14.3 µs | 1.0× | ❌ | none | community² |
| Pawn.CMD (with callbacks) | ~9.6 µs | ~1.5× | plugin | community² | |
| I-ZCMD | ~7.4 µs | ~1.9× | ❌ | none | community² |
| Pawn.CMD (no callbacks) | ~3.3 µs | ~4.3× | plugin | community² | |
| y_commands (ycmd) | ≈ zcmd | ~1× | ✅ full | YSI | per author³ |
¹ open.mp, macOS arm64, 500,000 dispatches of the same command — run test/benchmark.pwn yourself.
² 150,000 calls on old SA-MP 32-bit — I-ZCMD thread, “Faster than I-ZCMD”.
³ “ZCMD and y_commands perform equally well” — Yashas; a live number wasn’t obtainable (YSI doesn’t link cleanly on the modern open.mp toolchain).
Takeaways (honest):
- On identical modern hardware, omp-cmd in case-sensitive mode is the fastest pure-Pawn processor measured — edging out zcmd and I-ZCMD — while being the only one with built-in permissions, help, aliases and runtime control.
- Pawn.CMD can be faster, but it’s a binary plugin. omp-cmd is one pure-Pawn file.
- Dispatch is never a real bottleneck anyway (a busy server runs maybe a few hundred commands/sec total). omp-cmd’s real value is features per cost.
How the speed is achieved: a single funcidx() lookup; the handler name is built with a native string copy (no per-call format()); params are referenced in place (no copy); and each command’s permission level is cached after the first query, so repeated dispatches are a single call.
// simple, always allowed (gate it yourself if you want)
CMD:name(playerid, params[]) { ... return 1; }
// permission level + self-help (the `help` flag is set when omp-cmd queries it)
OCMD:name(playerid, params[], help)
{
if (help) return OHelp(playerid, LEVEL, "/name <args> - what it does");
... return 1;
}Tell omp-cmd how to read a level (before including it):
#define OMPCMD_LEVEL_PROVIDER Admin_GetLevel // returns the player's level (int)
#include <omp-cmd>OCMD: commands are auto-denied (with a message, or OnPlayerCommandDenied) when
OMPCMD_LEVEL_PROVIDER(playerid) < requiredLevel. Plain CMD: commands are level 0.
OALIAS(tp, goto) // /tp runs /goto, same level + helpOmpCmd_Disable("ban"); OmpCmd_Enable("ban");
OmpCmd_Exists("ban"); OmpCmd_LevelOf("ban");omp-cmd dispatch needs no registry, but to list commands for a help dialog, register the ones you want shown:
OmpCmd_List_Add("ban", 3, "/ban <id> <reason> - ban a player");
// then iterate OmpCmd_List_Count() / OmpCmd_List_Get(i, name, level, help, len)See examples/example.pwn for a complete /cmds dialog.
public OnPlayerCommandReceived(playerid, cmdtext[]) { return 1; } // 0 = block
public OnPlayerCommandPerformed(playerid, cmdtext[], success) { return success; }
public OnPlayerCommandDenied(playerid, const cmd[], reason) { return 1; }
// reason: OMPCMD_DENY_UNKNOWN / OMPCMD_DENY_LEVEL / OMPCMD_DENY_DISABLED| Define | Default | Effect |
|---|---|---|
OMPCMD_LEVEL_PROVIDER |
returns 0 | your permission-level function |
OMPCMD_CASE_SENSITIVE |
off | skip lowercasing → fastest dispatch |
OMPCMD_MAX_NAME |
28 | longest command name |
Drop omp-cmd.inc into your pawno/qawno include/ folder and:
#include <omp-cmd>omp-cmd hooks OnPlayerCommandText (via the ALS chain), so it coexists with other
includes. Nothing else to install.
omp-cmd is CMD:-compatible — your existing zcmd commands work unchanged. Add
permissions/help by switching a command to OCMD: when you want them. (Don’t use
zcmd and omp-cmd at the same time — both hook OnPlayerCommandText.)
Coming from y_commands? XCMD:, XHelp, XReply and XALIAS are kept as
aliases of OCMD:, OHelp, OReply and OALIAS, so existing handlers compile as-is.
MIT — use it anywhere. Author: Xyranaut (Mac Andreas).
Inspired by zcmd (Zeex), I-ZCMD (Yashas), y_commands (Y-Less) and Pawn.CMD (katursis) — no code reused. Independent clean-room implementation.