Это первый из серии гайдов, которые обьяснят и представят некоторые красивые штучки, пришедшие с NVSE4 и внесшие новизну в сообщество Нью Вегаса: UDFы, строковые переменные и массивы. Моддеры Обливиона, незнакомые с ОБСЕ версии 16 тоже получат пользу от этих статей. Эти статьи написаны для тех, кто уже понимает ванильный скриптинг в целом, знаком с теми или иными функциями, но чувствует себя потерянным, глядя на документацию ОБСЕ и пытаясь понять, как применить все это добро на практическом уровне.

Начнем с основ. Немного новенького синтакикса, делающего больше меньшими усилиями. Посмотрим на выражения ОБСЕ.
Если скриптинг – язык, давайте назовем функции вокабуляром и идиомами, а то, что вы видете здесь – синтаксис, собирающий всё воедино и придающий ему смысл. Кое-что уже должно быть вам известно: == != > < >= <=     + - * /       ()    && ||
Сравнение, базовая математика, скобки, меняющие последовательность, логические операторы AND и OR, уяснили, да? Если нет, то отложите статью пока не усвоите базу.

Метнемся к самым полезным из новых особенностей, под которыми я имею в виду то, что поможет связать весь скрипт воедино, тем более некоторые из них уж очень удобны чтобы их  игнорировать:
1. LET
2. IF EVAL
3. "МАТЕМАТИКА " И ПРИСВОЕНИЕ
4. ЦИКЛЫ WHILE
5. SCRIPT COMPILER OVERRIDE

1. LET

В нвсе4 «let» заменяет «set». Конечно set всё еще можно использовать как и раньше, но let может больше, и правильнее перейти на него при работе с более продвинутым синтаксом ниже, и со строками и массивами, о которых в другой раз. Собственно, всё, что нужно запомнить, так это то, что вместо этого:

Set someVar to someValue

мы пишем:

let someVar := someValue
Видите штучку := в коде? Не забудьте о ней. Это «присваивание», то есть «пусть А := Б», означает «Пусть А будет равно Б», в библейском духе «Да будет свет».

2. IF EVAL

Та же история с if-if eval.  If eval нужен чтобы компилятор понял более продвинутые вещи. Всегда используйте его со всем, что связано с массивами и строками, и он не подведет.

If eval SomeCondition

Elseif eval SomeCondition

Else

endif

Заметьте, что можно миксовать старые if’ы и новые if eval’ы, если хотите.

if eval someCondition

elseif someCondition

endif

3. "МАТЕМАТИКА" И ПРИСВОЕНИЕ

+=   прибавить и присвоить

-=    вычесть и присвоить

*=   умножить и присвоить

/=    разделить и присвоить

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

Set someQuestID.someVar to ThatSameQuestID.ThatSameVAR + 1

И начать писать так:

let someQuestID.someVar += 1

Меньше писанины, короче скрипты, нет смысла делать по старому. О, и еще кое-что о таймерах:

Set fTimer to fTimer + GetSecondsPassed

If fTimer > 10

               ; ваш скрипт делает то и то

Endif

Теперь могут быть записаны:

If eval (fTimer += GetSecondsPassed) >  10

               ; ваш скрипт делает то и то

Endif

Польузуйтесь, обожайте, не оборачивайтесь назад.

4. ЦИКЛЫ WHILE

Эти циклы комбинируют циклический аспект циклов Label/GoTo с возможностью делать циклы зависящими от условия “if eval” (включая математику и присвоение из пункта выше).

 Цикл while –  все, что между  условием "while"   и командой "цикл"(loop): это блок в духе блока if-endif:

While somecondition

    ; check/do stuff

Loop

Если условие while ложно, содержимое блока проматывается. Если условие while истино, блок между ним и командой «цикл» работает и доходя до Loop, скрипт возвращается к условию while и выполняет его снова. Немного примеров:

Хотите чтобы отрывок кода отработал 80 раз? Раньше это писали так:


Label 1

If iNum < 80

               Set iNum to iNum + 1

               ; run your code

               GoTo 1

Endif

Теперь нынешний эквивалент:


While (iNum += 1) < 81

; run your code

Loop

Опять же, он короче и нвсе не нужно париться над отслеживанием индексов лейбла, и вам в том числе. К тому же цикл представлен в виде блока, когда как GoTo едва ли дотягивает до статуса лейбла, а endif вашего if редко когда-либо использовался.

Это может звучать вьедливо, но чем сложнее скрипт, тем важнее его читаемость.
Label/GoTo – уродство.

Однако имейте в виду, что iNum уже добавлена в первое условие while, потому оно начнет с 1, а не с 0 в условии if из старого примера, так что чтобы получить именно 80 нужно указать значение 81.

Применим это к классическому циклу. С label/goto типичный цикл, работающий с каждым элементом в списке и  затем удаляющий его из него, будет выглядеть так:

Set iCount to ListGetCount rList

Label 1

If 0 < iCount

               Set iCount to iCount – 1

               Set rForm to ListGetNthForm rList iCount

               ; do stuff with rForm

               ListRemoveNthForm rList iCount

               GoTo 1

Endif

Теперь же:

Let iCount := listgetcount rList

While 0 <= (iCount -= 1) ; note: iCount is already subtracted from rightaway, so I made it a <=

               Let rForm := ListGetNthForm rList iCount

               ; do stuff with rForm

               ListRemoveNthForm rList iCount

Loop

И, конечно, удалить что-либо без задействования элементов списка теперь проще простого:

while (listgetcount rList)

     ListRemoveNthForm rList 0

loop

Хоть циклы намного красивее и элегантнее, чем Label/GoTo, но как и любые другие циклы они нуждаются во внимательности, иначе циклы перейдут в бесконечность и крашнут игру.

while (iNum += 1) > 0

    ; do stuff

Loop

Это ужаснейший пример, непременно приведет к крашу игры.

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

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

let iNum := listgetcount ActorsInCellList

while (iNum -= 1) >= 0

   let rActor := ListGetNthForm ActorsInCellList iNum

   if rActor.IsChild

     let iChildAlert := 1

     break

   endif

loop

if iChildAlert == 0

   ; do stuff you don't want kids to be around

endif

Промотать часть кода с телом условия while можно командой continue, которая подобна return, и возвращает скрипт обратно к условию while.

let iNum := listgetcount ActorsInCellList

while (iNum -= 1) >= 0

   let rActor := ListGetNthForm ActorsInCellList iNum

   if rActor.GetIsReference playerref

      continue

   endif

   ; do stuff you won't want to apply to the player

loop

Как обычно, правильная практика – помещать continue как можно выше в теле цикла как и return в скрипте заклинания или обьектном скрипте.

SCRIPT COMPILER OVERRIDE

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

Конечно, let, if eval, while, math & assig и т.д. уже сами по себе хороши, а мы еще и не дошли до комбинаций UDF, строк, массивов и переменных nx. Впрочем, когда дойдем, нас доканает компилятор ванильных скриптов, не принимающих их как параметры к ванильным и старым нвсе функциям.

Пример #1: представим, у нас есть число, для которого мы хотим получить нижний предел (get the floor of), ближайшее целое число меньше числа с плавающей запятой. Ванильный компилятор будет знать, что мы хотим сделать, если мы укажем это как переменную с плавающей запятой (float), которую мы передадим функции Floor в качестве параметра:

let iSomeInt := floor fSomeFloat

 Однако он не поймет, чего мы хотим, если мы скажем ему получить число с плавающей запятой (float) из элемента массива или из вызова UDF:

let iSomeInt := floor somearray[someIndex]

let iSomeInt := floor call someUDF

это не скомпилируется. Ванильный компилятор принимает лишь переменные float как параметр к функции floor и при любом раскладе не имеет понятия, чего ожидать от массива или UDF во время компиляции. Там может быть что угодно, и э-э-э на деле это не правда.

Чтобы обойти это, мы должны перемотнуть через скриптовый блок, задействовав компилятор НВСЕ, что можно сделать, предварив нужный блок кода подчеркиванием:

Begin _Gamemode (_Menumode, _Scripteffectstart, _OnActivate, _Function, etc etc)

Let someInt := floor somearray[somekey]

Let someInt := floor (call someFunction)

End

И затем идти по своим делам, вычислять числа, быть красавчиком. Вы буквально говорите компилятору принять всё как есть и что, мол, всё будет окей, и вы знаете, что там float в ключе массива, или что float вернется из UDF. Этим ходом вы берете немного ответственности у компилятора в обмен ан свободу. В итоге вам нужно быть осознанным, не запороть всё дело, протестировать всё в игре вместо того чтобы опираться на отчеты об ошибках из ГЕККа, и использовать скобки чуть больше чтобы убедиться, что компилятор не скомпилировал то, что не может работать в игре.

Пример #2: мы хотим проверить пачку разных свойств актора (actor values) SPECIAL на НПС и дать им bump если они меньше 5:

ref rActor

 

Begin OnActivate

 

if rActor.GetAV Charisma < 5

   rActor.SetAV Charisma 5

endif

if rActor.GetAV Intelligence < 5

  rActor.SetAV Intelligence 5

endif

...yawn... I'm not gonna bother with the other ones. Huge waste of script space, bound to give you RSI

End

Теперь вы должны увидеть, что параметры Харизма, Интеллект и другие на деле – строки, комбинация знаков. Чтобы сузить круг поиска и установить 7 свойств актора мы прилепим их к массиву, извлечем их через цикл foreach и привяжем к строковой переменной, и проверим вот таким образом:

ref rActor

array_var entry

array_var somearray

string_var somestringvar

 

Begin _OnActivate

 

let somearray := ar_list "Agility", "Charisma", "Endurance", "Intelligence", "Luck", "Perception", "Strength"

foreach entry <- somearray

   let somestringvar := entry[value]

   if eval 5 > rActor.GetAV somestringvar

       rActor.SetAV somestringvar 5

  endif

loop

 

End

но это всего лишь окольный способ показать, что переопределение позволит использовать string_var, а не фактическую строку для значения актера. Строго говоря, мы можем сократить это еще больше:

ref rActor

array_var somearray

array_var entry

 

Begin _OnActivate

 

let somearray := ar_list "Agility", "Charisma", "Endurance", "Intelligence", "Luck", "Perception", "Strength"

foreach entry <- somearray

   if eval 5 > rActor.GetAV entry[value]

       rActor.SetAV entry[value] 5

  endif

loop

 

End

Пример # 3: пример ОБСЕ документации, которую вы уже можете усвоить, если усвоили предыдущую информацию:

string_var axis

float pos

array_var somearray

 

Begin GameMode

 

let axis := somearray[index]  ; the axis paramater for the setpos function is held in an array as a string that can be "x", "y" or "z"

let pos := (call someFunction) + someRef.GetPos z  ; the setpos function will only accept a float for the position parameter so that needs to be calculated first

if eval axis == "z"

    setpos z pos

elseif eval axis == "y"

   setpos y pos

elseif eval axis == "x"

   setpos x pos

endif

 

End

Становится эпически коротким:

begin _gamemode

  setPos someArray[index], (call SomeFunction)  + someRef.getPos z

end

Еще одним достоинством нового компилятора является то, что вместо этого:

Set fHealth to rActor.GetAV Health  ; йоу, йоу или let fHealth := rActor.GetAV somestringvar/somearrayelement, помните?

Можно сосласться на свойство актора по его кодовому числу, который вы можете найти на дне ужасающе устаревшей документации НВСЕ:


let fHealth := rActor.GetAV 16

Почему это преимущество? Ну потому что с числами можно делать то, чего нельяз со строками, типа «Здоровье». Ну например, привязать их к int:

Let fHealth := rActor.GetAV iSomeInt

Так что можно привязать кучу свойств актора через цикл с условием while и циклом математики и присвоения тоже, вот так:


int iSomeInt

Begin _ScriptEffectStart

 

Let iSomeInt := 24

While (iSomeInt += 1) < 32 ; loops through 25-31, the AV codes for your body parts’ condition

    if eval 100 > GetAV iSomeInt

         SetAV iSomeInt 100

    endif

loop

 

End

Если Вы хотите «Супер-Дупер Мешок Доктора» или «Кнопку Немедленного Подхила».

Материал подготовлен ArtemSH специально для TGM — Tesall Game Magazine.
Переводчик: ArtSH
Автор: DoctaSax
Источник: Перейти
1

Комментарии

Lord RZ
администратор
01.12.2024 — 22:33

Спасибо большое!

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