نکات و اصول مهم در برنامه نویسی 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