نکات و اصول مهم در برنامه نویسی Pawn/SA-MP

1- همیشه از تابع mysql_tquery() بجای mysql_query() استفاده کنید. تابع mysql_tquery از نوع multi-thread هستش و میزان وقفه/lag در سرور رو بسیار کاهش میده. (عملیات رو بین Thread ها تقسیم میکنه)
2- درصورتی که از سیستم های ضد hack/cheat استفاده می کنید، همیشه EnableStuntBonusForAll رو 0 قرار بدید.
در غیراینصورت سرور کاربر رو هکر قلمداد میکنه.
3- توسط پلاگین crashdetect میتونید crash های سرور رو ردیابی کنید.
https://github.com/Zeex/samp-plugin-crashdetect
نکته: برای استفاده از این پلاگین در ویندوز، مطعمن بشید که VC++ runtime 2010 - VC10 نگارش x86 و x64 رو نصب دارید.
4- همیشه دستورات AddPlayerClass() رو در مختصات مناسب و قابل دسترس تنظیم کنید تا در صورت بروز باگ یا مشکلات شبکه ای(packet lost) کاربر به موقعیت مناسبی teleport بشه.
5- با include کردن کتابخانه MapFix بیشتر باگهای mapping world رفع خواهند شد.
http://forum.sa-mp.com/showthread.php?t=591476
6- توسط فیلتر اسکریپت PAOE براحتی attachment های کاربر رو اضافه، حذف و ویرایش کنید!
http://forum.sa-mp.com/showthread.php?t=416138
7- حتاالمکان بجای استفاده از Timer های متعدد و کاهش performance سرور، از دستور GetTickCount() استفاده کنید.
8- هنگام تعریف اولیه resource هایی مثل Text3D, PlayerText, Text, Actor, Vehicle, Player, GangZone همیشه مقدار پیشفرض رو INVALID قرار بدید تا موقع بررسی وجود resource با مشکل مواجه نشید.
INVALID_3DTEXT_ID, INVALID_PLAYER_ID, INVALID_ACTOR_ID, INVALID_VEHICLE_ID...
9- بجای تکرار مختصات 0.0, 0.0, 0.0 در همه قسمتهای برنامه، میتونید اینهارو به ثابت define تبدیل و در همه جا فراخونی کنید:
#DEFINE COORDINATE_DEFAULT 0.0, 0.0, 0.0
10- یادتون نره که در کالبک OnGameModeExit داده های همه کاربران رو save کنید، درغیراین صورت هنگام stop شدن سرور(دستی یا crash) داده های کاربر از بین خواهند رفت!
11- برای بهبود کارایی سیستم ban تعبیه شده در SA-MP، بهتره پارامتر reloadbans رو توسط دستور SendRconCommand() در کالبک OnIncomingConnection قرار بدید:
public OnIncomingConnection(playerid, ip_address[], port)
{
SendRconCommand("reloadbans");
}
12- از انجایی که برای پروژه های Pawn/SA-MP ساختار استاندارد و اصولی وجود نداره، پیشنهاد می کنم از این ساختار استفاده کنید:
commands.inc برای دستورات
utils.inc برای توابع و رویه ها
textdraws.inc برای ساخت و نگهداری textdraw ها
variables.inc برای تعریف متغیر های سراسری
objects.inc برای ساخت و نگهداری game object و mapping ها
enumerates.inc برای enum ها
tasks.inc برای timer ها(درصورتی که از کتابخانه y_less استفاده می کنید)
configuration.inc برای پیکربندی اولیه و ثابت های define
و همگی باید در پوشه include باشن.
13- بجای تعریف متغیر برای رشته های static(مثل help/) میتونید از یک متغیر سراسری استفاده کنید. یا حتی اونهارو بصورت ثابت define تعریف کنید.
14- نکته جزیی: جهت افزایش پرفومنس هنگام پرس و جو با SQL بهتره با Id کار کنید و نه نام، عنوان و...
15- جای استفاده از دستور mysql_escape_string() بهمراه format()، میتونید از دستور mysql_format() با e% استفاده کنید. با اینکار MySQL بصورت خودکار رشته رو در مقابل حملات SQL Injection ایمن میکنه.
16- درصورتی که از NPC(نه Actor) استفاده می کنید، برای جلوگیری از ایجاد باگ و تداخل بین کاربر و NPC عبارت if (IsPlayerNPC(playerid)) return 1; رو در همه کالبک های OnPlayer* قرار بدید. مثال:
public OnPlayerStateChange(playerid, newstate, oldstate)
{
if (IsPlayerNPC(playerid))
{
return 1;
}
// ...
17- برای بررسی valid بودن vehicle از کد زیر استفاده کنید:
bool:IsVehicleInGame(const vehicleid)
{
new Float:x, Float:y, Float:z;
GetVehiclePos(vehicleid, x, y, z);
if (((x == 0.0) && (y == 0.0) && (z == 0.0)) || (vehicleid == INVALID_VEHICLE_ID) || (vehicleid == INVALID_VALUE))
{
return false;
}
return true;
}
18- نکته جزیی: با استفاده از دستور SetPlayerWorldBounds() میتونید برای کاربران محدودیت مختصاتی ایجاد کنید.
19- توجه کنید که دستور TextDrawCreate() با دستور CreatePlayerTextDraw() متفاوته! و هرکدوم باید درجای خودش استفاده بشه. PlayerTextDraws میتونه با خروج کاربر از سرور نابود بشه، اما TextDrawCreate استاتیک هستش و فقط با TextDrawDestroy() نابود میشه.
20- برای گرفتن نام کاربر و جلوگیری از تکرار کد میتونید تابع زیر رو بکار ببندید:
ReturnPlayerName(const playerid)
{
new name[MAX_PLAYER_NAME];
GetPlayerName(playerid, name, sizeof name);
return name;
}
21- بجای استفاده از ثابت MAX_PLAYERS در حلقه for برای عملیات روی کاربران باید از روش زیر استفاده کنید:
for (new i = 0, j = GetPlayerPoolSize(); i <= j; i ++)
{
SetPlayerHealth(i, 100.0);
}
چون ثابت MAX_PLAYERS حاوی ظرفیت کل سرور هستش(50-500-1000...) اما دستور GetPlayerPoolSize() حاوی آخرین ID کاربری هستش که وارد سرور شده. پس بسیار بهینه و بصرفه هست.
22- کاملترین روش برای بررسی وجود کاربر موردنظر در سرور استفاده از تابع زیر هست:
bool:IsPlayerInGame(const playerid)
{
if (!IsPlayerConnected(playerid) || (playerid == INVALID_PLAYER_ID) || IsPlayerNPC(playerid))
{
return false;
}
return true;
}
23- برای ثابت نگه داشتن Health کارمندان سرور میتونید از روش زیر استفاده کنید:
public OnPlayerUpdate(playerid)
{
if (playerData[playerid][pd_IsOnDutyAdmin]) // !
{
SetPlayerHealth(playerid, 65535.0);
}
return 1;
}
24- نکاتی راجب کالبک های OnPlayerRequestClass, OnPlayerRequestSpawn و OnPlayerSpawn:
کالبک OnPlayerRequestClass: هنگام ورود کاربر به صفحه Class Selection و چپ و راست کردن بین Class ها صدا زده میشه.
کالبک OnPlayerRequestSpawn: هنگامی که کاربر دکمه Spawn رو فشار میده صدا زده میشه.
کالبک OnPlayerSpawn: هر زمان که کاربر Spawn میشه یا دستور Spawn() صراحتاً اجرا میشه، صدا زده میشه.
نکته: در بعضی از قسمت های سرور با Spawn شدن کاربر، $100 از CASH کاربر برداشت میشه.
نکته 2: این کالبک OnPlayerSpawn در Filterscript ها همیشه اول از همه کالبکهای کاربر صدا زده میشه.
25- مقدار 0 / 1 بازگشتی return در کالبک های اصلی SA-MP تعیین میکنن که آیا کالبک در پایان عملیات به Filterscript ها هم منتقل بشه یا خیر.
26- هرگز float و integer رو با یکدیگر mix نکنید، اینکار باعث ایجاد خطا(tag mismatch) یا اشتباه در برنامه نویسی خواهد شد:
غلط:
new Float:result = 2.0 + 1;
صحیح:
new Float:result = 2.0 + 1.0;
27- نکته جزیی: بهترین محل برای reset کردن داده های کاربر داخل کالبک OnPlayerConnect هستش.
28- نکته جزیی: یادتون باشه که حداکثر طول رشته ای SA-MP میتونه نمایش بده 128 کاراکتر هستش، پس برای ارسال پیام به کاربر بیش از این مقدار رو اختصاص ندید!
29- درصورتی که از سیستم های ضد hack/cheat استفاده می کنید، به هیچ وجه از pickupmodel های 1212, 1240, 1242, 1274 و weapon در دستور CreatePickup() استفاده نکنید.
در غیراینصورت سیستم کاربر رو هکر قلمداد میکنه.
30- عدد پارامتر iconid در تابع SetPlayerMapIcon() تعیین کننده slot استفاده شده mapicon هستش. در صورت استفاده از slot تکراری، mapicon جدید جایگزین mapicon قبلی خواهد شد.
31- برای امنیت بیشتر سرور بهتره مقدار rcon در server.cfg رو 0 قرار بدید.
32- نکته جزیی: برای غیرفعال کردن Chat پیشفرض SA-MP کافیه مقدار بازگشتی return در کالبک OnPlayerText رو 0 قرار بدید.
31- برای تمیز نگه داشتن console سرور بهتره مقدار chatlogging در server.cfg رو 0 قرار بدید تا از ثبت Chat کاربران در console جلوگیری بشه.
32- همیشه مقدار lagcompmode در server.cfg رو 1 قرار بدید تا lag بین کاربران و فعالیتشون از بین بره.
33- برای جلوگیری از disconnect/timeout شدن کاربران مقدار ackslimit در server.cfg رو 4000 قرار بدید.
34- نکته جزیی: مقدار stream_distance در server.cfg تعیین کننده برد stream شدن world/game object ها در صفحه نمایش کاربر هستش.
35- برای حل مشکل Knife کافیه عبارت زیر رو در کالبک OnPlayerTakeDamage قرار بدید:
if ((weaponid == WEAPON_KNIFE) && (bodypart == 9))
{
SetPlayerHealth(playerid, 0.0);
}
36- برای teleport از طریق کلیک روی Map میتونید از کد زیر استفاده کنید:
public OnPlayerClickMap(playerid, Float:fX, Float:fY, Float:fZ)
{
if (IsPlayerAdmin(playerid))
{
SetPlayerPosFindZ(playerid, fX, fY, fZ + 3.0);
// Stop all player's sounds.
}
return 1;
}
37- برای جلوگیری از crash کاربران توسط اکسپلویت های bullet میتونید از کد زیر استفاده کنید:
public OnPlayerWeaponShot(playerid, weaponid, hittype, hitid, Float:fX, Float:fY, Float:fZ)
{
if ((fX > 2140000000) || (fY > 2140000000) || (fZ > 2140000000))
{
Kick(playerid);
return 0; // Desync the shot.
}
return 1;
}
38- برای جلوگیری از crash سرور توسط کاراکتر % در کالبک OnDialogResponse کافیه از کد زیر استفاده کنید:
public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
{
if (strfind(inputtext, "%", true) != INVALID_VALUE) // Fixes crash.
{
return 0;
}
return 1;
}
39- بعد از اینکه Dialog ای در کالبک OnDialogResponse هندل می کنید، قطعاً باید مقدار return 1 رو بازگشت بدید!
41- برای خوانایی و دسته بندی بهتر ثابت های define اونها رو بر اساس نوع resource دسته بندی کنید:
#define MAPICON_SKULL 23 #define WEATHER_SUNNY 1 #define WEAPON_NIGHTVISIONGOGGLES 44 #define SKIN_TRUTH 1 #define VEHICLE_HYDRA 520 #define SOUND_PUNCH 1130 #define PICKUPICON_BRIEFCASE 1210 #define COLOR_BLACK 0x000000FF #define INTERIOR_OUTSIDE 0 // ...
42- بهتره زمانی از enum استفاده بشه که مقادیر پشت هم یا وضعیتی هستن... مثل انواع آب و هوا. درجات کاربر. جنسیت و... در غیر اینصورت بهتره که define استفاده بشه.
// Dialog
enum
{
DIALOG_GLOBAL, // 0
DIALOG_REGISTER_ISSUE,
DIALOG_REGISTER,
DIALOG_LOGIN,
DIALOG_SHOP,
DIALOG_COMMANDS, // 5
DIALOG_CLASSSELECTION
};
43- نکته جزیی: برای خوانایی بهتر همیشه متغیرهایی که فقط دو وضعیت(0 / 1) دارن رو از نوع bool تعریف کنید.
44- تفاوت تابع نوع stock و public:
تابع stock: این نوع تابع در صورت قید شدن ولی استفاده نشدن هشدارهای کامپایلر رو خفه میکنه. میتونه مقدار return داشته/نداشته باشه. مناسب برای کتابخانه ها و Include ها هستش.
تابع public: این نوع تابع حتماً باید مقدار return داشته باشه. مقادیر string رو نمیتونه return کنه. مناسب برای استفاده در کالبک توابع CallLocalFunction, CallRemoteFunction, SetTimer, و SetTimerEx.
44- نکته جزیی: همیشه متغیرها و آرایه هایی که مقدار ثابتی دارند رو از نوع const تعریف کنید.
45- هنگام طراحی Filterscript ها همه game object های ساخته شده رو در کالبک OnFilterScriptExit نابود/destroy کنید.
46- کتابخانه i-zcmd، یک command processor ساده، سبک و پرسرعت!
https://github.com/YashasSamaga/I-ZCMD/blob/master/izcmd.inc
47- بکمک تابع زیر میتونید nitro/non-nitro بودن vehicle رو بررسی کنید:
bool:IsNonNitroVehicle(const vehiclemodelid)
{
static const nonNitroVehicles[29] = {581, 523, 462, 521, 463, 522, 461, 448, 468, 586, 509, 481, 510, 472, 473, 493, 595, 484, 430, 453, 452, 446, 454, 590, 569, 537, 538, 570, 449};
for (new i = 0, j = sizeof nonNitroVehicles; i != j; ++ i)
{
if (vehiclemodelid == nonNitroVehicles[i])
{
return true;
}
}
return false;
}
48- در صورت خالی گذاشتن پارامتر title در دستور ShowPlayerDialog، دیالوگ مورد نظر نمایش داده نخواهد شد!
49- درصورتی که از پلاگین streamer استفاده می کنید، دیگر نیازی به include کردن فایل a_samp در ابتدای فایل ندارید.
50- بهتره همیشه زمان world رو با زمان server در کالبک OnGameModeInit یکی کنید:
new hour, minute, second; gettime(hour, minute, second); SetWorldTime(hour);
51- توسط پلاگین nativechecker میتونید خطاهای runtime سرور رو ردیابی کنید.
http://forum.sa-mp.com/showthread.php?t=249226
52- هنگام استفاده از پلاگین MySQL در لینوکس، اگر کتابخانه به libmysqlclient دسترسی ندارید یا با خطا مواجه هستید از فایل mysql_static.so این پلاگین استفاده کنید.
53- برای استفاده از پلاگین MySQL در ویندوز، باید VC++ runtime 2015 - VC14 نگارش x86 و x64 رو نصب داشته باشید.
نکته: فایل libmysql.dll نگارش 32 بیتی رو هم در کنار فایلهای سرور باید داشته باشید.
54- یادتون نره که پلاگین crashdetect باید همیشه دومین include سرور شما باشه:
#include <a_samp> #include <streamer> #include <crashdetect>
55- توسط فیلتر اسکریپت Zamaroht's TextDraw Editor براحتی TextDraw های سرور رو اضافه، حذف و ویرایش کنید.
http://forum.sa-mp.com/index.php?topic=143025.0
56- پلاگین FCNPC قدرتمند ترین و سریعترین پلاگین کار با NPC!
https://github.com/ziggi/FCNPC
نکته: برای استفاده از این پلاگین در ویندوز باید VC++ runtime 2015 - VC14 نگارش x86 و x64 رو نصب داشته باشید.
57- برای تبدیل integer به boolean میتونید مثل زیر عمل کنید:
new intVariable = 33; new bool:boolVariable = !!intVariable;
58- هرگز source فایلها با پسوند pwn و inc رو در سرور آپلود نکنید، فقط فایلهای با پسوند amx رو آپلود کنید!
59- نکته جزیی: هرگز دایرکتوری sa-mp رو در لینوکس به 777 chmod نکنید.
60- همیشه متغیر های local رو در محل استفاده تعریف کنید، و نه در ابتدای کار.
غلط:
// WRONG!
CMD:getip(playerid, params[])
{
new targetName[MAX_PLAYER_NAME], message[128], ip[40];
new targetId;
if (sscanf(params, "u", targetId))
{
return SendClientMessage(playerid, -1, "/getip <player>");
}
// ...
61- نکته جزیی: از پسوردهای ایمن و طولانی برای RCon استفاده کنید.
62- نکته جزیی: همیشه پسوردها رو بصورت hash شده در دیتابیس/فایل ذخیره کنید.
63- بجای استفاده های مکرر و طولانی format() و SendClientMessage() از تابع بهینه شده زیر استفاده کنید:
stock SendClientMessageEx(playerid, color, form[], {Float, _}: ...)
{
#pragma unused form
static tmp[145];
new t1 = playerid, t2 = color ;
const n4 = -4, n16 = -16, size = sizeof tmp;
#emit stack 28
#emit push.c size
#emit push.c tmp
#emit stack n4
#emit sysreq.c format
#emit stack n16
return SendClientMessage(t1, t2, tmp);
}
64- همیشه بصورت روزانه یا هفتگی از داده های سرور backup بگیرید!
65- نکته جزیی: یادتون باشه که سرعت پردازش متغیر آرایه از متغیر عادی کندتر هست.
66- توسط دستور memcpy میتونید همه داده های کاربر جدید رو در OnPlayerConnect یکجا ریست کنید:
public OnPlayerConnect(playerid)
{
memcpy(playerData[playerid], playerData[MAX_PLAYERS], 0, sizeof(playerData[]) * 4, sizeof(playerData[]));
}
67- قطعاً سرعت توابع Native خود SA-MP/Pawn از توابع دست نویس شما سریعتر هستن!
68- نکته جزیی: برای غیرفعال کردن Chat پیشفرض SA-MP کافیه مقدار بازگشتی return در کالبک OnPlayerText رو 0 قرار بدید.
69- توسط SA-MP GDK - SAMP Gamemode Development Kit میتونید موتور بازی رو با زبان Cpp طراحی کنید:
http://zeex.github.io/sampgdk/ http://forum.sa-mp.com/showthread.php?t=421090
70- دلیل خطای Format specifier does not match parameter count در پلاگین sscanf این هست که یک پارامتر قید شده اما برای گرفتن متغیر تعریف نشده.
sscanf(params, "fsi", coordinate, playerName)
71- اگر هنگام استفاده از پلاگین socket با خطای Error: Function not registered: 'socket_create' مواجه شدید، مطمعن بشید که پلاگین به درستی در server.cfg نوشته شده و کتابخانه OpenSSL هم نصب باشه.
72- بهتره همیشه هنگام #include کردن فایلهای سورس، پسوند فایل inc رو هم بنویسید تا با فایلهای pwn همنام مغایرت نداشته باشن.
#include <a_samp.inc> #include <streamer.inc> #include <crashdetect.inc>
73- زمانی میتونید 2 رشته رو برابر هم قرار بدید که رشته مقصد بزرگتر از رشته مبدا باشه. (بجای استفاده از format)
new stringDestination[20], stringSource[10];
stringSource = "12345";
stringDestination = stringSource;
printf("stringDestination: %s", stringDestination);
74- برای کشتن بازیکن بهتره از مقدار 1- بجای 0.0 استفاده کنید تا سریعاً پروسه مرگ انجام بشه.
SetPlayerHealth(%0, -1);
75- اگر هنگام اجرای gamemode با خطای Run time error 18: "File is for a newer version of the AMX" مواجه شدید، مطعمن بشید که پارامتر -O2 در دستور کامپایل وجود نداشته باشد.
76- نکته جزیی: سرعت پردازش دستور format نسبت به دستور mysql_format (درصورتی که اتصال واقعی برقرار باشه) بسیار بسیار بیشتر هست.
۷۷- برای حل برخی مشکلات کامپایلر و کاهش زمان کامپایل، از کامپایلر Zeex استفاده کنید:
لینک: https://github.com/pawn-lang/compiler/releases

عالیه!
برای کاهش زمان کامپایل، از کامپایلر Zeex استفاده کنین!
لینک: https://github.com/pawn-lang/compiler/releases