Это руководство призвано дать вам самые основные представления по написанию сприптов в SourcePawn. Pawn - это “скриптовый” язык используемый для внедрения функциональности в других программах. Это означает, что это не самостоятельный язык, как C++ или Java, и его элементы будут отличаться в различных приложениях. SourcePawn - это вариация языка Pawn, используемая в SourceMod.
Это руководство не расскажет вам, как писать SourceMod плагины; оно предназначено для получения общих представлений о синтаксисе и семантике этого языка.
Особенности языка
Pawn может показаться очень похожим на другие языки программирования, например C, но Pawn от них фундаментально отличается. Не столь важно, чтобы вы сейчас же поняли его отличия, но они понадобятся, если вы уже знаете один из языков программирования:
- Pawn не собирает мусор
Pawn, как язык, не имеет встроенных ресурсов памяти, и потому он не мусорит. Если функция выделит память, то вы отвечаете за её освобождение. - Pawn не объектно-ориентированный язык
Pawn является процедурным и полагается на подпрограммы. Также у него нету C подобных структур. - Pawn не функциональный
Pawn является процедурным и не поддерживает функции “лямбды” (Lambda), поздние присвоения и все то, что можно найти в языках высшего уровня, таких как Python и Ruby. - Pawn однопоточный
- Pawn не интерпретируемый
Ну, почти. Он интерпретируется на очень низком уровне. Вы должны скомпилировать код, из которого получится бинарный файл. Эта программа будет работать на той платформе, которую использует сервер. Это ускоряет загрузку и позволяет легче находить ошибки.
Этот язык был выпущен ITB CompuPhase. Язык разработан для устройств низкого уровня и таким образом конечные программы очень маленькие по размеру и очень быстрые.
Идентификаторы/ключевые слова
Идентификаторы представляет собой набор букв, цифр и/или нижнего подчеркивания, что представляет собой нечто уникальное. Идентификаторы вводятся с учетом регистра (в отличие от PHP, где иногда это не требуется). Идентификаторы не начинаются с какого-либо специального символа, но они должны начинаться с буквы.
Есть несколько зарезервированных символов, которые имеют особое значение. Например, if , for и return специальные конструкции в языке, которые будут описаны позднее. Они не могут быть использованы в качестве названий идентификаторов.
Переменные
Существует несколько важных конструкций, которые вы должны знать, прежде чем приступить к написанию сценария. Во-первых, это переменные . Переменная - это идентификатор, который содержит данные. Например, переменная “a” может содержать числа “2”, “16”, “0” и так далее. Переменные создаются для хранения данных внутри программы. Переменные должны быть объявлены до их использования, с помощью ключевого слова "new ". Данные можно присвоить переменной, используя знак равенства (= ). Пример:
new a, b, c, d;
a = 5;
b = 16;
c = 0;
d = 500;
В SourcePawn, переменные бывают двух типов:
- Однострочные (могут содержать только произвольные числовые данные), как показано выше
- Многострочные (могут содержать целый ряд текстовых символов)
Однострочные могут содержать 32 бита цифровых данных. Многострочные - последовательный список из UTF-8 символов. Однострочные не имеет своего типа, однако они могут быть маркированы(tagged). Тег позволяет вам указывать, где определенную ячейку можно использовать. Типичные теги:
- (пусто), или _ - Нет тега. Обычно используют для целых чисел (Integers).
- Float - используют для чисел с плавающей точкой (небольших).
- bool - используют для хранения значений true (истина) или false (ложь).
Со строками все по другому, они будут рассмотрены далее.
Объявления
Примеры разных правильных объявлений переменных:
new a = 5;
new Float:b = 5.0;
new bool:c = true;
new bool:d = 0; // работает, поскольку 0 равно false (ложь)
Неправильные объявления переменных:
new a = 5.0; // несоответствие тегов. 5.0 должо быть с тегом Float
new Float:b = 5; // несоответствие тегов. 5 должно быть без тега.
Если переменная не определена в объявлении, то ее значение станет 0:
new a; // значение 0
new Float:b; // значение 0.0
new bool:c; // значение false
Присвоение
Переменным могут быть присвоены данные после создания. Пример:
new a, Float:b, bool:c;
a = 5;
b = 5.0;
c = true;
Комментарии
Примечания и любой текст, который пишется после "// " считается “комментарием”, а не фактическим кодом. Есть два стиля комментариев:
- // - двойная косая черта, всё, следующее после этой строки, игнорируется
- /* */ - много-строчный комментарий, весь текст, внутри звездочек, игнорируются
Массивы
Вы можете группировать код в виде “массивов”, разделенных { и }. Это фактически создает возможность работать с целым массивом как с одним оператором. Например:
{
здесь;
какой-то;
код;
}
Массивы с фигурными скобками используются достаточно широко в программировании. Массивы кода могут быть вложенными друг в друга. Это хорошая возможность адаптировать последовательность кода и сделать его удобочитаемым, благодаря отступам код не будет смотреться, как одна большая и длинная макаронина.
Массив объявляется с помощью квадратных скобок. Вот некоторые примеры массивов:
new players[32]; // Набор из 32 однострочных (числовых) данных
new Float:origin[3]; // Набор из 3 чисел с плавающей точкой
По умолчанию, массивам присваиваются нули. Вы можете присвоить им разные значения, однако:
new numbers[5] = {1, 2, 3, 4, 5}; // В numbers будут храниться значения 1, 2, 3, 4 ,5
new Float:origin[3] = {1.0, 2.0, 3.0}; // В origin будут храниться значения 1.0, 2.0, 3.0
Вы можете оставить массив без размера, если вы собираетесь заранее присвоить ему данные. Например:
new numbers[] = {1, 3, 5, 7, 9};
Компилятор будет автоматически делать вывод о том, что вы хотите получить массив размером 5.
Использование массива равносильно использованию обычной переменной. Единственное отличие состоит в том, что он должен быть индексируемым. Индексирование массива означает присутствие возможности выбрать элемент, который вы хотите использовать.
Вот пример кода с использованием индексов:
new numbers[5], Float:origin[3];
numbers[0] = 1;
numbers[1] = 2;
numbers[2] = 3;
numbers[3] = 4;
numbers[4] = 5;
origin[0] = 1.0;
origin[1] = 2.0;
origin[2] = 3.0;
Заметим, что индекс это текст, который находится в квадратных скобках. Индекс всегда начинается с нуля. То есть, если массив имеет N элементов, его действительный индекс от 0 до N-1. Доступ к данным с индексами работает так же, как с обычной переменной.
Использование неверного индекса вызовет ошибку. Например:
new numbers[5];
numbers[5] = 20;
Это может выглядеть верно, но число 5 не является допустимым индексом. Наибольшим значением индекса является число 4.
Вы можете использовать любые выражения, как индекс. Например:
new a, numbers[5];
a = 1; // сделает a = 1
numbers[a] = 4; // сделает numbers[1] = 4
numbers[numbers[a]] = 2; // сделает numbers[4] = 2
Строки
Строки являются удобным способом хранения текста. Символы хранятся в массиве. Строка ограничивается нулевым символом или 0. Без нулевого символа, Pawn не знает, где остановить чтение строки. Все строки в SourcePawn используют кодировку UTF-8.
Отметим, что строки имеют комбинацию из массивов и однострочных переменных. Это означает, что вы должны знать заранее, как много места будут использовать строки, поэтому строки не являются динамичными. Они могут лишь вырасти до размера, которым вы их ограничили.
Примечание для специалистов: они фактически не однострочные. SourcePawn использует 8-битные строки для хранения массивов в качестве оптимизации. Это и есть то, что делает строки типом, а не меткой.
Строки были созданы почти в равной степени и для массивов. Например:
new String:message[] = "Hello!";
new String:clams[6] = "Clams";
Это равносильно следующему:
new String:message[7], String:clams[6];
message[0] = 'H';
message[1] = 'e';
message[2] = 'l';
message[3] = 'l';
message[4] = 'o';
message[5] = '!';
message[6] = 0;
clams[0] = 'C';
clams[1] = 'l';
clams[2] = 'a';
clams[3] = 'm';
clams[4] = 's';
clams[5] = 0;
Хотя строки редко инициализируют таким образом, очень важно помнить о концепции нулевого символа, который свидетельствует о конце строки. Компилятор и большинство SourceMod функций будут автоматически остановлены нулевым символом, поэтому он является очень важным при манипулировании строками напрямую.
Заметим, что строка должна быть заключена в двойных кавычках, а символ в одиночных.
Символы
Особенность текста может быть использована в любой строке или однострочной переменной. Например:
new String:text[] = "Crab";
new clam;
clam = 'D'; // Устанавливает однострочной переменной значение 'D'
text[0] = 'A'; // Меняет 'C' на 'A', сейчас получилось 'Arab'
clam = text[0]; // Устанавливает однострочной переменной значение 'A'
text[1] = clam; // Меняет 'r' на 'A', сейчас получилось 'AAab'
То, что вы не можете сделать, это соотнести символы массивов со строками. Внутреннее хранение отличается. Например:
new clams[] = "Clams"; // Не верно, нужен тип String
new clams[] = {'C', 'l', 'a', 'm', 's', 0}; // Верно, но это НЕ СТРОКА.
Функции
Следующим важным понятием являются функции. Функции идентификаторов или имен, которые выполняют действия. Это означает, что когда вы их активируете, они выполняют конкретную последовательность кода. Есть несколько типов функций, но все функции активируется одинаковым образом. “Вызов функции” является термином ссылающимся на функцию действия. Функция числовых переменных строятся так:
функция(<параметры>)
Примеры:
show(56); // Активирует функцию "show" и присваивает ей число 56
show(); // Активирует функцию "show" без каких-либо данных, пустую
show(a); // Активирует функцию "show" и присваивает ей переменную с данными переменной a
Каждый фрагмент данных передаваемый вызываемой функции, называется параметр . Функция может иметь любое количество параметров (но есть “допустимый” предел в SourceMod: 32).
Существуют два типа вызова функции:
- Прямой вызов - вы специально вызываете функцию в своем коде.
- Обратный вызов - применение вызова функции в вашем коде, как если бы это было событием триггера (совокупность условий, инициирующих выполнение действия).
Существуют пять видов функций:
- native - прямая, внутренняя функция, предусмотренная в приложении.
- public - функция обратного вызова, что делает её видимой для приложения и других сценариев.
- normal - нормальная функция, которую вы можете только вызвать.
- stock - нормальная функция, которая если не используется, то не компилируется.
- forward - функция представляет собой глобальное событие, если вы его вызвали, то она будет исполняться.
Весь код в Pawn должен существовать в функциях. Это основное отличие от языков, таких как PHP, Perl и Python, которые позволяют вам писать глобальный код. Это происходит потому, что Pawn вызывается на основе другого языка: он реагирует на действия от родительского приложения и функции должны быть написаны для обработки этих действий.
В отличие от переменных функции не нужно объявлять, прежде чем использовать их. Функции имеют две части: модель и тело . Модель содержит имя вашей функции и параметры, которые она будет принимать. Тело представляет собой простой блок кода.
Пример функции:
AddTwoNumbers(first, second)
{
new sum = first + second;
return sum;
}
Это простая функция. Модель этой строки:
AddTwoNumbers(first, second)
Разберем по отдельности:
AddTwoNumbers - название функции.
first - название первого параметра, который представляет собой простой элемент.
second - название второго параметра, который представляет собой простой элемент.
В теле создается новая переменная sum и присваивается ей значение этих двух параметров, добавленных совместно (другие выражения будут позже). Важно заметить, что оператор return обозначает конец функции и возврат с полученными значениями из этой функции. Все функции возвращают значения после завершения. Это означает, например:
new sum = AddTwoNumbers(4, 5);
Приведенный выше код будет присваивать число 9 к sum. Функция получает два значения и передает новое значение sum в качестве возвращаемого значения. Если функция не имеет возвращаемого значения или не имеет значений для возврата, то возвращается 0 по умолчанию.
Функция может принимать любые типы значений. Она может вернуть любую однострочную переменную, но не массивы или строки. Пример:
Float:AddTwoFloats(Float:a, Float:b)
{
new Float:sum = a + b;
return sum;
}
Заметим, что если в приведенной выше функции вам вернулось не Float значение, то вы получите не соответствие значений.
Можно, конечно, передавать переменные в функции:
new numbers[3] = {1, 2, 0};
numbers[2] = AddTwoNumbers(numbers[0], numbers[1]);
Заметим, что однострочные переменные передаются по значению. То есть их значение не может быть изменено функцией. Например:
new a = 5;
ChangeValue(a);
ChangeValue(b)
{
b = 5;
}
Этот код не будет менять значение a. Это происходит потому, что копия этого значения передается в b вместо a самостоятельно.
public
Публичные функции используются для осуществления обратных вызовов. Вы не должны создавать какую-либо публичную функцию, если это вынудит выполнение обратного вызова. Например, вот два обратных вызова из sourcemod.inc:
forward OnPluginStart();
forward OnClientDisconnected(client);
Чтобы выполнить и получить эти два события, вы должны написать такие функции как:
public OnPluginStart()
{
/* Здесь код */
}
public OnClientDisconnected(client)
{
/* Здесь код */
}
Ключевое слово public делает функцию публичной, а также позволяет родительскому приложению непосредственно вызывать функцию.
native
Native имеют встроенные функции, предоставляемые приложением. Вы можете вызвать их, как если бы они были normal функциями. Например, SourceMod имеет следующие функции:
native FloatRound(Float:num);
Её можно вызвать таким образом:
new num = FloatRound(5.2); // Результат в num = 5