Перейти к содержанию
Авторизация  
Hardtmuth

Способ редактирования скрипта без выхода из игры

Рекомендуемые сообщения

 

Способ редактирования скрипта без выхода из игры.

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

Не для кого не секрет каким образом зачастую отлаживаются скрипты на ошибки, алгоритм до безобразия прост:
1. Пишем скрипт.
2. Запускаем игру.
3. Получаем вылет.
4. Смотрим в лог и правим скрипт.
5. Повторяем п. 2 - 4 до тех пор, пока вылеты не прекратятся.
Ко всему, скрипт зачастую вызывается не на прямую, а через какое-то игровое действие - через диалог, поднятие/использование предмета и т.д.
Как по мне - это чистой воды мазохизм. "Но что делать?" - скажите Вы, - "Если так устроена игра." Решать проблему, а точнее две!
Первая проблема - это "бесконечные" вылеты.
Вторая проблема - определённое действие для вызова скрипта, дабы проверить работоспособность.
 


Первая проблема решается при помощи двух стандартных функций самого Lua, а именно dofile и pcall.

Спойлер

dofile


resultChunk = dofile("C:\\Study\\My.script")

Открывает указанный файл и выполняет его содержимое как chunk. Возвращает результат выполнения или же генерирует ошибку.

Chunk - в переводе с английского, порция или блок. В Lua же порция - это любая последовательность операторов Lua.

Здесь важно понять, что такое "последовательность операторов". function - это тоже порция или другими словами блок операторов. Таким образом, выполняя при помощи dofile какой-либо скрипт, в файле следует писать именно содержимое блока, который нужно выполнить.

pcall


pcall (funcName, arg1, ...)

Вызывает функцию funcName с данными параметрами (arg1, и далее, через запятую необходимое для функции funcName количество параметров) в так называемом защищенном режиме (protected mode).
Это значит, что при возникновении любой ошибки внутри функции funcName она дальше не передается; напротив, pcall перехватывает ошибку и возвращает статус. pcall возвращает сначала статус (тип boolean), который равен true если вызов завершился без ошибок, и все результаты от работы защищённой им функции. В случае возникновения ошибки, pcall возвращает false и сообщение об ошибке.
Может показаться сложным и запутанным, но на деле это не так. Попытаюсь показать Вам на примере.

Имеем простую функцию возвращающая сумму двух чисел:


function Addition (a, b)
      return a + b
end

Если вызвать функцию с параметром b отличным от числа (тип number) :


sum = Addition(13, true)

то произойдёт ошибка выполнения следующего содержания:


attempt to perform arithmetic on local 'b' (a boolean value)

Если же задействовать функцию pcall, то результат будет совершенно иным.


status, result = pcall(Addition, 13, true)

Как и говорилось, функция pcall принимает в качестве первого аргумента имя функции, а в качестве последующих - аргументы, которые принимает уже сама вызываемая функция.
Возвращает же два значения. Первое - результат выполнения, статус определяющий возникла ли ошибка (false - если возникла, true - если нет). Второе - результат выполнения функции, если ошибки не возникло, или же сообщение об ошибке, если внутри функции возникла ошибка.
В примере в переменной status будет значение false, а в переменной result - строка "attempt to perform arithmetic on local 'b' (a boolean value)".
При передаче правильных параметров, которые не вызовут ошибку, в переменной status будет значение true, а в result - сумма двух чисел, переданных в качестве этих самых параметров.

В обоих случаях ошибки выполнения, а следовательно и вылета в контексте игры, не будет!

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

function ProtectedFunction()
      local status, result = pcall(dofile, [[..\gamedata\scripts\__test.script]])
      get_console():execute(status and "Successful!" or string.gsub(result, " ", "_"))
end

Всё очень просто. Вызываем pcall, в качестве параметра передаём функцию dofile и аргумент для неё - путь до нашего файла __test.scrip, который нужно предварительно создать в папке scripts.

Опытным путём было установлено, что в ТЧ корневым каталогом Lua считает папку bin, поэтому путь пишем относительно неё. В ЧН и ЗП - это корневая папка установленной игры, поэтому путь для этих частей будет таким: "[[gamedata\scripts\__test.script]]"

Вторая строка выводит в консоль сообщение "Successful!" если скрипт в файле _test.script выполнился без ошибок или же сообщение ошибки выполнения, если произошла ошибка. Сообщение выводится со знаком подчёркивания вместо пробела. Это необходимая мера для метода execute.

Результат выводить в консоль не имеет смысла, т.к. результат нам покажет сама игра.
Теперь если вызвать функцию ProtectedFunction, то выполнится код написанный в файле __test.script. При этом, сам код в этом файле можно менять не выходя из игры, а просто свернув её.

Поэтому, получив ошибку внутри вызываемого файла - в консоле появится сообщение с описанием данной ошибки. Далее: сворачиваете игру; правите ошибку; восстанавливаете игру; запускаете вызов функции ProtectedFunction по новой; смотрите на результаты работы исправленного скрипта.

Хочу ещё раз обратить Ваше внимание на то, что в файле __test.script должен присутствовать именно блок операторов!

Спойлер

Содержимое файла может быть таким:


function MyFunction ()
      db.actor.health = 1
end

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


function MyFunction ()
      db.actor.health = 1
end
MyFunction()

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

В процессе игры, не выходя из неё, Вы можете подредактировать файл __test.script, скажем, изменив свойство health на power, тем самым последующий вызов защищённой функции пополнит уже не здоровье, а выносливость.

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


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

Спойлер

Ключевым звеном данного способа является команда консоли bind_cosole. Команда позволяет назначить выполнение другой консольной команды на любую клавишу на клавиатуре. Её синтаксис выглядит так:
bind_cosole <Имя консольной команды> <Значение для команды> <Код клавиши>

Например:


bind_cosole save 1 knumpad1

Таким образом, при нажатии на единицу на цифровой клавиатуре, в консоле будет вызвана команда "save 1", что приведёт к сохранению игры под именем "1".

Есть команды консоли, которые сохраняют в себе переданное ей значение, например "mm_net_player_name". Команда изменяет/возвращает имя игрока в мультиплеере, соответственно в одиночной игре она не задействована, и именно по этому автор остановил свой выбор на ней.

В скриптах, есть возможность получения значений команд консоли, при помощи методов объекта консоли CConsole, в игре такой объект можно получить с помощью функции глобального пространства имён get_console.

Вот такой код:


local mmPlayrName = get_console():get_string("mm_net_player_name")

Запишет в переменную имя игрока в сетевой игре.

Такой тандем из из команды bind_cosole и метода get_string, позволяют при нажатии на клавишу задать для команды "mm_net_player_name" значение имени функции, которую нужно выполнить, и в нужный момент получить имя этой функции для того, чтобы её вызвать.
В этом и состоит весь принцип работы горячих клавиш данного способа.
Изначально мы проходимся по файлу в поисках функций с именем, которое совпадает с перечислением имён класса DIK_keys, только написанное строчными литерами. Присваиваем соответствующей этому имени клавише команду консоли "mm_net_player_name <имя функции>". Таким образом при нажатии на такую клавишу, данному параметру будет присвоено имя функции. Отлавливаем значение данной команды с помощью метода get_string, если это имя функция, которая находится в текущем файле, то вызовем её, и тут же присвоим команде значение по умолчанию, чтобы функция не вызывалась в дальнейшем. При повторном нажатии на клавишу, команде снова присвоится значение с именем функции, которое отловится на ближайшем апдейте. И так по кругу.

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

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

Я выберу клавишу Scroll Lock, чтобы хоть как-то оправдать её присутствие на клавиатуре. Код этой клавиши - dik_scroll, именно так и должна называться функция, которую будет вызывать выбранная мной клавиша.

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

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

function dik_scroll()
      local status, result = pcall(dofile, [[..\gamedata\scripts\__test.script]])
      get_console():execute(status and "Successful!" or string.gsub(result, " ", "_"))
end

Как видите, в тело функции я написал содержимое нашей "защищённой функции", а не вызывал отдельно её - зачем лишний раз без необходимости плодить код?


Всё, друзья мои!
Теперь, зайдя в игру и загрузив сохранение (или начав её заново), Вы можете полюбопытствовать работой нашей конструкции. Нажимаете Scroll Lock (или другую выбранную Вами клавишу) и видите запуск содержимого в файле __test.script. Если в файле не было ошибок, то в консоле вы увидите надпись "Successful!" или же сообщение об ошибке. Попытайтесь не выходя из игры, свернуть её при помощи комбинации Alt+Tab, изменить содержимое файла, а затем в восстановленной игре опять нажать горячую клавишу - как Вы сами убедитесь, результат будет основываться на изменённых данных.

Готовую мини-gamedat'у, можно скачать здесь: Яндекс.Диск

Всё, что Вам потребуется сделать - поместить два скрипта в вашу рабочую папку scripts и прописать в апдейте актора следующую строку:

bind_keys.update()

Зайти в игру и нажать на клавишу Scroll Lock.

  • Лайк 1
  • Спасибо 2

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
 

script Syntax Checker от Gun12
script_Syntax_Checker.7z

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
 

помогите написать скрипт для оп 2.2 когда одеваем броню - автоматом присваивает фракцию брони

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

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

Создать учетную запись

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

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

Войти

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

Войти
Авторизация  

  • Последние посетители   0 пользователей онлайн

    Ни одного зарегистрированного пользователя не просматривает данную страницу