DENFER
Пишу плагины за два биг тести и картошку фри..
- Сообщения
- 260
- Реакции
- 289
Доброе время суток, товарищи! В данной теме мне хотелось затронуть довольно обширную, но совершенно на первый взгляд незаметную тему в скриптинге. Да-да, именно общий стиль написания кода на таком языке, как SourcePawn. Казалось бы, какая разница, как писать код - главное, что работает, но как-только дело доходит до командной работы, анализа чужого кода или вы возвращаетесь к своим старым проектам именно с этого момента начинаются огромные проблемы. Данный урок (пару рекомендаций) базируются на моем чисто-сугубом мнение и другим статьям, которые были взяты с официальной документации SourceMode.
Перед тем, как окунуться в просторы моей статьи, пробегусь по паре тезисов, а именно:
1. На данном этапе развития, мы имеем два различных синтаксиса, первый - это до версии 1.7 языка SourcePawn, а второй - после.
* на сам деле, совместимость двух синтаксисов это хорошо, но с другой стороны вносит определенную путаницу и непонимание у новичков, а иногда и профессионалов своего дела *
2. Язык SourcePawn, как и многие другие: С++, D, Java, Objective C, C#, PHP, Perl, Nemerle - является Си-подобным, а значит наследует их особенности, такие как:
- Чувствительность к регистру.
- Количество пробелов, табуляция и переводы строки не влияют на семантику нигде, кроме строковых литералов. Это, в частности, значит, что возможен перевод строки в любом удобном месте.
- Бинарные операторы имеют инфиксную форму.
- и т.д.
* набор данные постулатов, говорит о том, что следует придерживаться четкому "Си" правилу написания кода, о котором поговорим дальше *
3. SourcePawn - процедурный скриптовый язык, не стоит опираться на него, как на объектно-ориентированный используя methodmap и размышлять объектами (это клево, если у вас есть способность представлять и мыслить ими, но в данном случае лучше абстрагироваться), пишите простой и четкий, одним словом - лаконичный код.
4. Комментарии - это наше все, как и в любом другом языке, просто оставляйте их, да хоть они будут излишне и в избытке, просто оставляйте и учитесь пояснять свой код правильно, в будущем они помогут вам прокачать свои способности, а хороший комментарий многого стоит, уделите этому определенные силы, тем более, пока мысли свежи - это сыграет огромную роль, как для вас, так и для других людей, которые будут анализировать и разбираться в вашем коде.
* есть целая наука, название, которой я забыл. Полистать хабр и прочесть пару статей по данной теме, никогда не помешает даже Гуру *
5. Структура! Уделите внимание структуре исходника, где лучше находиться глобальным переменным, директивам и различным функциям.
* не мало важный критерий, который в разы облегчит, как чтение кода, так и обрадует ваши глаза *
Опираясь на данные тезисы (да, я знаю, что звучат они многословно), давайте перейдем к сути дела и по ходу будем обращаться к выше стоящим 5 аксиомам.
Не зря, я обратил внимание на структуру файла, так как код считывается сверху вниз, то более логично начать с верхушки файла, и что нас там ждет? Директивы! Именно, начнем, пожалуй, с них.
1. Директивы препроцессора.
Чаще всего встречаются такие директивы, как:
- #include
- #define
- #pragma
- #if / #else
Обычно, это типичная схема, она очень простая: сначала мы включаем в исходный файл основные библиотеки и прочие вторичные файлы, потом обзаводимся "константами" и только после - устанавливаем свои правила чтения данного файла, чтобы правила подключенного другого исходника не мешали вашему. 4 пункт с #if / #else он более спонтанный и может присутствовать совершенно в разных местах, но главное, чтобы все было по делу.
1. #include - ничего сложного тут нет, для обычных библиотек, находящихся в папке .inc используйте, исключительно, только угловые скобочки <name> (без формата файла) и двойные кавычки "directory/name.sp", если файл находится непосредственно в другой папке.
* пути к файлам не должны содержать пробелов *
2. #define - определяет идентификатор и последовательность символов на которые он будет заменяться, в простонародье их еще называют константами препроцессора или макросами. Обычно, идентификатор, то бишь название стоит писать заглавными буквами, чтобы ярко выделить в коде.
3. #pragma - определяет функции компилятора. Стоит использовать после всех инклюдов, чтобы быть уверенным, что ваш плагин компилируется, исключительно, с данными настройками.
4. #if / #else - условия препроцессора. Тут стоит обратить внимание на ветвление, в принципе ничего больше не добавить.
С дополнительной информацию о директивах препроцессора, вы сможете ознакомиться тут.
- Википедия
- Microsoft
2. Глобальные переменные.
Дальше, на нашем тернистом пути встречаются глобальные переменные, уделим им немного времени.
Глобальные переменные видны во всей программе и могут быть задействованы в любом участке кода. Они хранят свои значения на протяжении всей работы программы. Поэтому их нужно выделить по-особому, одарить их заботой и вниманием.
Глобальная переменная должна иметь:
- Яркое, самоговорящее название.
- Иметь префикс g_ (что расшифровывается, как global_).
- Содержать тип самой переменной (вы же помните, что хоть мы и пользуемся новым синтаксисом, указанный тип переменной в её название, облегчит вашу совместную жизнь с ней в разы).
Приведу примеры и тем самым сформулирую некоторое правило для написания глобальных переменных.
Как вы можете заметить, во-первых, переменные имеют префикс, указывающий, что переменная находится в глобальной области видимости и вы ее уже никогда не спутаете с локальной,
во-вторых, ее тип и при использование переменной, вы четко знаете ее тип и сможете осуществлять операции куда более разумнее, ну и конечно, простое, но понятное название, которое говорит само за себя.
Хотелось бы выделить пару особенностей, таких как:
- Консольные переменные или еще их обзывают кварами - имеют особый вид наименования, так как любую консольную переменную можно представить разными типами, то лучше всего использовать в название - основной тип представления.
- Производным от типа Handle лучше к префиксу приписывать h, тем самым получая gh_ и указывать соответствующее наименования производного типа, лично по мне, лучше использовать 1-2 буквы от производного типа в начале, чтобы на всякий случай спастись от пересечения имен.
И вот мы получили пару исключений и строгий набор правил для написания глобальных переменных, скажите же, что так читается намного проще и приятнее?
Ссылка по данной теме.
- Википедия
3. Локальные переменные
Теперь думаю, будет куда разумнее перейти от глобальных к локальным переменным.
С локальными переменными дела обстоят еще хуже, как их только не называют и в каком только стиле не пишут, думаю определенный строгий стиль написания им не помешал.
Область видимости локальной переменной - это функция в которой она определена, обычно в скриптовых языках, да и в общем на практике писать большие функции - не комильфо.
Поэтому, вполне разумным будет для локальной переменной присвоить название, которое соответствует ее назначению, не стоит дописывать название типа переменной, так как оно и так на виду и старайтесь использовать такой стиль написания, как
Вот вам и пример с использованием локальных переменных:
Ссылка по данной теме.
- Википедия
4. Функции.
В прошлом примере мы использовали функцию, они тоже содержат свои особенности в наименование, давайте пробежимся по ним.
- Название функций в SourcePawn содержит такой стиль, как
- Название должно быть, как и любая переменная - самоговорящая за свое назначение.
- Если функция является колбеком, то лучше добавить соответствующий префикс, пробежимся по некоторым из них:
И по таком принципу вы можете наименовывать свои функции. Согласны, что так выглядит намного проще и приятнее для чтения, не нужно гадать, что за обратный вызов или зачем та или иная функция - экономия времени, залог успеха!
5. Перечисления.
В редких случаях, но они все-таки бывают, используют перечисления, с ними иногда удобно и они имеют место быть:
Тут только добавлю, что бывают ситуации, когда перечислители стоит называть в
Ссылка по данной теме.
- Википедия
6. Структуры.
Структуры строятся по аналогии с перечислениями, только поля структуры (переменные внутри нее), работают по принципу локальных переменных, а методы, как функции.
Ссылка по данной теме.
- Википедия
Закончив с базой, можно переходить к мелочам и деталям.
7. Пробелы.
Из маленьких штришков и мазков складывается картина, точно самое и с кодом. Небольшая деталь, но показывает на сколько разработчик аккуратен и следит за своей разработкой.
Отделяйте литералы и операнды - используйте пробелы и не забывайте про них, вы обязательно заметите, как ваш код преобразуется не внеся никаких изменений в его логику.
8. Комментарии.
Теперь немного окуну вас в науку с комментирование кода, поплаваем на поверхности.
В начале статьи, я уже пытался ввести вас в курс дела, если вы уже ознакомились с ссылками на внешние статьи, которые я приводил - это будет хорошим плюсом.
Я выделяю 3 вида комментарий в SourcePawn:
- Строчный комментарий.
- Блочный комментарий.
- JSDoc комментарий.
Пробежимся по ним.
1. Строчный комментарий - обычно используется, если нужно внести небольшую пометку, которая уместится в одну строчку.
Совершенно очевидный и простой код, не советую использовать на практике, написан исключительно в образовательных целях. Обратите внимание, что строчные комментарии я еще поделил на два типа, первый - комментарий находится над строчкой, тогда я выделяю целый блок, и второй - комментарий, который непосредственно относится к строчке кода, которую я комментирую. Это личное мое, но мне кажется, так даже удобнее.
2. Блочный комментарий - используется, когда вы хотите вылить все свои мысли на определенный участок кода, обычно использую перед функциями или когда нужно оставить заметку непосредственно в процессе написания кода.
3. JSDoc комментарий - один из самых интересных стилей введения комментариев, так как он подсвечивает ваш комментарий при написание той или иной функции (в зависимости от среды разработки). Тут советую почитать дополнительную информацию в интернете, хотя в пределах SourcePawn такой вид комментария используется только при объявление нативных функций или форвардов.
Советую прочитать про теги и в целом про вид данного комментария.
- Википедия
Думаю, базовую концепцию стиля написания на языке SourcePawn я смог разъяснить, вы будете исключительно правы - выдвигая свои способы и методы написания кода, так как в большинстве - это индивидуально и сравнимо с почерком, каждый пишет код так, как считает нужным, НО люди, которые только начинают основывать язык и в большинстве случаев даже не являются программистами или скриптерами (два разных понятия, подмечу, пожалуй), пишут совершенно абсурдные вещи, мешают старый синтаксис с новым, теряются сами в своем коде и просят помощи, подытожу. Данная статья была написана, исключительно для ребят, которые мечутся и не могут выбрать точный стиль написания, это был урок по стилю написания кода на SourcePawn - пишите свой код чище, уделяйте внимание мелочам и не забывайте про комментарии, всем спасибо и всем хорошего кодирования!
Пару ссылок:
Венгерская нотация
Гайд по оформлению кода на C++
Введение в SourcePawn 1.7
Переходный синтаксис SourcePawn
Комментирование кода: хороший, плохой, злой
Перед тем, как окунуться в просторы моей статьи, пробегусь по паре тезисов, а именно:
1. На данном этапе развития, мы имеем два различных синтаксиса, первый - это до версии 1.7 языка SourcePawn, а второй - после.
* на сам деле, совместимость двух синтаксисов это хорошо, но с другой стороны вносит определенную путаницу и непонимание у новичков, а иногда и профессионалов своего дела *
2. Язык SourcePawn, как и многие другие: С++, D, Java, Objective C, C#, PHP, Perl, Nemerle - является Си-подобным, а значит наследует их особенности, такие как:
- Чувствительность к регистру.
- Количество пробелов, табуляция и переводы строки не влияют на семантику нигде, кроме строковых литералов. Это, в частности, значит, что возможен перевод строки в любом удобном месте.
- Бинарные операторы имеют инфиксную форму.
- и т.д.
* набор данные постулатов, говорит о том, что следует придерживаться четкому "Си" правилу написания кода, о котором поговорим дальше *
3. SourcePawn - процедурный скриптовый язык, не стоит опираться на него, как на объектно-ориентированный используя methodmap и размышлять объектами (это клево, если у вас есть способность представлять и мыслить ими, но в данном случае лучше абстрагироваться), пишите простой и четкий, одним словом - лаконичный код.
4. Комментарии - это наше все, как и в любом другом языке, просто оставляйте их, да хоть они будут излишне и в избытке, просто оставляйте и учитесь пояснять свой код правильно, в будущем они помогут вам прокачать свои способности, а хороший комментарий многого стоит, уделите этому определенные силы, тем более, пока мысли свежи - это сыграет огромную роль, как для вас, так и для других людей, которые будут анализировать и разбираться в вашем коде.
* есть целая наука, название, которой я забыл. Полистать хабр и прочесть пару статей по данной теме, никогда не помешает даже Гуру *
5. Структура! Уделите внимание структуре исходника, где лучше находиться глобальным переменным, директивам и различным функциям.
* не мало важный критерий, который в разы облегчит, как чтение кода, так и обрадует ваши глаза *
Опираясь на данные тезисы (да, я знаю, что звучат они многословно), давайте перейдем к сути дела и по ходу будем обращаться к выше стоящим 5 аксиомам.
Не зря, я обратил внимание на структуру файла, так как код считывается сверху вниз, то более логично начать с верхушки файла, и что нас там ждет? Директивы! Именно, начнем, пожалуй, с них.
1. Директивы препроцессора.
Чаще всего встречаются такие директивы, как:
- #include
- #define
- #pragma
- #if / #else
Обычно, это типичная схема, она очень простая: сначала мы включаем в исходный файл основные библиотеки и прочие вторичные файлы, потом обзаводимся "константами" и только после - устанавливаем свои правила чтения данного файла, чтобы правила подключенного другого исходника не мешали вашему. 4 пункт с #if / #else он более спонтанный и может присутствовать совершенно в разных местах, но главное, чтобы все было по делу.
1. #include - ничего сложного тут нет, для обычных библиотек, находящихся в папке .inc используйте, исключительно, только угловые скобочки <name> (без формата файла) и двойные кавычки "directory/name.sp", если файл находится непосредственно в другой папке.
SourcePawn:
// основные библиотеки SourceMod
#include <sourcemod>
#include <cstrike>
#include <sdktools>
// Подмечу, что между основными файлами и дополнительными, можно, а порой даже
// нужно использовать такую директиву, как #define, чтобы она распространялась, как
// на основной код вашего исходника, так и непосредственно на ваши включаемые дополнительные файлы.
// дополнительные файлы
#include "DENFER/AutoBalance/others.sp"
#include "DENFER/AutoBalance/algorithms.sp"
#include "DENFER/AutoBalance/forwards.sp"
* пути к файлам не должны содержать пробелов *
2. #define - определяет идентификатор и последовательность символов на которые он будет заменяться, в простонародье их еще называют константами препроцессора или макросами. Обычно, идентификатор, то бишь название стоит писать заглавными буквами, чтобы ярко выделить в коде.
SourcePawn:
#define PI 3.141592653589793238462643 // что по памяти вспомнил
#define EXP 2.71828182
#define PLUGIN_VERSION "1.0.0"
#define AUTHOR "DENFER"
3. #pragma - определяет функции компилятора. Стоит использовать после всех инклюдов, чтобы быть уверенным, что ваш плагин компилируется, исключительно, с данными настройками.
4. #if / #else - условия препроцессора. Тут стоит обратить внимание на ветвление, в принципе ничего больше не добавить.
SourcePawn:
#if (константное выражение)
...
#elif (константное выражение)
...
#elif (константное выражение)
...
#else
...
#endif
...
С дополнительной информацию о директивах препроцессора, вы сможете ознакомиться тут.
- Википедия
- Microsoft
2. Глобальные переменные.
Дальше, на нашем тернистом пути встречаются глобальные переменные, уделим им немного времени.
Глобальные переменные видны во всей программе и могут быть задействованы в любом участке кода. Они хранят свои значения на протяжении всей работы программы. Поэтому их нужно выделить по-особому, одарить их заботой и вниманием.
Глобальная переменная должна иметь:
- Яркое, самоговорящее название.
- Иметь префикс g_ (что расшифровывается, как global_).
- Содержать тип самой переменной (вы же помните, что хоть мы и пользуемся новым синтаксисом, указанный тип переменной в её название, облегчит вашу совместную жизнь с ней в разы).
Приведу примеры и тем самым сформулирую некоторое правило для написания глобальных переменных.
SourcePawn:
// StringMaps
StringMap gh_smTree;
// ArrayLists
ArrayList gh_alPlayers;
// DataPacks
DataPack gh_dpDetails;
// ConVars
ConVar gc_bPlugin;
// Handles
Handle g_hTimer;
// Strings
char g_szPrefix[32];
// Floats
float g_flSpeed;
// Integers
int g_iFrags;
// Booleans
bool g_bIsAlive;
Как вы можете заметить, во-первых, переменные имеют префикс, указывающий, что переменная находится в глобальной области видимости и вы ее уже никогда не спутаете с локальной,
во-вторых, ее тип и при использование переменной, вы четко знаете ее тип и сможете осуществлять операции куда более разумнее, ну и конечно, простое, но понятное название, которое говорит само за себя.
Хотелось бы выделить пару особенностей, таких как:
- Консольные переменные или еще их обзывают кварами - имеют особый вид наименования, так как любую консольную переменную можно представить разными типами, то лучше всего использовать в название - основной тип представления.
- Производным от типа Handle лучше к префиксу приписывать h, тем самым получая gh_ и указывать соответствующее наименования производного типа, лично по мне, лучше использовать 1-2 буквы от производного типа в начале, чтобы на всякий случай спастись от пересечения имен.
И вот мы получили пару исключений и строгий набор правил для написания глобальных переменных, скажите же, что так читается намного проще и приятнее?
Ссылка по данной теме.
- Википедия
3. Локальные переменные
Теперь думаю, будет куда разумнее перейти от глобальных к локальным переменным.
С локальными переменными дела обстоят еще хуже, как их только не называют и в каком только стиле не пишут, думаю определенный строгий стиль написания им не помешал.
Область видимости локальной переменной - это функция в которой она определена, обычно в скриптовых языках, да и в общем на практике писать большие функции - не комильфо.
Поэтому, вполне разумным будет для локальной переменной присвоить название, которое соответствует ее назначению, не стоит дописывать название типа переменной, так как оно и так на виду и старайтесь использовать такой стиль написания, как
верблюжийРегистр
.Вот вам и пример с использованием локальных переменных:
SourcePawn:
// обычная функция, которая содержит вашу реализацию
void Foo()
{
// используем верблюжий регистр, но заметьте, что если переменная содержит одно слово,
// то мы пишем, его строчными буквами и выглядит все по феншую, все информативно и понятно.
int amountHealth;
float coordinatesPlayer[3];
char name[MAX_NAME_LENGHT];
// дальше идет ваша реализация функции
...
}
Ссылка по данной теме.
- Википедия
4. Функции.
В прошлом примере мы использовали функцию, они тоже содержат свои особенности в наименование, давайте пробежимся по ним.
- Название функций в SourcePawn содержит такой стиль, как
ПаскальныйРегистр
.- Название должно быть, как и любая переменная - самоговорящая за свое назначение.
- Если функция является колбеком, то лучше добавить соответствующий префикс, пробежимся по некоторым из них:
SourcePawn:
// Наименование функции имеет префикс - означающий, что функция является колбеком
// определенного ивента, и громкое название - сообщающее, что событие является началом раунда.
Action Event_RoundStart (Event event, const char[] name, bool dontBroadcast)
{
...
}
// Наименование функции имеет префикс - означающий, что функция является обработчиком,
// а само название, говорит о том, что обрабатывает основное меню плагина.
int Handler_MainMenu (Menu menu, MenuAction action, int param1, int param2)
{
...
}
// Наименование функции, так же содержит префикс, который сообщает, что это обратный вызов,
// а само название, предупреждает, что данный колбек вызывается при вводе консольной команды.
Action CallBack_ConsoleCmd (int client, int args)
5. Перечисления.
В редких случаях, но они все-таки бывают, используют перечисления, с ними иногда удобно и они имеют место быть:
SourcePawn:
// ПаскальРегистр
enum ArmorType
{
// Наименование полей строго капсом
ARMOR_NONE,
ARMOR_KEVLAR,
ARMOR_VESTHELM
};
ПаскальРегистре
, вполне, допустимо, а яркий пример - MenuAction.Ссылка по данной теме.
- Википедия
6. Структуры.
Структуры строятся по аналогии с перечислениями, только поля структуры (переменные внутри нее), работают по принципу локальных переменных, а методы, как функции.
SourcePawn:
// структура
struct Statistics
{
// поля
char name[MAX_NAME_LENGTH];
int frags;
int deaths;
float headshotAccuracy;
// метод
int GetFrags()
{
return frags;
}
...
};
Ссылка по данной теме.
- Википедия
Закончив с базой, можно переходить к мелочам и деталям.
7. Пробелы.
Из маленьких штришков и мазков складывается картина, точно самое и с кодом. Небольшая деталь, но показывает на сколько разработчик аккуратен и следит за своей разработкой.
SourcePawn:
// отделяйте условия пробелами, тем самым это выглядит куда более гармонично, чем если
// вы лепили все с друг другом
if (param1)
{
for (int i = 1; i <= MaxClients; ++i)
{
...
}
}
else if (param2)
{
while (flag)
{
...
}
}
else
{
// главное не отделяйте пробелом передачу аргументов во функцию!
Foo();
}
Отделяйте литералы и операнды - используйте пробелы и не забывайте про них, вы обязательно заметите, как ваш код преобразуется не внеся никаких изменений в его логику.
SourcePawn:
bool Foo(int param1, int param2)
{
result = param1 + param2;
result += 2;
result -= 2;
// старайтесь брать в круглые скобочки условия или операции, превышающие одного слова,
// тем самым вы подчеркиваете важность данного условия и оно в первую очередь бросится в глаза.
return (result > 0) ? true : false;
}
8. Комментарии.
Теперь немного окуну вас в науку с комментирование кода, поплаваем на поверхности.
В начале статьи, я уже пытался ввести вас в курс дела, если вы уже ознакомились с ссылками на внешние статьи, которые я приводил - это будет хорошим плюсом.
Я выделяю 3 вида комментарий в SourcePawn:
- Строчный комментарий.
- Блочный комментарий.
- JSDoc комментарий.
Пробежимся по ним.
1. Строчный комментарий - обычно используется, если нужно внести небольшую пометку, которая уместится в одну строчку.
SourcePawn:
// пункты меню
#define ITEAM_1 1
#define ITEAM_2 2
#define ITEAM_3 3
#define ITEAM_BACKBUTTON 9 // кнопка назад
void Foo()
{
// пробегаемся по клиентам с четными индексами
for (int i = 1; i <= MaxClients; ++i)
{
if(IsClientInGame(i) && i % 2)
{
...
}
}
}
Совершенно очевидный и простой код, не советую использовать на практике, написан исключительно в образовательных целях. Обратите внимание, что строчные комментарии я еще поделил на два типа, первый - комментарий находится над строчкой, тогда я выделяю целый блок, и второй - комментарий, который непосредственно относится к строчке кода, которую я комментирую. Это личное мое, но мне кажется, так даже удобнее.
2. Блочный комментарий - используется, когда вы хотите вылить все свои мысли на определенный участок кода, обычно использую перед функциями или когда нужно оставить заметку непосредственно в процессе написания кода.
SourcePawn:
/*
Совершенно бесполезная функция, возвращает переданное число.
TODO: Хорошо бы переписать её, чтобы она несла пользу.
*/
int Foo(int param)
{
...
return param;
}
3. JSDoc комментарий - один из самых интересных стилей введения комментариев, так как он подсвечивает ваш комментарий при написание той или иной функции (в зависимости от среды разработки). Тут советую почитать дополнительную информацию в интернете, хотя в пределах SourcePawn такой вид комментария используется только при объявление нативных функций или форвардов.
SourcePawn:
/**
* Проверяет игрока на нахождение в игре.
*
* @param client - индекс игрока.
* @return true, если игрок в игре,
* иначе false
*/
native bool IsClientInGame(int client);
Советую прочитать про теги и в целом про вид данного комментария.
- Википедия
Думаю, базовую концепцию стиля написания на языке SourcePawn я смог разъяснить, вы будете исключительно правы - выдвигая свои способы и методы написания кода, так как в большинстве - это индивидуально и сравнимо с почерком, каждый пишет код так, как считает нужным, НО люди, которые только начинают основывать язык и в большинстве случаев даже не являются программистами или скриптерами (два разных понятия, подмечу, пожалуй), пишут совершенно абсурдные вещи, мешают старый синтаксис с новым, теряются сами в своем коде и просят помощи, подытожу. Данная статья была написана, исключительно для ребят, которые мечутся и не могут выбрать точный стиль написания, это был урок по стилю написания кода на SourcePawn - пишите свой код чище, уделяйте внимание мелочам и не забывайте про комментарии, всем спасибо и всем хорошего кодирования!
Пару ссылок:
Венгерская нотация
Гайд по оформлению кода на C++
Введение в SourcePawn 1.7
Переходный синтаксис SourcePawn
Комментирование кода: хороший, плохой, злой
Последнее редактирование: