Pawn skills
Skill darviewyrl/pawn-skills
Essential Pawn best practices, high-performance optimization techniques, and architectural guidelines for both SA-MP and open.mp. Use when writing new systems, refactoring code, optimizing server lag, or working with databases and streamer plugins. Triggers on: pawn, samp, open.mp, y_hooks, mysql, optimization, streamer.From its SKILL.md
npx -y skills add darviewyrl/pawn-skillsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
10.4 KB, ~2.7k tokens by cl100k_base, as published. Nobody here has run it
Pawn Skills
A comprehensive guide to modern Pawn standards, performance optimization, and architectural best practices for open.mp.
Before You Start
This skill prevents legacy SA-MP bad practices and optimizes overall server performance.
Known Issues This Skill Prevents
- Server lag from inefficient
MAX_PLAYERSloops. - Memory leaks and stack overflows from oversized string allocations.
- Main thread freezing from unthreaded MySQL queries.
- Callback conflicts from manual hooking instead of using
y_hooks. - Unnecessary processing overhead from misusing the Streamer plugin.
1. Naming & Consistency
Clear naming is the foundation of maintainable code.
- Variables:
- Local: Use
camelCase(e.g.,targetId,playerScore). - Global/Static: Use
gs_VariableNameorVariableName. - Strings: Always account for the Null Terminator (+1).
- Local: Use
- Functions/Stocks:
- Use
Module_Actionnaming (e.g.,Player_Spawn,Vehicle_LoadData). - Ensure consistency with
Get/Setpairs (e.g.,GetPlayerPoints/SetPlayerPoints).
- Use
- Macros & Constants:
- Use
SCREAMING_SNAKE_CASE(e.g.,MAX_VEHICLE_SLOTS). - Safety: Always wrap macro parameters in parentheses
((%0))to prevent order-of-operation bugs.
- Use
Pawn Macro Mastery (#define)
Treat macros purely as "Text Replacement".
- Safety: Always wrap parameters in parentheses
(%0)to avoid logic errors during replacement. - Structure: Do not use trailing semicolons in
#define. Use backslashes (\) for multi-line macros. - Comments: Use macros for readability, but remember that commented-out code (
//) is removed during preprocessing and consumes zero RAM.
2. Advanced Optimization (Anti-Lag)
Pawn is an interpreted language; every opcode counts.
A. Looping Patterns
The standard MAX_PLAYERS loop is the most common cause of lag ($O(N)$ where $N$ is maximum capacity).
- Best: Use
foreach(new i : Player)(requires the foreach library). - Backward Iteration: The fastest standard loop is backward iteration using pre-calculated bounds.
for(new i = GetPlayerPoolSize(); i != -1; i--) { ... }
B. Array & Memory Management
- Standalone Variables: Fetching coordinates into separate variables (x, y, z) is faster than using an array due to lower indexing overhead in AMX.
- Fast Copying: Use
memcpyfor bulk array transfers instead of manual loops. - Stack Conservation: Declare complex arrays exactly when needed, after early return checks.
- Multi-Variable Assignment: Save bytecode size with
x = y = z = 0;. - 2D Array Optimization: Dereferencing a 2D array inside a loop
arr[id][i]is expensive. Pass the sub-array to a functionstock Work(sub[])to treat it as 1D, which is significantly faster. - Memset for Arrays: Use
memsetinstead offorloops to zero out large arrays.
C. High-Performance Math
- Avoid Square Roots: Calculating
floatsqrtis expensive. Compare squared distances instead. - Explicit Floats: Never mix integers in float math (e.g., use
5.0 * 2.0, not5.0 * 2) to avoid runtime conversion.
Memory & Variable Guard
- Variable Sizing: Avoid "Lazy Allocation". Do not use
512for a short 32-character message. - Null Terminator: Always account for the
+1cell required for the null terminator in strings. - Comma-Separated: Declare related variables in a single statement (e.g.,
new a, b, c;) to reduce opcode overhead. - On-Demand: Declare variables only at the moment they are needed to conserve AMX stack space.
3. Syntactic Sugar (Modern Pawn)
Pawn supports unique syntax that makes code cleaner and faster.
- Zero/False Checks: Use logical NOT
!.- ✅
if (!GetFactionOnline(type))
- ✅
- Range Checking: Use chained relational operators.
- ✅
if (0 < health < 100) - ✅
if (!(0 < health < 100))(for "Outside" range)
- ✅
- Inline Assignments: Fetch and check in one line.
- ✅
if ((id = GetTarget(playerid)) != INVALID_PLAYER_ID)
- ✅
4. Architectural Standards
- Stock vs Public: Use
stockfor internal logic. Usepubliconly for Timers, RPCs, or Engine Callbacks.stockallows the compiler to remove unused code. - Identity Management: Never use hardcoded IDs (Magic Numbers). Use
enumfor Dialog IDs and data slots to prevent collisions. - Variadic Support: If using a framework with variadic messaging, do not manually
format()strings.- ✅
SendClientMessage(playerid, -1, "ID: %d", id);
- ✅
- Manual Hooking: For precision, avoid auto-hooking systems. Call system-specific procedures manually in main callbacks (e.g.,
main\loader\hooks.inc) to control execution order exactly. - YSI Hooking (Automated): If using the YSI library, utilize
y_hooksfor modularity. It manages execution order via library-internal priorities.- ✅
hook OnPlayerConnect(playerid) { ... }
- ✅
- Capture Flow: Use boolean signals to stop callback chains early if an event is "captured".
if (Dialog_SystemA(playerid, ...)) return 1; // Handled, stop here if (Dialog_SystemB(playerid, ...)) return 1; // Not handled by A, check B - Functional Pawn (y_inline): Use
y_inlineto create closures for MySQL or Dialog callbacks, keeping related logic in one place. - Compile-time Guards: Avoid
CallLocalFunctionif the name is known. Use#if defined FunctionNameto call it directly. It avoids expensive internal string comparisons.
5. Comparison Examples: Common vs. Best Practice
Ex 1: Distance Comparison (Performance)
- ❌ Bad (Slow):
if (floatsqrt((x*x) + (y*y)) > 5.0) return 1; - ✅ Best (Fast):
if (((x*x) + (y*y)) > 25.0) return 1; // 5 squared is 25
Ex 2: Player Looping
- ❌ Bad (Legacy/Slow):
for(new i = 0; i < MAX_PLAYERS; i++) { if (!IsPlayerConnected(i)) continue; // logic } - ✅ Best (Modern):
foreach(new i : Player) { // logic (Foreach handles connection check) }
Ex 3: Range Checking
- ❌ Bad (Verbose):
if (playerScore >= 100 && playerScore <= 500) { ... } - ✅ Best (Clean):
if (100 <= playerScore <= 500) { ... }
Ex 4: Memory Usage (Large Strings)
- ❌ Bad (Static Bloat):
new msg[1024]; // Declared at the top, stays on stack for the whole function if (!valid) return; format(msg, ...); - ✅ Best (Deferred/Ondemand):
if (!valid) return; new msg[1024]; // Only declares if logic reaches this point format(msg, ...);
Ex 5: Hooking Methods
- 🔧 Manual (Total Control):
// main\loader\hooks.inc public OnPlayerConnect(playerid) { Auth_OnConnect(playerid); Inv_OnConnect(playerid); // Order is explicit return 1; } - 🚀 YSI (Automated Modularity):
#include <YSI_Coding\y_hooks> hook OnPlayerConnect(playerid) { // Just write logic, YSI handles the link return 1; }
6. Pro Tips: Color Management
Use a dual-constant system for colors to keep code readable.
COLOR_RED(Hex):0xFF0000FF(for function arguments)CL_RED(String):"{FF0000}"(for embedding in messages)
Standard Usage:
SendClientMessage(playerid, COLOR_WHITE, "This is "CL_RED"Red "CL_WHITE"and this is White");
7. open.mp & Database Standards
- OPENMP_COMPAT: Define
#define OPENMP_COMPATfor legacy SA-MP native support in open.mp environments. - SQL Injection: Mandatory use of
mysql_format. Never pass raw string values directly into queries. - Threaded Queries: Use
mysql_pqueryto prevent the main thread from freezing during heavy DB tasks. - Native Superiority: Always prefer Native functions (
strcat,memcpy,strfind) over manual Pawn loops. Natives run on the actual CPU, bypassing the virtual machine overhead. - Memory Footprint: Save RAM by redefining core constants to your actual server capacity:
#undef MAX_PLAYERS #define MAX_PLAYERS 100 // Instead of 500 or 1000
8. Streamer Plugin Strategy
Do not use the Streamer plugin unless absolutely necessary. Using it for small numbers of objects (e.g., < 1000) adds unnecessary overhead.
A. When to Avoid
- SAMP Limits: SAMP natively supports up to 1000 objects. If your map is small, use
CreateObject. - Performance Cost: Streamer is a database of objects that updates based on player distance. This process runs on a separate tick and has a cost.
B. The Smart Switch Trick
If you aren't sure if you'll need a streamer later, write your code using a dynamic-ready style, but point it to native functions:
// At the top of your script (if not using streamer yet)
#define CreateDynamicObject CreateObject
#define DestroyDynamicObject DestroyObject
C. Hybrid Placement
- Static Zones (High Traffic): Use
CreateObjectfor objects at Spawn points or main cities. Since players are always there, the streamer would create them anyway—using native saves the "check" cost. - Remote Zones: Use
CreateDynamicObjectfor distant areas that players rarely visit to save on the 1000-object limit.
9. Anti-Patterns to Avoid
- Thread Blocking: Never run heavy SQL queries without
mysql_pquery(threaded). - Hardcoded Integers: Always use
enumto manage Dialogs, Menus, and Data Slots. - OnPlayerUpdate Bloat: Never place complex logic or database saving inside
OnPlayerUpdate. Use timers. - Returning Functions in Callbacks: Never
return SendClientMessage(...). Call it first, then return1or0explicitly to ensure predictable callback behavior. - Mixed Encoding: Always ensure
.pwnfiles are saved inWindows-874when using Thai characters for SAMP. - Unnecessary Streamer Dependency: Don't include the streamer plugin if you are managing less than 1000 total objects.
What ships with it: 2 files
3.1 KB alongside SKILL.md