Scripting under MWSE 2.1 LUA
mintmike (C) 01 2020
https://www.fullrest.ru/forum/topic/41708-mwse-21-uroki-po-lua-skriptam/
Примечание!
от 10 2022
Данные, приведенные в статье, несколько устарели.
Т.к. эти два года, МВСЕ активно развивалось!
Отчего, новейшие версии МВСЕ 2.0 могут содержать гораздо больше команд и заменять означенные здесь.
Рекомендуется посетить сайт разработчиков и свериться с текущей документацией!
И не забудьте проверить плагины этого автора на Nexuse!
https://www.nexusmods.com/morrowind/mods/47015
содержат огромное кол-во всевозможных примеров!
MWSE - "расширитель скриптов Мирровинда" или просто МыВСЕ:)
Описание функций MWSE:
https://mwse.readthedocs.io/en/latest/mwscript/functions/index.html
Константы игрового мира:
https://mwse.readthedocs.io/en/latest/mwscript/references.html
Настройки игры, Game Settings (глобальные переменные):
https://mwse.readthedocs.io/en/latest/mwscript/GMSTs.html
MWSE, начиная с версии 2.1, поддерживает простой и быстрый скриптовый язык Lua, для чего имеет несколько библиотек:
event - библиотека событий, при которых будут вызываться lua-функции
json - взаимодействие с очень удобным json-форматом сохранения/загрузки игровых данных
mge - вызов функций MGE XE
mwscript - вызов оригинальных Морровинд-скриптов. Используйте только в случае необходимости
mwse - небольшое количество общих функций MWSE
string - обращение со строкой
table - взаимодействие с таблицами
tes3 - основной функционал для управления Морровиндом
tes3ui - управление пользовательским интерфейсом игры (все эти окна и меню игрока)
timer - удобные таймеры
|
Об использовании этих возможностей у нас и пойдёт с вами речь. Уроки задумывались как справочник, идут в целом по усложнению, но читать можно с любого места.
Самая лучшая книга по Lua: https://vk.com/doc-127857710_437836162
Руководство на русском языке: https://lua.org.ru/contents_ru.html
Сокращенное руководство: https://lua.org.ru/main.html#the-little-lua-book
А мы с вами давайте лучше напишем и запустим в игре наш первый скрипт!
*** Урок 1: Сообщения ***
Для запуска lua-скриптов, вам не нужно создавать мод в Construction Set. Достаточно открыть блокнот и записать, к примеру, вот такую функцию:
local function myFun(e)
mwse.log("Запущено сохранение: %s", e.filename)
end
event.register("loaded", myFun)
Мы создали функцию local myFun(e), которая пишет имя загруженного в игре сохранения в лог-файл (по адресу <Путь к папке Morrowind>\MWSE.log).
local - означает, что функция локальная (не может быть вызвана из других файлов),
e - информация события (типа event), которая будет передаваться в вашу функцию,
%s - команда форматирования, которая вставляет вместо "%s" строку из переменной e.filename.
event.register - регистрирует вашу функцию для события (привязывает функцию к событию)
"loaded" - имя события, данное событие вызывается при окончании загрузки сохранения в игре. Каждый раз, при возникновении события "loaded"(загрузке сохранения) будет запускаться ваша функция.
Итак, первый lua-скрипт к Морровинду написан. Сохраняем файл как:
<Путь к папке Morrowind>\Data Files\MWSE\mods\<ваш мод>\main.lua
Имя основного файла должно быть обязательно "main.lua", таковы требования МыВСЕ.
Имя подкаталога <ваш мод> может быть любым. При старте игры MWSE сканирует все подкаталоги ..\Data Files\MWSE\ и компилирует найденные скрипты. Ваш скрипт также скомпилируется, и если синтаксических ошибок нет, скрипт запустится один раз. Что означает "скрипт запустится один раз"? Это значит, что ваш файл исполнится при старте игры, лишь раз. Но так как фунция "myFun" будет зарегистрирована для события"loaded", то и Запускаться она будет каждый раз по событию. При старте игры функция "myFun" вызвана не будет.
Заходим в Морровинд, загружаем любое сохранение и выходим. Где-то в середине файле логов MWSE.log должна появится запись:
"Запущено сохранение: <имя файл сохранения>"
Если допущена ошибка, вы получите сообщение в файле логов:
<Путь к папке Morrowind>\MWSE.log
Используйте поиск по тексту с именем <вашего мода>, чтобы быстро отыскать сообщения об ошибках в вашем lua-коде.
В логах после слова Error указано описание ошибки и строка, в которой она возникла. Исправляйте ошибку и запускайте
Морровинд снова. Ну что же. Поздравляю, первый lua-скрипт для Морровинда создан!
Давайте запустим ещё какой-нибудь пример. Замените текст в вашем файле, или просто добавьте ниже:
local function showMessage(e)
tes3.messageBox("Вы взяли " .. tes3.getMobilePlayer().readiedWeapon.object.id)
end
event.register("weaponReadied", showMessage)
Здесь при экипировке оружия будет выведено сообщение на экран. Событие "weaponReadied" вызывается, когда орудие взято в руки готово к атаке.
.. - две точки есть операция слияния строк. id является строкой
getMobilePlayer() - возвращает ссылку на mobile игрока (на вашу мобилку :), ваш игровой болванчик, а точнее тело)
Подробнее о типах объектов, актеров, предметов в следующих уроках.
Ну вот, оказывается, создавать моды очень просто. Только идеи подавай!
*** Урок 2: События ***
Вот все типы событий: https://mwse.readthedocs.io/en/latest/lua/event.html
Рассмотрим с дюжину основных и как они работают:
"loaded" - игра загружена (из сохранения)
"initialized" - игра инициализирована, .esm и .esp файлы загружены
"simulate" - начало каждого игрового кадра (не пауза и не меню)
"enterFrame" - начало любого кадра
"skillRaised" - произошло повышение скила
"attack" - ближняя физ атака (с участием актера или существа, не обязательно игрока)
"activate" - активация (кто-либо нажал на активатор)
"activationTargetChanged" - в прицеле игрока объект для активации изменился
"spellCast" - кто-либо начал произносить заклинание
"magicCasted" - заклинание успешно произнесено или алхимия дала эффект
"equip" - момент экипировки предмета (начало момента)
"keyDown" - нажата клавиша на клавиатуре
"menuEnter" - вызвано меню (на пр.кн. мыши)
"weaponReadied" - экипировано оружие
|
На событиях держится все обращения от MWSE к вашему коду.
event.register(<событие>, <функция>) -- регистрирует имя вашей функции в специальной таблице
Событие одно, а привязанных к нему функций может быть много. Функции даже могут иметь одни и те же имена в разных файлах.
Все они будет вызываться при каждом наступлении условий и возниконвении связанного с ними события.
Таким образом, для каждого события MWSE вызывает свои, зарегистрированные именно для него функции.
Мы можем очистить таблицу для конкретного события, написав:
event.clear("имя события") Все запомненные для события функции будут отменены.
*** Урок 3: Клавиши ***
Коды всех клавиш есть тут:
https://mwse.readthedocs.io/en/latest/lua/guide/scancodes.html
Для клавиатуры нам достаточно двух событий:
event.register("keyDown", checkKeys) - вызывается при нажатии клавиши
event.register("keyUp", checkKeys) - вызывается при отпускании клавиши
Как сделать так, чтобы нажатие на кнопку клавиатуры что-нибудь совершало?
local function checkKeys(e)
-- pressed 'V'
if (e.keyCode == 47) then
tes3.messageBox("You pressed 'V'")
end
end
event.register("keyDown", checkKeys)
Функция checkKeys(e) проверяет код нажатой клавиши. Если это код клавиши 'v', то будет выведено сообщение.
Когда несколько клавиш задействовано, то удобнее оформить так:
local function dance()
...
end
local function savePoint()
...
end
local function createItem()
...
end
local function checkKeys(e)
-- pressed 'U'
if (e.keyCode == 22) then dance(); return end
-- pressed 'I'
if (e.keyCode == 23) then createItem(); return end
-- pressed 'O'
if (e.keyCode == 24) then savePoint(); return end
end
local function onLoaded()
event.register("keyDown", checkKeys)
end
event.register("loaded", onLoaded)
Ну что, готовы к чему-то побольше, навроде циклов?
*** Урок 4: Казначей в локации ***
Давайте напишем функцию, которая просканирует локацию и выведет в лог имена и координаты всех и всего найденного в ней. Зарегистрируйте функцию самостоятельно, на событие "loaded".
local function makeLog()
local cell = tes3.getPlayerCell() -- получаем ячейку нахождения игрока
local step = 1 -- это счетчик шагов цикла
local str = tostring(step) -- запишем значение счётчика в строку str
for ref in cell:iterateReferences() do
if (ref.object) then
str = str .. " type: " .. tostring(ref.object.objectType)
str = str .. " name: " .. ref.object.name
str = str .. " id: " .. ref.object.id
str = str .. " pos: " .. tostring(ref.position)
if (ref.object.objectType == tes3.objectType.npc) then
str = str .. " " .. tostring(ref.object.race.name)
end
print(str) -- печатам нашу строку в лог
step = step + 1
str = tostring(step) -- перезапишем всю строку, записав туда снова значение нашего счётчика
end
end
end
Данный цикл на каждом шаге печатает в лог-файл новую заполненную строку.
Функция print() после вывода сама переходит в начало след.строки без доп.команд.
ref - пустая новая переменная-ссылка.
iterateReferences() - метод для перебора ссылок на все объекты в ячейке. Методы вызываются через двоеточие.
Цикл for ref in cell:iterateReferences() do при каждом шаге кладёт в пустую переменную ref новую ссылку на очередной объект до тех пор, пока объекты не закончатся.
do ... end - тело цикла. В теле цикла мы проверяем if (ref.object) - содержит ли наша ссылка объект. Если объект пустой (nil то есть нуль), то условие будет ложным (false), и мы переходим далее, на новый шаг. Если ref.object отлично от нуля, условие истинно (true), и выполняется всё, что между then ... end.
tostring(ref.object.objectType) - дополняет нашу строку типом объекта. objectType является числом, поэтому мы переводим его в строку c помощью tostring()
if (ref.object.objectType == tes3.objectType.npc) then - проверяет, равен ли тип объекта npc. Если истина, то дополним нашу строку расой объекта.
objectType - по сути одно из чисел из таблицы ниже. Таблица находится по адресу tes3.objectType. Это таблица типов (неполная, на самом деле типов больше, см. 7 урок).
Value Code Meaning
1230259009 ACTI Activator
1212369985 ACTI Alchemy
1330466113 AMMO Ammunition
1095782465 ACTI Apparatus
1330467393 ARMO Armor
1263488834 BOOK Book
1414483011 CLOT Clothing
1414418243 CONT Container
1095062083 CREA Creature
1380929348 DOOR Door
1380404809 INGR Ingredient
1129727308 LEVC Levelled Creature
1230390604 LEVI Levelled Item
1212631372 LIGH Light
1262702412 LOCK Lockpick
1129531725 MISC Misc Item
1598246990 NPC_ NPC -- наш случай
1112494672 PROB Probe
1095779666 REPA Repair Item
1414546259 SCPT Script
1195658835 SNDG Sound Generator
1279610963 SPEL Spell
1413567571 STAT Static
1346454871 WEAP Weapon
***Урок 5 Кровати-Двери-Сундуки ***
В предыдущем уроке мы нашли не одну сотню объектов. Давайте присмотримся к некоторой мебели. Вдруг не для каждого нпс найдется кровать или спальник? Где им спать, в сундуках? Ах да, в Морровинде время же заморожено, никто не спит, кроме нашего героя. Вот беда. Может быть, мы поправим это в последнем уроке?
local beds = 0
local doors = 0
local chests = 0
Три локальные переменные для мебели. Если убрать слово local, то переменные станут глобальными (доступными из любого модуля). Локальные переменные могут быть свободно доступны только функциям, определенным в их зоне видимости (то есть в нашем файле).
Как выделить из всех предметов, например, кровати? У них имя object.name = 'Кровать'. Но это ненадежно, вдруг имена на английском. Удобнее ориентироваться по id. id для каждого предмета уникален.
Как найти слово в строке id? Для поиска в строке существует:
string.find(id,'[Bb]ed')
-- [Bb] - означает один любой из символов 'B' 'b'
Если в строке id есть слово "bed" или "Bed", функция вернёт нам 2 числа (или nil, если ничего не найдено):
-- pos1 - на каком символе начинается наше слово
-- pos2 - на каком символе заканчивается наше слово
local pos1, pos2 = string.find(id,'[Bb]ed')
Теперь давайте дополним функцию makeLog:
local function makeLog()
local cell = tes3.getPlayerCell()
local step = 1
local str = tostring(step)
for ref in cell:iterateReferences() do
if (ref.object) then
if (ref.object.name) then -- не у всех предметов имеется поле name
str = str .. " " .. ref.object.name
end
str = str .. " id: " .. ref.object.id
if (ref.object.class) then -- не у всех предметов имеется поле class
str = str .. " class: " .. ref.object.class.name
end
if (ref.object.objectType == tes3.objectType.npc) then
str = str .. " " .. tostring(ref.object.race.name)
end
if (string.find(ref.object.id,'[Bb]ed')) then -- если находим кровать
beds = beds + 1
str = str .. " BED#" .. tostring(beds) .. ""
elseif (string.find(ref.object.id,'[Dd]oor')) then -- иначе если находим дверь
doors = doors + 1
str = str .. " DOOR#" .. tostring(doors)
elseif (string.find(ref.object.id,'[Cc]hest')) then -- иначе если находим сундук
chests = chests + 1
str = str .. " Chest#" .. tostring(chests)
end
print(str)
step = step + 1
str = tostring(step)
end
end
tes3.messageBox("Найдено кроватей %d, дверей %d, сундуков %d", beds, doors, chests)
end
%d - на это место будет вставлено число из переменной, переданной в функцию messageBox следом за строкой (процесс называется "форматирование строки").
Запускаем, проверяем! Если появились хорошие идеи, пишите в комментариях.
Кого создать?
*** Урок 6: Bторой муж для Тойвалэ Отрален***
Из урока 4 у нас есть лог объектов в локации, из него можно узнать id - уникальные идентификаторы мировых персонажей, тварей, мебели, и прочих вещей. Давайте заглянем к Арриллу на огонёк и клонируем его (: Надеюсь, его жена не упадёт в обморок. Или это не его жена стоит рядом?
--[[ Комментарий.
tes3.createReference({...}) - это функция для создания кого-либо, чего-либо,
на вход получает таблицу {...}
Можно записать без круглых скобок tes3.createReference{...}
Делает то же, что и mwscript.PlaceAtPC или xPlace.
]]
local ref = tes3.createReference
{ -- начало таблицы
object = "arrille00000000", -- первый элемент таблицыб можно элементы менять местами, порядок не важен
position = tes3.getPlayerRef().position,
orientation = tes3.getPlayerRef().orientation,
cell = tes3.getPlayerCell(),
scale = 1
} -- конец таблицы
tes3.messageBox("Привет, Тойвалэ!")
end
Не вздумайте запускать эту функцию не в начальной локации. Аррилл против такого! Нужны данные из незагруженной ячейки игрового мира. Morrowind чего-то не находит в доступе и крашится. Жалко.
Ладно. Тут у нас между фигурными скобками целая таблица с полями
object, - сюда подойдет id объекта или объект типа tes3physicalObject
position, - позиция создаваемого объекта, tes3vector3(x,y,z)
orientation, - отриентация создаваемого объекта, tes3vector3(x,y,z)
cell, - id ячейки или ячейка tes3cell
scale - масштаб, обычно у всех = 1
Как нам усовершенствовать наш код, чтобы клонировать кого угодно от бутылки до Вивека? Сделаем в следующих уроках ^^)
Типы tes3объектов (tes3.objectType)
-- '>>' обозначим наследование (в исходниках MWSE)
GMST (objectType: 'TSMG')
PathGrid (objectType: 'DRGP')
Region (objectType: 'NGER')
Birthsign (objectType: 'NGSB')
BaseObject >> Object >> Reference (objectType: 'RFER')
BaseObject >> Object >> Spell (objectType: 'LEPS')
BaseObject >> Object >> Enchantment (objectType: 'HCNE')
BaseObject >> Object >> PhisicalObject
PhisicalObject >> Activator (objectType: 'ITCA')
PhisicalObject >> Actor >> Container (objectType: 'TNOC')
PhisicalObject >> Actor >> Creature (objectType: 'AERC')
PhisicalObject >> Actor >> NPC (objectType: '_CPN')
PhisicalObject >> BodyPart (objectType: 'YDOB')
PhisicalObject >> Door (objectType: 'ROOD')
PhisicalObject >> Land (objectType: 'DNAL')
PhisicalObject >> LandTexture (objectType: 'XETL')
PhisicalObject >> LeveledCreature (objectType: 'CVEL')
PhisicalObject >> LeveledItem (objectType: 'IVEL')
PhisicalObject >> Static (objectType: 'TATS')
PhisicalObject >> Item >> Armor (objectType: 'OMRA')
PhisicalObject >> Item >> Alchemy (objectType: 'HCLA')
PhisicalObject >> Item >> Apparatus (objectType: 'APPA')
PhisicalObject >> Item >> Book (objectType: 'KOOB')
PhisicalObject >> Item >> Clothing (objectType: 'TOLC')
PhisicalObject >> Item >> Ingredient (objectType: 'RGNI')
PhisicalObject >> Item >> Light (objectType: 'HGIL')
PhisicalObject >> Item >> Lockpick (objectType: 'KCOL')
PhisicalObject >> Item >> Lockpick >> Probe (objectType: 'BORP')
PhisicalObject >> Item >> Misc (objectType: 'CSIM')
PhisicalObject >> Item >> Weapon (objectType: 'PEAW')
Ammo (objectType: 'OMMA')
Repair (objectType: 'APER')
BaseObject >> AnimationGroup
BaseObject >> Class (objectType: 'SALC')
BaseObject >> Cell (objectType: 'LLEC')
BaseObject >> Dialogue (objectType: 'LAID')
BaseObject >> DialogueInfo (objectType: 'OFNI')
BaseObject >> Faction (objectType: 'TCAF')
BaseObject >> MagicEffect (objectType: 'FEGM')
BaseObject >> Quest (objectType: 'SEUQ')
BaseObject >> Global (objectType: 'BOLG')
BaseObject >> MagicSourceInstance (objectType: 'LLPS')
BaseObject >> Race (objectType: 'ECAR')
BaseObject >> Script (objectType: 'TPCS')
BaseObject >> Skill (objectType: 'LIKS')
BaseObject >> Sound (objectType: 'NUOS')
BaseObject >> SoundGenerator (objectType: 'GDNS')
MobileObject >> MobileActor >> MobileNPC (objectType: 'HCAM')
MobileObject >> MobileActor >> MobileNPC >> MobilePlayer (objectType: 'PCAM')
MobileObject >> MobileActor >> MobileCreature (objectType: 'RCAM')
MobileObject >> MobileProjectile >> MobileSpellProjectile (objectType: 'JRPM')
***Урок 7: Что от чего происходит в мире tes3 ***
Все что мы видим и слышим в мире tes3, MWSE разделяет на tes3объекты разных типов (см. таблицу tes3типов выше).
GMST -- глобальные переменные игры (objectType: 'TSMG')
PathGrid -- сетка для поиска пути актёрами (objectType: 'DRGP')
Region -- задаёт погоду и звуки региона (objectType: 'NGER')
Birthsign -- знак рождения героя (objectType: 'NGSB')
BaseObject -- базовый объект для остальных объектов
MobileObject -- мобильный объект - это подвижная модель, 'болванка', ваше tes3 (или чьё-то) бренное тельце, а также снаряд
(objectType: 'XXXX') - это 4 символа, которые также можно представить уникальным числом. Таблица с типами находится по адресу tes3.objectType. Тип необходим для выведения разновидностей объектов и проверки в программе, с каким объектом мы имеем дело. Давайте рассмотрим основные важные типы поближе.
tes3.objectType.object
BaseObject >> Object >> Reference (objectType: 'RFER')
BaseObject >> Object >> Spell (objectType: 'LEPS')
BaseObject >> Object >> Enchantment (objectType: 'HCNE')
BaseObject >> Object >> PhisicalObject
Как мы видим, объект (tes3object) происходит от базового объекта и может быть ссылкой (reference), заклинанием, зачарованием или физическим объектом. Физические объекты подразделяются на (см.таблицу): активаторы, актёров, части тела, двери, мировые поверхности, их текстуры, статики (неподвижные объекты), вещи, а также уровневые существа и уровневые вещи. Почти всё, что передставлено в Construction Set, является физическим объектом (tes3.objectType.phisicalObject).
Все объекты типа tes3object имеют три поля:
object.id (string) -- "айди", идентификатор
object.objectType (number) -- tes3-тип это просто уникальное число из таблицы
object.sourceMod (string) -- мод-источник объекта
Кстати объекты actor, npc не являются видимыми моделями, движущимися игре, а являются описаниями, содержащими имя и прочие свойства. "Болванчиков" представляют объекты tes3mobileObject.
tes3.objectType.mobileObject
Представляет свойства подвижной модели персонажа, существа, выпущенной стрелы. Имеет свойства для управления движением и положением в пространстве.
https://mwse.readthedocs.io/en/latest/lua/type/tes3mobileObject.html
MobileObject >> MobileActor >> MobileNPC (objectType: 'HCAM') -- тело персонажа
MobileObject >> MobileActor >> MobileNPC >> MobilePlayer (objectType: 'PCAM') -- тело персонажа игрока
MobileObject >> MobileActor >> MobileCreature (objectType: 'RCAM') -- тело сщество
MobileObject >> MobileProjectile >> MobileSpellProjectile (objectType: 'JRPM') -- тело снаряда
Тело персонажа имеет:
инвентарь
атрибуты (сила, ловкость, и т.д.)
скилы
наложенные эффекты
список друзей
список врагов
управляющие флаги
AI данные
tes3.objectType.reference
Очень нужный и важный тип tes3reference, или tes3-ссылка (далее просто "ссылка").
https://mwse.readthedocs.io/en/latest/lua/type/tes3reference.html
С помощью этого объекта из функции в функцию передаётся информация об остальных tes3объектах: вещах, актёрах (tes3actor), заклинаниях, генераторах звука и т.п. Давайте взглянем на её происхождение:
BaseObject >> Object >> Reference (objectType: 'RFER')
Как мы видим, ссылка имеет tes3тип (tes3reference) и происходит от объекта, а он в свою очередь от базового объекта. Это означает, что можно написать:
ref.id -- то же, что и ref.object.id
ref.objectType -- а вот тип у ссылки свой (objectType: 'RFER')
ref.sourceMod -- то же, что и ref.object.sourceMod
Поскольку ссылка - объект, настроенный на другой объект, то в её свойствах мы айди самого объекта, мод и т.п. также. Все кроме типа, тип у ссылки собственный.
Ссылок на один и тот же объект может быть много. Обращаться через ссылку очень удобно. Например, мы можем проверить ref, на кого или что она настроена:
if (ref.objectType == tes3.objectType.сontainer) then ... end -- выполнится, если ref это контейнер
if (ref.objectType == tes3.objectType.phisicalObject) then ... end -- выполнится, если ref физ.объект
if (ref.objectType == tes3.objectType.light) then ... end -- выполнится, если ref источник света
Тут последняя версия уроков, а также рассортированное описание TES3 классов:
https://yadi.sk/d/pNaDhws2m1-6AA
3.4 – Выражения, все виды операторов
3.4.9 – Конструкторы таблиц (как создавать {...} таблицы)
3.4.8 – Приоритет операторов (что быстрее выполнится + или >= и т.п)
3.5 – Правила видимости (создание/удаление локальных переменных)
Использует недокументированную (ещё) функцию addMagicEffect, которая позволяет добавлять новые эффекты. Примеры использования можно посмотреть в скриптах мода.
tes3.addMagicEffect
{
name, -- string,
id, -- number, EffectID
description, -- string, описние
lighting -- vector3, цвет свечения, RGB
icon, -- string, путь к иконке
particleTexture , -- string, путь к текстуре частиц
baseCost, -- 1.0f по умолчанию
school, -- 0 по умолчанию
size, -- 1.0f по умолчанию
sizeCap, -- 1.0f по умолчанию
speed, -- 1.0f по умолчанию
castSound, -- string, звуки
boltSound
hitSound,
areaSound,
castVFX, -- сами эффекты, PhysicalObject
boltVFX,
hitVFX,
areaVFX,
allowEnchanting, -- дальше идут флаги (true/false)
allowSpellmaking,
appliesOnce,
canCastSelf,
canCastTarget,
canCastTouch,
casterLinked,
hasContinuousVFX,
hasNoDuration,
hasNoMagnitude,
illegalDaedra,
isHarmful,
nonRecastable,
targetsAttributes,
targetsSkills,
unreflectable,
usesNegativeLighting
onTick,
onCollision
}
Можно надеяться, что появится и функция типа addNewSkill ...
Урок 8: "Как создать / прочитать текстовый файл"
--=== Функции Lua для работы с файлами ===--
io.input() -- задаём файл для чтения
io.output() -- задаём файл для записи
io.read() -- читаем строку из файла
io.write() -- пишем строку в файл
Для примера посчитаем количество строк и слов в интересующем нас файле, пусть он называется inp.txt
Запускать программу будем на компиляторе lua53.exe (последняя версия).
1 Скачаем, если у вас его нет. Скачать можно тут: http://luabinaries.s...t/download.html
2. Создаём в одном каталоге с lua53.exe новый текстовый файл с текстом программы, например "prog.lua":
io.output('out.txt') -- задаём файл с результатом
io.input('inp.txt') -- откуда будем читать
local str = io.read() -- читаем первую строку из файла
local snum = 0 -- количество строк
local wnum = 0 -- количество слов
while str do -- цикл исполняется пока str не равно nil
snum = snum + 1 -- увеличиваем счётчик строк на 1
for s in str:gmatch("[%w'-]+") do -- для каждой найденной в строке цепочки цифро-букв, "'" и "-"
if s:find("%a") then -- если цепочка имеет хотя бы одну букву
wnum = wnum + 1 -- увеличиваем счётчик слов на 1
end
end
str = io.read() -- читаем следующую строку
end
io.write('Всего строк в файле ',snum,'\n') -- выводим результат в файл
io.write('Bсего слов в файле ',wnum,'\n') -- \n -- служебный символ перехода на новую строку
print('Всего строк в файле',snum,' всего слов в файле',wnum)
Замечание: все файлы должны лежать в одной папке.
3. Перетаскиваем мышкой нашу программу "prog.lua" на lua53.exe и отпускаем, рядом появляется наш файл с результатом "res.txt".
4. Для повторных запусков можно создать prog.bat файл, и запускать нашу програму нажатием на него
lua53 prog.lua
cmd
Про что сделать следующий сложный урок? (не более 2 вариантов в ответе, или свои варианты)
1. Как правильно подключить и использовать mcm (Настройка Модов в Главном меню)
2. Как создавать окошки и кнопочки. Внедрим кнопку в окно с диалогом с нпс. По нажатию он будет нам отвечать про погоду и силу ветра.
3. Как заставить нпс`а: упасть на кровать и лежать на ней ничком.
4. Появляется и исчезает бог (Вивек, например), все сбегаются к месту силы.
5. Как работает самый частый пакет ии: AIWander (шататься окрест)
6. Как работает AIActivate. Как заставить нпс`а выйти на улицу, а Фаргота бегать по кругу.
7. Выделить стат-ку из редактора и посмотреть: сколько и каджитов и орков на Ввардэнфелле и чем они занимаются, какой ср.уровень, скорость, магия.
Но сначала простой урок номер 9: "Как создавать окна, меню, кнопки в Морровинд"
Самый короткий вариант создать окошко. Назовём его Винни. Короткий плагин из десятка строчек:
local winny
local function showWinny()
winny = tes3ui.createMenu -- создается новое меню (просто окно)
{
id = tes3ui.registerID('winny'), -- нашему окну выдаётся id, по которому можно затем к нему обращаться
fixedFrame = true, -- с жёстко фиксированной рамкой
}
winny:updateLayout() -- обновить элементы на экране
end
event.register("keyDown", showWinny, {filter = 44}) -- при нажатии на "z" будет появляться Винни
Запускаем Главное меню Морровинд. Жмакаем на "z".
Появляясь, наше окно не хочет исчезать. Давайте добавим текст в наше окно, а также кнопку "назад":
local winny
local function hideWinny()
winny:destroy() -- функция для удаления Винни
end
local function showWinny()
winny = tes3ui.createMenu
{
id = tes3ui.registerID('winny'),
fixedFrame = true,
}
winny:createLabel{ text = 'winny'} -- добавляем текст в окно
local button = winny:createButton -- добавляем кнопку
{
id = tes3ui.registerID('Cancel'),
text = 'Назад',
}
button:register("mouseClick", hideWinny) -- при щелчке по кнопке будет вызываться hideWinny()
winny:updateLayout()
end
event.register("keyDown", showWinny, {filter = 44}) -- z
Теперь, если нажать "z" в игре, окно появляется и маячит посреди экрана. Как перейти в режим меню?
tes3ui.enterMenuMode(winny) -- попадаем в режим, когда игра на паузе и появляется указатель мыши для управления в меню
Добавим ещё несколько полезных строк:
local winny
local winny_id -- сохраним id окна отдельно
local function hideWinny()
winny:destroy()
if tes3ui.menuMode then tes3ui.leaveMenuMode() end -- выходим из режима меню
end
local function showWinny()
if tes3ui.findMenu(winny_id) then return end -- если окно уже есть, не создаём его повторно
winny_id = tes3ui.registerID('winny')
winny = tes3ui.createMenu
{
id = winny_id,
fixedFrame = true,
}
winny:createLabel { text = 'winny'}
local button = winny:createButton
{
id = tes3ui.registerID('Cancel'),
text = 'Назад',
}
button:register("mouseClick", hideWinny)
winny:updateLayout()
tes3ui.enterMenuMode(winny) -- режим меню
end
event.register("keyDown", showWinny, {filter = 44}) -- z
Готово. Также, в окно можо добавлять блоки (свободное пространство для элементов), а в блоки уже добавлять тексты, изображения и кнопки.
local block = winny:createBlock{}
block.width = 1000 -- ширина
block.autoHeight = true -- высота выбирается автоматически
block.childAlignX = 0.5 -- горизонт. выравнивание элементов по центру
block.childAlignY = 0.5 -- верт. выравнивание элементов по центру
block.flowDirection = 'top_to_bottom' -- разместить элементы сверху вниз
local l = block:createLabel{ ... } -- добавить запись
local b = block:createButton{ ... } -- добавить кнопку
local img = block:createImage{ ... } -- добавить изображение
local nif = block:createNif{ ... } -- добавить модель
Теперь вы можете создавать меню в Морровинд под свои нужды.
Полные возможности см. в описании TES3 классов (последняя ссылка в шапке темы)
Урок 10: "Разговоры"
Сегодня мы:
1. Создадим модуль, который будет штамповать нам окна
2. Используя этот модуль, сотворим окно для разговора с НИП'ом (Не Игровым Персонажем)
3. Внедрим кнопку в MenuDalogue (в основное окно для диалогов в Морровинде)
4. Добавим функции для ответа: "кто вы?", "который час?", "как погода?"
Для понимания этого материала нужно основательно усвоить предыдущий урок.
Пусть у нас есть каталог с модом MyMod. А в нём основной модуль main.lua
Создадим вспомогательный модуль (файл) win.lua. Он нужен нам для того, чтобы потом затем пользоваться им, не повторяя один и тот же код over9000+ раз.
win.lua будет иметь всего одну функцию new(name) - создавать новое окно:
local this = {}
this.new = function(name) -- функция, создающая новое окно и возвращающая нам его данные
local m = { -- данные нашего нового окна
id = tes3ui.registerID(name),
ltext = ' Enter: ',
block = {[1] = ''},
}
m.menu = tes3ui.createMenu{id = m.id, fixedFrame = true}
m.menu.alpha = 1.0 -- нулевая прозрачность
m.show = function() -- отобразить окно
m.menu:updateLayout()
tes3ui.enterMenuMode(m.menu)
end
m.hide = function() -- спрятать окно
if not tes3ui.findMenu(m.id) then return end
m.menu:destroy()
if tes3ui.menuMode then tes3ui.leaveMenuMode() end
end
m.addBlock = function(n, width, ltext) -- добавить блок с номером n, шириной [и необязательным текстом надписи]
m.block[n] = m.menu:createBlock{}
m.block[n].width = width or 360
m.block[n].autoHeight = true
m.block[n].childAlignX = 0.5
m.block[n].childAlignY = 0.5
m.block[n].flowDirection = 'top_to_bottom'
if ltext then -- если указан 3 параметр - текст надписи, то добавляем её
m.block[n]:createLabel {text = ltext}
end
end
m.addBlockButton = function(n, buttext, func) -- добавить в блок номер n кнопку с текстом buttext
local button = m.block[n]:createButton
{
id = tes3ui.registerID(buttext),
text = buttext,
}
button:register("mouseClick", func) -- при нажатии на кнопку будет вызываться функция func
end
return m -- возвращаем созданную таблицу с данными и функциями для нашего нового окна
end
return this
Добавим этот модуль в наш основной модуль main.lua
local win = require("MyMod.win")
Теперь мы можем:
w = win.new(...), -- создавать новые окна
w.addBlock (...), -- добавлять в них болки
w.addBlockButton (...) -- конопки
w.show (...) -- показывать
w.hide (...) -- и прятать их
Что мы и сделаем:
local win = require("Clever Actors Dialogues.win")
local winny
local function winnyShow()
local width = 360
winny = win.new('winny') -- создадим новое окно
winny.addBlock(1, width, 'Сказать...') -- добавим блок номер 1
winny.addBlock(2, width, '----------') -- добавим блок номер 2
winny.addBlockButton(2, 'назад', winny.hide) -- добавим в блок номер 2 кнопку назад
winny.show() -- а теперь выведем окно
end
event.register("keyDown", winnyShow, {filter = 44}) -- z
При нажатии на "z" окно покажется, при нажатии "назад" - спрячется.
Теперь создадим функцию, которая внедрит в меню диалога (MenuDialog) новую кнопку "Диалог с <имя персонажа>"
Есть такое событие "menuEnter". Оно возникает, когда на экран выводится любое окно (меню). В данных этого события отправляется id этого нового окна (просто число).
Подробнее о всех события тут:
https://mwse.readthedocs.io/en/latest/lua/event.html
Это нам и нужно. Зарегистрируем на событие "menuEnter" новую функцию в main.lua:
local md_id -- id MenuDialog
local md_db -- это будет наша кнопка "Диалог с npc"
local md_db_id -- id нашей кнопки
local function dlgButton(e) -- е - это данные события "menuEnter"
md_id = tes3ui.registerID("MenuDialog")
if e.menu.id ~= md_id then return end -- выходим, если id окна не совпадает с MenuDialog (открыто что-то другое)
md = tes3ui.findMenu(md_id) -- иначе, продолжаем. Сохраняем меню MenuDialog как md
md_db_id = tes3ui.registerID("md_db")
md_db = md:createButton { -- добавляем нашу кнопку в MenuDialog (появится внизу окна)
id = md_db_id,
text = 'Диалог c '..tes3ui.getServiceActor().object.name,
}
md_db.widthProportional = 1.0
md_db:register("mouseClick", winnyShow) -- при нажатии на кнопку будет вызываться наше окно Winny
end
event.register("menuEnter", dlgButton) -- регистрируем функцию на событие "menuEnter"
Можно пробовать запускать. Теперь не хватает вопросов и ответов от нпс.
При использовании tes3.messageBox(...) и открытом MenuDialog текст выводится прямо в него, а не появляется отдельно.
Этим и воспользуемся.
НИП будет нам отвечать:
local function selfDialog() -- говорит своё имя, расу и класс
local mbl = tes3ui.getServiceActor()
local str = 'Это я, '..mbl.object.name..', '.. mbl.object.race.name ..', '..mbl.object.class.id
tes3.messageBox(str)
end
local function daytimeDialog() -- говорит сколько часов до конца дня
local wc = tes3.getCurrentWeather().controller
local str = tostring(wc.daysRemaining)
str = str ..' день , час ' .. tostring(wc.hoursRemaining)
tes3.messageBox(str)
end
local function weatherDialog() -- проверяет состояние погоды, озвучивает
local w = tes3.getCurrentWeather()
local str = 'Нынче'
if w.index == 1 then str = str..' ясно'
else str = str..'... что там, не знаю'
end
tes3.messageBox(str)
end
Теперь нужно вызывать эти функции из нашего winny.
Дополним получившийся main.lua
local function winnyShow()
local width = 360
local chlist = -- лист ответов ("Choiсe list")
{
['Кто вы?'] = selfDialog,
['Который час?'] = daytimeDialog,
['О погоде'] = weatherDialog,
}
winny = win.new('winny')
winny.addBlock(1, width, 'Сказать...')
for chtext,fun in pairs(chlist) do -- для каждой записи из chlist
winny.addBlockButton(1, chtext, function() -- добавить функцию
winny.hide() -- прячущую окно winny
fun() -- и вызывающую соответствующую функцию
end)
end
winny.addBlock(2, width, '----------')
winny.addBlockButton(2, 'назад', winny.hide)
winny.show()
end
local md_id -- Menu Dialogue id
local md_db -- Menu Dialogue _ Dialogue Button
local md_db_id
local win = require("Clever Actors Dialogues.win")
local winny
local function dlgButton(e)
--collectgarbage('collect')
md_id = tes3ui.registerID("MenuDialog")
if e.menu.id ~= md_id then return end
md = tes3ui.findMenu(md_id)
md_db_id = tes3ui.registerID("cleveractors:md_db")
md_db = md:createButton {
id = md_db_id,
text = 'Диалог c '..tes3ui.getServiceActor().object.name,
}
md_db.widthProportional = 1.0
md_db:register("mouseClick", winnyShow)
end
local function onLoad()
event.register("menuEnter", dlgButton)
end
event.register("loaded", onLoad)
После добавления нашей кнопки в меню диалога, его нужно обновить (updateLayout). Иначе наша кнопка будет появляться не сразу, а только после первого ответа нип`а на любую из тем, или любого др. обновляющего окно события.
Если есть идеи, как улучшить ответы нпс, предлагайте. Или, ещё лучше, дополняйте плагин самостоятельно. Публикую opensource :)
Последняя версия планина "Morrowind-Dialogues" https://gitlab.com/Scoommich/morrowind-dialogues