Ru:Fundamental Basics of AMX Mod X Scripting

From AlliedModders Wiki
Jump to: navigation, search

Фундаментальные основы AMX Mod X скриптинга

Данная статья не дает готовых "рецептов" по AMX Mod X скриптингу, но раскрывает его фундаментальные основы. Это и типы данных, и прототипы функций и многое другое, без знания чего невозможно писать AMX Mod X плагины.


Введение

Прежде чем начинать писать AMX Mod X плагины, в первую очередь необходимо разобраться в основах Pawn.

Pawn – это скриптовый язык, созданный компанией ITB CompuPhase. Ранее Pawn назывался Small, но с версии 3.0 языку было решено дать более характерное название. Т.к. "pawn" в переводе с английского языка означает "пешка", можно догадаться, что основной характерной чертой данного языка является простота.

Если вы владеете английским языком, рекомендуется ознакомиться с полным руководством по Pawn - Pawn The Language.

Код плагина представляет собой текст (как правило, заключенный в файл типа *.sma), включающий множество элементов языка: комментарии, переменные, функции и др. Поэтому для оформления кода потребуется текстовый редактор. Из простейших можно выделить, например, Microsoft Notepad. Также существует AMXX-Studio – специализированый редактор для AMX Mod X плагинов, позволяющий максимально эффективно работать в соответствующей среде. Последняя версия данного редактора может быть найдена в секции downloads официального сайта AMX Mod X.

Чтобы позволить AMX Mod X выполнять код, файл с кодом необходимо откомпилировать с помощью компилятора AMXXPC (AMX Mod X Pawn Compiler). Операция компилирования преобразовывает набор текстовых инструкций в последовательность инструкций абстрактной машины (от англ. abstract machine), она же AMX, а также интерпретатор (от англ. interpreter).

Откомпилированый код обычно помещается в файл типа *.amxx, имеющий двоичный формат. Такой файл называют AMX Mod X плагином (от англ. plugin).

Для ознакомления с инструкциями по компилированию и установке плагинов смотрите Ru Compiling Plugins (AMX Mod X) и Ru Configuring AMX Mod X.


Pawn

Уровни кода

В основе любого кода лежит уровневая структура. Причиной этому является нелинейность инструкций.

Т.н. "нулевой уровень" или "глобальное пространство" является неотъемлемой частью любого кода. Как только вы приступаете к написанию нового кода, вы оказываетесь в его глобальном пространстве. Оно же в свою очередь будет включать в себя пространства с более низкими уровнями.

Обычно глобальное пространство содержит всю общую информацию, которая может потребоваться в ходе выполнения кода пространствам более низкого уровня. Примером такой информации могут служить общие константы, переменные, списки, макросы.


Комментарии

Комментарий – это, как правило, текст информативного характера. Например, чтобы дать описание плагину можно использовать многострочный комментарий:
/* здесь вы помещаете
необходимую информацию */
Как видно из примера, многострочный комментарий ограничивается символами /* и */, отмечающими начало и конец комментария соответственно. Недопустимо открывать последующий комментарий, не закрыв при этом предыдущий.
Примером однострочного комментария может быть:
/* ваш комментарий */
Хотя, можно упростить конструкцию:
// ваш комментарий


Как видно из последнего примера, комментарий начинается с двойного симовола обратного слеша //. Заканчивается такой комментарий вместе с концом текущей строки, поэтому не требует закрывающих символов. Такие комментарии могут быть только однострочными. Не обязательно комментарий должен начинаться с новой строки. Например, /* ... */ комментарий может быть расположен непосредственно в самом коде.

Т.к. комментарии не считаются кодом, они исключаются из обработки. Данная особенность позволяет при необходимости исключать части кода путем комментирования, что может быть использовано, например, при отладке кода.


Типы данных

Технически основой всех типов данных в Pawn является т.н. "cell" – ячейка памяти, состоящая из последовательности определенного количества бит. Количество бит в такой ячейке постоянно для определенной платформы. Так для 32х битной платформы оно будет составлять 32, а для 64х битной – 64.



Т.к. технически все данные не имеют отличия, для обозначения их типа применяются т.н. "тэги". Если при создании переменной тэг не указан, то переменная является целым числом:
new ivar // создана целочисленная переменная с именем "ivar"

Если при создании переменной ей не присваивается какое-либо конкретное значение, то переменная будет равна нулю. Таким образом, вышеприведенный пример технически соответствует:
new ivar = 0

Чтобы создать дробную переменную, необходимо использовать тэг "Float":
new Float:fvar
Переменная fvar также будет равна нулю, но ввиду соответствия типу данных это будет 0.0, т.е. технический аналог примера будет:
new Float:fvar = 0.0
Таким образом, мы обеспечиваем типовое соответствие "левой" и "правой" части.
Одним из особых типов данных является т.н. "булевые" переменные, которые технически могут иметь только два значение, логически интерпретируемые как "истина" и "ложь" (true и false). Чтобы создать булевую переменную, необходимо использовать тэг "bool":
new bool:bvar
Т.к. численно false является нулем, то технический аналог примера будет выглядеть как:
new bool:bvar = false

Примечание: чтобы запретить изменение значения переменной, необходимо использовать атрибут const, например:
new const var = 1

Pawn также позволяет создавать массивы данных, представляющие собой набор значений определенного типа. Так примером простого массива, содержащего два целочисленных значения будет:
new array[2]
Технически данный пример выглядит следующим образом:
new array[2] = {0, 0}
Технические аналоги для Float и bool массивов выглядят как:
new Float:farray[2] = {0.0, 0.0}
new bool:barray[2] = {false, false}

Также существует особый тип данных, называемый "строка", технически являющийся массивом целых чисел. Каждое целое число в строковом массиве соответствует числовому ASCII коду символа. Например, код 32 соответствует пробелу. Таким образом,
new string[3] = {'h', 'i', '^0'}
является символьным представлением строкового массива
new string[3] = "hi"
Обратите внимание на наличие специального символа '^0'. Это обязательный элемент строки, указывающий на ее окончание (численно равен нулю).
Также Pawn поддерживает многоуровневые массивы, например:
new multiarray[2][2]
Технически такой массив равен:
new multiarray[2][2] = {{0, 0}, {0, 0}}
Максимальное количество уровней массива равно 3.
Для любого явно заданного массива его размер может не указываться, например:
new array[] = {1, 2, 3} // размер массива равен 3
new string[] = "hello" // размер массива равен 6, т.к. помимо 5 символов также включает в себя символ окончания строки '^0'

Чтобы инициализировать все элементы массива каким-либо конкретным значением, можно использовать символ троеточия ..., например:
new bool:is_active[16] = {true, ...}


Прекомпиляция

На начальной стадии компилирования Pawn компилятор обрабатывает т.н. "статическую" часть кода. К ней можно отнести директивы, глобальные константы, списки и др.



Директивы являются специальными инструкциями компилятора. Одна из самых широко используемых директив – это "include". Пример ее использования может быть следующим:
#include <amxmodx>
Директива как бы "влаживает" содержимое указанного файла в текущую позицию. В данном случае указывается файл amxmodx.inc, который обычно расположен в amxmodx\scripting\include директории, и декларирует основные AMX Mod X функции.
Другая директива, позволяющая конструировать т.н. "макросы", имеет название "define". Макрос удобен тем, что способен заменять простые блоки кода. Один из простейших макросов – это т.н. "макроконстанта", например:
#define VALUE 1
В названиях макросов принято использовать буквы только верхнего регистра.
Нетехническим аналогом вышеприведенной макроконстанты является т.н. "глобальная константа":
stock const value = 1
stock атрибут позволяет не включать константу в плагин, если она не используется в коде.
Т.н. "список" – это набор нумерованных элементов определенного типа. Например:
enum {zero, one, two} // соответствует 0, 1, 2
enum {elem1 = 1, elem2, elem3} // соответствует 1, 2, 3
enum steeps {steep1 = 10, steep2 = 20} // соответствует 10, 20
Тип списка (в вышеприведенном примере типом списка является steeps) – необязательный атрибут, по сути является тэгом, указывающим тип элементов, поэтому следующий пример является верным:
new steeps:steep = steep2


Функции

Существует несколько типов функций.

  • т.н. "обычная" – обычно т.н. "вспомогательная" функция; может быть вызвана непосредственно только другими функциями данного плагина; не может быть вызвана непосредственно AMX Mod X либо его модулями.
  • public – обычно функция, вызываемая непосредственно AMX Mod X либо его модулями; может быть также вызвана непосредственно другими функциями данного плагина.
  • native – функция, имеющая т.н. "глобальный" характер; может быть вызвана AMX Mod X плагинами.
  • forward - функция, имеющая т.н. "глобальный" характер; при наличии в плагине public функции с соответствующим именем функция будет вызываться AMX Mod X или его модулями.
  • stock – обычно функция, состоящая в т.н. "библиотеке" функций; не включается в плагин, если не используется в коде.

В общем виде заголовок, а также прототип любой функции условно выглядит следующим образом (символами треуголных скобок <> ограничены обязательные элементы, символами квадратных скобок [] ограничены необязательные элементы, которые могут быть опущены в определенных случаях):
[type] [tag]:<name>([[param1], [param2], ..., [paramN]])
, где
  • [type] – соответствует типу функции (для обычной функции тип не указывается)
  • [tag] – соответствует типу данных возвращаемого результата функции
  • <name> - соответствует имени функции
  • ([...]) – соответствует набору параметров функции; количество параметров может быть от нуля (параметры отсутствуют) до N, где N – целое положительное число

За заголовком функции следует т.н. "тело" функции, ограниченное символами фигурных скобок {}. Таким образом, примером простейшей функции является:
function()
{
	// это пустое тело функции, имеющей имя "function"
}
Технически данная функция не выполняет никаких действий, т.к. в своем теле содержит только комментарий. Обратите внимание, что комментарий как бы сдвинут вправо относительно заголовка функции. В данном случае функция инициализирует новый уровень кода. Каждый уровень кода в целях удобочитаемости принято оформлять с соответствующим отступом. Чем более низкий уровень кода, тем больший отступ он будет иметь. Обычно в качестве отступа принято использовать символ табуляции, т.к. многие редакторы позволяют задавать видимую ширину табулированого отступа.


Существует два способа передачи параметров функции. Т.н. "основной" способ заключается в том, что при передаче данные дублируются путем создания соответствующих копий в памяти. Т.н. способ передачи параметров "по ссылке" (от англ. by reference) состоит в том, что данные передаются "как есть", т.е. их дублирования не осуществляется, что позволяет функции изменять оригинальные данные непосредственно.



Для параметров функции все типы массивов, в том числе и строковые, могут передаваться исключительно по ссылке. Передача остальных данных по умолчанию осуществляется основным способом. Чтобы произвести передачу параметра функции по ссылке, в заголовке функции перед параметром необходимо добавлять символ амперсанда &, например:
function(&Float:fparam)

Параметры функции могут иметь значения по умолчанию, например:
function(param = 1)
Таким образом, чтобы вызвать данную функцию с параметром по умолчанию, параметр можно не указывать:
function()
Также в таких случаях можно использовать символ подчеркивания _:
function(_)

Чтобы запретить функции изменение передаваемых данных, необходимо использовать атрибут const, например:
function(const array[])
Таким образом, можно утверждать, что в данном случае целью функции не является изменение данных массива, чего нельзя утверждать о следующей функции:
swaparray(array[2])
Исходя из названия функции, типа параметра, его размера и отсутствия запрета на изменение, можно сделать предположение о том, что данная функция меняет элементы массива местами.
Важным свойством функции является способность возвращать результат, значение которого имеет определенный тип, например:
bool:is_true()
{
	return true
}

Чтобы указать допустимые типы параметра функции, в заголовке функции перед параметром необходимо добавлять конструкцию, которая условно выглядит как {tag1, tag2, ..., tagN}:, т.е. представляет собой набор тэгов, количество которых может быть от одного до N, где N – целое положительное число, например:
get_integer({bool, _}:value)
{
	return _:value // возвращается значение типа "целое число"
}
Смысл вышеприведенной функции заключается в том, что она принимает параметр как типа "целое число", так и булевого типа, в результате возвращая значение параметра в числовой форме.


AMX Mod X

Функции

При написании AMX Mod X плагинов важно уметь понимать назначение и принцип действия AMX Mod X функций.

Прототипы функций, а также используемые ими константы и списки заключены в файлы типа *.inc, т.н. "инклуды" (от англ. include), которые обычно расположены в amxmodx\scripting\include директории.

Иногда одни инклуды включают в себя другие. Так основные AMX Mod X функции (т.н. функции AMX Mod X "ядра") продекларированы в файле amxmodx.inc, который также включает векторные и другие инклуды, декларирующие функции, константы и списки, также имеющие непосредственное отношение к AMX Mod X ядру.

Одной из основных функций AMX Mod X является forward функция plugin_init. Если в коде плагина имеется public функция plugin_init, она будет вызвана AMX Mod X после загрузки карты на сервере. Обычно в plugin_init регистрируют сам плагин, его команды, переменные и т.п.



Плагин регистрируют с помощью native функции register_plugin. Т.к. прототип register_plugin соответствует:
native register_plugin(const plugin_name[], const version[], const author[])
, можно понять, что в качестве параметров функция принимает три строковых массива, соответствующих названию плагина, номеру его версии и автору плагина.
Итак, примером простого AMX Mod X плагина является:
#include <amxmodx>
 
public plugin_init()
{
	register_plugin("Test", "0.1", "VEN")
}
По сути данный плагин не выполняет каких-либо действий, но регистрирует себя в AMX Mod X с указанными данными.


Системы функционирования

AMX Mod X предоставляет большое количество определенных систем функционирования. Так одна из основных систем – это система контроля уровней доступа. Здесь имеют место соответствующие функции, например get_user_flags, а также константы стандартных уровней доступа типа ADMIN_*.

Система регистрирования и контроля консольных команд также "пересекается" с системой контроля уровней доступа и использует такие функции, как например register_concmd и cmd_access.

Также существует множество других систем, среди которых имеется система регистрирования и контроля серверных консольных переменных (англ.: Server CVars), клиентских меню и др.

Как правило, системы регистрирования используют т.н. "handle" (или "hook") функции, которые вызываются системой в необходимый момент.



Так после регистрирования консольной команды "action":
register_concmd("action", "action_handler")
при исполнении данной команды из консоли сервера или клиента система попытается найти и выполнить public функцию action_handler.


Система строкового форматирования

Многие AMX Mod X функции используют систему строкового форматирования. Например, это все *_print функции.



Такие функции принимают практически неограниченное количество параметров, которые используются при форматировании соответствующей строковой конструкции. Для наглядности рассмотрим пример:
server_print("Formatted string: %d %f %s %c", 1, 1.234567, "hello", '!')
Функция server_print отформатирует строковую конструкцию "Formatted string: %d %f %s %c", используя переданные параметры, и выведет в серверной консоли строку:
Formatted string: 1 1.234567 hello !
Таким образом, %d, %f, %s, %c – специальные элементы, используемые для форматирования целого числа, дробного числа, строки и символа соответственно.
Опасно при использовании функций, поддерживающих форматирование, передавать строковую переменную непосредственно в параметр строковой конструкции, например:
server_print(string)
Безопасный вариант выглядит, следующим образом:
server_print("%s", string)

Т.к. символ процента % является, т.н. "специальным символом", для его форматирования в строковой конструкции необходимо использовать двойной символ процента %%, например:
server_print("This is a single symbol of percent: %%")
выведет в серверной консоли:
This is a single symbol of percent: %

Также существует т.н. "контрольный символ" (от англ. control character), позволяющий использовать т.н. "специальные символы форматирования". Контрольным символом по умолчанию является символ ^, который может быть изменен директивой pragma ctrlchar, например:
#pragma ctrlchar '\'
изменит контрольный символ на \.


Примеры некоторых специальных символов форматирования приведены ниже:

  • ^t – табуляция
  • ^n – новая строка
  • ^xHH – символ, представленный в шестнадцатиричном формате, где HH – двойное число, представленное в шестнадцатиричном формате