Перейти к содержанию
  • Переводчик: ArtSH
    Автор оригинала: DoctaSax

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

    Эти статьи написаны для тех, кто в целом уже понимает скриптинг, знаком с теми или иными функциями, но теряется, глядя на документацию ОБСЕ, не понимая, как применить всё это добро на практике.

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

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

    1. LET

    В NVSE4 «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

        ; проверяет\делает ваши скриптовые штуки

    Loop

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

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

    Цитата:


    Label 1

    If iNum < 80

                   Set iNum to iNum + 1

                   ; запускает ваш код

                   GoTo 1

    Endif

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

    Цитата:


    While (iNum += 1) < 81

    ; запускает ваш код

    Loop

    Опять же, он короче, и NVSE не нужно париться над отслеживанием индексов лейбла, и вам в том числе. К тому же цикл представлен в виде блока, когда как 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

                   ; что-то делает с rForm

                   ListRemoveNthForm rList iCount

                   GoTo 1

    Endif

    Теперь же:

    Цитата:

    Let iCount := listgetcount rList

    While 0 <= (iCount -= 1) ; отмечу: iCount уже вычтено, так что я написал a <=

                   Let rForm := ListGetNthForm rList iCount

                   ; что-то делает с 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

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

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

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

    Цитата:

    let iSomeInt := floor fSomeFloat

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

    Цитата:

    let iSomeInt := floor somearray[someIndex]

    let iSomeInt := floor call someUDF

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

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

    Цитата:

    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, помните?

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

    Цитата:


    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

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


    Обратная связь

    Рекомендуемые комментарии

    Lord RZ

    Опубликовано

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

    • Нравится 3


    Для публикации сообщений создайте учётную запись или авторизуйтесь

    Вы должны быть пользователем, чтобы оставить комментарий

    Создать аккаунт

    Зарегистрируйте новый аккаунт в нашем сообществе. Это очень просто!

    Регистрация нового пользователя

    Войти

    Уже есть аккаунт? Войти в систему.

    Войти

×
×
  • Создать...