upthorn / gens-rerecording

Automatically exported from code.google.com/p/gens-rerecording
0 stars 0 forks source link

Lua Scripting Support (DeHackEd's) #3

Closed GoogleCodeExporter closed 8 years ago

GoogleCodeExporter commented 8 years ago
Same scripting type support as in FCEU.28, FCEUX, and SNES1.43

Original issue reported on code.google.com by andres.d...@gmail.com on 25 Jul 2008 at 1:37

GoogleCodeExporter commented 8 years ago

Original comment by andres.d...@gmail.com on 25 Jul 2008 at 1:45

GoogleCodeExporter commented 8 years ago
First pass done as of revision 138. It's missing niceties like a recent files 
list
and there are probably some glaring omissions in the API. Still need to make a 
Gens
Lua API page but for now you can take a look at the lua_samples directory for 
examples.

The approach is a little different from what I saw of the Snes9x API: 
registering
functions is the preferred method of use, to encourage creating stand-alone 
scripts
that can be loaded separately to combine their functionality.

I haven't looked at how well-suited this is for making bots yet, although it 
seems
like gens.emulateframefast() and joypad.set() and state.save()/load() should be
enough for it to be possible.

Original comment by nitsuja-@hotmail.com on 6 Oct 2008 at 1:18

GoogleCodeExporter commented 8 years ago
Have you checked out the FCEUX lua documentation?  Ideally all three (and future
emulators) are uniform in their lua handling.  At least this is the intent of 
zeromus
& DeHackEd.

Original comment by andres.d...@gmail.com on 6 Oct 2008 at 1:36

GoogleCodeExporter commented 8 years ago
I mostly referred to http://dehacked.2y.net/snes9x-lua.html. I followed it 
pretty
closely (many functions like memory.readbyte are exactly the same). But there 
are
some things they did that I can't justify repeating only for the sake of 
uniformity.
For example, control over the transparency of GUI drawing was quite poor (RGB 
only
with a weird special case for transparency and a single global setting for
translucency). I chose to provide full RGBA control instead. For another 
example,
joypad.write didn't provide an easy way to write over some of the input and 
leave the
rest of it alone, and joypad.read couldn't read input from a playing movie. 
Also,
some things in Gens just work differently enough that the interface shouldn't 
be the
same (e.g. they use movie.framecount(), but in Gens the framecount is more a 
property
of the emulation than a property of the movie). I think it's reasonable to 
expect
Gens to support everything (or almost everything) that can be done in Lua 
scripts in
FCEUX and Snes9x1.43, but I don't think it's reasonable to try to force them to 
all
handle Lua scripts identically.

Original comment by nitsuja-@hotmail.com on 7 Oct 2008 at 5:05

GoogleCodeExporter commented 8 years ago
That being said, I'm going to try to make it more similar. Some of the 
functions I
don't think I'll be able/willing to implement though (especially 
memory.register()).

Original comment by nitsuja-@hotmail.com on 7 Oct 2008 at 5:40

GoogleCodeExporter commented 8 years ago
Yeah, I'm going to try to tackle the RAM and/or PC hook angles, since I've 
already
done some work relating to that when porting tracing over from genstrace, and 
already
plan to do more work relating to it for the planned new cheat system.

Original comment by Upth...@gmail.com on 10 Oct 2008 at 11:50

GoogleCodeExporter commented 8 years ago
OK, add those whenever you like then. Hopefully it can be done without slowing 
down
the emulation much or at all.

I believe I've now made everything in the "basic master emulator control" 
section
work like the other emulators now. (This includes gens.speedmode(),
gens.frameadvance(), gens.message(), gens.pause(), gens.wait(), and the way they
interact with the GUI drawing functions.)

Original comment by nitsuja-@hotmail.com on 11 Oct 2008 at 9:57

GoogleCodeExporter commented 8 years ago
Any suggestions for which parts of this task that remain should have the highest
priority? So far, when I have time to work on this I just kind of pick 
something at
random...

Original comment by nitsuja-@hotmail.com on 4 Nov 2008 at 9:38

GoogleCodeExporter commented 8 years ago
Well I should finish up the memory.register stuff, so that there's actually a 
hook
which calls the registered lua functions (I've been making slow headway due to
midterms and other stuff, but my last one is on Wednesday). But other than 
that, I
don't really know without like, a list of DeHackEd's lua standard, with all the
implemented bits checked off or strike-out'd.

Original comment by Upth...@gmail.com on 4 Nov 2008 at 11:09

GoogleCodeExporter commented 8 years ago
Until a similar document is made for Gens (which I'll try to do soon), continue 
to
refer to http://dehacked.2y.net/snes9x-lua.html.

Currently missing/incompatible functionality:
BIT() -- is missing
movie.mode() -- is missing
movie.rerecordcounting() -- is missing
movie.stop() -- is missing
gui.gdscreenshot() -- is missing
gui.gdoverlay() -- is missing
gui.popup() -- is missing
memory.register() -- not complete, I think
savestate.create() -- only supports anonymous savestates. The functionality for
numbered savestates is currently in savestate.save()
savestate.save() -- takes a state number instead of a savestate object (the
functionality for that is currently in savestate.get())
savestate.load() -- takes a state number instead of a savestate object
savestate.get() -- should be removed after savestate.save() handles both cases
savestate.set() -- should be removed after savestate.load() handles both cases
savestate.registersave() -- doesn't support saving extra data with the 
savestate by
returning the data
savestate.registerload() -- doesn't support loading data that was returned by
savestate.registersave()

Possibly-breaking differences which I'd like to leave this way intentionally 
for one
reason or another but might in some cases be persuaded to change:
movie.framecount() -- renamed to gens.framecount() and works when no movie is 
active
joypad.read() -- will read input from a playing movie. (It is still possible to 
check
non-joypad user input while a movie is playing by using input.get().)
joypad.read() -- uses true/false instead of 1/nil, to stay consistent with
joypad.set(). They are equivalent in Lua conditional expressions, so unless the 
code
specifically and unnecessarily checks for 1/nil, then code that uses 
joypad.read()
will not need to be changed (besides, of course, changing the button names to 
match
the Genesis controller).
joypad.set() -- uses true/false instead of 1/nil, and nil now means "don't 
change",
because otherwise there is no way to support specifying that a button should 
change
without also changing all of the other buttons. The only implication is that 
the user
can overlay their input over the unpressed buttons after a joypad.set() unless 
the
nils passed in are changed to false.
gui -- uses 32-bit color with alpha instead of 16-bit color with 1 meaning 
transparent
gui -- does not signal an error when told to draw out of bounds
gui.getpixel() -- can read gui-drawn colors if not called in the part of the 
frame
that is before gui functions get drawn. Because it would require an extra 
memory copy
of the entire screen per frame to fix it, and what if someone actually wants to
perform operations on the pixels they already drew?

There are many other differences that need to be document but I believe all of 
the
ones not listed above are extra functionality that does not interfere with 
anything
else (i.e. there is an additional function or a function takes an optional extra
argument or accepts a wider range of arguments).

Original comment by nitsuja-@hotmail.com on 10 Nov 2008 at 12:40

GoogleCodeExporter commented 8 years ago
I think fixing up savestate.registersave() and savestate.registerload() to 
support
adding script-specific data to savestates (possibly useful for warbots and 
such),
would be a good part of lua for you to focus on for right now. Since that one 
seems
way beyond my capabilities.

Original comment by Upth...@gmail.com on 10 Nov 2008 at 2:54

GoogleCodeExporter commented 8 years ago
I'm working on an EmuLua reference page where I try to list all the emulator 
specific functions. I'm trying to keep up with all emulators that have Lua, 
which is 
not so hard for the moment. I will try to add emulator specifics as I get them.

The page can be found at http://cbc.qfox.nl/emulua

Original comment by qFo...@gmail.com on 10 Nov 2008 at 3:26

GoogleCodeExporter commented 8 years ago
[deleted comment]
GoogleCodeExporter commented 8 years ago
Thanks! I've changed those descriptions.

I actually like the nil value input method!

As I've said on IRC, I think it'd be best if the button names are like the 
others, 
meaning all lowercase unless it's a single character (ABCXYZ) which get upper 
case. 
Because that's how the other emulators have it and it will be annoying 
otherwise.

As for the color, doesn't alpha blending allow for this with alpha set to 0? 
Can't 
you create an alias for this like the string "transparent" ?

I don't know how many games use more than 4 inputs so that's not a call I can 
make.

What does input.get exactly return? Can you supply the fingerprint?

Original comment by qFo...@gmail.com on 10 Nov 2008 at 7:46

GoogleCodeExporter commented 8 years ago
input.get() returns a table that has at least two values set, "xmouse" and 
"ymouse"
being integer X and Y coordinates of the mouse on the game screen (so if you 
pass
table.xmouse, table.ymouse into a GUI drawing function it should draw something 
over
the game exactly where the mouse is pointing). Additionally, the table contains 
zero
or more of the following, set to true if the key is pressed/active or nil if 
the key
is not pressed:
leftclick, rightclick, middleclick, capslock, numlock, scrolllock,
0,1,2,3,4,5,6,7,8,9, A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z, 
backspace,
tab, enter, shift, control, alt, pause, escape, space, pageup, pagedown, end, 
home,
left, up, right, down, insert, delete,
numpad0,numpad1,numpad2,numpad3,numpad4,numpad5,numpad6,numpad7,numpad8,numpad9,
numpad*,numpad+, numpad-,numpad.,numpad/, 
F1,F2,F3,F4,F5,F6,F7,F8,F9,F10,F11,F12,
F13,F14,F15,F16,F17,F18,F19,F20,F21,F22,F23,F24, semicolon, plus, minus, comma,
period, slash, backslash, tilde, quote, leftbracket, rightbracket

There should probably be more worry-free ways of getting non-joypad user input 
(maybe
a set of 10 or so hotkeys that put some specific values into input.get()) but I 
think
having the ability to check the actual keyboard keys will still be useful.

Original comment by nitsuja-@hotmail.com on 11 Nov 2008 at 12:22

GoogleCodeExporter commented 8 years ago
I added movie.stop() and movie.mode(), and gave in and added movie.framecount() 
(it
just does the same thing as gens.framecount()).

Original comment by nitsuja-@hotmail.com on 11 Nov 2008 at 12:54

GoogleCodeExporter commented 8 years ago
Cool. I've updated the ref. to reflect this.
I like getting the input, especially for the mouse.

The thing I've been thinking about though is what joypad.read (and input.read) 
should return. Should it return the values as they were at the start of a 
frame? Or 
at the moment of polling?

Original comment by qFo...@gmail.com on 11 Nov 2008 at 1:53

GoogleCodeExporter commented 8 years ago
joypad.read() gives you the emulated system's view of the input, which at least 
for
our purposes gets updated once at the beginning of each frame. input.read() 
gives you
the user's computer's view of the input, which should be practically 
asynchronous
(whatever buttons you had held down when the function got called, that's what it
returns).

This makes me think it would be useful to have another function: joypad.peek(). 
It
would basically be an asynchronous version of joypad.read() that only checks 
what the
user is currently pressing, not what's in the movie or what the game received or
anything. That way a script could look at which joypad buttons get pressed 
in-between
frames.

It could also be nice to have a version that takes a movie frame number and 
returns
what the joypad input was on that particular frame.

Original comment by nitsuja-@hotmail.com on 12 Nov 2008 at 10:42

GoogleCodeExporter commented 8 years ago
Some other notes, mostly to answer questions (or comment on inconsistencies) 
that I'm
noticing on that EmuLua page:

emu.redraw(): "Refreshes the screen while in a wait() loop."
- All it does is redraw the game screen. (Note that this triggers any
gui.register()'d functions to run again.) The most obvious place where that 
would be
useful is in a loop where you're calling emu.wait() to pause the game but keep 
the
script in control, but this function doesn't need to be used in conjunction 
with that
function.

emu.frameadvance(): "Almost any script will need this or else your script will 
freeze."
- Actually I think all of the Gens example scripts so far (in the lua_samples
directory) work fine without using this function at all or any direct 
equivalent.
None of them are bots, however. I realize this is counter-intuitive to someone 
who's
used to how Lua is used now in Snes9x/FCEU, but besides bots, I recommend not 
using
the frameadvance function or any kind of main loop that advances the frame. 
Instead,
simply register some functions with Gens (e.g. with gui.register) and let the 
script
reach the end (the registered functions will keep running). This way your 
script will
automatically be able to run simultaneously with other scripts without 
conflicting
with them, even if the other scripts don't follow this advice. (You can run 
more than
one script at a time in Gens, there should be no need to hack them together to
combine their effects.)

emu.emulateframe(): "?Same as frameadvance?."
- It's not the same because it only emulates and draws one frame. 
emu.frameadvance()
does a bunch of extra stuff like checking for hotkeys and allowing the user to 
pause
or frame advance the game, and updating menus and windows/whatever stuff so the
application isn't considered inactive. Also, emu.emulateframe() always acts as 
if the
speedmode is "normal" for that frame no matter what the speedmode is set to.

emu.emulateframefastnoskipping()
- like emu.emulateframe() but acts as if speedmode is "nothrottle"

emu.emulateframefast()
- like emu.emulateframe() but acts as if speedmode is "turbo"

emu.emulateframeinvisible()
- like emu.emulateframe() but acts as if speedmode is "maximum". Also, it can 
safely
be called from inside a gui.register()'d function and it won't generate sound. 
This
function is useful for "prediction", which you can see an example of in
sonic1speedometer.lua. There it is used along with the savestate functions to 
"look 2
frames into the future, pretending the B button is held, and get what the X and 
Y
velocity of the player will be", and even though that happens every frame while 
the
script is running, the game looks and sounds like it's running normally.

emu.registerbefore()
- Registers a function to run immediately before each frame gets emulated. This 
runs
after the next frame's input is known but before it's used, so this is your only
chance to set the next frame's input using the next frame's would-be input. For
example, if you want to make a script that filters or modifies ongoing user 
input,
such as making the game think "left" is pressed whenever you press "right", you 
can
do it easily with this.

emu.registerafter()
- Registers a function to run immediately after each frame gets emulated. It 
runs at
a similar time as gui.register(), except unlike gui.register() it doesn't also 
get
called again whenever the screen gets redrawn.

emu.registerexit()
- Registers a function that runs when the script stops. Whether the script 
stops on
its own or the user tells it to stop, or even if the script crashes or the user 
tries
to close the emulator, the emulator will try to run whatever Lua code you put 
in here
first. So if you want to make sure some code runs that cleans up some external
resources or saves your progress to a file or just says some last words, you 
could
put it here. (Of course, a forceful termination of the application or a crash 
from
inside the registered exit function will still prevent the code from running.)

input.get(): "Additionally one of these keys will be set to true if they were 
pressed"
- This is a little inaccurate. Better would be "Additionally each of these keys 
will
be set to true if they are currently held".

emu.framecount()/movie.framecount()
- It's worth noting that these will never ever return nil in Gens. There is 
always a
valid frame count even if there is no movie.

emu.lagcount()
- Note that even if the user hits the "reset lag count" button to set the lag 
count
back to zero, this function ignores that and returns what the lag count would 
be if
it had not been cleared. So you can safely assume that the lag count this 
function
gives you is accurate/consistent.

movie.mode()
- Gens has one additional status it can return called "finished". That means the
movie was playing, but is now past the last frame of the movie, and it will 
switch
automatically to "playback" or "recording" (depending on the read-only status) 
if you
load a savestate that was made before the movie finished.

movie.replay()
- This switches the mode to "playback" and starts playing the current movie from
frame 0. It's invalid to call this when there is no movie loaded.

gui.transparency()
- note that Gens also supports decimal values here, not only integers.

gui.drawbox()
- Gens allows two color arguments (one for fill color, another for outline 
color).

gui.text()
- I can't find the section describing this function, but anyway, Gens has two
additional arguments (one for font color, another for outline color).

On colors: "There's also something called "chartreuse" (??)"
- See http://en.wikipedia.org/wiki/Chartreuse_(color)
On colors: "HTML string: "#rrggbb"
- Gens also accepts "#rrggbbaa".

gens.message()/etc.
- It's worth noting that in Gens you can pass any table into string-printing
functions like gui.text() or gens.message() or gens.print(), to print the 
contents of
the table. For example, calling gens.message(input.get()) every frame is a good 
way
to see what kind of output input.get() is giving you.

Original comment by nitsuja-@hotmail.com on 13 Nov 2008 at 12:11

GoogleCodeExporter commented 8 years ago
Thanks! I've merged your comments with the reference :)

Original comment by qFo...@gmail.com on 16 Nov 2008 at 2:04

GoogleCodeExporter commented 8 years ago
Functions registered by savestate.registersave() and savestate.registerload() 
should
now support saving from one and loading into the other. The save function can 
return
one or more Lua objects (integers, strings, booleans, or tables) and the load
function gets them as arguments after its first argument (which is always the
savestate number). The sonic1savestatecomparison.lua example was updated (and
simplified) to use this as its only method of persisting data.

Currently the data is saved in a separate file that sits alongside the .gs# 
savestate
files. I'm not sure if that's the best way to do it (it gives the user more 
power to
manage the data separately, but that also makes it easy to screw things up if 
they
move/rename their save files without doing the same for their .luasav files).

Also, because multiple scripts can run simultaneously and each script can 
register
savestate functions independently (potentially more than one set of arguments is
saved with each savestate if you're running multiple scripts), the data is
automatically keyed to the script using its filename when saving, otherwise Gens
wouldn't know which part of the data to pass to which script when it tries to 
load it
later. If it bothers you that renaming the script file would make it unable to 
load
old saved data, or that completely different scripts might malfunction if 
renamed to
the same filename and run together, then you can override the save key to 
something
other than the filename by providing your own key as an extra (the second) 
argument
to either savestate.registersave() or savestate.registerload().

Original comment by nitsuja-@hotmail.com on 28 Dec 2008 at 12:17

GoogleCodeExporter commented 8 years ago
savestate.create()/savestate.load()/savestate.save() should now behave like 
they do
in Snes9x for both anonymous savestates and numbered save slots. (This means 
that all
of the Gens-specific notes on the savestate functions that are currently on the
EmuLua page should be removed, as they are now obsolete/incorrect.)

Note that you are still allowed to directly pass integers instead of savestate
objects into savestate.load() and savestate.save(), but this is only an extra
feature, not a requirement anymore.

I have deleted savestate.get() and savestate.set() because this change makes 
them
unnecessary (if you have already written code that uses them, simply change 
"get" to
"save" and "set" to "load" and it should work as before).

Original comment by nitsuja-@hotmail.com on 7 Jan 2009 at 10:00

GoogleCodeExporter commented 8 years ago
Also, I implemented memory.readbyterange(int address, int length). Except, I'm 
not
sure why it returns a string in Snes9x. That seems rather insane to me... 
shouldn't
it simply return an array of bytes? Well, that's what I've made it do instead of
returning a string and it seems to work fine.

Original comment by nitsuja-@hotmail.com on 7 Jan 2009 at 10:09

GoogleCodeExporter commented 8 years ago

Original comment by nitsuja-@hotmail.com on 16 Feb 2009 at 12:42

GoogleCodeExporter commented 8 years ago
I think the biggest thing missing at this point is the documentation. I'm 
working on
it, but it's taking longer than expected. I don't know exactly where I should 
put it
when it's ready but the lua_samples directory seems good enough.

Original comment by nitsuja-@hotmail.com on 17 Feb 2009 at 1:12

GoogleCodeExporter commented 8 years ago
OK, the documentation should be done now. Pretty much anything that's missing 
is also
currently missing from Gens.

Original comment by nitsuja-@hotmail.com on 5 Mar 2009 at 8:02

GoogleCodeExporter commented 8 years ago
With the gui.gdscreenshot and gui.gdoverlay functions in now, I can't think of
anything else major that's missing. Actually, it would be nice if anonymous
savestates could trigger savestate callback functions, although I've personally 
never
wanted them to do that in any scripts I've written so far. But that's about it.

Original comment by nitsuja-@hotmail.com on 6 Mar 2009 at 12:53

GoogleCodeExporter commented 8 years ago
I guess I should mark this as "Fixed" since it's implemented. It could surely 
use
more improvements or bugfixes but separate issues should be created for those 
as they
come up.

Original comment by nitsuja-@hotmail.com on 18 Mar 2009 at 5:25