Here is a description of all functions provided by inputreplayer
--[[
*******************************************************************************
lsnes-input-replayer v0.3 manual
*******************************************************************************
0) Quickstart instructions
1) What is it good for?
2) How it works
3) global variables
4) global functions
5) inputreplayer API
5.1) inputreplayer.functions
5.2) inputreplayer.config
5.3) inputreplayer.state
6) inputreplayer_ui
7) limitations and known (possible) bugs
*******************************************************************************
0) Quickstart instructions (to get some impressions on how to use this thing)
*******************************************************************************
step 1: set lsnes_version inside inputreplayer-v0.3-config.lua to
either "wxwidgets" or "SDL"
step 2: play some movie up to a point where you can control both characters
and instantly see the reactions of them for different input.
(step 2a: be sure to have a backup of your movie!)
step 3: create a savestate and switch to read-write mode
step 4: start the script and press numpad_divide (-> hides menu)
step 5: play around a bit, then pause the game.
step 6: press numpad_divide, then numpad1 (-> you have selected "set mode")
then numpad7 (-> you have selected "switch all to REPLAY")
then numpad_divide (-> hides the menu)
step 7: load the savestate in read-write mode
step 8: magic, although you are in read-write mode, your previous input is
used!
step 9: play around with "set offset", and see how it changes the behaviour.
*******************************************************************************
1) What is it good for?
*******************************************************************************
You're recording a TAS. You got some part of a level done, and now you want to
improve it. This tool will give you an easy way to verify if you really
improved, and where you might have lost some time.
You have recorded quite a long part, and now you notice a mistake near the
beginning. This tool will help you to reuse your progress afterwards, you
won't have to repeat all the input again.
You have a game with random lag. You have recorded quite a long part, and now
you notice a mistake near the beginning. You fix it, but the lag pattern
changes. This tool will help you to resync your movie without having to edit it
by hand.
*******************************************************************************
2) How it works
*******************************************************************************
You start lsnes-input-replayer, and start recording your process. You create
some savestates here and there. lsnes-input-replayer is in "recording" mode,
and saves all your input, and also some important game data like your position.
When you load a savestate, lsnes-input-replayer will switch to "replay" mode.
(In this version, you have to do this by hand.) The recorded buttons will be
the "reference input". As long as you don't hold any gamepad buttons, it will
apply the previously recorded input for you. If you decide to use your gamepad,
it will become inactive until you explicitly switch back to "replay" mode.
If you decide your new try was clearly better than the old one, but you want to
improve even more, then you can overwrite the "reference input" with your new
attempt.
You can also define the offset to apply the input. A positive offset will let
you apply your input earlier than in the reference, a negative offset will
apply your input later than before.
Additionally, it is possible to remap the joypad input and the recorded input.
This is a useful feature if you're creating a multiplayer TAS. For example you
can swap controller 1 and controller 2 this way, and the same can be done with
your recorded input.
*******************************************************************************
3) global variables
*******************************************************************************
keyhook
table which maps keys to functions. Used for on_keyhook.
Don't modify this directly, use assignkey(key, func) instead.
index_to_player_for_on_snoop
used to tell the script how to convert
ncontroller,nport to a player number.
0,0 is player 1
1,0 is player 2
TODO: I'm not sure about the other values.
player_to_index_for_setinput
used to tell the script how to convert
the playernumber to a number which can be used for input.set
player 1 requires input.set(0, ..., ...)
player 2 requires input.set(4, ..., ...)
TODO: I'm not sure about the other values.
button_by_name
if you want to know how you need to call input.set to press a
specific button, this is for you. Example: input.set(0, button_by_name["Y"], 1)
button_by_index
given an index from 0 to 11, it will tell you which button you
would manipulate with it when used for input.set.
Example:
input.set(4, 5, 1)
button_by_index[5] == "d"
player_to_index_for_setinput[2] == 4
------------------------------------
you tell lsnes to hold "down" on controller 2
This table is used to show how this script modified the input for you.
Putting all these together, we can now understand
on_snoop(nport, ncontroller, cindex, cvalue)
Example: nport == 1
ncontroller == 0
cindex == 7
cvalue == 1
-----------
index_to_player_for_on_snoop[1][0] == 2
button_by_index[7] == "r"
-----------
lsnes will receive "right is pressed" for player two
And you can also use this to make your scripts quite readable
(but lengthy), for example
input.set(player_to_index_for_setinput[2], button_by_name["u"], 1)
will tell the reader instantly that you want to press "up" for player 2.
input.set(4, 4, 1)
would do the same, but to read it, you would need to look up
the information in two different places.
lsnes_version
must be set manually inside the config file!
Key names are different in wxwidgets and SDL, so setting this
up in the right way is necessary!
temp
may be used for different purposes. Don't make any
assumptions about its value.
player
used several times for different loops. Don't
make any assumptions about its value.
*******************************************************************************
4) global functions
*******************************************************************************
compressinput(inputtable)
useful for storing all the recorded information to your harddrive,
saves about 80% file size while still being human-readable.
returns a string representing the input in a human-readable form.
inputtable
has all buttons in the way they are read from on_snoop, so it is a
table of this kind:
{[0] = 0 , [1] = 0 , ... [15] = 0 }
or or or
1 1 1
decompressinput(inputstring)
if we compress the input, it must be decompressed as well.
inputstring
is a string that must be formatted exactly in the same way
as compressinput would have returned it.
inputstring must have at least 12 characters.
if string.sub(inputstring, index - 1, index - 1) ~= "." then
button button_by_index[index] shall be pressed.
returns a table which can be easily used for input.set
assignkey(key, func)
use these to add additional hotkeys.
Example: You want to use numpad+ to increase the offset for player
one by one. Then you would use
assignkey(
"numpad_plus", -- or "kp_plus", depending on lsnes_version!
function()
inputreplayer.functions.addoffset(1, 1)
end
)
to delete the hotkey, use assignkey(key, nil).
*******************************************************************************
5) inputreplayer API
*******************************************************************************
*******************************************************************************
5.1) functions
*******************************************************************************
inputreplayer.functions.setmode(player, mode)
sets the specified mode for the specified player.
see above how these modes work.
player
1 to 8
mode
"RECORD", "REPLAY", "INACTIVE"
inputreplayer.functions.remaprecordedinput(player, remap)
If there is recorded data available for this frame, the recorded input
from the specified player for frame
movie.currentframe() + inputreplayer.state.player[player].offset
will be offered to all players in the remap table. If the player in
the remap table is in REPLAY mode at that time, and there is no other
current joypad input for this player at the moment, this offer will
be accepted.
player
1 to 8
remap
a table containing all players which shall receive the
recorded input.
examples:
example one:
inputreplayer.functions.remaprecordedinput(1, {2})
inputreplayer.functions.remaprecordedinput(2, {1})
this together will swap controller 1 and two.
example two:
inputreplayer.functions.remaprecordedinput(1, {1, 2})
this will send all input from controller 1 to
controller 2, so this is like duplicating the
input.
example three:
inputreplayer.functions.remaprecordedinput(1, {2})
This will actually do the same as example two
because controller one didn't get overwritten,
and then it defaults to just using the usual
input.
inputreplayer.functions.remapjoypadinput(player, remap)
If you setup a joypad remap for player, it will overwrite the input
from all players listed in the remap table with the input from the
specified player. This is done before the current recording mode
is taken into account.
So if you're in RECORD mode, and you have used
remapjoypadinput(1,{2})
remapjoypadinput(2,{1})
all input you do for player 1 will be
used AND recorded for player 2 and vice versa.
player
see above
remap
see above
inputreplayer.functions.addoffset(player,frames)
If you want to apply the recorded input sooner or later than it
has been recorded, you can set an offset for this for each player.
Offset is applied before remapping is taken into account.
positive values will apply the recorded input sooner than it was recorded.
negative values will apply the recorded input later than it was recorded.
player
1 to 8
frames
number of frames to add to current offset
inputreplayer.functions.setoffset(player,frames)
sets the offset for the specified player to the specified number of frames.
see above for more details.
inputreplayer.functions.setnewreference()
While in INACTIVE mode, this script will record your current attempt to
inputreplayer.state.player[player].newinput[frame]
If you want to use this attempt as new reference (because you have improved)
then run this command, and your current attempt will become the new
reference.
inputreplayer.functions.on_snoop(nport, ncontroller, cindex, cvalue)
To be put into on_snoop callback.
This function handles all the necessary joypad recording.
inputreplayer.functions.on_input()
To be put into on_input callback.
This function will modify all your input in the desired way.
*******************************************************************************
5.2) inputreplayer.config
*******************************************************************************
see also config file
inputreplayer.config.playercount
Tells the script how many players should be taken care of.
TODO:
menus don't take care of this,
they assume playercount == 2.
*******************************************************************************
5.3) inputreplayer.state
*******************************************************************************
holds all data used by inputreplayer.
inputreplayer.state.player[player]
table holding all recorded data for the specified player.
inputreplayer.state.player[player].input
table containing an inputstring (to be used with decompressinput)
for each frame while the specified player was in RECORD mode.
inputreplayer.state.player[player].input[frame]
This holds a string expressing which button the specified
player has pressed in the specified frame.
inputreplayer.state.player[player].offset
Current offset for the specified player.
See above how this offset is applied.
inputreplayer.state.player[player].remaprecordedinput
Either nil (no remapping) or a table.
If it is a table, it contains all players
which shall receive the recorded input from the
specified player. Offset is applied before
this remapping is done.
inputreplayer.state.player[player].remapjoypadinput
Either nil (no remapping) or a table.
If it is a table, it contains all players
which shall receive the current joypad input
from the specified player, overwriting their
own joypad input. The remapping is done
before the input is actually recorded, so the
remapping will also influence what is recorded.
inputreplayer.state.player[player].newinput
structure: same as inputreplayer.state.player[player].input
When the specified player is in INACTIVE mode, all
input will still be recorded, but it is stored at this
place.
If you decide that your current attempt is clearly better
than your old one, you can overwrite your old attempt
by calling inputreplayer.functions.setnewreference()
*******************************************************************************
6) inputreplayer_ui
*******************************************************************************
contains everything necessary to make use of all the powerful functions
provided by inputreplayer.
If you can create a better UI, this is your chance to replace the
default user interface with your own one, just rewrite this part.
inputreplayer_ui.on_paint
To be called from on_paint callback. Will draw all the necessary
information on screen to use this script. will also draw the menu.
inputreplayer_ui.menu
This includes the complete menu structure.
inputreplayer_ui.menu is a table of numbered items. Number must
be from 1 to 9 (otherwise they are inaccessible or won't appear).
There must be no gap within these numbers. (If you specify item
1 and 3, but not 2, item 3 won't be dispayed, but it will still
react to whatever item 3 is if you press numpad3. You don't want
such a silly behaviour.)
Each item has the following structure:
[itemnumber] == {
string text,
function action,
submenu menu,
boolean goback
}
text
a string to display the intention of this item
action
a function to be called when this item is selected
optional.
If present, and goback ~= false, you will go back
one step in the menu afterwards. Otherwise, the
menu will stay the way it is.
behavoir is undefined if
action ~= nil and submenu ~= nil
submenu
a menu to be displayed when this item is to be
selected.
optional.
behavoir is undefined if
action ~= nil and submenu ~= nil
goback
by default, you will go one step back
in the menu structure if an item has an
action applied on it. If you set goback
to false, this will not happen.
Menu can be opened or closed with the menu key.
Menu key is stored in inputreplayer_ui.config.keys.menu
Menu key is also used to go back one step in the menu.
You can navigate through the menu by using numbers from the
numpad by default.
inputreplayer_ui.config
everything about the UI which can be configured goes here.
Especially, you can configure the keys for the menu navigation.
*******************************************************************************
7) limitations and known (possible) bugs
*******************************************************************************
- No support for multiple input on the same frame. Might never be changed, because
those games will most probably have a lot of randomness.
- Interface assumes you are using exactly 2 players right now. Support for more
than 2 players hasn't been tested.
- there is nothing implemented for watching the memory at the moment. Showing
differences in memory between recorded version and current version will make
this script really powerful, as it allows for a very easy way to see your
improvements.
- The menu-driven interface forces a lot more button presses than necessary, but
it is easy to add hotkeys for the most important functions. There is a really
straight-forward API for this purpose.
- This script doesn't care about resets at all! I assume that this script isn't
useful at all while you reset. Just activate the script afterwards, and
everything will be fine.
- Right now it doesn't support saving the recorded data to your harddrive. Therefore,
loading recorded data is also not implemented.
- If English is your first language, you can fix my mistakes... so:
- maybe find a better name for this script?
--]]