Операторы, функции, аргументы. GOSUB, FUNC, ARGS
Очень часто новички, пришедшие в QSP, задают вопрос: что такое ARGS, и как их использовать? И хотя информации по данному вопросу очень много, вся она довольно разрознена, отвечает на вопрос слишком общо, или слишком конкретно, вплоть до одного единственного примера.
Эта статья является попыткой ответить на данный вопрос максимально просто, точно и подробно, насколько это в принципе будет возможно.

Все примеры рекомендую испытать самостоятельно, побаловаться, изменяя значения, и посмотреть, что из этого выйдет. Теория без практики — мертва.
В статье приведены фрагменты кода QSP, некоторые из которых написаны в формате TXT2GAM. Такие фрагменты легко отличить, так как в них отдельными строками прописывается начало локации (# start) и её конец (--- start ---). Если вы работаете в редакторе Quest Generator, данные строчки прописывать в коде локаций не нужно, только содержимое между ними.
Пробуем воду. Немного определений
Операторы
При написании QSP-кода мы всегда используем различные операторы. Например, мы используем оператор *pl для вывода текста в окно основного описания, или оператор addobj для добавления предмета, или например оператор goto для перехода между локациями. Иными словами операторы нужны нам, чтобы что-то делать с игрой или с плеером, выполнять какие-то процедуры.
Если мы внимательно посмотрим на все эти операторы, то мы увидим, что для некоторых операторов, чтобы они что-то сделали, нужно обязательно указать какое-то значение. Например, чтобы оператор addobj добавил предмет в окно предметов, мы должны указать название предмета для этого оператора:
addobj "Апельсин"

Такое указание значения в программировании называется передачей значения оператору. Оператор принимает от нас указанное значение и что-то с ним делает. Например, создаёт предмет с указанным названием. Само значение, которое мы передаём оператору, нередко называют аргументом или параметром.
Поэтому если вы встречаете где-то такое выражение "передаю параметры оператору", или "указываю аргументы для оператора", это значит, что оператору передаются какие-то значения, которые он принимает и обрабатывает.
В случае с оператором addobj мы должны обязательно указать хотя бы один аргумент, но в QSP есть операторы, для которых можно совсем не указывать аргументы. Например, оператору clear, который очищает окно дополнительного описания, не нужны аргументы, он и так знает, что ему делать. Или оператор killall, ему тоже не нужен аргумент. Ещё, совсем без аргумента, может использоваться оператор *pl, тогда он просто выводит на экран пустую строку и совершает переход на новую строку в окне основного описания.
Некоторым операторам можно передавать больше одного аргумента. Если взглянуть на тот же оператор addobj, мы можем передавать ему и два и три аргумента:
addobj "Апельсин","pic/orange.png",4
В данном случае мы передаём оператору три аргумента: название предмета, картинку предмета, и позицию в окне предметов, в которую мы данный предмет хотим поместить. Аргументы перечисляются через запятую.
В зависимости от числа принимаемых аргументов, операторы могут вести себя по-разному:
killvar '$mass',4 & ! два аргумента. Удаляем ячейку номер 4 из массива $mass
killvar '$array' & ! один аргумент. Удаляем весь массив $array
killvar & ! без аргументов. Удаляет все массивы и переменные в игре
Нужно помнить, сколько аргументов ожидает получить каждый оператор. Операторы типа clear и killall вообще не ждут аргументов, поэтому передавать им какие-либо значения бесполезно. Оперторы типа addobj, goto и act ждут хотя бы один аргумент, но могут принимать больше. Если передать оператору больше аргументов, чем он может принять, или меньше, чем он ожидает, это приведёт к ошибке №120: "Неверное число аргументов оператора/функции", — поэтому будьте внимательны при написании кода и чаще сверяйтесь со справкой.

Функции
Помимо операторов в QSP есть ещё такие вещи, как функции. Функции тоже могут что-нибудь делать с игрой или плеером, но в основном они нужны, чтобы получать какие-либо значения.
Например, с помощью функции rnd мы можем получить случайное число от 1 до 1000:
num=rnd & ! в переменную num запишется случайное число от 1 до 1000
В этом и заключается основное отличие функции от оператора, функция умеет возвращать значение. Значение, возвращаемое функцией, ещё называют результатом, поэтому можно сказать, что функция возвращает результат.

Точно так же, как оператор, функция может принимать аргументы, однако аргументы, которые мы хотим передать функции, нужно заключать в круглые скобки, в отличие от операторов, которым такие скобки не нужны.
num=rand(1,1000) & ! аргументы функции rand заключены в скобки
killvar '$mass',9 & ! аргументы оператора killvar не нужно заключать в скобки
Таким образом функция принимает аргументы и возвращает результат. Или получает параметры и возвращает значение. Говорите, как вам удобнее, главное, чтобы вы понимали, что все эти слова значат.
В QSP есть много разных функций, и так же, как и операторам, им можно передавать разное число аргументов. Есть функции, которым вообще не нужно передавать никаких аргументов, например $selact, $curact, $curloc и т.д. А есть функции, которые требуют строго определённого числа аргументов, например isnum, которая ожидает всегда только один аргумент, не больше, не меньше. Ну и конечно же есть функции, которым можно передавать разное число аргументов, и чьё поведение будет меняться в зависимости от числа аргументов:
max('mass') & ! передан один строковый аргумент. Функция вернёт максимальное число из массива mass
max('mass','gass','dass') & ! передано три строковых аргумента. Функция вернёт тот из них, который идёт позже в алфавитном порядке
max(12) & ! данная строчка кода вызовет ошибку 114
max(12,34) & ! функция вернёт наибольшее из двух чисел (34)

Плаваем у берега. Пользовательские функции и процедуры
Как быть, когда нам не хватает функционала уже имеющихся в QSP функций и операторов? Что если нам нужно, например, постоянно выводить на экран значения некоторых переменных, причём не просто выводить, а выводить эти значения внутри определённой строки. Например, у нас есть вот такой набор переменных:
$name='Леголас'
age=137
$arms='Лук и стрелы'
И мы хотим периодически видеть на экране что-то вроде:
Вас зовут Леголас.
Вам 137 лет.
Вы используете Лук и стрелы.
Для этой ситуации в QSP предусмотрено создание собственных аналогов функций и операторов. Для этого мы должны создать отдельную локацию и написать на ней нужный код. В нашем случае локация будет называться "pl_stat":
! # pl_stat
*pl "Вас зовут <<$name>>."
*pl "Вам <<age>> лет."
*pl "Вы используете <<$arms>>."
И теперь, когда у нас есть такая локация, нам достаточно вызвать её с помощью оператора gosub в любом месте нашей игры, и мы увидим на экране нужный текст со вставленной информацией из наших переменных.
gosub 'pl_stat'
Очевидно, что работа данной локации стала похожа на работу некоей расширенной версии оператора *pl.
Что же дальше? А дальше нам хотелось бы написать какой-нибудь аналог функции, например, чтобы посчитать квадрат числа N. И мы можем это сделать. Создадим локацию "square" и на ней напишем следующий код:
! # square
R=N*N
Вот такой простой код. Если мы вызовем данную локацию с помощью func, то в переменную R у нас запишется результат возведения в квадрат числа N.
N=12
func('square')
*pl R & ! на экране появится число 144
Однако с таким же успехом мы можем вызывать данную локацию через gosub, ведь результат вычислений всё равно пишется в переменную R прямо на локации "square". Зачем нам здесь использовать func? Действительно незачем. Сейчас наша локация просто вычисляет значение и записывает его в переменную, однако мы можем заставить её возвращать значение! То есть она будет работать почти как полноценная функция QSP.
Чтобы заставить нашу локацию возвращать значение, мы должны результат вычислений записать в специальную системную переменную result.
! # square
result=N*N
Теперь наша локация будет возвращать нам значение аналогично любой встроенной в QSP функции. А значит мы можем присвоить значение переменной уже в той локации, из которой вызвали локацию "square":
N=12
R=func('square')
*pl R & ! на экране появится число 144
Или вообще не использовать промежуточную переменную:
N=12
*pl func('square') & ! на экране появится число 144
Уникальность переменной result заключается в том, что она действительно уникальная на каждой локации, из которой мы её вызываем. То есть, если мы на локации "start" (для примера) записали в переменную result какое-либо значение, то, если мы вызовем из локации "start" локацию "square", где мы тоже используем result, на локации "start" значение в переменной result не изменится. Это легко проверить:
# start
result=123 & ! записываем число в переменную result
N=24
*pl func('square') & ! выведет на экран 576
*pl result & ! выведет на экран 123
-- start --
# square
result = N * N
-- square --
И всё-таки наш код не такой удобный, как бы нам хотелось, ведь функциям QSP мы просто передаём аргументы, и не заполняем заранее разные переменные перед тем, как вызвать ту или иную функцию. Что же делать?
А всё очень просто. Разработчик QSP предусмотрел такую необходимость, и мы можем передавать нашей локации до девяти аргументов (в плеере версии 5.7.0). Просто берём и внутри круглых скобок ставим запятую после названия нашей локации "square" и указываем значение, которое хотим передать локации:
*pl func('square',25)
Вот и всё. На локацию "square" передан параметр, или иными словами локация "square" получила в качестве аргумента значение 25. Но как же это значение использовать на локации "square", ведь теперь мы не вносили его ни в одну переменную. А вся хитрость в том, что данное значение плеер сам автоматически вносит в специальный системный массив args. В данном случае число 25 нужно искать в нулевой ячейке этого массива на локации "square".
! # square
*pl args[0] & ! выведет на экран число, которое мы отправляем на эту локацию
result=args[0]*args[0] & ! вместо N мы теперь используем число из нулевой ячейки массива args
Попробуйте запустить код из предыдущего примера, но теперь без переменной N:
# start
result=123 & ! записываем число в переменную result
*pl func('square',24) & ! выведет на экран 576
*pl result & ! выведет на экран 123
-- start --
# square
*pl args[0] & ! поскольку выше мы передаём на square число 24, то эта команда выведет на экран число 24
result=args[0]*args[0]
-- square --
Итак, мы знаем, что можем передавать на нашу локацию-функцию до девяти аргументов. Мы нашли, где искать аргумент, если он один. А где же искать все остальные? Всё очень просто. Каждый последующий аргумент будет автоматически помещаться в следующую ячейку массива args. Таким образом, если мы передадим на локацию все девять аргументов, мы сможем их найти в массиве args, в ячейках с нулевой по восьмую.
Давайте напишем функцию, которая будет складывать девять чисел, передаваемых ей в качестве аргументов. Для этого создаём локацию "summ":
! # summ
result=args[0]+args[1]+args[2]+args[3]+args[4]+args[5]+args[6]+args[7]+args[8]
А вот примеры вызова этой локации "summ". Мы передаём на эту локацию девять слагаемых, а она возвращает нам сумму всех девяти слагаемых:
*pl func('summ',1,2,3,4,5,6,7,8,9) & ! на экране появится число 45
*pl func('summ',23,45,67,89,0,11,23,5,-1) & ! на экране появится число 262
Если мы укажем не все аргументы, значения несуществующих ячеек массива args на локации "summ" окажутся равны нулю:
*pl func('summ',1,2,3,4) & ! на экране появится число 10
*pl func('summ',23,45,67) & ! на экране появится число 135
С массивом args можно работать, как с обычным массивом QSP. Т.е. вы легко можете добавлять в него новые ячейки, или удалять с помощью killvar. Однако у этого массива есть одна особенность, которая роднит его с переменной result. Для каждой отдельной локации массив args уникален. То есть вы можете помещать в него любые значения, увеличивать или уменьшать его размер, это никак не повлияет на массив args в других локациях.
Более того. После того, как код локации, на которой вы работали с массивом args, будет выполнен, этот уникальный массив будет полностью уничтожен, а значит он уже не будет занимать место в оперативной памяти. Но, надо понимать, что в других работающих локациях свои собственные массивы args продолжат существовать.
! данный код выведет на экран последовательно числа 11, 1507, 19
# start
args[0]=19 & ! это нулевая ячейка массива args на локации start, ей присваиваем 19
*pl func('foo',11) & ! выведет на экран 1507
*pl args[0] & ! выведет на экран 19
-- start --
# foo
! на локации foo собственный уникальный массив args,
! в который автоматически записываются значения, переданные на локацию в виде аргументов
*pl args[0] & ! выше сюда было передано число 11, вот его мы и увидим на экране
result = args[0] * 137
-- foo --
Давайте вернёмся к локации "pl_stat", она, как вы помните, выводила на экран значения определённых переменных. Теперь мы можем переписать и её, чтобы она работала не с конкретными переменными, а с аргументами, которые мы на эту локацию передаём.
! # pl_stat
*pl "Вас зовут <<$args[0]>>."
*pl "Вам <<args[1]>> лет."
*pl "Вы используете <<$args[2]>>."
Как видите, мы подставляем в строки данные из $args[0], args[1] и $args[2], где соответсвенно мы должны найти имя, возраст и оружие персонажа. Значит и передавать на "pl_stat" аргументы мы должны в той же последовательности:
$name='Леголас'
age=137
$arms='Лук и стрелы'
! вызов локации `pl_stat`
gosub "pl_stat",$name,age,$arms
Это даст на экране всё тот же текст:
Вас зовут Леголас.
Вам 137 лет.
Вы используете Лук и стрелы.
Само собой, если нет необходимости, заранее объявлять переменные не обязательно:
gosub "pl_stat",'Леголас',137,'Лук и стрелы'
И теперь, один раз написанная локация позволяет нам выводить текст с нужными вставками сколько угодно раз:
gosub 'pl_stat','Гимли',91,'Молот'
gosub 'pl_stat','Арагорн',29,'Палаш'
gosub 'pl_stat','Фродо Бэггинс',19,'Кольцо Всевластия'
Вот результат таких вызовов:
Вас зовут Гимли.
Вам 91 лет.
Вы используете Молот.
Вас зовут Арагорн.
Вам 29 лет.
Вы используете палаш.
Вас зовут Фродо Бэггинс.
Вам 19 лет.
Вы используете Кольцо Всевластия.
Объявить переменную — для QSP значит записать в программе её имя. Когда мы присваиваем переменной какое-либо значение, мы тем самым автоматически объявляем переменную. Ещё это называется инициализировать переменную.