Статья «OBSE в Массы» специально для TESALL.RU

Операторы

Операторы присваивания  
:= Присвоить
+= Прибавить и присвоить
-+ Отнять и присвоить
*= Умножить и присвоить
/= Разделить и присвоить
^= Возвести в степень и присвоить
Логические операторы  
|| Логическое ИЛИ
&& Логическое И
< Строго меньше
> Строго больше
>= Больше или равно
<= Меньше или равно
== Равно
!= Не равно
! Логическое отрицание (НЕТ)
Математические операторы  
+ Прибавление
- Вычитание
* Умножение
/ Деление
% Остаток от деления
^ Возведение в степень
Побитовые операторы  
>> Побитовый сдвиг влево
<< Побитовый сдвиг вправо
| Побитовое ИЛИ
& Побитовое И
! Побитовое НЕТ
Операторы для работы с данными
:: Пара "ключ"::"значение"
-> Оператор обращения к элементу массива Массив -> Элемент, тоже, что и Массив[Элемент].
$ Строка
# Номер

Оператор присваивания

Для всех новых конструкций языка стоит использовать новый оператор присваивания Let вместо стандартного Set. Синтаксис:

Let <имя_переменной> := <значение>

Например:

Let a := 10

Что эквивалентно:

Set a to 10

Также можно использовать вместо знака присваивания знаки: прибавить и присвоить (+=), отнять и присвоить (-=), умножить и присвоить (*=), поделить и присвоить (/=), возвести в степень и присвоить (^=):

Let a := 10    ; Set a to 10
Let b := 2     ; Set b to 2
Let c := a + b ; Set c to a + b 
               ; c == 12
Let c += b     ; Set c to c + b 
               ; c == 14
Let c -= a     ; Set c to c - a 
               ; c == 4
Let c *= a     ; Set c to c * a 
               ; c == 40
Let c /= b     ; Set c to c / b 
               ; c == 20
Let c ^= b     ; Set c to c ^ b 
               ; c == 400

Проверка истинности

В связи с тем, что некоторые новые конструкции языка, появившиеся с OBSE, некорректно работают со стандартными конструкциями, был введён новый оператор If. Так следующий фрагмент кода не будет работать:

ScriptName SomeScript
array_var SomeArr
Begin GameMode
      Let SomeArr := ar_Construct Array
      Let SomeArr[0] := GetSelf
      If SomeArr[0] == Player
            ;…
      EndIf
End

Его стоит переписать следующим образом:

ScriptName SomeScript
array_var SomeArr
Begin GameMode
      Let SomeArr := ar_Construct Array
      Let Arr[0] := GetSelf
      If Eval (SomeArr[0] == Player)
            ;…
      EndIf
End

Обработка исключений

В OBSE существует оператор TestExpr, он служит для проверки исключений. Что такое исключение? Исключение - это события, приводящие к ошибке. Этим оператором стоит пользоваться, чтобы избежать ошибок в ходе работы скрипта. Например, к заранее непредвиденным ошибкам может приводить выход индекса массива за диапазон допустимых значений, продемонстрируем это в следующем примере:

ScriptName SomeScript
array_var Arr
short Iterator
Begin GameMode
      Let Arr := ar_Construct Array
      Let Arr[0] := 1
      Let Arr[1] := 2
      Let Arr[2] := 3
      While Iterator < 10
            If TestExpr (Arr[Iterator])
                  Print ToString Arr[Iterator]
            Else
                  Break
            Endif
            Let Iterator += 1
      Loop   
End

Операторы ToString и ToNumber

Операторы ToNumber применяются для преобразования строк в числа и чисел в строки соответственно.

Print ToString 10
; Оператор ToString имеет сокращение: $
; Следующая строка эквивалентна предыдущей
Print $10

Оператор ToNumber и его сокращение # преобразовывает строку, содержащую численные символы, в численное значение.

Let a += ToNumber "10"
; a == 10

Возможно преобразование строки, содержащей шестнадцатеричное значение, если она начинается с символов "0x" или установлен флаг bIsHex

Let a += ToNumber "0xFF" 
; a == 255
Let a += ToNumber "abc" 1
; a == 2748

Оператор TypeOf

Оператор TypeOf возвращает строку с типом данных. Возможные значения:

"String" Строка
"Number" Число
"Form" Форма
"Array" Индексный массив
"Map" Вещественный массив
"StringMap" Ассоциативный массив

Операторы цикла

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

Понятие цикла не таит в себе ничего невероятного, это просто обусловленное циклическое повторение операторов замкнутых в тело цикла.

Обратите внимание на то, что в теле операторов цикла (это не касается операторов безусловного перехода) нельзя использовать ключевое слово Return.

Операторы безусловного перехода Label

Операторы безусловного перехода служат для управления ходом выполнением скрипта. Оператор Goto.

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

ScriptName SomeScript
Begin GameMode
  ; …
  Label 1 ; Метка 1
  ; …  
  Label 2 ; Метка 2
  If ; Если что-то произошло
    ; Переходим к метке 1
    Goto 1
  Else
    ; Иначе – к метке 2
    Goto 2
  EndIf
End

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

Пожалуй, стоит упомянуть, что операторы RestoreIP соответственно, какие из них и в какой комбинации использовать – зависит только от вас.

Цикл с предусловием While

Синтаксис:

ScriptName SomeScript
Begin GameMode
  While ; Условие
    ; Тело цикла
  Loop
End

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

ScriptName SomeScript
short i
Begin GameMode
  While i < 10
    Let i += 1
    Print ToString i
  Loop
End

Цикл перебора массивов ForEach

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

В случае с контейнерами переменная-итератор должна иметь тип ref. За каждый проход цикла она будет получать значение ссылки на следующий объект выбранного контейнера.

Следующий скрипт находит количество непустых камней душ в инвентаре игрока.

ScriptName GetPlayerSoulGemCount
ref Iterator
short SoulGemCount
Begin ScriptEffectStart
  ForEach Iterator <- GetSelf
    If Iterator.IsSoulGem && Iterator.GetSoulLevel > 0
      Let SoulGemCount += Iterator.GetRefCount
    EndIf
  Loop
  Print ToString SoulGemCount
End

Если цикл используется для перебора массивов, то переменная-итератор должна быть типа array_var. За каждый проход цикла два элемента массива-итератора (переменной-итератора), "key" и "value" получают следующую пару значений «ключ – значение» итерируемого массива.

ScriptName ArrayForEachScript
array_var Arr
array_var StrMp
array_var Mp
array_var Iterator
Begin ScriptEffectStart
  Let Arr   := ar_Construct Array
  ; Arr – индексный массив
  Let StrMp := ar_Construct StringMap
  ; StrMp – ассоциативный массив
  Let Mp    := ar_Construct Map
  ; Mp – вещественный массив
; ----------------------------------------------------------------------
  Let Arr[0] := 100500
  Let Arr[1] := 100501
  Let Arr[2] := 100502
  ForEach Iterator <- Arr
    ; Первый проход скрипта
    ; Iterator["key"]   == 0         Ключ
    ; Iterator["value"] == 100500    Значение
    ; Второй проход скрипта
    ; Iterator["key"]   == 1         Ключ
    ; Iterator["value"] == 100500    Значение
    ; Третий проход скрипта
    ; Iterator["key"]   == 2         Ключ
    ; Iterator["value"] == 100500    Значение
  Loop
; ----------------------------------------------------------------------
  Let StrMp["Elem0"] := 100500
  Let StrMp["Elem1"] := 100501
  Let StrMp["Elem2"] := 100502
  ForEach Iterator <- StrMp
    ; Первый проход скрипта
    ; Iterator["key"]   == "Elem0"   Ключ
    ; Iterator["value"] == 100500    Значение
    ; Второй проход скрипта
    ; Iterator["key"]   == "Elem1"   Ключ
    ; Iterator["value"] == 100500    Значение
    ; Третий проход скрипта
    ; Iterator["key"]   == "Elem2"   Ключ
    ; Iterator["value"] == 100500    Значение
  Loop
; ----------------------------------------------------------------------
  Let Mp[0.15] := 100500
  Let Mp[3.14] := 100501
  Let Mp[6.21] := 100502
  ForEach Iterator <- Mp
    ; Первый проход скрипта
    ; Iterator["key"]   == 0.15      Ключ
    ; Iterator["value"] == 100500    Значение
    ; Второй проход скрипта
    ; Iterator["key"]   == 3.14      Ключ
    ; Iterator["value"] == 100500    Значение
    ; Третий проход скрипта
    ; Iterator["key"]   == 6.21      Ключ
    ; Iterator["value"] == 100500    Значение
  Loop
End

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

Для примера составим небольшой скрипт, который будет искать заданное слово в строке.

ScriptName StringForEachScript
string_var Source   ; Строка-источник, в ней и будем искать слово.
                    ; Итерируемая переменная.
string_var Iterator ; Переменная-итератор
string_var Tmp      ; Промежуточная переменная
string_var Valid    ; Искомая строка.
Begin GameMode
  ; Задаём значение строке, в которой будем искать слово.
  Let Source := "Honey, I'm home."
  ; Задаём значение строке, которую будем искать.
  Let Valide := "home"
  ForEach Iterator <- Source
    ; Если найдём в строке символ пробела, или символы пунктуации
    If Eval (Iterator == " " || Iterator == "," || Iterator == "." )
      ; Очистим промежуточную строку и
      sv_Destruct Tmp
      ; пропустим текущую фазу цикла.
      Continue
    ; Иначе
    Else
      ; Будем наращивать промежуточное значение символами из строки-источника.
      Let Tmp += Iterator
    EndIf
    ; Как только найдём то, что искали -
    If Eval (Tmp == Valid)
      ; - скажем об этом
      Print Valid + "was found"
      ; и прервём цикл.
      Break
    EndIf
  Loop
End

Операторы Break и Continue

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

ScriptName SomeScript
short Var
Begin GameMode
  ; Зацикливаем : условие всегда истинно (бесконечный цикл)
  While 1
    ; Давайте выведем в консоль только парные числа: от двух до двадцати
    ; Увеличиваем значение переменной на единицу
    Let Var += 1
    ; Если значение нашей переменной превысило 20 -
    If Var > 20
      ; - прерываем цикл.
      Break
    EndIf
    ; Проверяем парность значения переменной
    If Var % 2 != 0
      ; Если число непарное – прерываем текущую фазу цикла и переходим к следующей.
      ; Все последующие операторы в теле цикла не будут выполнены в этой фазе.
      Continue
    EndIf
    ; Выводим значение переменной в консоль
    Print ToString Var
  Loop
End

Массивы и строки

В OBSE появилось два принципиально новых типа данных, это массивы (array_var) и строковые переменные (string_var).

Массивы являются неотъемлемым элементом любого языка программирования, так как представляют собою очень удобную и гибкую систему управления данными. Теперь они доступны и скриптерам для игры TES IV: Oblivion и серии игр Fallout 3.

Что такое массивы?

Массив – это область хранения данных, объединенная под общим именем. Доступ к элементам массива осуществляется по ключу. В зависимости от того, что представляет собою ключ, массивы делятся на индексные, вещественные и ассоциативные.

Основными характеристиками массива есть его имя и размер. Массивы могут быть одномерными и многомерными, но, в основном, используются только одно-, двух- и реже трёхмерные массивы.

Индексные массивы

Массив, ключами элементов которого есть множество натуральных чисел, включая ноль, называется индексным.

Для создания индексного массива следует вызвать функцию ar_Construct с аргументом Array:

ScriptName ArrayExample
array_var SomeArr
Begin GameMode
  Let SomeArr := ar_Construct Array
  ; Теперь SomeArr – индексный массив
End

Теперь массив стал доступным для работы. Можем присваивать значение его элементам, умножать, делить, в общем обращаться как с обычными переменными.

ScriptName ArrayExample

array_var Arr1

Begin GameMode
      Let Arr1 := ar_Construct Array
      Let Arr1[0] := "Этому элементу присвоено строковое значение"
      Let Arr1[1] := 107
End

Индексные массивы лучше подходят для использования в автоматизированных процессах, поскольку намного проще оперировать численными значениями ключей элементов массива.

Для примера рассмотрим следующий скрипт, который создаёт десять элементов массива, присваивая им точно такие значения, будем использовать уже знакомый нам цикл While:

ScriptName SomeScript
array_var Arr
short Tmp
Begin GameMode
  Let Arr := ar_Construct Array
  While Tmp < 10
    Let Arr[Tmp] := Tmp
    Let Tmp += 1
  Loop
End

Значение переменной Tmp меняется в диапазоне от нуля до девяти. В цикле создаются десять элементов массива, которым присваивается текущее значение переменной. Если вывести по порядку содержимое массива Arr, то мы увидим следующее:

0
1
2
3
4
5
6
7
8
9

Как я уже говорил раньше, но, возможно, вы не поняли, отсчёт элементов индексных массивов всегда следует начинать с нуля. Т.е. первый элемент всегда нулевой, второй – первый, и так далее.

Let Arr1 := ar_Construct Array
Let Arr1[0] := 100500 ; Это правильная последовательность
Let Arr1[1] := 100501 ; задания значения элементам массива
Let Arr1[2] := 100502
; …
; --------------------------------------------------------
Let Arr2 := ar_Construct Array
Let Arr2[1] := 1 ; Это неправильная последовательность!
Let Arr2[2] := 2 ; Элементы с индексами 1, 2, 3 не могут
Let Arr2[3] := 3 ; существовать, так как элемент с
                 ; индексом 0 не существует

Стоит помнить, что в OBSE массивы безразмерные, т.е. нет необходимости заранее задавать размер массива и чётко его придерживаться в процессе написания, а в последующем и обеспечивать это в процессе выполнения скриптов, но нельзя допускать выход индекса за диапазон его допустимых значений (размер массива).

Как мы уже успели увидеть, элементы массивов могут принимать любое значение: целое, дробное, строковое и т.д.

Ассоциативные массивы

Массив, ключами элементов которого есть строковые константы, называется ассоциативным.

Для создания ассоциативного массива следует вызвать функцию ar_Construct с аргументом StringMap:

ScriptName ArrayExample
array_var SomeArr
Begin GameMode
  Let SomeArr := ar_Construct StringMap 
  ; SomeArr – ассоциативный массив 
End

Давайте создадим несколько элементов ассоциативного массива:

ScriptName ArrayExample
array_var SomeArr
ref this
Begin GameMode
  Let SomeArr := ar_Construct StringMap 
  ; SomeArr – ассоциативный массив 
  Let SomeArr["Reference"] := this := GetSelf 
  Let SomeArr["Name"] := this.GetName
  Let SomeArr["Health"] := this.GetAV Health
End

В случае ассоциативного массива совершенно не важна последовательность ключей массива.

Запомните, что ключом ассоциативного массива в OBSE может быть только строковая константа, применение индексов недопустимо! Во всех остальных отношениях индексные и ассоциативные массивы одинаковы.

Ассоциативные массивы, безусловно, проще с точки зрения человеческого восприятия, но ими сложнее манипулировать в автоматизированных операциях программ, опять же, с точки зрения нашего восприятия.

Вещественные массивы

Вещественные массивы ранее никогда не встречались мною ни в одном языке программирования, с которым приходилось работать, да и при подготовке этого учебника мне не удалось найти что-то подобное в литературе, поэтому я взял на себя право дать им такое название исходя из их сущности и, конечно, моего мнения. Если где ошибся – поправьте.

Итак. Вещественным массивом будем назвать массив, ключами элементов которого есть множество вещественных чисел.

Для вещественных массивов не важен порядок ключей, как для индексного массива.

Создаётся вещественный массив функцией ar_Construct с аргументом Map.

ScriptName ArrayExample
array_var SomeArr
Begin GameMode
  Let SomeArr               := ar_Construct Map 
  ; SomeArr – вещественный массив
  Let SomeArr[3.14]         := 100500
  Let SomeArr[5 ^ (-1 / 2)] := "trololo"
End

Как видите, число π допустимый ключ вещественного массива. Да что там, как вы можете себе представить элемент с ключом 1/√5 ? Элементарно! Ну вот, теперь можете ощущать простор для экспериментов в ваших работах.

Если, а главное – для чего, вы будете использовать вещественные массивы – решите сами.

Многомерные массивы

До этого момента мы были знакомы только с одномерными массивами. Мы уже слегка коснулись темы размерности массивов на начале этой главы, теперь рассмотрим этот вопрос более подробно.

Механизм создания многомерного массива в OBSE несколько отличается от подобной процедуры в большинстве из языков программирования, если вам приходилось ранее быть знакомым с каким-либо из них. Для увеличения размерности массива от выбранного элемента следует присвоить ему значение типа array_var, т.е. создать массив в массиве. Давайте посмотрим на примере, так будет понятнее:

ScriptName ArrayExample
array_var Arr
Begin GameMode
  Let Arr       := ar_Construct Array
  Let Arr[0]    := ar_Construct Array
  Let Arr[0][0] := 1
  Let Arr[0][1] := 2
  Let Arr[0]    := ar_Construct Array
  Let Arr[0][0] := 1
  Let Arr[0][1] := 2
End

Получается, что мы сначала создали массив Arr, потом его нулевой элемент тоже сделали массивом. Нулевому элементу уже массива Arr[0] присвоили значение 1, а его первому элементу – значение 2. Затем тоже самое мы проделали с первым элементом массива Arr…

Делаем всё точно также, если хотим создать трёхмерный и массив больших размерностей.

Из такого принципа построения массивов следует, что:

  • Элемент индексного массива может быть ассоциативным или вещественным массивом, и наоборот:
ScriptName ArrayExample
array_var Arr
Begin GameMode
  Let Arr                 := ar_Construct Array
  Let Arr[0]              := ar_Construct StringMap
  Let Arr[0]["SomeElem"]  := 1
  Let Arr[0]["OtherElem"] := 2
End
  • Массив может иметь неодинаковую размерность в направлении любого из своих элементов
ScriptName ArrayExample
array_var Arr
Begin GameMode
  Let Arr          := ar_Construct Array
  Let Arr[0]       := ar_Construct Array
  Let Arr[0][0]    := ar_Construct Array
  Let Arr[0][0][0] := 36.6
  Let Arr[0][1]    := "ololo"
  Let Arr[1]       := Player
End

Строковые переменные

Строковые перемененные имеют тип string_var.

До семнадцатой версии OBSE требовалось определять строковые переменные при помощи функции sv_Construct, подобно массивам, но теперь делать этого не нужно.

ScriptName StringVarExample
string_var Str
Begin GameMode
  Let Str := "Это значение строковой переменной"
End

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

ScriptName StringVarExample
string_var Str1
string_var Str2
Begin GameMode
      Let Str1 := "Это значение одной строковой переменной"
      Let Str2 := "Это значение другой строковой переменной."
      Let Str1 += ". " + Str2
      Print Str1
      ; Str1 == "Это значение одной строковой переменной. Это значение другой строковой переменной."
End

К элементам строковых переменных можно обращаться так же, как и к элементам одномерного индексного массива. Каждый элемент строки есть один её символ.

Let Str := "Привет"
; Str[0] == "П"
; Str[1] == "р"
; Str[2] == "и"
; Str[3] == "в"
; Str[4] == "е"
; Str[5] == "т"

Изменение значения элемента строковой переменной приводит к его замене. В случае если мы заменяем один элемент более длинной строкой, то её значение просто подставляется в заменяемую строку начиная с выбранного положения.

Let Str := "Здесь что-то написано"
;           ^    ^
;           0    5
Let Str[5] := " может быть "
; Str == "Здесь может быть что-то написано"

Массивы строк

Массивы строк – обычный массив, может быть индексным, строковым или вещественным, произвольной размерности, элементу которого присвоено значение строковой переменной или строковой константы.

ScriptName ArrayOfString
array_var Arr
string_var Str
Begin GameMode
  Let Arr := ar_Construct Array
  Let Str := "Hello"
  Let Arr[0] := Str
  ; Arr[0][0] == "H"
  ; Arr[0][1] == "e"
  ; Arr[0][2] == "l"
  ; Arr[0][3] == "l"
  ; Arr[0][4] == "o"
End

Диапазоны элементов

Диапазоны элементов доступны для массивов и строк. Для того чтобы задать диапазон элементов массива или строки, стоит написать их индексы через двоеточие: слева – стартовый индекс, справа – конечный. В случае с массивами вызов диапазона элементов создаёт их копию:

ScriptName ArrayRange
array_var Arr1
array_var Arr2
Begin GameMode
  Let Arr1 := Arr2 := ar_Construct Array
  Let Arr1[0] := 1
  Let Arr1[1] := 2
  Let Arr1[2] := 3
  Let Arr1[3] := 4
  Let Arr1[4] := 5
  Let Arr2 := Arr1[1:3]
  ; Arr2[0] == 2
  ; Arr2[1] == 3
  ; Arr2[2] == 4
  Let Arr1[0] := Arr1[0:4]
  ; Arr1[0][0] == 1
  ; Arr1[0][1] == 2
  ; Arr1[0][2] == 3
  ; Arr1[0][3] == 4
  ; Arr1[0][4] == 5
End

В случае со строками, вызов диапазона создаст подстроку от стартового до конечного элемента.

ScriptName StringRange
string_var Str
Begin GameMode
  Let Str := "Здесь что-то написано"
  ;           ^   ^ ^    ^ ^      ^
  ;           0   4 6   11 13    20
  Print Str[0:4]   ; Здесь
  Print Str[6:11]  ; что-то
  Print Str[13:20] ; написано
; ----------------------------------------------------------------------
  Let Str := "Это строковая переменная"
  ;           ^ ^ ^       ^ ^        ^
  ;           0 2 4      12 14      23
  Let Str[4:23] := "СПАРТА!!!"
  ; Str == "Это СПАРТА!!!"
End

Пользовательские функции

Что такое функция?

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

Функция, которая не предусматривает возвращение результатов (не имеет значения), называется процедурой.

Вызов функцией самой себя называется рекурсией.

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

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

Пользовательские функции

Для того, чтобы создать функцию, следует создать новый скрипт. Имя скрипта – это имя функции. На то, что этот скрипт есть функцией, указывает ключевое слово Function, которое используется в качестве типа блока с ключевым словом Begin. В скрипте функции может находиться только один блок Begin – End и только типа Function.

Аргументы функции перечисляются в системных скобках, за ключевым словом Function, через запятую. Функция в OBSE может иметь до десяти аргументов, или не иметь их вовсе. Все аргументы функции – переменные, объявляются как в обычном скрипте.

Значение функции определяется ключевым словом SetFunctionValue. Значением функции может служить как константа, так и переменная. Возврат из функции, имеющей или не имеющей значения, выполняется ключевым словом Return. Возврат из функции означает прекращение её выполнения. Помните, нельзя использовать Return в циклах!

ScriptName FunctionName ; Имя функции
type arg1    ; Первый аргумент функции
type arg2    ; Второй аргумент функции
…
type arg10   ; Десятый аргумент функции
type RetVal  ; Значение функции
Begin Function {arg1, arg2, …, arg10}
  ; …
  SetFunctionValue RetVal ; Определение значения функции
  Return                  ; Возврат из функции
End

Для примера составим функцию, суммирующую два числа.

ScriptName Sum
float Num1 ; Первое число
float Num2 ; Второе число
Begin Function {Num1, Num2}
  Let Num1 += Num2       ; Находим сумму чисел
  SetFunctionValue Num1  ; Задаём возвращаемое значение
  Return                 ; Возвращаем результат
End

Для вызова пользовательской функции нужно использовать ключевое слово Call.

ScriptName SomeScript
float Var
Begin GameMode
  ; Вызываем только что созданную намии функцию
  Let Var := Call Sum 10 20
  ; Var == 30
End

Как я уже говорил ранее, не все функции обязательно должны иметь аргументы и/или возвращать значение. Давайте составим пример такой функции, она будет просто добавлять сто Септимов в карман игрока.

ScriptName ProcedureExample
Begin Function {}
  Player.AddItem Gold001 100
End

В OBSE существует функция, которая позволяет определить скрипт (функция возвращает ссылку на него), из которого произошёл вызов функции, она называется GetCallingScript.

ScriptName GetCallingScriptExample
Begin Function {}
  If GetCallingScript == MyQuestScript
    SetFunctionValue 1
  Else
    SetFunctionValue 0
  EndIf
  Return
End

Функция GetCallingScript ничего не возвращает, если вызов функции произошёл без помощи оператора Call, или функция была использована как обработчик события.

Обработчики событий

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

OnHit Цель:ref Атакующий:ref Происходит при нанесении атакующим урона цели
OnHitWith Цель:ref Оружие:ref Происходит при нанесении оружием урона цели
OnMagicEffectHit Цель:ref Магический_эффект:string (четырёхсимвольный код) Происходит при наложении магического эффекта на цель
OnActorEquip Цель:ref Предмет:form Происходит при надевании предмета целью
OnDeath Цель:ref Убийца:form Происходит при смерти цели
OnMurder Цель:ref Убийца:form Происходит при убийстве цели убийцей
OnKnockout Цель:ref   Происходит при нокаутировании цели
OnActorUnequip Цель:ref Предмет:form Происходит при снятии предмета целью
OnAlarm Trespass Заметивший:ref Нарушитель:ref Происходит, когда заметивший актёр поднимает тревогу, если нарушитель совершил взлом.
OnAlarm Steal Заметивший:ref Нарушитель:ref Происходит, когда заметивший актёр поднимает тревогу, если нарушитель совершил кражу.
OnAlarm Attack Заметивший:ref Нарушитель:ref Происходит, когда заметивший актёр поднимает тревогу, если нарушитель совершил нападение.
OnAlarm Pickpocket Заметивший:ref Нарушитель:ref Происходит, когда заметивший актёр поднимает тревогу, если нарушитель совершил карманную кражу.
OnAlarm Murder Заметивший:ref Нарушитель:ref Происходит, когда заметивший актёр поднимает тревогу, если нарушитель совершил убийство.
OnPackageChange Цель:ref Пакет:form Происходит при смене целью AI пакета
OnPackageStart Цель:ref Пакет:form Происходит при старте целью AI пакета
OnPackageDone Цель:ref Пакет:form Происходит при завершении целью AI пакета
OnStartCombat Цель:ref Опонент:ref Происходит, когда цель нападает на опонента
OnActivate Активатор:ref Цель:ref Происходит, когда цель активирует активатор
OnVampireFeed NONE   Происходит, когда вампир перестаёт питаться
OnSkillUp Код_навыка:int   Происходит при повышении навыка игрока
OnScriptedSkillUp skillActorValueCode:int amount:int   Происходит перед изменением навыка игрока скриптовой командой
OnDrinkPotion Цель:ref Зелье:form Происходит, когда цель принимает зелье
OnEatIngredient Цель:ref Ингридиент:form Происходит, когда цель съедает ингридиент
OnActorDrop Цель:ref Предмет:ref Происходит, когда цель выбрасывает/роняет предмет
OnSpellCast Цель:ref Заклинание:form Происходит, когда цель произносит заклинание
OnScrollCast Цель:ref свиток:form Происходит, когда цель читает заклинание со свитка
OnFallImpact Цель:ref   Происходит, когда цель упадёт с опасной высоты, перед тем, как получить повреждение
OnMapMarkerAdd Маркер:ref   Происходит при добавлении маркера на карту игрока
OnHealthDamage Величина_повреждения:float Атакующий:ref Происходит перед тем, как актёр получит повреждение. Если актёр свалится с высоты, то аргумент «Атакующий» будет иметь нулевое значение. Для определения актёра получившего повреждение стоит использовать функцию GetSelf.
OnCreateSpell Заклинание:ref   Происходит, когда игрок создаёт новое заклинание
OnCreatePotion Зелье:ref Уникальное_ли:int Происходит, когда игрок приготовит новое зелье. Второй аргумент будет равен 1, если игрок впервые приготовил такое зелье, иначе он будет равен 0.
OnEnchant Предмет:ref   Происходит, когда игрок зачаровывает предмет.
OnAttack Цель:ref   Происходит, когда цель начинает проигрывать анимацию атаки или наложения заклинания.
OnBowAttack Цель:ref   Происходит, когда цель начинает проигрывать анимацию стрельбы из лука.
OnRelease Цель:ref   Происходит, когда цель заканчивает проигрывать анимацию атаки, стрельбы или наложения заклятья.
OnBlock Цель:ref   Происходит, когда цель начинает проигрывать анимацию блокирования ударов.
OnRecoil Цель:ref   Происходит, когда цель начинает проигрывать анимацию получения отдачи.
OnStagger Цель:ref   Происходит, когда цель начинает проигрывать анимацию получения урона.
OnDodge Цель:ref   Происходит, когда цель начинает проигрывать анимацию уклонения от атаки.
LoadGame Имя_файла:string   Происходит при загрузке игры
SaveGame Имя_файла:string   Происходит при сохранени игры
PostLoadGame Игра_загружена_удачно:bool   Происходит после загрузки игры. Передаёт функции-обработчику события 1, если игра была загружена удачно, 0 - если нет
ExitGame NONE   Происходит при выходе из игры
ExitToMainMenu NONE   Происходит при выходе в главное меню
QQQ NONE   Происходит при выходе из игры посредствам вызова консольной команды QQQ
OnNewGame NONE   Происходит при старте новой игры
 

Для того, чтобы задать обработчик события существует функция SetEventHandler, её синтаксис таков:

(success:bool) SetEventHandler eventID:string functionScript:ref filter1:pair filter2:pair

Где,

  • eventID – имя события, одно из тех, которые наведены в таблице;
  • functionScript – имя функция, которая будет использована для обработки события;
  • filter1 – фильтр первого аргумента функции-обработчика;
  • filter2 – фильтр второго аргумента функции-обработчика.

Функция SetEventHandler возвращает единицу, если удалось назначить указанную функцию обработчиком события, или 0 – если нет.

Аргументы filter1 и filter2 применяются для фильтрации аргументов передаваемых функции-обработчику события. Если их не задавать, то обработчику события будут передаваться все аргументы инициируемые определённым событием.

Например, давайте составим функцию, которая будет использована для обработки события OnHit

ScriptName OnHitHandlerExample
ref Target
ref Attacker

Begin Function {Target, Attacker}
  Print Attacker.GetName + " attacked " + Target.GetName
End

И инициализируем её как обработчик события:

ScriptName OnHitHandlerQuestScript
Begin GameMode
  SetEventHandler "OnHit" OnHitHandlerExample
End

В таком случае, в ячейке, где разворачиваются игровые действия, на данный момент, любое событие OnHit инициализирует выполнение функции OnHitd]

Если мы хотим, допустим, чтобы это событие выполнялось только когда атакуют игрока, следует установить фильтр первому аргументу, согласно описания события OnHit.

ScriptName OnHitHandlerQuestScript
Begin GameMode
  SetEventHandler "OnHit" OnHitHandlerExample "ref"::Player.GetSelf
End

Оператор :: создаёт пару "Ключ"::"Значение". Ключ должен иметь значение типа соответствующего аргумента согласно перечню событий.

Не зависимо от того, что первый аргумент функции-обработчика теперь может принимать только одно значение и мы заведомо знаем об этом, функция-обработчик всёравно должна иметь два аргумента.

Если мы хотим избавиться от обработчика события, следует использовать функцию RemoveEventHandler. Её синтаксис аналогичен функции SetEventHandler.

ScriptName OnHitHandlerQuestScript
Begin GameMode
  RemoveEventHandler "OnHit" OnHitHandlerExample
End

[p]Если у нас есть несколько обработчиков события OnHit, то вызов функции RemoveEventd]

ScriptName OnHitHandlerQuestScript
Begin GameMode
  SetEventHandler "OnHit" OnHitHandlerExample
  SetEventHandler "OnHit" OnHitHandlerExample "ref"::Player.GetSelf
; ----------------------------------------------------------------------
  RemoveEventHandler "OnHit" OnHitHandlerExample
  ; Удалит все события OnHit
; ----------------------------------------------------------------------
  RemoveEventHandler "OnHit" OnHitHandlerExample "ref"::Player.GetSelf
  ; Удалит события только с соответствующим фильтром
End

[p]С помощью функции GetCurrentEventName, вызванной из функции-обработчика события, можно узнать имя текущего события. Это полезно, если одна функция используется как обработчик нескольких событий.

ScriptName MultiEventHandlerFunction
ref Arg1
ref Arg2
Begin Function {Arg1, Arg2}
  If Eval (GetCurrentEventName == "OnHit")
    Print Arg2.GetName + " атаковал " + Arg1.GetName
  ElseIf Eval (GetCurrentEventName == "OnActorEquip")
    Print "Игрок " + Arg1.GetName + " одел " + Arg2.GetName
  EndIf
End
9

Комментарии

Супер. Спасибо, хоть начну понимать, как использовать хендлеры.

вопрос остался по массивам: я так понял foreach по массиву не работает? тк выдает ошибку когда пишу нечто вроде foreach refVar <- ArrayVar

Авторизуйтесь, чтобы оставить новый комментарий. Или зарегистрируйтесь.