Найти тему

Life Force с мышой

Оглавление

Поддержка LUA скриптов в эмуляторе FCEUX предоставляет разнообразное расширение возможностей в играх. Как например в игре Life Force управлять мышью, конечно от резких движений очень большой шанс вплюхнуться в потолок или еще куда-нибудь. Но это дело практики. Мне бы хотелось показать как это возможно осуществить.

Подготовка данных

Первым делом нам надо подготовить данные с которыми мы будем оперировать, и это координаты нашего корабля по осям X и Y.

В большинстве случаев прорисовка 2D графики на компьютерах происходит в четвертой четверти координат.

-2

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

Для начала ознакомимся с окошком поиска адресов в памяти которое вызывается из меню Tools -> RAM Search…

-3

Начнем с группой переключателей (RadioBox) «Comparison Operator»(оператор сравнения) и «Compare To / By»(сравнить с / по)

«Less Than» (меньше чем) - сравнивает текущее состояние адресов с выбранным в группе «Compare To / By»: «Previous Value»(предыдущее значение), «Specific Value»(точное значение), «Specific Address»(точный адрес), «Number of Changes»(количество изменений значения)

«Greater Than» (больше чем >)

«Less Than or Equal To» (меньше или равно ≤)

«GreaterThan or Equal To» (больше или равно ≥)

«Equal To» (равно ==)

«Not Equal To» (не равно !=) (~= для Lua)

«Different By» (изменилось на) - указывается на сколько изменилось значение как в большую так и в меньшую стороны, обычно применяется при сравнении с предыдущем

«Modulo» (отсев по модулю)

Группа «Data Size» отвечает за тип (размер) значения, поскольку NES обладает 8-ми битным процессором чаще всего тип используется также 8-ми битный (1 byte)

«Check Misaligned» - проверка смещенности начала адреса. Теперь по-деревенски. Это так называемая кратность адреса. Если мы ищем (оперируем) 2-х байтным адресом то с выключенной этой опции поиск будет происходить по адресам 00, 02, 04, 06, 08, 0A, 0C и так далее т. е. кратно, поиск не будет затрагивать адрес 09 или 05. Также и с 4 байтами 00, 04, 08, 0C. А при включенной оперирует всеми адресами. Для 1 байта она погоды не сделает.

«Data Type / Display» - вид отображения/представления значений адресов (знаковое и без знаковое десятичное, шестнадцатеричное)

Что касается кнопок тут интуитивно все понятно: начало поиска по установленным условиям, сброс для нового поиска, сброс счетчика изменений значений(иногда полезная вещь)

Итак давайте все-таки найдем координаты.

Начните игру, откройте поиск адресов, сместите корабль вниз. Тем самым мы увеличили значение по Y, ставьте на паузу и в окне поиска выбираем «Greater Then», «Previous Value», оперируем как я уже говорил с 1 байтным размером и для дальнейшего удобства отображать значения рекомендую в HEX, нажимаем «Search». Затем можно не смещаться вверх/вниз тем самым мы не изменим Y и можем применить условие равен предыдущему (Equal To). Затем по-манипулируйте с различными изменениями положения корабля и условиями поиска.

После нескольких таких манипуляций у меня получился такой результат

-4

Здесь стоит обратить внимание на столбец Changes(изменения): 13 изменений слишком мало для осуществивших мной маневров, следовательно самый подходящий адрес это 32F(0x32F). Проверим. Кликнем на него правой кнопкой мыши (ПМК) и откроется окно Hex Editor с установленным курсором на интересующий нас адрес.

-5

Изменим немного его значение где-то на 0x10 (0x4A+0x10 = 0x5A) (вот почему я советовал отображать в HEX). Возвращаемся в игру и наблюдаем смещение корабля вниз. Отлично Y найден, запомним/запишем его. Тем же способом найдем X. У меня это получилось 0x350. Для нашей задачи этих данных достаточно так что можно приступать к коду.

Ну что покодим

При подключении скрипта к FCEUX он просто выполнится и завершиться, что бы он постоянно работал нам нужно циклическое выполнение скрипта но если мы просто пропишем цикл он зациклится в скрипте не давай работать эмулятору. Для выхода их этой ситуации в встраиваемых функциях эмулятора, которые описаны в Help-е к эмулятору есть функция emu.frameadvence() которая передает управление эмулятору на один игровой кадр.

Открою свой любимый текстовой редактор и впишу туда

while 1 do
emu.frameadvance()
end

сохраню где-нибудь с расширением .lua и подключу к FCEUX (File -> Lua -> New Lua Script Window...)

-6

Browse... находим наш файл затем запускаем (Run)

Скрипт вполне работает но пользы от него пока никакой.

Для удобства добавим отладочную функцию которая отображает текущие координаты на экране.

Вставьте ее перед циклом while

function display()
local x_pos = memory.readbyte(0x350)
local y_pos = memory.readbyte(0x32f)
gui.text(190, 10, string.format("x pos 0x%X", x_pos))
gui.text(190, 20, string.format("y pos 0x%X", y_pos))
end

memory.readbyte(addr) это функция возвращает значение по адресу addr

gui.text() рисует текст на экране эмулятора, string.format здесь использован для вывода в hex значении.

также вызовем только что созданную функцию из главного цикла.

while 1 do
emu.frameadvance()
display()
end

Сохраняем отредактированный скрипт и перезапускаем его (Restart)

Теперь узнаем координаты указателя мыши. В FCEUX есть библиотека input отвечающая за устройства ввода такие как клавиатура и мышь. Давайте добавим функции display входной параметр к примеру назовем его inp, это будет результат работы функции input.get() которая возвращает состояние всех клавиш клавиатуры(нажата/не нажата) и координаты курсора в окне эмулятора.

Весь скрип теперь должен иметь следующий вид

function display(inp)
local x_pos = memory.readbyte(0x350)
local y_pos = memory.readbyte(0x32f)
gui.text(190, 10, string.format("x pos 0x%X", x_pos))
gui.text(190, 20, string.format("y pos 0x%X", y_pos))
local x_mos = inp.xmouse
local y_mos = inp.ymouse
gui.text(190, 30, string.format("x mos 0x%X", x_mos))
gui.text(190, 40, string.format("y mos 0x%X", y_mos))
end
while 1 do
emu.frameadvance()
display(input.get())
end

Сохраняем рестартуем

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

-7

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

Для этого создадим еще одну функцию например mouse_control(inp) и так же пусть она принимает один аргумент

Также запишем ее до вызова а именно выше цикла

function mouse_control(inp)
memory.writebyte(0x350, inp.xmouse)
memory.writebyte(0x32f, inp.ymouse)
end

И не забываем вызывать ее в цикле.

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

Хотелось бы закончить но нет, поскольку функция называется управление мышью, так почему мы не стреляем ЛКМ.

Добавим в функцию mouse_control следующее

if inp.leftclick then
joypad.set(1,{B=true})
end

Функция input.get() как я уже говорил возвращает состояние клавиш в виде таблицы(тип структуры данных в Lua, чем-то похожа на словарь в питоне ) Вернет оно примерно это {q=false, w=false, e=false…и так вся клавиатура и мышь}если клавиша нажата значение ключа станет true. Вот эти мы и воспользовались если leftclick true значит выполним то что между then end. А там у нас лежит еще одна из встроенных библиотек joypad для работы с NES-овскими джойстиками(в нашем случаи эмуляторными, и не важно играем мы на клавиатуре или xbox pad). Ну так вот, между then end лежит у нас функция joypad.set() которая принимает помимо номера контроллера (1-й игрок 2-й игрок) почти такую же таблицу и мы ее говорим что хотим нажать клавишу B на первом геймпаде, что соответствует у нас выстрел.

Готовый код

function display(inp)
local x_pos = memory.readbyte(0x350)
local y_pos = memory.readbyte(0x32f)
gui.text(190,10,string.format("x pos 0x%X", x_pos))
gui.text(190,20,string.format("y pos 0x%X", y_pos))
local x_mos = inp.xmouse
local y_mos = inp.ymouse
gui.text(190,30,string.format("x mos 0x%X", x_mos))
gui.text(190,40,string.format("y mos 0x%X", y_mos))
end
function mouse_control(inp)
memory.writebyte(0x350, inp.xmouse)
memory.writebyte(0x32f, inp.ymouse)
if inp.leftclick then
joypad.set(1,{B=true})
end
end
while 1 do
emu.frameadvance()
--display(input.get())
mouse_control(input.get())
end

Ну теперь все.